@terrazzo/cli 2.0.0-beta.2 → 2.0.0-beta.4
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/CHANGELOG.md +9 -1
- package/bin/cli.js +25 -4
- package/dist/index.d.ts +50 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +895 -102
- package/dist/index.js.map +1 -1
- package/dist/lab/assets/index-Bnka3dzO.js.map +1 -1
- package/lab.tsx +1 -0
- package/package.json +6 -4
- package/terrazzo.config.ts +0 -12
- package/tokens-example.json +0 -50
- package/vite.config.ts +0 -18
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
3
|
import { build, defineConfig as defineConfig$1, parse } from "@terrazzo/parser";
|
|
4
|
-
import fs
|
|
4
|
+
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import pc from "picocolors";
|
|
7
7
|
import { createServer } from "vite";
|
|
@@ -9,13 +9,15 @@ import { ViteNodeRunner } from "vite-node/client";
|
|
|
9
9
|
import { ViteNodeServer } from "vite-node/server";
|
|
10
10
|
import chokidar from "chokidar";
|
|
11
11
|
import yamlToMomoa from "yaml-to-momoa";
|
|
12
|
+
import fs$1 from "node:fs/promises";
|
|
13
|
+
import { isAlias, pluralize } from "@terrazzo/token-tools";
|
|
14
|
+
import { merge } from "merge-anything";
|
|
15
|
+
import { camelCase } from "scule";
|
|
12
16
|
import { spawn } from "node:child_process";
|
|
13
17
|
import { confirm, intro, multiselect, outro, select, spinner } from "@clack/prompts";
|
|
14
|
-
import { isAlias, pluralize } from "@terrazzo/token-tools";
|
|
15
18
|
import { detect } from "detect-package-manager";
|
|
16
19
|
import { generate } from "escodegen";
|
|
17
20
|
import { parseModule } from "meriyah";
|
|
18
|
-
import { readdir } from "node:fs/promises";
|
|
19
21
|
import { Readable, Writable } from "node:stream";
|
|
20
22
|
import { serve } from "@hono/node-server";
|
|
21
23
|
import mime from "mime";
|
|
@@ -377,21 +379,584 @@ async function checkCmd({ config, logger, positionals }) {
|
|
|
377
379
|
function helpCmd() {
|
|
378
380
|
console.log(`tz
|
|
379
381
|
[commands]
|
|
380
|
-
build
|
|
381
|
-
--watch, -w
|
|
382
|
-
--no-lint
|
|
383
|
-
check [path]
|
|
384
|
-
lint [path]
|
|
385
|
-
init
|
|
386
|
-
lab
|
|
382
|
+
build Build token artifacts from tokens.json
|
|
383
|
+
--watch, -w Watch tokens.json for changes and recompile
|
|
384
|
+
--no-lint Disable linters running on build
|
|
385
|
+
check [path] Check tokens.json for errors and run linters
|
|
386
|
+
lint [path] (alias of check)
|
|
387
|
+
init Create a starter tokens.json file
|
|
388
|
+
lab Manage your tokens with a web interface
|
|
389
|
+
import [path] Import from a Figma Design file
|
|
390
|
+
--o [file] Save imported JSON
|
|
391
|
+
--unpublished Include unpublished Variables
|
|
392
|
+
--skip-styles Don’t import styles
|
|
393
|
+
--skip-variables
|
|
394
|
+
Don’t import variables
|
|
387
395
|
|
|
388
396
|
[options]
|
|
389
|
-
--help
|
|
390
|
-
--config, -c
|
|
391
|
-
--quiet
|
|
397
|
+
--help Show this message
|
|
398
|
+
--config, -c Path to config (default: ./terrazzo.config.ts)
|
|
399
|
+
--quiet Suppress warnings
|
|
392
400
|
`);
|
|
393
401
|
}
|
|
394
402
|
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region src/import/figma/lib.ts
|
|
405
|
+
const KEY = ":key";
|
|
406
|
+
const FILE_KEY = ":file_key";
|
|
407
|
+
const API = {
|
|
408
|
+
file: `https://api.figma.com/v1/files/${FILE_KEY}`,
|
|
409
|
+
fileNodes: `https://api.figma.com/v1/files/${FILE_KEY}/nodes`,
|
|
410
|
+
fileStyles: `https://api.figma.com/v1/files/${FILE_KEY}/styles`,
|
|
411
|
+
localVariables: `https://api.figma.com/v1/files/${FILE_KEY}/variables/local`,
|
|
412
|
+
publishedVariables: `https://api.figma.com/v1/files/${FILE_KEY}/variables/published`,
|
|
413
|
+
styles: `https://api.figma.com/v1/styles/${KEY}`
|
|
414
|
+
};
|
|
415
|
+
/** Wrapper around camelCase to handle more cases */
|
|
416
|
+
function formatName(name) {
|
|
417
|
+
return camelCase(name.replace(/\s+/g, "-"));
|
|
418
|
+
}
|
|
419
|
+
const nf = new Intl.NumberFormat("en-us");
|
|
420
|
+
/** Wrapper around camelCase to handle more cases */
|
|
421
|
+
function formatNumber(number) {
|
|
422
|
+
return nf.format(number);
|
|
423
|
+
}
|
|
424
|
+
/** Get File ID from design URL */
|
|
425
|
+
function getFileID(url) {
|
|
426
|
+
return url.match(/^https:\/\/(www\.)?figma\.com\/design\/([^/]+)/)?.[2];
|
|
427
|
+
}
|
|
428
|
+
/** /v1/files/:file_key */
|
|
429
|
+
async function getFile(fileKey, { logger }) {
|
|
430
|
+
const res = await fetch(API.file.replace(FILE_KEY, fileKey), {
|
|
431
|
+
method: "GET",
|
|
432
|
+
headers: { "X-Figma-Token": process.env.FIGMA_ACCESS_TOKEN }
|
|
433
|
+
});
|
|
434
|
+
if (!res.ok) logger.error({
|
|
435
|
+
group: "import",
|
|
436
|
+
message: `${res.status} ${await res.text()}`
|
|
437
|
+
});
|
|
438
|
+
return await res.json();
|
|
439
|
+
}
|
|
440
|
+
/** /v1/files/:file_key/nodes */
|
|
441
|
+
async function getFileNodes(fileKey, { ids, logger }) {
|
|
442
|
+
let url = API.fileNodes.replace(FILE_KEY, fileKey);
|
|
443
|
+
if (ids?.length) url += `?ids=${ids.join(",")}`;
|
|
444
|
+
const res = await fetch(url, {
|
|
445
|
+
method: "GET",
|
|
446
|
+
headers: { "X-Figma-Token": process.env.FIGMA_ACCESS_TOKEN }
|
|
447
|
+
});
|
|
448
|
+
if (!res.ok) logger.error({
|
|
449
|
+
group: "import",
|
|
450
|
+
message: `${res.status} ${await res.text()}`
|
|
451
|
+
});
|
|
452
|
+
return await res.json();
|
|
453
|
+
}
|
|
454
|
+
/** /v1/files/:file_key/styles */
|
|
455
|
+
async function getFileStyles(fileKey, { logger }) {
|
|
456
|
+
const res = await fetch(API.fileStyles.replace(FILE_KEY, fileKey), {
|
|
457
|
+
method: "GET",
|
|
458
|
+
headers: { "X-Figma-Token": process.env.FIGMA_ACCESS_TOKEN }
|
|
459
|
+
});
|
|
460
|
+
if (!res.ok) logger.error({
|
|
461
|
+
group: "import",
|
|
462
|
+
message: `${res.status} ${await res.text()}`
|
|
463
|
+
});
|
|
464
|
+
return await res.json();
|
|
465
|
+
}
|
|
466
|
+
/** /v1/files/:file_key/variables/local */
|
|
467
|
+
async function getFileLocalVariables(fileKey, { logger }) {
|
|
468
|
+
const res = await fetch(API.localVariables.replace(FILE_KEY, fileKey), {
|
|
469
|
+
method: "GET",
|
|
470
|
+
headers: { "X-Figma-Token": process.env.FIGMA_ACCESS_TOKEN }
|
|
471
|
+
});
|
|
472
|
+
if (!res.ok) logger.error({
|
|
473
|
+
group: "import",
|
|
474
|
+
message: `${res.status} ${await res.text}`
|
|
475
|
+
});
|
|
476
|
+
return await res.json();
|
|
477
|
+
}
|
|
478
|
+
/** /v1/files/:file_key/variables/published */
|
|
479
|
+
async function getFilePublishedVariables(fileKey, { logger }) {
|
|
480
|
+
const res = await fetch(API.publishedVariables.replace(FILE_KEY, fileKey), {
|
|
481
|
+
method: "GET",
|
|
482
|
+
headers: { "X-Figma-Token": process.env.FIGMA_ACCESS_TOKEN }
|
|
483
|
+
});
|
|
484
|
+
if (!res.ok) logger.error({
|
|
485
|
+
group: "import",
|
|
486
|
+
message: `${res.status} ${await res.text}`
|
|
487
|
+
});
|
|
488
|
+
return await res.json();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
//#endregion
|
|
492
|
+
//#region src/import/figma/styles.ts
|
|
493
|
+
/** /v1/files/:file_key/styles */
|
|
494
|
+
async function getStyles(fileKey, { logger, unpublished }) {
|
|
495
|
+
const result = {
|
|
496
|
+
count: 0,
|
|
497
|
+
code: { sets: { styles: { sources: [{}] } } }
|
|
498
|
+
};
|
|
499
|
+
const styleNodeIDs = /* @__PURE__ */ new Set();
|
|
500
|
+
const stylesByID = /* @__PURE__ */ new Map();
|
|
501
|
+
if (unpublished) {
|
|
502
|
+
const styles = await getFile(fileKey, { logger });
|
|
503
|
+
for (const [id, style] of Object.entries(styles.styles)) {
|
|
504
|
+
styleNodeIDs.add(id);
|
|
505
|
+
stylesByID.set(id, style);
|
|
506
|
+
}
|
|
507
|
+
} else {
|
|
508
|
+
const styles = await getFileStyles(fileKey, { logger });
|
|
509
|
+
for (const style of styles.meta.styles) {
|
|
510
|
+
styleNodeIDs.add(style.node_id);
|
|
511
|
+
stylesByID.set(style.node_id, style);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
const fileNodes = await getFileNodes(fileKey, {
|
|
515
|
+
ids: [...styleNodeIDs],
|
|
516
|
+
logger
|
|
517
|
+
});
|
|
518
|
+
result.count += styleNodeIDs.size;
|
|
519
|
+
for (const [id, s] of stylesByID) {
|
|
520
|
+
const styleNode = fileNodes.nodes[id];
|
|
521
|
+
if (!styleNode) {
|
|
522
|
+
logger.warn({
|
|
523
|
+
group: "import",
|
|
524
|
+
message: `Style ${s.name} not found in file nodes. Does it need to be published?`
|
|
525
|
+
});
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const styleType = "style_type" in s ? s.style_type : s.styleType;
|
|
529
|
+
const tokenBase = {
|
|
530
|
+
$type: void 0,
|
|
531
|
+
$description: s.description || void 0,
|
|
532
|
+
$value: void 0,
|
|
533
|
+
$extensions: { "figma.com": {
|
|
534
|
+
name: s.name,
|
|
535
|
+
node_id: id,
|
|
536
|
+
created_at: "created_at" in s ? s.created_at : void 0,
|
|
537
|
+
updated_at: "updated_at" in s ? s.updated_at : void 0
|
|
538
|
+
} }
|
|
539
|
+
};
|
|
540
|
+
switch (styleType) {
|
|
541
|
+
case "FILL": {
|
|
542
|
+
const $value = fillStyle(styleNode.document);
|
|
543
|
+
if (!$value) logger.error({
|
|
544
|
+
group: "import",
|
|
545
|
+
message: `Could not parse fill for ${s.name}`,
|
|
546
|
+
continueOnError: true
|
|
547
|
+
});
|
|
548
|
+
if (Array.isArray($value)) tokenBase.$type = "gradient";
|
|
549
|
+
else tokenBase.$type = "color";
|
|
550
|
+
tokenBase.$value = $value;
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
case "TEXT": {
|
|
554
|
+
const $value = textStyle(styleNode.document);
|
|
555
|
+
if (!$value) logger.error({
|
|
556
|
+
group: "import",
|
|
557
|
+
message: `Could not parse text for ${s.name}`,
|
|
558
|
+
continueOnError: true
|
|
559
|
+
});
|
|
560
|
+
tokenBase.$type = "typography";
|
|
561
|
+
tokenBase.$value = $value;
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
case "EFFECT": {
|
|
565
|
+
const $value = effectStyle(styleNode.document);
|
|
566
|
+
if (!$value) logger.error({
|
|
567
|
+
group: "import",
|
|
568
|
+
message: `Could not parse effect for ${s.name}`,
|
|
569
|
+
continueOnError: true
|
|
570
|
+
});
|
|
571
|
+
tokenBase.$type = "shadow";
|
|
572
|
+
tokenBase.$value = $value;
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
case "GRID": {
|
|
576
|
+
const layoutGrids = gridStyles(styleNode.document);
|
|
577
|
+
if (!layoutGrids) logger.error({
|
|
578
|
+
group: "import",
|
|
579
|
+
message: `Could not parse grid for ${s.name}`,
|
|
580
|
+
continueOnError: true
|
|
581
|
+
});
|
|
582
|
+
let node = result.code.sets.styles.sources[0];
|
|
583
|
+
const path = s.name.split("/").map(formatName);
|
|
584
|
+
const name = path.pop();
|
|
585
|
+
for (const key of path) {
|
|
586
|
+
if (!(key in node)) node[key] = {};
|
|
587
|
+
node = node[key];
|
|
588
|
+
}
|
|
589
|
+
node[name] = layoutGrids;
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (tokenBase.$type !== void 0) {
|
|
594
|
+
let node = result.code.sets.styles.sources[0];
|
|
595
|
+
const path = s.name.split("/").map(formatName);
|
|
596
|
+
const name = path.pop();
|
|
597
|
+
for (const key of path) {
|
|
598
|
+
if (!(key in node)) node[key] = {};
|
|
599
|
+
node = node[key];
|
|
600
|
+
}
|
|
601
|
+
node[name] = tokenBase;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return result;
|
|
605
|
+
}
|
|
606
|
+
/** Return a shadow token from an effect */
|
|
607
|
+
function effectStyle(node) {
|
|
608
|
+
if ("effects" in node) {
|
|
609
|
+
const shadows = node.effects.filter((e) => e.type === "DROP_SHADOW" || e.type === "INNER_SHADOW");
|
|
610
|
+
if (shadows.length) return shadows.map((s) => ({
|
|
611
|
+
inset: s.type === "INNER_SHADOW",
|
|
612
|
+
offsetX: {
|
|
613
|
+
value: s.offset.x,
|
|
614
|
+
unit: "px"
|
|
615
|
+
},
|
|
616
|
+
offsetY: {
|
|
617
|
+
value: s.offset.y,
|
|
618
|
+
unit: "px"
|
|
619
|
+
},
|
|
620
|
+
blur: {
|
|
621
|
+
value: s.radius,
|
|
622
|
+
unit: "px"
|
|
623
|
+
},
|
|
624
|
+
spread: {
|
|
625
|
+
value: s.spread ?? 0,
|
|
626
|
+
unit: "px"
|
|
627
|
+
},
|
|
628
|
+
color: {
|
|
629
|
+
colorSpace: "srgb",
|
|
630
|
+
components: [
|
|
631
|
+
s.color.r,
|
|
632
|
+
s.color.g,
|
|
633
|
+
s.color.b
|
|
634
|
+
],
|
|
635
|
+
alpha: s.color.a
|
|
636
|
+
}
|
|
637
|
+
}));
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
/** Return a color or gradient token from a fill */
|
|
641
|
+
function fillStyle(node) {
|
|
642
|
+
if ("fills" in node) for (const fill of node.fills) switch (fill.type) {
|
|
643
|
+
case "SOLID": return {
|
|
644
|
+
colorSpace: "srgb",
|
|
645
|
+
components: [
|
|
646
|
+
fill.color.r,
|
|
647
|
+
fill.color.g,
|
|
648
|
+
fill.color.b
|
|
649
|
+
],
|
|
650
|
+
alpha: fill.color.a
|
|
651
|
+
};
|
|
652
|
+
case "GRADIENT_LINEAR":
|
|
653
|
+
case "GRADIENT_RADIAL":
|
|
654
|
+
case "GRADIENT_ANGULAR":
|
|
655
|
+
case "GRADIENT_DIAMOND": return fill.gradientStops.map((stop) => ({
|
|
656
|
+
position: stop.position,
|
|
657
|
+
color: {
|
|
658
|
+
colorSpace: "srgb",
|
|
659
|
+
components: [
|
|
660
|
+
stop.color.r,
|
|
661
|
+
stop.color.g,
|
|
662
|
+
stop.color.b
|
|
663
|
+
],
|
|
664
|
+
alpha: stop.color.a
|
|
665
|
+
}
|
|
666
|
+
}));
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
/** Return a dimension token from grid */
|
|
670
|
+
function gridStyles(node) {
|
|
671
|
+
if (!("layoutGrids" in node) || !node.layoutGrids?.length) return;
|
|
672
|
+
const values = {};
|
|
673
|
+
for (const grid of node.layoutGrids) {
|
|
674
|
+
const pattern = grid.pattern.toLowerCase();
|
|
675
|
+
if (values[pattern]) continue;
|
|
676
|
+
values[pattern] = {
|
|
677
|
+
sectionSize: {
|
|
678
|
+
$type: "dimension",
|
|
679
|
+
$value: {
|
|
680
|
+
value: grid.sectionSize,
|
|
681
|
+
unit: "px"
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
gutterSize: {
|
|
685
|
+
$type: "dimension",
|
|
686
|
+
$value: {
|
|
687
|
+
value: grid.sectionSize,
|
|
688
|
+
unit: "px"
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
if (grid.count > 0) values[pattern].count = {
|
|
693
|
+
$type: "number",
|
|
694
|
+
$value: grid.count
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
return values;
|
|
698
|
+
}
|
|
699
|
+
/** Return a typography token from text */
|
|
700
|
+
function textStyle(node) {
|
|
701
|
+
if (!("style" in node)) return;
|
|
702
|
+
return {
|
|
703
|
+
fontFamily: [node.style.fontFamily],
|
|
704
|
+
fontWeight: node.style.fontWeight,
|
|
705
|
+
fontStyle: node.style.fontStyle,
|
|
706
|
+
fontSize: node.style.fontSize ? {
|
|
707
|
+
value: node.style.fontSize,
|
|
708
|
+
unit: "px"
|
|
709
|
+
} : {
|
|
710
|
+
value: 1,
|
|
711
|
+
unit: "em"
|
|
712
|
+
},
|
|
713
|
+
letterSpacing: {
|
|
714
|
+
value: node.style.letterSpacing ?? 0,
|
|
715
|
+
unit: "px"
|
|
716
|
+
},
|
|
717
|
+
lineHeight: "lineHeightPercentFontSize" in node.style ? node.style.lineHeightPercentFontSize : "lineHeightPx" in node.style ? {
|
|
718
|
+
value: node.style.lineHeightPx,
|
|
719
|
+
unit: "px"
|
|
720
|
+
} : 1
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
//#endregion
|
|
725
|
+
//#region src/import/figma/variables.ts
|
|
726
|
+
/** /v1/files/:file_key/variables/published | /v1/files/:file_key/variables/local */
|
|
727
|
+
async function getVariables(fileKey, { logger, unpublished, matchers }) {
|
|
728
|
+
const result = {
|
|
729
|
+
count: 0,
|
|
730
|
+
remoteCount: 0,
|
|
731
|
+
code: {
|
|
732
|
+
sets: {},
|
|
733
|
+
modifiers: {}
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
const allVariables = {};
|
|
737
|
+
const variableCollections = {};
|
|
738
|
+
let finalVariables = {};
|
|
739
|
+
const modeIDToName = {};
|
|
740
|
+
const local = await getFileLocalVariables(fileKey, { logger });
|
|
741
|
+
for (const id of Object.keys(local.meta.variables)) {
|
|
742
|
+
if (local.meta.variables[id].hiddenFromPublishing) continue;
|
|
743
|
+
allVariables[id] = local.meta.variables[id];
|
|
744
|
+
}
|
|
745
|
+
for (const id of Object.keys(local.meta.variableCollections)) {
|
|
746
|
+
variableCollections[id] = local.meta.variableCollections[id];
|
|
747
|
+
for (const mode of local.meta.variableCollections[id].modes) modeIDToName[mode.modeId] = formatName(mode.name);
|
|
748
|
+
}
|
|
749
|
+
if (unpublished) finalVariables = allVariables;
|
|
750
|
+
else {
|
|
751
|
+
const published = await getFilePublishedVariables(fileKey, { logger });
|
|
752
|
+
for (const id of Object.keys(published.meta.variables)) finalVariables[id] = allVariables[id];
|
|
753
|
+
}
|
|
754
|
+
const remoteIDs = /* @__PURE__ */ new Set();
|
|
755
|
+
for (const id of Object.keys(finalVariables)) {
|
|
756
|
+
const variable = finalVariables[id];
|
|
757
|
+
const collection = variableCollections[variable.variableCollectionId];
|
|
758
|
+
const collectionName = formatName(collection.name);
|
|
759
|
+
const hasMultipleModes = collection.modes.length > 1;
|
|
760
|
+
if (hasMultipleModes) {
|
|
761
|
+
if (!(collectionName in result.code.modifiers)) result.code.modifiers[collectionName] = {
|
|
762
|
+
contexts: Object.fromEntries(collection.modes.map((m) => [formatName(m.name), [{}]])),
|
|
763
|
+
default: modeIDToName[collection.defaultModeId]
|
|
764
|
+
};
|
|
765
|
+
} else if (!(collectionName in result.code.sets)) result.code.sets[collectionName] = { sources: [{}] };
|
|
766
|
+
const matches = matchers.fontFamily?.test(variable.name) && "fontFamily" || matchers.fontWeight?.test(variable.name) && "fontWeight" || matchers.number?.test(variable.name) && "number" || void 0;
|
|
767
|
+
for (const [modeID, value] of Object.entries(variable.valuesByMode)) {
|
|
768
|
+
const modeName = modeIDToName[modeID];
|
|
769
|
+
let node = result.code;
|
|
770
|
+
if (hasMultipleModes) {
|
|
771
|
+
if (!(modeName in result.code.modifiers[collectionName].contexts)) result.code.modifiers[collectionName].contexts[modeName] = [{}];
|
|
772
|
+
node = result.code.modifiers[collectionName].contexts[modeName][0];
|
|
773
|
+
} else node = result.code.sets[collectionName].sources[0];
|
|
774
|
+
const tokenBase = {
|
|
775
|
+
$type: void 0,
|
|
776
|
+
$description: variable.description || void 0,
|
|
777
|
+
$value: void 0,
|
|
778
|
+
$extensions: { "figma.com": {
|
|
779
|
+
name: variable.name,
|
|
780
|
+
id: variable.id,
|
|
781
|
+
variableCollectionId: variable.variableCollectionId,
|
|
782
|
+
codeSyntax: Object.keys(variable.codeSyntax).length ? variable.codeSyntax : void 0
|
|
783
|
+
} }
|
|
784
|
+
};
|
|
785
|
+
const isAliasOfID = typeof value === "object" && "type" in value && value.type === "VARIABLE_ALIAS" && value.id || void 0;
|
|
786
|
+
if (isAliasOfID) if (allVariables[isAliasOfID]) {
|
|
787
|
+
tokenBase.$type = matches || {
|
|
788
|
+
COLOR: "color",
|
|
789
|
+
BOOLEAN: "boolean",
|
|
790
|
+
STRING: "string",
|
|
791
|
+
FLOAT: "dimension"
|
|
792
|
+
}[variable.resolvedType];
|
|
793
|
+
tokenBase.$value = `{${allVariables[isAliasOfID].name.split("/").map(formatName).join(".")}}`;
|
|
794
|
+
} else {
|
|
795
|
+
remoteIDs.add(isAliasOfID);
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
else if (matches === "fontFamily") {
|
|
799
|
+
tokenBase.$type = "fontFamily";
|
|
800
|
+
tokenBase.$value = String(value).split(",");
|
|
801
|
+
} else if (matches === "fontWeight") {
|
|
802
|
+
tokenBase.$type = "fontWeight";
|
|
803
|
+
tokenBase.$value = value;
|
|
804
|
+
} else if (matches === "number") {
|
|
805
|
+
if (typeof value === "object") throw new Error(`Can’t coerce ${variable.name} into number type.`);
|
|
806
|
+
tokenBase.$type = "number";
|
|
807
|
+
tokenBase.$value = Number(value);
|
|
808
|
+
} else switch (variable.resolvedType) {
|
|
809
|
+
case "BOOLEAN":
|
|
810
|
+
case "STRING":
|
|
811
|
+
tokenBase.$type = variable.resolvedType.toLowerCase();
|
|
812
|
+
tokenBase.$value = value;
|
|
813
|
+
break;
|
|
814
|
+
case "FLOAT":
|
|
815
|
+
tokenBase.$type = "dimension";
|
|
816
|
+
tokenBase.$value = {
|
|
817
|
+
value,
|
|
818
|
+
unit: "px"
|
|
819
|
+
};
|
|
820
|
+
break;
|
|
821
|
+
case "COLOR": {
|
|
822
|
+
const { r, g, b, a } = value;
|
|
823
|
+
tokenBase.$type = "color";
|
|
824
|
+
tokenBase.$value = {
|
|
825
|
+
colorSpace: "srgb",
|
|
826
|
+
components: [
|
|
827
|
+
r,
|
|
828
|
+
g,
|
|
829
|
+
b
|
|
830
|
+
],
|
|
831
|
+
alpha: a
|
|
832
|
+
};
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
if (tokenBase.$value !== void 0) {
|
|
837
|
+
const path = variable.name.split("/").map(formatName);
|
|
838
|
+
const name = path.pop();
|
|
839
|
+
for (const key of path) {
|
|
840
|
+
if (!(key in node)) node[key] = {};
|
|
841
|
+
node = node[key];
|
|
842
|
+
}
|
|
843
|
+
node[name] = tokenBase;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
result.count = Object.keys(finalVariables).length;
|
|
848
|
+
result.remoteCount = remoteIDs.size;
|
|
849
|
+
return result;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
//#endregion
|
|
853
|
+
//#region src/import/figma/index.ts
|
|
854
|
+
async function importFromFigma({ url, logger, unpublished, skipStyles, skipVariables, fontFamilyNames, fontWeightNames, numberNames }) {
|
|
855
|
+
const fileKey = getFileID(url);
|
|
856
|
+
if (!fileKey) logger.error({
|
|
857
|
+
group: "import",
|
|
858
|
+
message: `Invalid Figma URL: ${url}`
|
|
859
|
+
});
|
|
860
|
+
const result = {
|
|
861
|
+
variableCount: 0,
|
|
862
|
+
styleCount: 0,
|
|
863
|
+
code: {
|
|
864
|
+
$schema: "https://www.designtokens.org/schemas/2025.10/resolver.json",
|
|
865
|
+
version: "2025.10",
|
|
866
|
+
resolutionOrder: [],
|
|
867
|
+
sets: {},
|
|
868
|
+
modifiers: {}
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
try {
|
|
872
|
+
const [styles, vars] = await Promise.all([!skipStyles ? getStyles(fileKey, { logger }) : null, !skipVariables ? getVariables(fileKey, {
|
|
873
|
+
logger,
|
|
874
|
+
unpublished,
|
|
875
|
+
matchers: {
|
|
876
|
+
fontFamily: new RegExp(fontFamilyNames || "/fontFamily$"),
|
|
877
|
+
fontWeight: new RegExp(fontWeightNames || "/fontWeight$"),
|
|
878
|
+
number: numberNames ? new RegExp(numberNames) : void 0
|
|
879
|
+
}
|
|
880
|
+
}) : null]);
|
|
881
|
+
if (styles) {
|
|
882
|
+
result.styleCount += styles.count;
|
|
883
|
+
result.code = merge(result.code, styles.code);
|
|
884
|
+
}
|
|
885
|
+
if (vars) {
|
|
886
|
+
result.variableCount += vars.count;
|
|
887
|
+
result.code = merge(result.code, vars.code);
|
|
888
|
+
if (vars.remoteCount) logger.warn({
|
|
889
|
+
group: "import",
|
|
890
|
+
message: `${formatNumber(vars.remoteCount)} ${pluralize(vars.remoteCount, "Variable", "Variables")} were remote and could not be accessed. Try importing from other files to grab them.`
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
} catch (err) {
|
|
894
|
+
logger.error({
|
|
895
|
+
group: "import",
|
|
896
|
+
message: err.message
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
for (const group of ["sets", "modifiers"]) for (const name of Object.keys(result.code[group])) result.code.resolutionOrder.push({ $ref: `#/${group}/${name}` });
|
|
900
|
+
return result;
|
|
901
|
+
}
|
|
902
|
+
/** Is this a valid URL, and one belonging to a Figma file? */
|
|
903
|
+
function isFigmaPath(url) {
|
|
904
|
+
try {
|
|
905
|
+
new URL(url);
|
|
906
|
+
return /^https:\/\/(www\.)?figma\.com\/design\/[A-Za-z0-9]+/.test(url);
|
|
907
|
+
} catch {
|
|
908
|
+
return false;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
//#endregion
|
|
913
|
+
//#region src/import/index.ts
|
|
914
|
+
async function importCmd({ flags, positionals, logger }) {
|
|
915
|
+
const [_cmd, url] = positionals;
|
|
916
|
+
if (!url) logger.error({
|
|
917
|
+
group: "import",
|
|
918
|
+
message: "Missing import path. Expected `tz import [file]`."
|
|
919
|
+
});
|
|
920
|
+
if (isFigmaPath(url)) {
|
|
921
|
+
const { FIGMA_ACCESS_TOKEN } = process.env;
|
|
922
|
+
if (!FIGMA_ACCESS_TOKEN) logger.error({
|
|
923
|
+
group: "import",
|
|
924
|
+
message: `FIGMA_ACCESS_TOKEN not set! See https://terrazzo.app/docs/guides/import-from-figma`
|
|
925
|
+
});
|
|
926
|
+
const start = performance.now();
|
|
927
|
+
const result = await importFromFigma({
|
|
928
|
+
url,
|
|
929
|
+
logger,
|
|
930
|
+
unpublished: flags.unpublished,
|
|
931
|
+
skipStyles: flags["skip-styles"],
|
|
932
|
+
skipVariables: flags["skip-variables"],
|
|
933
|
+
fontFamilyNames: flags["font-family-names"],
|
|
934
|
+
fontWeightNames: flags["font-weight-names"],
|
|
935
|
+
numberNames: flags["number-names"]
|
|
936
|
+
});
|
|
937
|
+
const end = performance.now() - start;
|
|
938
|
+
if (flags.output) {
|
|
939
|
+
const oldFile = fs.existsSync(flags.output) ? JSON.parse(await fs$1.readFile(flags.output, "utf8")) : {};
|
|
940
|
+
const code = {
|
|
941
|
+
$schema: result.code.$schema,
|
|
942
|
+
version: result.code.version,
|
|
943
|
+
resolutionOrder: oldFile.resolutionOrder?.length ? oldFile.resolutionOrder : result.code.resolutionOrder,
|
|
944
|
+
sets: result.code.sets,
|
|
945
|
+
modifiers: result.code.modifiers,
|
|
946
|
+
$defs: oldFile.$defs,
|
|
947
|
+
$extensions: oldFile.$extensions
|
|
948
|
+
};
|
|
949
|
+
await fs$1.writeFile(flags.output, `${JSON.stringify(code, void 0, 2)}\n`);
|
|
950
|
+
logger.info({
|
|
951
|
+
group: "import",
|
|
952
|
+
message: `Imported ${formatNumber(result.variableCount)} ${pluralize(result.variableCount, "Variable", "Variables")}, ${formatNumber(result.styleCount)} ${pluralize(result.styleCount, "Style", "Styles")} → ${flags.output}`,
|
|
953
|
+
timing: end
|
|
954
|
+
});
|
|
955
|
+
} else process.stdout.write(JSON.stringify(result.code));
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
395
960
|
//#endregion
|
|
396
961
|
//#region src/init.ts
|
|
397
962
|
const INSTALL_COMMAND = {
|
|
@@ -400,6 +965,12 @@ const INSTALL_COMMAND = {
|
|
|
400
965
|
pnpm: "add -D --silent",
|
|
401
966
|
bun: "install -D --silent"
|
|
402
967
|
};
|
|
968
|
+
const SYNTAX_SETTINGS = { format: {
|
|
969
|
+
indent: { style: " " },
|
|
970
|
+
quotes: "single",
|
|
971
|
+
semicolons: true
|
|
972
|
+
} };
|
|
973
|
+
const EXAMPLE_TOKENS_PATH = "my-tokens.tokens.json";
|
|
403
974
|
const DESIGN_SYSTEMS = {
|
|
404
975
|
"adobe-spectrum": {
|
|
405
976
|
name: "Spectrum",
|
|
@@ -441,19 +1012,19 @@ async function initCmd({ logger }) {
|
|
|
441
1012
|
try {
|
|
442
1013
|
intro("⛋ Welcome to Terrazzo");
|
|
443
1014
|
const packageManager = await detect({ cwd: fileURLToPath(cwd) });
|
|
444
|
-
const { config, configPath } = await loadConfig({
|
|
1015
|
+
const { config, configPath = "terrazzo.config.ts" } = await loadConfig({
|
|
445
1016
|
cmd: "init",
|
|
446
1017
|
flags: {},
|
|
447
1018
|
logger
|
|
448
1019
|
});
|
|
449
|
-
const
|
|
450
|
-
|
|
1020
|
+
const tokensPath = config.tokens[0];
|
|
1021
|
+
const hasExistingConfig = fs.existsSync(configPath);
|
|
451
1022
|
let startFromDS = !(tokensPath && fs.existsSync(tokensPath));
|
|
452
1023
|
if (tokensPath && fs.existsSync(tokensPath)) {
|
|
453
1024
|
if (await confirm({ message: `Found tokens at ${path.relative(fileURLToPath(cwd), fileURLToPath(tokensPath))}. Overwrite with a new design system?` })) startFromDS = true;
|
|
454
|
-
}
|
|
1025
|
+
}
|
|
455
1026
|
if (startFromDS) {
|
|
456
|
-
|
|
1027
|
+
const ds = DESIGN_SYSTEMS[await select({
|
|
457
1028
|
message: "Start from existing design system?",
|
|
458
1029
|
options: [...Object.entries(DESIGN_SYSTEMS).map(([id, { author, name }]) => ({
|
|
459
1030
|
value: id,
|
|
@@ -462,7 +1033,8 @@ async function initCmd({ logger }) {
|
|
|
462
1033
|
value: "none",
|
|
463
1034
|
label: "None"
|
|
464
1035
|
}]
|
|
465
|
-
})]
|
|
1036
|
+
})];
|
|
1037
|
+
if (ds) {
|
|
466
1038
|
const s = spinner();
|
|
467
1039
|
s.start("Downloading");
|
|
468
1040
|
await new Promise((resolve, reject) => {
|
|
@@ -470,8 +1042,14 @@ async function initCmd({ logger }) {
|
|
|
470
1042
|
subprocess.on("error", reject);
|
|
471
1043
|
subprocess.on("exit", resolve);
|
|
472
1044
|
});
|
|
473
|
-
|
|
474
|
-
|
|
1045
|
+
s.stop("Download complete");
|
|
1046
|
+
if (hasExistingConfig) await updateConfigTokens(configPath, ds.tokens);
|
|
1047
|
+
else await newConfigFile(configPath, ds.tokens);
|
|
1048
|
+
} else startFromDS = false;
|
|
1049
|
+
}
|
|
1050
|
+
if (!hasExistingConfig) {
|
|
1051
|
+
await newConfigFile(configPath, [EXAMPLE_TOKENS_PATH]);
|
|
1052
|
+
await fs$1.writeFile(EXAMPLE_TOKENS_PATH, JSON.stringify(EXAMPLE_TOKENS, void 0, 2));
|
|
475
1053
|
}
|
|
476
1054
|
const existingPlugins = config.plugins.map((p) => p.name);
|
|
477
1055
|
const pluginSelection = await multiselect({
|
|
@@ -500,7 +1078,7 @@ async function initCmd({ logger }) {
|
|
|
500
1078
|
if (newPlugins?.length) {
|
|
501
1079
|
const plugins = newPlugins.map((p) => ({
|
|
502
1080
|
specifier: p.replace("@terrazzo/plugin-", ""),
|
|
503
|
-
|
|
1081
|
+
path: p
|
|
504
1082
|
}));
|
|
505
1083
|
const pluginCount = `${newPlugins.length} ${pluralize(newPlugins.length, "plugin", "plugins")}`;
|
|
506
1084
|
const s = spinner();
|
|
@@ -511,77 +1089,27 @@ async function initCmd({ logger }) {
|
|
|
511
1089
|
subprocess.on("exit", resolve);
|
|
512
1090
|
});
|
|
513
1091
|
s.message("Updating config");
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
attributes: []
|
|
531
|
-
})));
|
|
532
|
-
if (!astExport) {
|
|
533
|
-
logger.error({
|
|
534
|
-
group: "config",
|
|
535
|
-
message: `SyntaxError: ${relConfigPath} does not have default export.`
|
|
536
|
-
});
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
const astConfig = astExport.declaration.type === "CallExpression" ? astExport.declaration.arguments[0] : astExport.declaration;
|
|
540
|
-
if (astConfig.type !== "ObjectExpression") {
|
|
541
|
-
logger.error({
|
|
542
|
-
group: "config",
|
|
543
|
-
message: `Config: expected object default export, received ${astConfig.type}`
|
|
544
|
-
});
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
const pluginsArray = astConfig.properties.find((property) => property.type === "Property" && property.key.type === "Identifier" && property.key.name === "plugins")?.value;
|
|
548
|
-
const pluginsAst = plugins.map((p) => ({
|
|
549
|
-
type: "CallExpression",
|
|
550
|
-
callee: {
|
|
551
|
-
type: "Identifier",
|
|
552
|
-
name: p.specifier
|
|
553
|
-
},
|
|
554
|
-
arguments: [],
|
|
555
|
-
optional: false
|
|
556
|
-
}));
|
|
557
|
-
if (pluginsArray) pluginsArray.elements.push(...pluginsAst);
|
|
558
|
-
else astConfig.properties.push({
|
|
559
|
-
type: "Property",
|
|
560
|
-
key: {
|
|
561
|
-
type: "Identifier",
|
|
562
|
-
name: "plugins"
|
|
563
|
-
},
|
|
564
|
-
value: {
|
|
565
|
-
type: "ArrayExpression",
|
|
566
|
-
elements: pluginsAst
|
|
567
|
-
},
|
|
568
|
-
kind: "init",
|
|
569
|
-
computed: false,
|
|
570
|
-
method: false,
|
|
571
|
-
shorthand: false
|
|
572
|
-
});
|
|
573
|
-
fs.writeFileSync(configPath, generate(ast, { format: {
|
|
574
|
-
indent: { style: " " },
|
|
575
|
-
quotes: "single",
|
|
576
|
-
semicolons: true
|
|
577
|
-
} }));
|
|
578
|
-
} else fs.writeFileSync(DEFAULT_CONFIG_PATH, `import { defineConfig } from '@terrazzo/cli';
|
|
579
|
-
${plugins.map((p) => `import ${p.specifier} from '${p.package}';`).join("\n")}
|
|
1092
|
+
await updateConfigPlugins(configPath, plugins);
|
|
1093
|
+
s.stop(`Installed ${pluginCount}`);
|
|
1094
|
+
}
|
|
1095
|
+
if (!startFromDS && !newPlugins?.length) {
|
|
1096
|
+
outro("Nothing to do. Exiting.");
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
outro("⛋ Done! 🎉");
|
|
1100
|
+
} catch (err) {
|
|
1101
|
+
printError(err.message);
|
|
1102
|
+
process.exit(1);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
async function newConfigFile(configPath, tokens, imports = []) {
|
|
1106
|
+
await fs$1.writeFile(configPath, `import { defineConfig } from '@terrazzo/cli';
|
|
1107
|
+
${imports.map((p) => `import ${p.specifier} from '${p.path}';`).join("\n")}
|
|
580
1108
|
|
|
581
1109
|
export default defineConfig({
|
|
582
|
-
tokens: ['
|
|
1110
|
+
tokens: ['${tokens.join("', '")}'],
|
|
583
1111
|
plugins: [
|
|
584
|
-
${
|
|
1112
|
+
${imports.length ? imports.map((p) => `${p.specifier}(),`).join("\n ") : "/** @see https://terrazzo.app/docs */"}
|
|
585
1113
|
],
|
|
586
1114
|
outDir: './dist/',
|
|
587
1115
|
lint: {
|
|
@@ -610,14 +1138,279 @@ export default defineConfig({
|
|
|
610
1138
|
},
|
|
611
1139
|
},
|
|
612
1140
|
});`);
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
1141
|
+
}
|
|
1142
|
+
function getConfigObjFromAst(ast, configPath) {
|
|
1143
|
+
const astExport = ast.body.find((node) => node.type === "ExportDefaultDeclaration");
|
|
1144
|
+
if (!astExport) {
|
|
1145
|
+
const relConfigPath = configPath ? path.relative(fileURLToPath(cwd), fileURLToPath(new URL(configPath))) : void 0;
|
|
1146
|
+
throw new Error(`SyntaxError: ${relConfigPath} does not have default export.`);
|
|
619
1147
|
}
|
|
1148
|
+
const astConfig = astExport.declaration.type === "CallExpression" ? astExport.declaration.arguments[0] : astExport.declaration;
|
|
1149
|
+
if (astConfig.type !== "ObjectExpression") throw new Error(`Config: expected object default export, received ${astConfig.type}.`);
|
|
1150
|
+
return astConfig;
|
|
1151
|
+
}
|
|
1152
|
+
async function updateConfigTokens(configPath, tokens) {
|
|
1153
|
+
const ast = parseModule(await fs$1.readFile(configPath, "utf8"));
|
|
1154
|
+
const astConfig = getConfigObjFromAst(ast, configPath);
|
|
1155
|
+
let tokensKey = astConfig.properties.find((p) => p.type === "Property" && p.key.type === "Identifier" && p.key.name === "tokens");
|
|
1156
|
+
if (!tokensKey) {
|
|
1157
|
+
tokensKey = {
|
|
1158
|
+
type: "Property",
|
|
1159
|
+
key: {
|
|
1160
|
+
type: "Identifier",
|
|
1161
|
+
name: "tokens"
|
|
1162
|
+
},
|
|
1163
|
+
method: false,
|
|
1164
|
+
computed: false,
|
|
1165
|
+
shorthand: false,
|
|
1166
|
+
value: {
|
|
1167
|
+
type: "ArrayExpression",
|
|
1168
|
+
elements: tokens.map((value) => ({
|
|
1169
|
+
type: "Literal",
|
|
1170
|
+
value,
|
|
1171
|
+
raw: `'${value}'`
|
|
1172
|
+
}))
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
astConfig.properties.unshift(tokensKey);
|
|
1176
|
+
}
|
|
1177
|
+
await fs$1.writeFile(configPath, generate(ast, SYNTAX_SETTINGS));
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Add plugin imports
|
|
1181
|
+
* note: this has the potential to duplicate plugins, but we tried our
|
|
1182
|
+
* best to filter already, and this may be the user’s fault if they
|
|
1183
|
+
* selected to install a plugin already installed. But also, this is
|
|
1184
|
+
* easily-fixable, so let’s not waste too much time here (and possibly
|
|
1185
|
+
* introduce bugs).
|
|
1186
|
+
*/
|
|
1187
|
+
async function updateConfigPlugins(configPath, plugins) {
|
|
1188
|
+
const ast = parseModule(await fs$1.readFile(configPath, "utf8"));
|
|
1189
|
+
ast.body.push(...plugins.map((p) => ({
|
|
1190
|
+
type: "ImportDeclaration",
|
|
1191
|
+
source: {
|
|
1192
|
+
type: "Literal",
|
|
1193
|
+
value: p.path
|
|
1194
|
+
},
|
|
1195
|
+
specifiers: [{
|
|
1196
|
+
type: "ImportDefaultSpecifier",
|
|
1197
|
+
local: {
|
|
1198
|
+
type: "Identifier",
|
|
1199
|
+
name: p.specifier
|
|
1200
|
+
}
|
|
1201
|
+
}],
|
|
1202
|
+
attributes: []
|
|
1203
|
+
})));
|
|
1204
|
+
const astConfig = getConfigObjFromAst(ast, configPath);
|
|
1205
|
+
const pluginsArray = astConfig.properties.find((property) => property.type === "Property" && property.key.type === "Identifier" && property.key.name === "plugins")?.value;
|
|
1206
|
+
const pluginsAst = plugins.map((p) => ({
|
|
1207
|
+
type: "CallExpression",
|
|
1208
|
+
callee: {
|
|
1209
|
+
type: "Identifier",
|
|
1210
|
+
name: p.specifier
|
|
1211
|
+
},
|
|
1212
|
+
arguments: [],
|
|
1213
|
+
optional: false
|
|
1214
|
+
}));
|
|
1215
|
+
if (pluginsArray) pluginsArray.elements.push(...pluginsAst);
|
|
1216
|
+
else astConfig.properties.push({
|
|
1217
|
+
type: "Property",
|
|
1218
|
+
key: {
|
|
1219
|
+
type: "Identifier",
|
|
1220
|
+
name: "plugins"
|
|
1221
|
+
},
|
|
1222
|
+
value: {
|
|
1223
|
+
type: "ArrayExpression",
|
|
1224
|
+
elements: pluginsAst
|
|
1225
|
+
},
|
|
1226
|
+
kind: "init",
|
|
1227
|
+
computed: false,
|
|
1228
|
+
method: false,
|
|
1229
|
+
shorthand: false
|
|
1230
|
+
});
|
|
1231
|
+
await fs$1.writeFile(configPath, generate(ast, SYNTAX_SETTINGS));
|
|
620
1232
|
}
|
|
1233
|
+
const EXAMPLE_TOKENS = {
|
|
1234
|
+
color: {
|
|
1235
|
+
$description: "Color tokens",
|
|
1236
|
+
black: {
|
|
1237
|
+
"100": {
|
|
1238
|
+
$type: "color",
|
|
1239
|
+
$value: {
|
|
1240
|
+
colorSpace: "srgb",
|
|
1241
|
+
components: [
|
|
1242
|
+
.047,
|
|
1243
|
+
.047,
|
|
1244
|
+
.047
|
|
1245
|
+
],
|
|
1246
|
+
alpha: .05,
|
|
1247
|
+
hex: "#0c0c0d"
|
|
1248
|
+
}
|
|
1249
|
+
},
|
|
1250
|
+
"200": {
|
|
1251
|
+
$type: "color",
|
|
1252
|
+
$value: {
|
|
1253
|
+
colorSpace: "srgb",
|
|
1254
|
+
components: [
|
|
1255
|
+
.047,
|
|
1256
|
+
.047,
|
|
1257
|
+
.047
|
|
1258
|
+
],
|
|
1259
|
+
alpha: .1,
|
|
1260
|
+
hex: "#0c0c0d"
|
|
1261
|
+
}
|
|
1262
|
+
},
|
|
1263
|
+
"300": {
|
|
1264
|
+
$type: "color",
|
|
1265
|
+
$value: {
|
|
1266
|
+
colorSpace: "srgb",
|
|
1267
|
+
components: [
|
|
1268
|
+
.047,
|
|
1269
|
+
.047,
|
|
1270
|
+
.047
|
|
1271
|
+
],
|
|
1272
|
+
alpha: .2,
|
|
1273
|
+
hex: "#0c0c0d"
|
|
1274
|
+
}
|
|
1275
|
+
},
|
|
1276
|
+
"400": {
|
|
1277
|
+
$type: "color",
|
|
1278
|
+
$value: {
|
|
1279
|
+
colorSpace: "srgb",
|
|
1280
|
+
components: [
|
|
1281
|
+
.047,
|
|
1282
|
+
.047,
|
|
1283
|
+
.047
|
|
1284
|
+
],
|
|
1285
|
+
alpha: .34,
|
|
1286
|
+
hex: "#0c0c04"
|
|
1287
|
+
}
|
|
1288
|
+
},
|
|
1289
|
+
"500": {
|
|
1290
|
+
$type: "color",
|
|
1291
|
+
$value: {
|
|
1292
|
+
colorSpace: "srgb",
|
|
1293
|
+
components: [
|
|
1294
|
+
.047,
|
|
1295
|
+
.047,
|
|
1296
|
+
.047
|
|
1297
|
+
],
|
|
1298
|
+
alpha: .7,
|
|
1299
|
+
hex: "#0c0c0d"
|
|
1300
|
+
}
|
|
1301
|
+
},
|
|
1302
|
+
"600": {
|
|
1303
|
+
$type: "color",
|
|
1304
|
+
$value: {
|
|
1305
|
+
colorSpace: "srgb",
|
|
1306
|
+
components: [
|
|
1307
|
+
.047,
|
|
1308
|
+
.047,
|
|
1309
|
+
.047
|
|
1310
|
+
],
|
|
1311
|
+
alpha: .8,
|
|
1312
|
+
hex: "#0c0c0d"
|
|
1313
|
+
}
|
|
1314
|
+
},
|
|
1315
|
+
"700": {
|
|
1316
|
+
$type: "color",
|
|
1317
|
+
$value: {
|
|
1318
|
+
colorSpace: "srgb",
|
|
1319
|
+
components: [
|
|
1320
|
+
.047,
|
|
1321
|
+
.047,
|
|
1322
|
+
.047
|
|
1323
|
+
],
|
|
1324
|
+
alpha: .85,
|
|
1325
|
+
hex: "#0c0c0d"
|
|
1326
|
+
}
|
|
1327
|
+
},
|
|
1328
|
+
"800": {
|
|
1329
|
+
$type: "color",
|
|
1330
|
+
$value: {
|
|
1331
|
+
colorSpace: "srgb",
|
|
1332
|
+
components: [
|
|
1333
|
+
.047,
|
|
1334
|
+
.047,
|
|
1335
|
+
.047
|
|
1336
|
+
],
|
|
1337
|
+
alpha: .9,
|
|
1338
|
+
hex: "#0c0c0d"
|
|
1339
|
+
}
|
|
1340
|
+
},
|
|
1341
|
+
"900": {
|
|
1342
|
+
$type: "color",
|
|
1343
|
+
$value: {
|
|
1344
|
+
colorSpace: "srgb",
|
|
1345
|
+
components: [
|
|
1346
|
+
.047,
|
|
1347
|
+
.047,
|
|
1348
|
+
.047
|
|
1349
|
+
],
|
|
1350
|
+
alpha: .95,
|
|
1351
|
+
hex: "#0c0c0d"
|
|
1352
|
+
}
|
|
1353
|
+
},
|
|
1354
|
+
"1000": {
|
|
1355
|
+
$type: "color",
|
|
1356
|
+
$value: {
|
|
1357
|
+
colorSpace: "srgb",
|
|
1358
|
+
components: [
|
|
1359
|
+
.047,
|
|
1360
|
+
.047,
|
|
1361
|
+
.047
|
|
1362
|
+
],
|
|
1363
|
+
alpha: 0,
|
|
1364
|
+
hex: "#0c0c0d"
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
},
|
|
1369
|
+
border: {
|
|
1370
|
+
$description: "Border tokens",
|
|
1371
|
+
default: {
|
|
1372
|
+
type: "border",
|
|
1373
|
+
$value: {
|
|
1374
|
+
color: "{color.black.900}",
|
|
1375
|
+
style: "solid",
|
|
1376
|
+
width: {
|
|
1377
|
+
value: 1,
|
|
1378
|
+
unit: "px"
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
},
|
|
1383
|
+
radius: {
|
|
1384
|
+
$description: "Corner radius tokens",
|
|
1385
|
+
"100": { $value: {
|
|
1386
|
+
value: .25,
|
|
1387
|
+
unit: "rem"
|
|
1388
|
+
} }
|
|
1389
|
+
},
|
|
1390
|
+
space: {
|
|
1391
|
+
$description: "Dimension tokens",
|
|
1392
|
+
"100": { $value: {
|
|
1393
|
+
value: .25,
|
|
1394
|
+
unit: "rem"
|
|
1395
|
+
} }
|
|
1396
|
+
},
|
|
1397
|
+
typography: {
|
|
1398
|
+
$description: "Typography tokens",
|
|
1399
|
+
body: {
|
|
1400
|
+
$type: "typography",
|
|
1401
|
+
$value: {
|
|
1402
|
+
fontFamily: "{typography.family.sans}",
|
|
1403
|
+
fontSize: "{typography.scale.03}",
|
|
1404
|
+
fontWeight: "{typography.weight.regular}",
|
|
1405
|
+
letterSpacing: {
|
|
1406
|
+
value: 0,
|
|
1407
|
+
unit: "em"
|
|
1408
|
+
},
|
|
1409
|
+
lineHeight: 1
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
};
|
|
621
1414
|
|
|
622
1415
|
//#endregion
|
|
623
1416
|
//#region src/lab.ts
|
|
@@ -625,7 +1418,7 @@ async function labCmd({ config, logger }) {
|
|
|
625
1418
|
/** TODO: handle multiple files */
|
|
626
1419
|
const [tokenFileUrl] = config.tokens;
|
|
627
1420
|
const staticFiles = /* @__PURE__ */ new Set();
|
|
628
|
-
const dirEntries = await readdir(fileURLToPath(import.meta.resolve("./lab")), {
|
|
1421
|
+
const dirEntries = await fs$1.readdir(fileURLToPath(import.meta.resolve("./lab")), {
|
|
629
1422
|
withFileTypes: true,
|
|
630
1423
|
recursive: true
|
|
631
1424
|
});
|
|
@@ -639,18 +1432,18 @@ async function labCmd({ config, logger }) {
|
|
|
639
1432
|
overrideGlobalObjects: false,
|
|
640
1433
|
async fetch(request) {
|
|
641
1434
|
const pathname = new URL(request.url).pathname;
|
|
642
|
-
if (pathname === "/") return new Response(Readable.toWeb(createReadStream(fileURLToPath(import.meta.resolve("./lab/index.html")))), { headers: { "Content-Type": "text/html" } });
|
|
1435
|
+
if (pathname === "/") return new Response(Readable.toWeb(fs.createReadStream(fileURLToPath(import.meta.resolve("./lab/index.html")))), { headers: { "Content-Type": "text/html" } });
|
|
643
1436
|
if (pathname === "/api/tokens") {
|
|
644
|
-
if (request.method === "GET") return new Response(Readable.toWeb(createReadStream(tokenFileUrl)), { headers: {
|
|
1437
|
+
if (request.method === "GET") return new Response(Readable.toWeb(fs.createReadStream(tokenFileUrl)), { headers: {
|
|
645
1438
|
"Content-Type": "application/json",
|
|
646
1439
|
"Cache-Control": "no-cache"
|
|
647
1440
|
} });
|
|
648
1441
|
else if (request.method === "POST" && request.body) {
|
|
649
|
-
await request.body.pipeTo(Writable.toWeb(createWriteStream(tokenFileUrl)));
|
|
1442
|
+
await request.body.pipeTo(Writable.toWeb(fs.createWriteStream(tokenFileUrl)));
|
|
650
1443
|
return new Response(JSON.stringify({ success: true }), { headers: { "Content-Type": "application/json" } });
|
|
651
1444
|
}
|
|
652
1445
|
}
|
|
653
|
-
if (staticFiles.has(pathname)) return new Response(Readable.toWeb(createReadStream(fileURLToPath(import.meta.resolve(`./lab${pathname}`)))), { headers: { "Content-Type": mime.getType(pathname) ?? "application/octet-stream" } });
|
|
1446
|
+
if (staticFiles.has(pathname)) return new Response(Readable.toWeb(fs.createReadStream(fileURLToPath(import.meta.resolve(`./lab${pathname}`)))), { headers: { "Content-Type": mime.getType(pathname) ?? "application/octet-stream" } });
|
|
654
1447
|
return new Response("Not found", { status: 404 });
|
|
655
1448
|
}
|
|
656
1449
|
}, (info) => {
|
|
@@ -807,5 +1600,5 @@ function defineConfig(config) {
|
|
|
807
1600
|
}
|
|
808
1601
|
|
|
809
1602
|
//#endregion
|
|
810
|
-
export { DEFAULT_CONFIG_PATH, DEFAULT_TOKENS_PATH, GREEN_CHECK, buildCmd, checkCmd, cwd, defineConfig, helpCmd, initCmd, labCmd, loadConfig, loadTokens, normalizeCmd, printError, printSuccess, resolveConfig, resolveTokenPath, time, versionCmd, writeFiles };
|
|
1603
|
+
export { DEFAULT_CONFIG_PATH, DEFAULT_TOKENS_PATH, GREEN_CHECK, buildCmd, checkCmd, cwd, defineConfig, helpCmd, importCmd, importFromFigma, initCmd, isFigmaPath, labCmd, loadConfig, loadTokens, normalizeCmd, printError, printSuccess, resolveConfig, resolveTokenPath, time, versionCmd, writeFiles };
|
|
811
1604
|
//# sourceMappingURL=index.js.map
|