@clafoutis/cli 1.1.2 → 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,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import * as p2 from '@clack/prompts';
3
3
  import { Command } from 'commander';
4
+ import fs from 'fs';
5
+ import path2 from 'path';
6
+ import { logger } from '@clafoutis/shared';
4
7
  import { generate } from '@clafoutis/generators/figma';
5
8
  import { generate as generate$1 } from '@clafoutis/generators/tailwind';
6
- import { logger } from '@clafoutis/shared';
7
- import path from 'path';
8
9
  import StyleDictionary from 'style-dictionary';
9
10
  import { register } from 'tsx/esm/api';
10
11
  import { fileURLToPath, pathToFileURL } from 'url';
11
12
  import { Ajv } from 'ajv';
12
- import fs from 'fs/promises';
13
- import fs2 from 'fs';
13
+ import fs2 from 'fs/promises';
14
14
  import { spawn } from 'child_process';
15
15
 
16
16
  // src/utils/errors.ts
@@ -88,7 +88,104 @@ function tokensDirNotFoundError(tokensDir) {
88
88
  );
89
89
  }
90
90
 
91
- // 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
+ }
92
189
  function validateRepo(value) {
93
190
  if (!value) {
94
191
  return "Repository is required";
@@ -163,8 +260,8 @@ function findUnknownFields(config, schemaProperties, prefix = "") {
163
260
  }
164
261
  return unknown;
165
262
  }
166
- function hasField(config, path5) {
167
- const parts = path5.split(".");
263
+ function hasField(config, path6) {
264
+ const parts = path6.split(".");
168
265
  let current = config;
169
266
  for (const part of parts) {
170
267
  if (current && typeof current === "object" && part in current) {
@@ -378,7 +475,7 @@ var log3 = {
378
475
  };
379
476
  async function readConfig(configPath) {
380
477
  try {
381
- const content = await fs.readFile(configPath, "utf-8");
478
+ const content = await fs2.readFile(configPath, "utf-8");
382
479
  return JSON.parse(content);
383
480
  } catch {
384
481
  return null;
@@ -386,7 +483,7 @@ async function readConfig(configPath) {
386
483
  }
387
484
  async function readProducerConfig(configPath) {
388
485
  try {
389
- const content = await fs.readFile(configPath, "utf-8");
486
+ const content = await fs2.readFile(configPath, "utf-8");
390
487
  return JSON.parse(content);
391
488
  } catch {
392
489
  return null;
@@ -394,7 +491,7 @@ async function readProducerConfig(configPath) {
394
491
  }
395
492
  async function fileExists(filePath) {
396
493
  try {
397
- await fs.access(filePath);
494
+ await fs2.access(filePath);
398
495
  return true;
399
496
  } catch {
400
497
  return false;
@@ -493,12 +590,12 @@ function getConsumerGitignore() {
493
590
  `;
494
591
  }
495
592
  var __filename$1 = fileURLToPath(import.meta.url);
496
- var __dirname$1 = path.dirname(__filename$1);
593
+ var __dirname$1 = path2.dirname(__filename$1);
497
594
  function getTokensDir() {
498
- const devPath = path.resolve(__dirname$1, "tokens");
499
- if (fs2.existsSync(devPath)) return devPath;
500
- const distPath = path.resolve(__dirname$1, "templates", "tokens");
501
- if (fs2.existsSync(distPath)) return distPath;
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;
502
599
  throw new Error(
503
600
  `Starter token templates not found. Searched:
504
601
  ${devPath}
@@ -507,15 +604,15 @@ function getTokensDir() {
507
604
  }
508
605
  function walkTokensDir(dir, base = "") {
509
606
  const result = [];
510
- for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
511
- const relPath = base ? path.join(base, entry.name) : entry.name;
512
- const fullPath = path.join(dir, entry.name);
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);
513
610
  if (entry.isDirectory()) {
514
611
  result.push(...walkTokensDir(fullPath, relPath));
515
612
  } else if (entry.name.endsWith(".json")) {
516
613
  result.push({
517
614
  path: relPath,
518
- content: fs2.readFileSync(fullPath, "utf-8")
615
+ content: fs.readFileSync(fullPath, "utf-8")
519
616
  });
520
617
  }
521
618
  }
@@ -554,6 +651,9 @@ jobs:
554
651
  - name: Install Clafoutis
555
652
  run: npm install -D @clafoutis/cli
556
653
 
654
+ - name: Check token file formatting
655
+ run: npx clafoutis format --tokens tokens --check
656
+
557
657
  - name: Generate tokens
558
658
  run: npx clafoutis generate
559
659
 
@@ -796,7 +896,7 @@ async function createProducerConfig(answers, force, dryRun) {
796
896
  ];
797
897
  const starterTokens = getAllStarterTokens();
798
898
  for (const token of starterTokens) {
799
- const tokenPath = path.join(answers.tokens, token.path);
899
+ const tokenPath = path2.join(answers.tokens, token.path);
800
900
  if (!force && await fileExists(tokenPath)) {
801
901
  continue;
802
902
  }
@@ -854,7 +954,7 @@ async function createConsumerConfig(answers, force, dryRun) {
854
954
  const gitignorePath = ".gitignore";
855
955
  const consumerIgnore = getConsumerGitignore();
856
956
  if (await fileExists(gitignorePath)) {
857
- const existingContent = await fs.readFile(gitignorePath, "utf-8");
957
+ const existingContent = await fs2.readFile(gitignorePath, "utf-8");
858
958
  if (!existingContent.includes(".clafoutis/cache")) {
859
959
  filesToCreate.push({
860
960
  path: gitignorePath,
@@ -890,9 +990,9 @@ function showDryRunOutput(files) {
890
990
  }
891
991
  async function writeFiles(files) {
892
992
  for (const file of files) {
893
- const dir = path.dirname(file.path);
894
- await fs.mkdir(dir, { recursive: true });
895
- await fs.writeFile(file.path, file.content);
993
+ const dir = path2.dirname(file.path);
994
+ await fs2.mkdir(dir, { recursive: true });
995
+ await fs2.writeFile(file.path, file.content);
896
996
  log3.success(`Created ${file.path}`);
897
997
  }
898
998
  }
@@ -904,8 +1004,14 @@ function showNextSteps(mode, answers) {
904
1004
  log3.message(
905
1005
  ` 1. Edit ${producerAnswers.tokens}/colors/primitives.json with your design tokens`
906
1006
  );
907
- log3.message(" 2. Run: npx clafoutis generate");
908
- 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
+ }
909
1015
  } else {
910
1016
  log3.message(" 1. Run: npx clafoutis sync");
911
1017
  }
@@ -913,7 +1019,7 @@ function showNextSteps(mode, answers) {
913
1019
 
914
1020
  // src/commands/generate.ts
915
1021
  async function loadPlugin(pluginPath) {
916
- const absolutePath = path.resolve(process.cwd(), pluginPath);
1022
+ const absolutePath = path2.resolve(process.cwd(), pluginPath);
917
1023
  if (pluginPath.endsWith(".ts")) {
918
1024
  register();
919
1025
  }
@@ -959,8 +1065,8 @@ async function generateCommand(options) {
959
1065
  if (options.output) {
960
1066
  config.output = options.output;
961
1067
  }
962
- const tokensDir = path.resolve(process.cwd(), config.tokens || "./tokens");
963
- const outputDir = path.resolve(process.cwd(), config.output || "./build");
1068
+ const tokensDir = path2.resolve(process.cwd(), config.tokens || "./tokens");
1069
+ const outputDir = path2.resolve(process.cwd(), config.output || "./build");
964
1070
  if (!await fileExists(tokensDir)) {
965
1071
  throw tokensDirNotFoundError(tokensDir);
966
1072
  }
@@ -1009,7 +1115,7 @@ async function generateCommand(options) {
1009
1115
  }
1010
1116
  const context = {
1011
1117
  tokensDir,
1012
- outputDir: path.join(outputDir, name),
1118
+ outputDir: path2.join(outputDir, name),
1013
1119
  config,
1014
1120
  StyleDictionary
1015
1121
  };
@@ -1036,7 +1142,7 @@ var CACHE_DIR = ".clafoutis";
1036
1142
  var CACHE_FILE = `${CACHE_DIR}/cache`;
1037
1143
  async function readCache() {
1038
1144
  try {
1039
- return (await fs.readFile(CACHE_FILE, "utf-8")).trim();
1145
+ return (await fs2.readFile(CACHE_FILE, "utf-8")).trim();
1040
1146
  } catch (err) {
1041
1147
  if (err instanceof Error && err.code === "ENOENT") {
1042
1148
  return null;
@@ -1045,8 +1151,8 @@ async function readCache() {
1045
1151
  }
1046
1152
  }
1047
1153
  async function writeCache(version) {
1048
- await fs.mkdir(CACHE_DIR, { recursive: true });
1049
- await fs.writeFile(CACHE_FILE, version);
1154
+ await fs2.mkdir(CACHE_DIR, { recursive: true });
1155
+ await fs2.writeFile(CACHE_FILE, version);
1050
1156
  }
1051
1157
  async function downloadRelease(config) {
1052
1158
  const token = process.env.CLAFOUTIS_REPO_TOKEN;
@@ -1116,9 +1222,9 @@ async function writeOutput(config, files) {
1116
1222
  for (const [assetName, content] of files) {
1117
1223
  const configPath = config.files[assetName];
1118
1224
  if (!configPath) continue;
1119
- const outputPath = path.resolve(process.cwd(), configPath);
1120
- await fs.mkdir(path.dirname(outputPath), { recursive: true });
1121
- await fs.writeFile(outputPath, content);
1225
+ const outputPath = path2.resolve(process.cwd(), configPath);
1226
+ await fs2.mkdir(path2.dirname(outputPath), { recursive: true });
1227
+ await fs2.writeFile(outputPath, content);
1122
1228
  logger.success(`Written: ${outputPath}`);
1123
1229
  }
1124
1230
  }
@@ -1163,7 +1269,7 @@ async function syncCommand(options) {
1163
1269
  }
1164
1270
  return;
1165
1271
  }
1166
- const resolveOutputPaths = () => Object.values(config.files).map((p5) => path.resolve(process.cwd(), p5));
1272
+ const resolveOutputPaths = () => Object.values(config.files).map((p5) => path2.resolve(process.cwd(), p5));
1167
1273
  if (!isLatest && !options.force && config.version === cachedVersion) {
1168
1274
  const outputPaths = resolveOutputPaths();
1169
1275
  const existsResults = await Promise.all(
@@ -1313,6 +1419,10 @@ program.command("init").description("Initialize Clafoutis configuration").option
1313
1419
  "--files <mapping>",
1314
1420
  "File mappings for consumer: asset:dest,asset:dest"
1315
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));
1316
1426
  program.parse();
1317
1427
  //# sourceMappingURL=index.js.map
1318
1428
  //# sourceMappingURL=index.js.map