@kinetixui/cli 0.14.0 → 0.15.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/index.js +279 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import pc8 from "picocolors";
|
|
6
6
|
|
|
7
7
|
// src/commands/add.ts
|
|
8
8
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -248,11 +248,11 @@ async function doctor() {
|
|
|
248
248
|
levels.push(report("warn", `"${alias}" alias points to ${path5.relative(cwd, dir) || "."}, which doesn't exist yet.`));
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
|
-
const
|
|
252
|
-
if (existsSync5(
|
|
253
|
-
levels.push(report("pass", `Tailwind CSS file found at ${path5.relative(cwd,
|
|
251
|
+
const cssPath2 = path5.join(cwd, config.srcDir ? "src" : "", config.tailwind.css);
|
|
252
|
+
if (existsSync5(cssPath2)) {
|
|
253
|
+
levels.push(report("pass", `Tailwind CSS file found at ${path5.relative(cwd, cssPath2)}`));
|
|
254
254
|
} else {
|
|
255
|
-
levels.push(report("warn", `Tailwind CSS file not found at ${path5.relative(cwd,
|
|
255
|
+
levels.push(report("warn", `Tailwind CSS file not found at ${path5.relative(cwd, cssPath2)} \u2014 the token contract has nowhere to land.`));
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
let registryNames = null;
|
|
@@ -458,10 +458,260 @@ async function parity(names, options) {
|
|
|
458
458
|
console.log(`${rows.length} of ${components.length} component(s) shown.`);
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
+
// src/commands/theme.ts
|
|
462
|
+
import { existsSync as existsSync8 } from "fs";
|
|
463
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
464
|
+
import path8 from "path";
|
|
465
|
+
import pc7 from "picocolors";
|
|
466
|
+
|
|
467
|
+
// src/lib/color-math.ts
|
|
468
|
+
function hexToRgb(hex) {
|
|
469
|
+
const h = hex.replace("#", "");
|
|
470
|
+
const n = parseInt(h.length === 3 ? h.replace(/(.)/g, "$1$1") : h, 16);
|
|
471
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
472
|
+
}
|
|
473
|
+
function rgbToHsl(r, g, b) {
|
|
474
|
+
r /= 255, g /= 255, b /= 255;
|
|
475
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
476
|
+
let h = 0, s = 0;
|
|
477
|
+
const l = (max + min) / 2;
|
|
478
|
+
const d = max - min;
|
|
479
|
+
if (d !== 0) {
|
|
480
|
+
s = d / (1 - Math.abs(2 * l - 1));
|
|
481
|
+
switch (max) {
|
|
482
|
+
case r:
|
|
483
|
+
h = (g - b) / d % 6;
|
|
484
|
+
break;
|
|
485
|
+
case g:
|
|
486
|
+
h = (b - r) / d + 2;
|
|
487
|
+
break;
|
|
488
|
+
default:
|
|
489
|
+
h = (r - g) / d + 4;
|
|
490
|
+
}
|
|
491
|
+
h *= 60;
|
|
492
|
+
if (h < 0) h += 360;
|
|
493
|
+
}
|
|
494
|
+
return [Math.round(h), Math.round(s * 100), Math.round(l * 100)];
|
|
495
|
+
}
|
|
496
|
+
function hexToHslChannels(hex) {
|
|
497
|
+
const [h, s, l] = rgbToHsl(...hexToRgb(hex));
|
|
498
|
+
return `${h} ${s}% ${l}%`;
|
|
499
|
+
}
|
|
500
|
+
function relLuminance([r, g, b]) {
|
|
501
|
+
const [cr, cg, cb] = [r, g, b].map((v) => {
|
|
502
|
+
const s = v / 255;
|
|
503
|
+
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
504
|
+
});
|
|
505
|
+
return 0.2126 * cr + 0.7152 * cg + 0.0722 * cb;
|
|
506
|
+
}
|
|
507
|
+
function contrastRatio(hexA, hexB) {
|
|
508
|
+
const la = relLuminance(hexToRgb(hexA)) + 0.05;
|
|
509
|
+
const lb = relLuminance(hexToRgb(hexB)) + 0.05;
|
|
510
|
+
return la > lb ? la / lb : lb / la;
|
|
511
|
+
}
|
|
512
|
+
function bestTextHex(bgHex) {
|
|
513
|
+
return contrastRatio(bgHex, "#ffffff") >= contrastRatio(bgHex, "#000000") ? "#ffffff" : "#000000";
|
|
514
|
+
}
|
|
515
|
+
function isHex(v) {
|
|
516
|
+
return /^#?[0-9a-fA-F]{6}$/.test(v.trim());
|
|
517
|
+
}
|
|
518
|
+
function normalizeHex(v) {
|
|
519
|
+
const t = v.trim().replace(/^#/, "");
|
|
520
|
+
return `#${t.toLowerCase()}`;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/lib/theme.ts
|
|
524
|
+
var ACCEPTED_TOKENS = [
|
|
525
|
+
"background",
|
|
526
|
+
"foreground",
|
|
527
|
+
"card",
|
|
528
|
+
"card-foreground",
|
|
529
|
+
"popover",
|
|
530
|
+
"popover-foreground",
|
|
531
|
+
"primary",
|
|
532
|
+
"primary-foreground",
|
|
533
|
+
"secondary",
|
|
534
|
+
"secondary-foreground",
|
|
535
|
+
"muted",
|
|
536
|
+
"muted-foreground",
|
|
537
|
+
"accent",
|
|
538
|
+
"accent-foreground",
|
|
539
|
+
"destructive",
|
|
540
|
+
"destructive-foreground",
|
|
541
|
+
"border",
|
|
542
|
+
"input",
|
|
543
|
+
"ring"
|
|
544
|
+
];
|
|
545
|
+
var EXAMPLE_VALUES = {
|
|
546
|
+
background: "#ffffff",
|
|
547
|
+
foreground: "#0f172a",
|
|
548
|
+
card: "#ffffff",
|
|
549
|
+
"card-foreground": "#0f172a",
|
|
550
|
+
popover: "#ffffff",
|
|
551
|
+
"popover-foreground": "#0f172a",
|
|
552
|
+
primary: "#2563eb",
|
|
553
|
+
"primary-foreground": "#ffffff",
|
|
554
|
+
secondary: "#f1f5f9",
|
|
555
|
+
"secondary-foreground": "#0f172a",
|
|
556
|
+
muted: "#f1f5f9",
|
|
557
|
+
"muted-foreground": "#64748b",
|
|
558
|
+
accent: "#f1f5f9",
|
|
559
|
+
"accent-foreground": "#0f172a",
|
|
560
|
+
destructive: "#dc2626",
|
|
561
|
+
"destructive-foreground": "#ffffff",
|
|
562
|
+
border: "#e2e8f0",
|
|
563
|
+
input: "#e2e8f0",
|
|
564
|
+
ring: "#2563eb"
|
|
565
|
+
};
|
|
566
|
+
var FOREGROUND_OF = {
|
|
567
|
+
background: "foreground",
|
|
568
|
+
card: "card-foreground",
|
|
569
|
+
popover: "popover-foreground",
|
|
570
|
+
primary: "primary-foreground",
|
|
571
|
+
secondary: "secondary-foreground",
|
|
572
|
+
muted: "muted-foreground",
|
|
573
|
+
accent: "accent-foreground",
|
|
574
|
+
destructive: "destructive-foreground"
|
|
575
|
+
};
|
|
576
|
+
var CONTRAST_PAIRS = [
|
|
577
|
+
["background", "foreground"],
|
|
578
|
+
["card", "card-foreground"],
|
|
579
|
+
["popover", "popover-foreground"],
|
|
580
|
+
["primary", "primary-foreground"],
|
|
581
|
+
["secondary", "secondary-foreground"],
|
|
582
|
+
["muted", "muted-foreground"],
|
|
583
|
+
["accent", "accent-foreground"],
|
|
584
|
+
["destructive", "destructive-foreground"]
|
|
585
|
+
];
|
|
586
|
+
var isAccepted = (t) => ACCEPTED_TOKENS.includes(t);
|
|
587
|
+
function parseThemeFile(text) {
|
|
588
|
+
const values = {};
|
|
589
|
+
const errors = [];
|
|
590
|
+
const unknownTokens = [];
|
|
591
|
+
const lines = text.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
592
|
+
lines.forEach((line, i) => {
|
|
593
|
+
const cells = line.split(/[,\t]/).map((c) => c.trim().replace(/^"|"$/g, ""));
|
|
594
|
+
if (cells.length < 2) {
|
|
595
|
+
errors.push(`Line ${i + 1}: expected "token,hex" \u2014 got "${line}"`);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const [rawToken, rawHex] = cells;
|
|
599
|
+
const token = rawToken.toLowerCase().replace(/^--/, "").replace(/\s+/g, "-");
|
|
600
|
+
if (!isHex(rawHex)) {
|
|
601
|
+
errors.push(`Line ${i + 1}: "${rawHex}" isn't a hex color`);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (!isAccepted(token)) {
|
|
605
|
+
unknownTokens.push(rawToken);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
values[token] = normalizeHex(rawHex);
|
|
609
|
+
});
|
|
610
|
+
return { values, errors, unknownTokens };
|
|
611
|
+
}
|
|
612
|
+
function deriveForegrounds(values) {
|
|
613
|
+
const out = { ...values };
|
|
614
|
+
for (const [base, fg] of Object.entries(FOREGROUND_OF)) {
|
|
615
|
+
if (out[base] && !out[fg]) out[fg] = bestTextHex(out[base]);
|
|
616
|
+
}
|
|
617
|
+
return out;
|
|
618
|
+
}
|
|
619
|
+
function toCssBlock(values) {
|
|
620
|
+
const lines = ACCEPTED_TOKENS.filter((t) => values[t]).map((t) => ` --${t}: ${hexToHslChannels(values[t])};`);
|
|
621
|
+
return `:root {
|
|
622
|
+
${lines.join("\n")}
|
|
623
|
+
}
|
|
624
|
+
`;
|
|
625
|
+
}
|
|
626
|
+
function checkContrast(values) {
|
|
627
|
+
return CONTRAST_PAIRS.filter(([bg, fg]) => values[bg] && values[fg]).map(([bg, fg]) => {
|
|
628
|
+
const ratio = contrastRatio(values[bg], values[fg]);
|
|
629
|
+
return { pair: [bg, fg], ratio, pass: ratio >= 4.5 };
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
function scaffoldThemeFile(name) {
|
|
633
|
+
const lines = [
|
|
634
|
+
`# ${name} \u2014 KinetixUI theme override`,
|
|
635
|
+
"#",
|
|
636
|
+
"# One `token,hex` pair per line. Uncomment (remove the leading `# `) and",
|
|
637
|
+
"# edit any token you want to override; leave the rest commented out to",
|
|
638
|
+
"# keep KinetixUI's default. Run `kinetixui theme build " + name + "` to",
|
|
639
|
+
`# compile this into ${name}.css and get a WCAG AA contrast report.`,
|
|
640
|
+
"#",
|
|
641
|
+
"# Setting a base color (e.g. `primary`) without its `-foreground` pair",
|
|
642
|
+
"# auto-derives the foreground by contrast (best of white/black) \u2014 you",
|
|
643
|
+
"# don't have to set both unless you want a specific foreground.",
|
|
644
|
+
"#"
|
|
645
|
+
];
|
|
646
|
+
for (const token of ACCEPTED_TOKENS) {
|
|
647
|
+
lines.push(`# ${token},${EXAMPLE_VALUES[token]}`);
|
|
648
|
+
}
|
|
649
|
+
return lines.join("\n") + "\n";
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/commands/theme.ts
|
|
653
|
+
var THEME_DIR = "kinetixui-themes";
|
|
654
|
+
function csvPath(cwd, name) {
|
|
655
|
+
return path8.join(cwd, THEME_DIR, `${name}.csv`);
|
|
656
|
+
}
|
|
657
|
+
function cssPath(cwd, name) {
|
|
658
|
+
return path8.join(cwd, THEME_DIR, `${name}.css`);
|
|
659
|
+
}
|
|
660
|
+
async function themeCreate(name, options) {
|
|
661
|
+
const cwd = process.cwd();
|
|
662
|
+
const file = csvPath(cwd, name);
|
|
663
|
+
if (existsSync8(file) && !options.force) {
|
|
664
|
+
throw new Error(`${path8.relative(cwd, file)} already exists. Pass --force to overwrite it.`);
|
|
665
|
+
}
|
|
666
|
+
await mkdir2(path8.dirname(file), { recursive: true });
|
|
667
|
+
await writeFile3(file, scaffoldThemeFile(name), "utf8");
|
|
668
|
+
console.log(pc7.green("\u2714"), `Wrote ${path8.relative(cwd, file)}`);
|
|
669
|
+
console.log(`Uncomment and edit the tokens you want to override, then run:`);
|
|
670
|
+
console.log(pc7.dim(` kinetixui theme build ${name}`));
|
|
671
|
+
}
|
|
672
|
+
async function themeBuild(name, options) {
|
|
673
|
+
const cwd = process.cwd();
|
|
674
|
+
const src = csvPath(cwd, name);
|
|
675
|
+
if (!existsSync8(src)) {
|
|
676
|
+
throw new Error(`${path8.relative(cwd, src)} doesn't exist. Run "kinetixui theme create ${name}" first.`);
|
|
677
|
+
}
|
|
678
|
+
const text = await readFile2(src, "utf8");
|
|
679
|
+
const { values: parsed, errors, unknownTokens } = parseThemeFile(text);
|
|
680
|
+
for (const err of errors) console.log(pc7.yellow("!"), err);
|
|
681
|
+
for (const unknown of unknownTokens) console.log(pc7.yellow("!"), `"${unknown}" isn't a known token \u2014 skipped.`);
|
|
682
|
+
const values = deriveForegrounds(parsed);
|
|
683
|
+
const setCount = Object.keys(values).length;
|
|
684
|
+
if (setCount === 0) {
|
|
685
|
+
throw new Error(`No tokens set in ${path8.relative(cwd, src)} \u2014 nothing to build. Uncomment at least one line.`);
|
|
686
|
+
}
|
|
687
|
+
const dest = cssPath(cwd, name);
|
|
688
|
+
await writeFile3(dest, toCssBlock(values), "utf8");
|
|
689
|
+
console.log(pc7.green("\u2714"), `Wrote ${path8.relative(cwd, dest)} (${setCount} token${setCount === 1 ? "" : "s"})`);
|
|
690
|
+
console.log(`Paste it after ${pc7.dim('@import "@kinetixui/tokens/css";')} in your global CSS.`);
|
|
691
|
+
console.log();
|
|
692
|
+
console.log(pc7.dim("WCAG AA contrast (4.5:1)"));
|
|
693
|
+
const results = checkContrast(values);
|
|
694
|
+
let failed = 0;
|
|
695
|
+
for (const { pair, ratio, pass } of results) {
|
|
696
|
+
const mark = pass ? pc7.green("\u2714") : pc7.red("\u2716");
|
|
697
|
+
if (!pass) failed++;
|
|
698
|
+
console.log(`${mark} ${pair[0]} / ${pair[1]}`, pc7.dim(`${ratio.toFixed(2)}:1`));
|
|
699
|
+
}
|
|
700
|
+
if (results.length === 0) {
|
|
701
|
+
console.log(pc7.dim("(no checkable pairs \u2014 set at least one base + foreground, or just a base to auto-derive one)"));
|
|
702
|
+
}
|
|
703
|
+
console.log();
|
|
704
|
+
console.log("Native (SwiftUI/Compose/Flutter) theme output isn't built yet \u2014 this compiles to CSS only.");
|
|
705
|
+
console.log(pc7.dim("See https://kinetixui.com/docs/cli#theme for the current scope."));
|
|
706
|
+
if (failed > 0 && options.failOnContrast) {
|
|
707
|
+
process.exitCode = 1;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
461
711
|
// package.json
|
|
462
712
|
var package_default = {
|
|
463
713
|
name: "@kinetixui/cli",
|
|
464
|
-
version: "0.
|
|
714
|
+
version: "0.15.0",
|
|
465
715
|
description: "Add KinetixUI components and tokens to your project.",
|
|
466
716
|
type: "module",
|
|
467
717
|
license: "MIT",
|
|
@@ -505,7 +755,7 @@ program.command("init").description("Set up kinetixui.json and pull in the token
|
|
|
505
755
|
try {
|
|
506
756
|
await init({ registry: opts.registry, yes: opts.yes });
|
|
507
757
|
} catch (err) {
|
|
508
|
-
console.error(
|
|
758
|
+
console.error(pc8.red("\u2716"), err.message);
|
|
509
759
|
process.exitCode = 1;
|
|
510
760
|
}
|
|
511
761
|
});
|
|
@@ -513,7 +763,7 @@ program.command("add").description("Add one or more components to your project."
|
|
|
513
763
|
try {
|
|
514
764
|
await add(components, { registry: opts.registry, overwrite: opts.overwrite, all: opts.all });
|
|
515
765
|
} catch (err) {
|
|
516
|
-
console.error(
|
|
766
|
+
console.error(pc8.red("\u2716"), err.message);
|
|
517
767
|
process.exitCode = 1;
|
|
518
768
|
}
|
|
519
769
|
});
|
|
@@ -521,7 +771,7 @@ program.command("list").description("List every component available in the regis
|
|
|
521
771
|
try {
|
|
522
772
|
await list({ registry: opts.registry });
|
|
523
773
|
} catch (err) {
|
|
524
|
-
console.error(
|
|
774
|
+
console.error(pc8.red("\u2716"), err.message);
|
|
525
775
|
process.exitCode = 1;
|
|
526
776
|
}
|
|
527
777
|
});
|
|
@@ -529,7 +779,7 @@ program.command("inspect").description("Show everything the registry knows about
|
|
|
529
779
|
try {
|
|
530
780
|
await inspect(name, { registry: opts.registry });
|
|
531
781
|
} catch (err) {
|
|
532
|
-
console.error(
|
|
782
|
+
console.error(pc8.red("\u2716"), err.message);
|
|
533
783
|
process.exitCode = 1;
|
|
534
784
|
}
|
|
535
785
|
});
|
|
@@ -537,7 +787,7 @@ program.command("parity").description("Show which platforms carry each component
|
|
|
537
787
|
try {
|
|
538
788
|
await parity(components, { registry: opts.registry });
|
|
539
789
|
} catch (err) {
|
|
540
|
-
console.error(
|
|
790
|
+
console.error(pc8.red("\u2716"), err.message);
|
|
541
791
|
process.exitCode = 1;
|
|
542
792
|
}
|
|
543
793
|
});
|
|
@@ -545,7 +795,24 @@ program.command("doctor").description("Check kinetixui.json, its aliases, and th
|
|
|
545
795
|
try {
|
|
546
796
|
await doctor();
|
|
547
797
|
} catch (err) {
|
|
548
|
-
console.error(
|
|
798
|
+
console.error(pc8.red("\u2716"), err.message);
|
|
799
|
+
process.exitCode = 1;
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
var theme = program.command("theme").description("Scaffold and compile a local token override (CSS only \u2014 see 'theme build --help').");
|
|
803
|
+
theme.command("create").description("Scaffold kinetixui-themes/<name>.csv with every overridable token, commented out.").argument("<name>", "theme name, e.g. acme").option("-f, --force", "overwrite an existing theme file", false).action(async (name, opts) => {
|
|
804
|
+
try {
|
|
805
|
+
await themeCreate(name, { force: opts.force });
|
|
806
|
+
} catch (err) {
|
|
807
|
+
console.error(pc8.red("\u2716"), err.message);
|
|
808
|
+
process.exitCode = 1;
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
theme.command("build").description("Compile kinetixui-themes/<name>.csv to CSS and print a WCAG AA contrast report. CSS only \u2014 no native (SwiftUI/Compose/Flutter) output yet.").argument("<name>", "theme name, e.g. acme").option("--no-fail-on-contrast", "exit 0 even if a pair fails WCAG AA").action(async (name, opts) => {
|
|
812
|
+
try {
|
|
813
|
+
await themeBuild(name, { failOnContrast: opts.failOnContrast });
|
|
814
|
+
} catch (err) {
|
|
815
|
+
console.error(pc8.red("\u2716"), err.message);
|
|
549
816
|
process.exitCode = 1;
|
|
550
817
|
}
|
|
551
818
|
});
|