@clafoutis/cli 1.1.1 → 1.2.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 CHANGED
@@ -1,12 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import * as p2 from '@clack/prompts';
3
3
  import { Command } from 'commander';
4
- import { logger } from '@clafoutis/shared';
4
+ import fs from 'fs';
5
5
  import path2 from 'path';
6
+ import { logger } from '@clafoutis/shared';
7
+ import { generate } from '@clafoutis/generators/figma';
8
+ import { generate as generate$1 } from '@clafoutis/generators/tailwind';
6
9
  import StyleDictionary from 'style-dictionary';
7
- import { pathToFileURL } from 'url';
10
+ import { register } from 'tsx/esm/api';
11
+ import { fileURLToPath, pathToFileURL } from 'url';
8
12
  import { Ajv } from 'ajv';
9
- import fs from 'fs/promises';
13
+ import fs2 from 'fs/promises';
10
14
  import { spawn } from 'child_process';
11
15
 
12
16
  // src/utils/errors.ts
@@ -84,7 +88,104 @@ function tokensDirNotFoundError(tokensDir) {
84
88
  );
85
89
  }
86
90
 
87
- // src/cli/validation.ts
91
+ // src/commands/format.ts
92
+ function loadTokenFiles(dirPath) {
93
+ const files = [];
94
+ function walk(dir, prefix) {
95
+ if (!fs.existsSync(dir)) {
96
+ return;
97
+ }
98
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
99
+ const fullPath = path2.join(dir, entry.name);
100
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
101
+ if (entry.isDirectory()) {
102
+ walk(fullPath, relativePath);
103
+ } else if (entry.name.endsWith(".json")) {
104
+ try {
105
+ const content = fs.readFileSync(fullPath, "utf-8");
106
+ files.push({ relativePath, fullPath, content });
107
+ } catch (err) {
108
+ logger.warn(
109
+ `Failed to read ${relativePath}: ${err instanceof Error ? err.message : String(err)}`
110
+ );
111
+ }
112
+ }
113
+ }
114
+ }
115
+ walk(dirPath, "");
116
+ return files;
117
+ }
118
+ function formatJson(content) {
119
+ const parsed = JSON.parse(content);
120
+ return JSON.stringify(parsed, null, 2) + "\n";
121
+ }
122
+ function formatCommand(options) {
123
+ const tokensDir = options.tokens || "./tokens";
124
+ if (!fs.existsSync(tokensDir)) {
125
+ throw tokensDirNotFoundError(tokensDir);
126
+ }
127
+ logger.info(`Formatting token files in ${tokensDir}...`);
128
+ const files = loadTokenFiles(tokensDir);
129
+ const fileCount = files.length;
130
+ if (fileCount === 0) {
131
+ logger.warn(`No JSON files found in ${tokensDir}`);
132
+ return;
133
+ }
134
+ let changedCount = 0;
135
+ const unformattedFiles = [];
136
+ for (const { relativePath, fullPath, content } of files) {
137
+ try {
138
+ const formatted = formatJson(content);
139
+ if (content !== formatted) {
140
+ if (options.check) {
141
+ unformattedFiles.push(relativePath);
142
+ changedCount++;
143
+ } else if (options.dryRun) {
144
+ logger.info(`Would format: ${relativePath}`);
145
+ changedCount++;
146
+ } else {
147
+ fs.writeFileSync(fullPath, formatted, "utf-8");
148
+ logger.info(`Formatted: ${relativePath}`);
149
+ changedCount++;
150
+ }
151
+ }
152
+ } catch (err) {
153
+ logger.error(
154
+ `Failed to format ${relativePath}: ${err instanceof Error ? err.message : String(err)}`
155
+ );
156
+ throw err;
157
+ }
158
+ }
159
+ if (options.check) {
160
+ if (changedCount > 0) {
161
+ logger.error(
162
+ `${changedCount} of ${fileCount} files are not formatted correctly:`
163
+ );
164
+ for (const file of unformattedFiles) {
165
+ logger.error(` - ${file}`);
166
+ }
167
+ logger.info(
168
+ `Run 'npx clafoutis format --tokens ${tokensDir}' to fix formatting.`
169
+ );
170
+ process.exit(1);
171
+ }
172
+ logger.success(`All ${fileCount} files are correctly formatted`);
173
+ return;
174
+ }
175
+ if (options.dryRun) {
176
+ if (changedCount > 0) {
177
+ logger.info(`Would format ${changedCount} of ${fileCount} files`);
178
+ } else {
179
+ logger.info(`All ${fileCount} files are already formatted`);
180
+ }
181
+ return;
182
+ }
183
+ if (changedCount > 0) {
184
+ logger.success(`Formatted ${changedCount} of ${fileCount} files`);
185
+ } else {
186
+ logger.success(`All ${fileCount} files are already formatted`);
187
+ }
188
+ }
88
189
  function validateRepo(value) {
89
190
  if (!value) {
90
191
  return "Repository is required";
@@ -159,8 +260,8 @@ function findUnknownFields(config, schemaProperties, prefix = "") {
159
260
  }
160
261
  return unknown;
161
262
  }
162
- function hasField(config, path4) {
163
- const parts = path4.split(".");
263
+ function hasField(config, path6) {
264
+ const parts = path6.split(".");
164
265
  let current = config;
165
266
  for (const part of parts) {
166
267
  if (current && typeof current === "object" && part in current) {
@@ -374,7 +475,7 @@ var log3 = {
374
475
  };
375
476
  async function readConfig(configPath) {
376
477
  try {
377
- const content = await fs.readFile(configPath, "utf-8");
478
+ const content = await fs2.readFile(configPath, "utf-8");
378
479
  return JSON.parse(content);
379
480
  } catch {
380
481
  return null;
@@ -382,7 +483,7 @@ async function readConfig(configPath) {
382
483
  }
383
484
  async function readProducerConfig(configPath) {
384
485
  try {
385
- const content = await fs.readFile(configPath, "utf-8");
486
+ const content = await fs2.readFile(configPath, "utf-8");
386
487
  return JSON.parse(content);
387
488
  } catch {
388
489
  return null;
@@ -390,7 +491,7 @@ async function readProducerConfig(configPath) {
390
491
  }
391
492
  async function fileExists(filePath) {
392
493
  try {
393
- await fs.access(filePath);
494
+ await fs2.access(filePath);
394
495
  return true;
395
496
  } catch {
396
497
  return false;
@@ -488,232 +589,38 @@ function getConsumerGitignore() {
488
589
  .clafoutis/cache
489
590
  `;
490
591
  }
491
-
492
- // src/templates/tokens.ts
493
- var colorPrimitives = {
494
- color: {
495
- gray: {
496
- 50: { $value: "#fafafa" },
497
- 100: { $value: "#f5f5f5" },
498
- 200: { $value: "#e5e5e5" },
499
- 300: { $value: "#d4d4d4" },
500
- 400: { $value: "#a3a3a3" },
501
- 500: { $value: "#737373" },
502
- 600: { $value: "#525252" },
503
- 700: { $value: "#404040" },
504
- 800: { $value: "#262626" },
505
- 900: { $value: "#171717" }
506
- },
507
- blue: {
508
- 50: { $value: "#eff6ff" },
509
- 100: { $value: "#dbeafe" },
510
- 200: { $value: "#bfdbfe" },
511
- 300: { $value: "#93c5fd" },
512
- 400: { $value: "#60a5fa" },
513
- 500: { $value: "#3b82f6" },
514
- 600: { $value: "#2563eb" },
515
- 700: { $value: "#1d4ed8" },
516
- 800: { $value: "#1e40af" },
517
- 900: { $value: "#1e3a8a" }
518
- },
519
- green: {
520
- 50: { $value: "#f0fdf4" },
521
- 100: { $value: "#dcfce7" },
522
- 200: { $value: "#bbf7d0" },
523
- 300: { $value: "#86efac" },
524
- 400: { $value: "#4ade80" },
525
- 500: { $value: "#22c55e" },
526
- 600: { $value: "#16a34a" },
527
- 700: { $value: "#15803d" },
528
- 800: { $value: "#166534" },
529
- 900: { $value: "#14532d" }
530
- },
531
- red: {
532
- 50: { $value: "#fef2f2" },
533
- 100: { $value: "#fee2e2" },
534
- 200: { $value: "#fecaca" },
535
- 300: { $value: "#fca5a5" },
536
- 400: { $value: "#f87171" },
537
- 500: { $value: "#ef4444" },
538
- 600: { $value: "#dc2626" },
539
- 700: { $value: "#b91c1c" },
540
- 800: { $value: "#991b1b" },
541
- 900: { $value: "#7f1d1d" }
542
- },
543
- amber: {
544
- 50: { $value: "#fffbeb" },
545
- 100: { $value: "#fef3c7" },
546
- 200: { $value: "#fde68a" },
547
- 300: { $value: "#fcd34d" },
548
- 400: { $value: "#fbbf24" },
549
- 500: { $value: "#f59e0b" },
550
- 600: { $value: "#d97706" },
551
- 700: { $value: "#b45309" },
552
- 800: { $value: "#92400e" },
553
- 900: { $value: "#78350f" }
554
- }
555
- }
556
- };
557
- var colorSemantics = {
558
- color: {
559
- primary: {
560
- 50: { $value: "{color.blue.50}" },
561
- 100: { $value: "{color.blue.100}" },
562
- 200: { $value: "{color.blue.200}" },
563
- 300: { $value: "{color.blue.300}" },
564
- 400: { $value: "{color.blue.400}" },
565
- 500: { $value: "{color.blue.500}" },
566
- 600: { $value: "{color.blue.600}" },
567
- 700: { $value: "{color.blue.700}" },
568
- 800: { $value: "{color.blue.800}" },
569
- 900: { $value: "{color.blue.900}" }
570
- },
571
- neutral: {
572
- 50: { $value: "{color.gray.50}" },
573
- 100: { $value: "{color.gray.100}" },
574
- 200: { $value: "{color.gray.200}" },
575
- 300: { $value: "{color.gray.300}" },
576
- 400: { $value: "{color.gray.400}" },
577
- 500: { $value: "{color.gray.500}" },
578
- 600: { $value: "{color.gray.600}" },
579
- 700: { $value: "{color.gray.700}" },
580
- 800: { $value: "{color.gray.800}" },
581
- 900: { $value: "{color.gray.900}" }
582
- },
583
- success: {
584
- 50: { $value: "{color.green.50}" },
585
- 100: { $value: "{color.green.100}" },
586
- 200: { $value: "{color.green.200}" },
587
- 300: { $value: "{color.green.300}" },
588
- 400: { $value: "{color.green.400}" },
589
- 500: { $value: "{color.green.500}" },
590
- 600: { $value: "{color.green.600}" },
591
- 700: { $value: "{color.green.700}" },
592
- 800: { $value: "{color.green.800}" },
593
- 900: { $value: "{color.green.900}" }
594
- },
595
- warning: {
596
- 50: { $value: "{color.amber.50}" },
597
- 100: { $value: "{color.amber.100}" },
598
- 200: { $value: "{color.amber.200}" },
599
- 300: { $value: "{color.amber.300}" },
600
- 400: { $value: "{color.amber.400}" },
601
- 500: { $value: "{color.amber.500}" },
602
- 600: { $value: "{color.amber.600}" },
603
- 700: { $value: "{color.amber.700}" },
604
- 800: { $value: "{color.amber.800}" },
605
- 900: { $value: "{color.amber.900}" }
606
- },
607
- error: {
608
- 50: { $value: "{color.red.50}" },
609
- 100: { $value: "{color.red.100}" },
610
- 200: { $value: "{color.red.200}" },
611
- 300: { $value: "{color.red.300}" },
612
- 400: { $value: "{color.red.400}" },
613
- 500: { $value: "{color.red.500}" },
614
- 600: { $value: "{color.red.600}" },
615
- 700: { $value: "{color.red.700}" },
616
- 800: { $value: "{color.red.800}" },
617
- 900: { $value: "{color.red.900}" }
618
- },
619
- background: {
620
- default: { $value: "{color.gray.50}" },
621
- subtle: { $value: "{color.gray.100}" },
622
- muted: { $value: "{color.gray.200}" }
623
- },
624
- foreground: {
625
- default: { $value: "{color.gray.900}" },
626
- muted: { $value: "{color.gray.600}" },
627
- subtle: { $value: "{color.gray.500}" }
628
- },
629
- border: {
630
- default: { $value: "{color.gray.200}" },
631
- muted: { $value: "{color.gray.100}" }
632
- }
633
- }
634
- };
635
- var colorSemanticsDark = {
636
- color: {
637
- background: {
638
- default: { $value: "{color.gray.900}" },
639
- subtle: { $value: "{color.gray.800}" },
640
- muted: { $value: "{color.gray.700}" }
641
- },
642
- foreground: {
643
- default: { $value: "{color.gray.50}" },
644
- muted: { $value: "{color.gray.400}" },
645
- subtle: { $value: "{color.gray.500}" }
646
- },
647
- border: {
648
- default: { $value: "{color.gray.700}" },
649
- muted: { $value: "{color.gray.800}" }
592
+ var __filename$1 = fileURLToPath(import.meta.url);
593
+ var __dirname$1 = path2.dirname(__filename$1);
594
+ function getTokensDir() {
595
+ const devPath = path2.resolve(__dirname$1, "tokens");
596
+ if (fs.existsSync(devPath)) return devPath;
597
+ const distPath = path2.resolve(__dirname$1, "templates", "tokens");
598
+ if (fs.existsSync(distPath)) return distPath;
599
+ throw new Error(
600
+ `Starter token templates not found. Searched:
601
+ ${devPath}
602
+ ${distPath}`
603
+ );
604
+ }
605
+ function walkTokensDir(dir, base = "") {
606
+ const result = [];
607
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
608
+ const relPath = base ? path2.join(base, entry.name) : entry.name;
609
+ const fullPath = path2.join(dir, entry.name);
610
+ if (entry.isDirectory()) {
611
+ result.push(...walkTokensDir(fullPath, relPath));
612
+ } else if (entry.name.endsWith(".json")) {
613
+ result.push({
614
+ path: relPath,
615
+ content: fs.readFileSync(fullPath, "utf-8")
616
+ });
650
617
  }
651
618
  }
652
- };
653
- var spacingPrimitives = {
654
- spacing: {
655
- 0: { $value: "0" },
656
- 1: { $value: "0.25rem" },
657
- 2: { $value: "0.5rem" },
658
- 3: { $value: "0.75rem" },
659
- 4: { $value: "1rem" },
660
- 5: { $value: "1.25rem" },
661
- 6: { $value: "1.5rem" },
662
- 8: { $value: "2rem" },
663
- 10: { $value: "2.5rem" },
664
- 12: { $value: "3rem" },
665
- 16: { $value: "4rem" },
666
- 20: { $value: "5rem" },
667
- 24: { $value: "6rem" }
668
- }
669
- };
670
- var typographyPrimitives = {
671
- fontFamily: {
672
- sans: { $value: "ui-sans-serif, system-ui, sans-serif" },
673
- serif: { $value: "ui-serif, Georgia, serif" },
674
- mono: { $value: "ui-monospace, monospace" }
675
- },
676
- fontSize: {
677
- xs: { $value: "0.75rem" },
678
- sm: { $value: "0.875rem" },
679
- base: { $value: "1rem" },
680
- lg: { $value: "1.125rem" },
681
- xl: { $value: "1.25rem" },
682
- "2xl": { $value: "1.5rem" },
683
- "3xl": { $value: "1.875rem" },
684
- "4xl": { $value: "2.25rem" }
685
- },
686
- fontWeight: {
687
- normal: { $value: "400" },
688
- medium: { $value: "500" },
689
- semibold: { $value: "600" },
690
- bold: { $value: "700" }
691
- },
692
- lineHeight: {
693
- tight: { $value: "1.25" },
694
- normal: { $value: "1.5" },
695
- relaxed: { $value: "1.75" }
696
- }
697
- };
698
- var starterTokens = {
699
- "colors/primitives.json": colorPrimitives,
700
- "colors/semantic.json": colorSemantics,
701
- "colors/semantic.dark.json": colorSemanticsDark,
702
- "spacing/primitives.json": spacingPrimitives,
703
- "typography/primitives.json": typographyPrimitives
704
- };
705
- function getStarterTokenContent(fileName) {
706
- const tokens = starterTokens[fileName];
707
- if (!tokens) {
708
- throw new Error(`Unknown starter token file: ${fileName}`);
709
- }
710
- return JSON.stringify(tokens, null, 2) + "\n";
619
+ return result;
711
620
  }
712
621
  function getAllStarterTokens() {
713
- return Object.keys(starterTokens).map((fileName) => ({
714
- path: fileName,
715
- content: getStarterTokenContent(fileName)
716
- }));
622
+ const tokensDir = getTokensDir();
623
+ return walkTokensDir(tokensDir);
717
624
  }
718
625
 
719
626
  // src/templates/workflow.ts
@@ -744,6 +651,9 @@ jobs:
744
651
  - name: Install Clafoutis
745
652
  run: npm install -D @clafoutis/cli
746
653
 
654
+ - name: Check token file formatting
655
+ run: npx clafoutis format --tokens tokens --check
656
+
747
657
  - name: Generate tokens
748
658
  run: npx clafoutis generate
749
659
 
@@ -984,8 +894,8 @@ async function createProducerConfig(answers, force, dryRun) {
984
894
  description: `tokens: "${answers.tokens}", output: "${answers.output}"`
985
895
  }
986
896
  ];
987
- const starterTokens2 = getAllStarterTokens();
988
- for (const token of starterTokens2) {
897
+ const starterTokens = getAllStarterTokens();
898
+ for (const token of starterTokens) {
989
899
  const tokenPath = path2.join(answers.tokens, token.path);
990
900
  if (!force && await fileExists(tokenPath)) {
991
901
  continue;
@@ -1044,7 +954,7 @@ async function createConsumerConfig(answers, force, dryRun) {
1044
954
  const gitignorePath = ".gitignore";
1045
955
  const consumerIgnore = getConsumerGitignore();
1046
956
  if (await fileExists(gitignorePath)) {
1047
- const existingContent = await fs.readFile(gitignorePath, "utf-8");
957
+ const existingContent = await fs2.readFile(gitignorePath, "utf-8");
1048
958
  if (!existingContent.includes(".clafoutis/cache")) {
1049
959
  filesToCreate.push({
1050
960
  path: gitignorePath,
@@ -1081,8 +991,8 @@ function showDryRunOutput(files) {
1081
991
  async function writeFiles(files) {
1082
992
  for (const file of files) {
1083
993
  const dir = path2.dirname(file.path);
1084
- await fs.mkdir(dir, { recursive: true });
1085
- await fs.writeFile(file.path, file.content);
994
+ await fs2.mkdir(dir, { recursive: true });
995
+ await fs2.writeFile(file.path, file.content);
1086
996
  log3.success(`Created ${file.path}`);
1087
997
  }
1088
998
  }
@@ -1094,8 +1004,14 @@ function showNextSteps(mode, answers) {
1094
1004
  log3.message(
1095
1005
  ` 1. Edit ${producerAnswers.tokens}/colors/primitives.json with your design tokens`
1096
1006
  );
1097
- log3.message(" 2. Run: npx clafoutis generate");
1098
- log3.message(" 3. Push to GitHub - releases will be created automatically");
1007
+ if (producerAnswers.workflow) {
1008
+ log3.message(
1009
+ " 2. Push to GitHub - the workflow will generate outputs and create a release automatically"
1010
+ );
1011
+ } else {
1012
+ log3.message(" 2. Run: npx clafoutis generate");
1013
+ log3.message(" 3. Push to GitHub to share your design system");
1014
+ }
1099
1015
  } else {
1100
1016
  log3.message(" 1. Run: npx clafoutis sync");
1101
1017
  }
@@ -1105,7 +1021,6 @@ function showNextSteps(mode, answers) {
1105
1021
  async function loadPlugin(pluginPath) {
1106
1022
  const absolutePath = path2.resolve(process.cwd(), pluginPath);
1107
1023
  if (pluginPath.endsWith(".ts")) {
1108
- const { register } = await import('tsx/esm/api');
1109
1024
  register();
1110
1025
  }
1111
1026
  return import(pathToFileURL(absolutePath).href);
@@ -1190,18 +1105,13 @@ async function generateCommand(options) {
1190
1105
  }
1191
1106
  } else {
1192
1107
  const builtInGenerators = {
1193
- tailwind: async () => import('@clafoutis/generators/tailwind'),
1194
- figma: async () => import('@clafoutis/generators/figma')
1108
+ tailwind: { generate: () => generate$1() },
1109
+ figma: { generate: () => generate() }
1195
1110
  };
1196
1111
  if (!builtInGenerators[name]) {
1197
1112
  throw generatorNotFoundError(name);
1198
1113
  }
1199
- try {
1200
- generatorModule = await builtInGenerators[name]();
1201
- } catch (err) {
1202
- const errorMessage = err instanceof Error ? err.message : String(err);
1203
- throw pluginLoadError(`@clafoutis/generators/${name}`, errorMessage);
1204
- }
1114
+ generatorModule = builtInGenerators[name];
1205
1115
  }
1206
1116
  const context = {
1207
1117
  tokensDir,
@@ -1232,7 +1142,7 @@ var CACHE_DIR = ".clafoutis";
1232
1142
  var CACHE_FILE = `${CACHE_DIR}/cache`;
1233
1143
  async function readCache() {
1234
1144
  try {
1235
- return (await fs.readFile(CACHE_FILE, "utf-8")).trim();
1145
+ return (await fs2.readFile(CACHE_FILE, "utf-8")).trim();
1236
1146
  } catch (err) {
1237
1147
  if (err instanceof Error && err.code === "ENOENT") {
1238
1148
  return null;
@@ -1241,8 +1151,8 @@ async function readCache() {
1241
1151
  }
1242
1152
  }
1243
1153
  async function writeCache(version) {
1244
- await fs.mkdir(CACHE_DIR, { recursive: true });
1245
- await fs.writeFile(CACHE_FILE, version);
1154
+ await fs2.mkdir(CACHE_DIR, { recursive: true });
1155
+ await fs2.writeFile(CACHE_FILE, version);
1246
1156
  }
1247
1157
  async function downloadRelease(config) {
1248
1158
  const token = process.env.CLAFOUTIS_REPO_TOKEN;
@@ -1313,8 +1223,8 @@ async function writeOutput(config, files) {
1313
1223
  const configPath = config.files[assetName];
1314
1224
  if (!configPath) continue;
1315
1225
  const outputPath = path2.resolve(process.cwd(), configPath);
1316
- await fs.mkdir(path2.dirname(outputPath), { recursive: true });
1317
- await fs.writeFile(outputPath, content);
1226
+ await fs2.mkdir(path2.dirname(outputPath), { recursive: true });
1227
+ await fs2.writeFile(outputPath, content);
1318
1228
  logger.success(`Written: ${outputPath}`);
1319
1229
  }
1320
1230
  }
@@ -1509,6 +1419,10 @@ program.command("init").description("Initialize Clafoutis configuration").option
1509
1419
  "--files <mapping>",
1510
1420
  "File mappings for consumer: asset:dest,asset:dest"
1511
1421
  ).option("--force", "Overwrite existing configuration").option("--dry-run", "Preview changes without writing files").option("--non-interactive", "Skip prompts, use defaults or flags").action(withErrorHandling(initCommand));
1422
+ program.command("format").description("Format token JSON files for consistent formatting").option("-t, --tokens <path>", "Token directory path", "./tokens").option(
1423
+ "--check",
1424
+ "Check formatting without modifying files (fails if unformatted)"
1425
+ ).option("--dry-run", "Preview changes without writing files").action(withErrorHandling(formatCommand));
1512
1426
  program.parse();
1513
1427
  //# sourceMappingURL=index.js.map
1514
1428
  //# sourceMappingURL=index.js.map