@clafoutis/cli 1.1.2 → 1.2.1
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 +266 -73
- package/dist/index.js.map +1 -1
- package/dist/templates/tokens/colors/components.dark.json +608 -152
- package/dist/templates/tokens/colors/components.json +608 -152
- package/dist/templates/tokens/colors/semantics.dark.json +220 -55
- package/dist/templates/tokens/colors/semantics.json +220 -55
- package/dist/templates/tokens/dimensions/base.json +0 -2
- package/dist/templates/tokens/typography/base.json +0 -5
- package/package.json +1 -1
- package/src/templates/tokens/colors/components.dark.json +608 -152
- package/src/templates/tokens/colors/components.json +608 -152
- package/src/templates/tokens/colors/semantics.dark.json +220 -55
- package/src/templates/tokens/colors/semantics.json +220 -55
- package/src/templates/tokens/dimensions/base.json +0 -2
- package/src/templates/tokens/typography/base.json +0 -5
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 path 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
|
|
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,128 @@ function tokensDirNotFoundError(tokensDir) {
|
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
// src/
|
|
91
|
+
// src/utils/cwd.ts
|
|
92
|
+
function resolveCommandCwd(cwdOverride) {
|
|
93
|
+
const cwd = cwdOverride?.trim() ? cwdOverride : process.cwd();
|
|
94
|
+
return path.resolve(cwd);
|
|
95
|
+
}
|
|
96
|
+
function resolveInCwd(commandCwd, targetPath) {
|
|
97
|
+
return path.isAbsolute(targetPath) ? path.resolve(targetPath) : path.resolve(commandCwd, targetPath);
|
|
98
|
+
}
|
|
99
|
+
function displayPath(commandCwd, absolutePath) {
|
|
100
|
+
const rel = path.relative(commandCwd, absolutePath);
|
|
101
|
+
return rel && !rel.startsWith("..") ? rel : absolutePath;
|
|
102
|
+
}
|
|
103
|
+
function validateCwdOption(cwdOverride) {
|
|
104
|
+
if (cwdOverride !== void 0 && cwdOverride.trim() === "") {
|
|
105
|
+
throw new ClafoutisError(
|
|
106
|
+
"Invalid --cwd value",
|
|
107
|
+
"--cwd cannot be empty",
|
|
108
|
+
"Provide a valid directory path, for example: --cwd ./packages/design-system"
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/commands/format.ts
|
|
114
|
+
function loadTokenFiles(dirPath) {
|
|
115
|
+
const files = [];
|
|
116
|
+
function walk(dir, prefix) {
|
|
117
|
+
if (!fs.existsSync(dir)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
121
|
+
const fullPath = path.join(dir, entry.name);
|
|
122
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
123
|
+
if (entry.isDirectory()) {
|
|
124
|
+
walk(fullPath, relativePath);
|
|
125
|
+
} else if (entry.name.endsWith(".json")) {
|
|
126
|
+
try {
|
|
127
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
128
|
+
files.push({ relativePath, fullPath, content });
|
|
129
|
+
} catch (err) {
|
|
130
|
+
logger.warn(
|
|
131
|
+
`Failed to read ${relativePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
walk(dirPath, "");
|
|
138
|
+
return files;
|
|
139
|
+
}
|
|
140
|
+
function formatJson(content) {
|
|
141
|
+
const parsed = JSON.parse(content);
|
|
142
|
+
return JSON.stringify(parsed, null, 2) + "\n";
|
|
143
|
+
}
|
|
144
|
+
function formatCommand(options) {
|
|
145
|
+
validateCwdOption(options.cwd);
|
|
146
|
+
const commandCwd = resolveCommandCwd(options.cwd);
|
|
147
|
+
const tokensDir = resolveInCwd(commandCwd, options.tokens || "./tokens");
|
|
148
|
+
if (!fs.existsSync(tokensDir)) {
|
|
149
|
+
throw tokensDirNotFoundError(tokensDir);
|
|
150
|
+
}
|
|
151
|
+
logger.info(`Formatting token files in ${tokensDir}...`);
|
|
152
|
+
const files = loadTokenFiles(tokensDir);
|
|
153
|
+
const fileCount = files.length;
|
|
154
|
+
if (fileCount === 0) {
|
|
155
|
+
logger.warn(`No JSON files found in ${tokensDir}`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
let changedCount = 0;
|
|
159
|
+
const unformattedFiles = [];
|
|
160
|
+
for (const { relativePath, fullPath, content } of files) {
|
|
161
|
+
try {
|
|
162
|
+
const formatted = formatJson(content);
|
|
163
|
+
if (content !== formatted) {
|
|
164
|
+
if (options.check) {
|
|
165
|
+
unformattedFiles.push(relativePath);
|
|
166
|
+
changedCount++;
|
|
167
|
+
} else if (options.dryRun) {
|
|
168
|
+
logger.info(`Would format: ${relativePath}`);
|
|
169
|
+
changedCount++;
|
|
170
|
+
} else {
|
|
171
|
+
fs.writeFileSync(fullPath, formatted, "utf-8");
|
|
172
|
+
logger.info(`Formatted: ${relativePath}`);
|
|
173
|
+
changedCount++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} catch (err) {
|
|
177
|
+
logger.error(
|
|
178
|
+
`Failed to format ${relativePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
179
|
+
);
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (options.check) {
|
|
184
|
+
if (changedCount > 0) {
|
|
185
|
+
logger.error(
|
|
186
|
+
`${changedCount} of ${fileCount} files are not formatted correctly:`
|
|
187
|
+
);
|
|
188
|
+
for (const file of unformattedFiles) {
|
|
189
|
+
logger.error(` - ${file}`);
|
|
190
|
+
}
|
|
191
|
+
logger.info(
|
|
192
|
+
`Run 'npx clafoutis format --tokens ${tokensDir}' to fix formatting.`
|
|
193
|
+
);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
logger.success(`All ${fileCount} files are correctly formatted`);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (options.dryRun) {
|
|
200
|
+
if (changedCount > 0) {
|
|
201
|
+
logger.info(`Would format ${changedCount} of ${fileCount} files`);
|
|
202
|
+
} else {
|
|
203
|
+
logger.info(`All ${fileCount} files are already formatted`);
|
|
204
|
+
}
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (changedCount > 0) {
|
|
208
|
+
logger.success(`Formatted ${changedCount} of ${fileCount} files`);
|
|
209
|
+
} else {
|
|
210
|
+
logger.success(`All ${fileCount} files are already formatted`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
92
213
|
function validateRepo(value) {
|
|
93
214
|
if (!value) {
|
|
94
215
|
return "Repository is required";
|
|
@@ -163,8 +284,8 @@ function findUnknownFields(config, schemaProperties, prefix = "") {
|
|
|
163
284
|
}
|
|
164
285
|
return unknown;
|
|
165
286
|
}
|
|
166
|
-
function hasField(config,
|
|
167
|
-
const parts =
|
|
287
|
+
function hasField(config, path8) {
|
|
288
|
+
const parts = path8.split(".");
|
|
168
289
|
let current = config;
|
|
169
290
|
for (const part of parts) {
|
|
170
291
|
if (current && typeof current === "object" && part in current) {
|
|
@@ -378,7 +499,7 @@ var log3 = {
|
|
|
378
499
|
};
|
|
379
500
|
async function readConfig(configPath) {
|
|
380
501
|
try {
|
|
381
|
-
const content = await
|
|
502
|
+
const content = await fs2.readFile(configPath, "utf-8");
|
|
382
503
|
return JSON.parse(content);
|
|
383
504
|
} catch {
|
|
384
505
|
return null;
|
|
@@ -386,7 +507,7 @@ async function readConfig(configPath) {
|
|
|
386
507
|
}
|
|
387
508
|
async function readProducerConfig(configPath) {
|
|
388
509
|
try {
|
|
389
|
-
const content = await
|
|
510
|
+
const content = await fs2.readFile(configPath, "utf-8");
|
|
390
511
|
return JSON.parse(content);
|
|
391
512
|
} catch {
|
|
392
513
|
return null;
|
|
@@ -394,7 +515,7 @@ async function readProducerConfig(configPath) {
|
|
|
394
515
|
}
|
|
395
516
|
async function fileExists(filePath) {
|
|
396
517
|
try {
|
|
397
|
-
await
|
|
518
|
+
await fs2.access(filePath);
|
|
398
519
|
return true;
|
|
399
520
|
} catch {
|
|
400
521
|
return false;
|
|
@@ -496,9 +617,9 @@ var __filename$1 = fileURLToPath(import.meta.url);
|
|
|
496
617
|
var __dirname$1 = path.dirname(__filename$1);
|
|
497
618
|
function getTokensDir() {
|
|
498
619
|
const devPath = path.resolve(__dirname$1, "tokens");
|
|
499
|
-
if (
|
|
620
|
+
if (fs.existsSync(devPath)) return devPath;
|
|
500
621
|
const distPath = path.resolve(__dirname$1, "templates", "tokens");
|
|
501
|
-
if (
|
|
622
|
+
if (fs.existsSync(distPath)) return distPath;
|
|
502
623
|
throw new Error(
|
|
503
624
|
`Starter token templates not found. Searched:
|
|
504
625
|
${devPath}
|
|
@@ -507,7 +628,7 @@ function getTokensDir() {
|
|
|
507
628
|
}
|
|
508
629
|
function walkTokensDir(dir, base = "") {
|
|
509
630
|
const result = [];
|
|
510
|
-
for (const entry of
|
|
631
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
511
632
|
const relPath = base ? path.join(base, entry.name) : entry.name;
|
|
512
633
|
const fullPath = path.join(dir, entry.name);
|
|
513
634
|
if (entry.isDirectory()) {
|
|
@@ -515,7 +636,7 @@ function walkTokensDir(dir, base = "") {
|
|
|
515
636
|
} else if (entry.name.endsWith(".json")) {
|
|
516
637
|
result.push({
|
|
517
638
|
path: relPath,
|
|
518
|
-
content:
|
|
639
|
+
content: fs.readFileSync(fullPath, "utf-8")
|
|
519
640
|
});
|
|
520
641
|
}
|
|
521
642
|
}
|
|
@@ -535,6 +656,7 @@ on:
|
|
|
535
656
|
branches: [main]
|
|
536
657
|
paths:
|
|
537
658
|
- 'tokens/**'
|
|
659
|
+
- '.clafoutis/producer.json'
|
|
538
660
|
|
|
539
661
|
jobs:
|
|
540
662
|
release:
|
|
@@ -554,9 +676,24 @@ jobs:
|
|
|
554
676
|
- name: Install Clafoutis
|
|
555
677
|
run: npm install -D @clafoutis/cli
|
|
556
678
|
|
|
679
|
+
- name: Check token file formatting
|
|
680
|
+
run: npx clafoutis format --tokens tokens --check
|
|
681
|
+
|
|
557
682
|
- name: Generate tokens
|
|
558
683
|
run: npx clafoutis generate
|
|
559
684
|
|
|
685
|
+
- name: Commit generated build artifacts
|
|
686
|
+
run: |
|
|
687
|
+
if [ -z "$(git status --porcelain build)" ]; then
|
|
688
|
+
echo "No build changes to commit"
|
|
689
|
+
exit 0
|
|
690
|
+
fi
|
|
691
|
+
git config user.name "github-actions[bot]"
|
|
692
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
693
|
+
git add build
|
|
694
|
+
git commit -m "chore: update generated build artifacts"
|
|
695
|
+
git push
|
|
696
|
+
|
|
560
697
|
- name: Get next version
|
|
561
698
|
id: version
|
|
562
699
|
run: |
|
|
@@ -640,15 +777,17 @@ async function initCommand(options) {
|
|
|
640
777
|
"Choose one: --producer for design system repos, --consumer for application repos"
|
|
641
778
|
);
|
|
642
779
|
}
|
|
780
|
+
validateCwdOption(options.cwd);
|
|
781
|
+
const commandCwd = resolveCommandCwd(options.cwd);
|
|
643
782
|
const isInteractive = !options.nonInteractive && process.stdin.isTTY;
|
|
644
783
|
const isDryRun = options.dryRun ?? false;
|
|
645
784
|
if (isInteractive) {
|
|
646
|
-
await runInteractiveInit(options, isDryRun);
|
|
785
|
+
await runInteractiveInit(options, isDryRun, commandCwd);
|
|
647
786
|
} else {
|
|
648
|
-
await runNonInteractiveInit(options, isDryRun);
|
|
787
|
+
await runNonInteractiveInit(options, isDryRun, commandCwd);
|
|
649
788
|
}
|
|
650
789
|
}
|
|
651
|
-
async function runInteractiveInit(options, isDryRun) {
|
|
790
|
+
async function runInteractiveInit(options, isDryRun, commandCwd) {
|
|
652
791
|
showIntro(isDryRun);
|
|
653
792
|
let mode;
|
|
654
793
|
if (options.producer) {
|
|
@@ -668,13 +807,23 @@ async function runInteractiveInit(options, isDryRun) {
|
|
|
668
807
|
if (!answers) {
|
|
669
808
|
return;
|
|
670
809
|
}
|
|
671
|
-
await createProducerConfig(
|
|
810
|
+
await createProducerConfig(
|
|
811
|
+
answers,
|
|
812
|
+
options.force ?? false,
|
|
813
|
+
isDryRun,
|
|
814
|
+
commandCwd
|
|
815
|
+
);
|
|
672
816
|
} else {
|
|
673
817
|
const answers = await runConsumerWizard();
|
|
674
818
|
if (!answers) {
|
|
675
819
|
return;
|
|
676
820
|
}
|
|
677
|
-
await createConsumerConfig(
|
|
821
|
+
await createConsumerConfig(
|
|
822
|
+
answers,
|
|
823
|
+
options.force ?? false,
|
|
824
|
+
isDryRun,
|
|
825
|
+
commandCwd
|
|
826
|
+
);
|
|
678
827
|
}
|
|
679
828
|
if (isDryRun) {
|
|
680
829
|
showOutro("No files were written. Remove --dry-run to apply changes.");
|
|
@@ -682,7 +831,7 @@ async function runInteractiveInit(options, isDryRun) {
|
|
|
682
831
|
showOutro("Setup complete!");
|
|
683
832
|
}
|
|
684
833
|
}
|
|
685
|
-
async function runNonInteractiveInit(options, isDryRun) {
|
|
834
|
+
async function runNonInteractiveInit(options, isDryRun, commandCwd) {
|
|
686
835
|
if (!options.producer && !options.consumer) {
|
|
687
836
|
throw new ClafoutisError(
|
|
688
837
|
"Mode required",
|
|
@@ -705,7 +854,12 @@ async function runNonInteractiveInit(options, isDryRun) {
|
|
|
705
854
|
output: options.output ?? "./build",
|
|
706
855
|
workflow: options.workflow ?? true
|
|
707
856
|
};
|
|
708
|
-
await createProducerConfig(
|
|
857
|
+
await createProducerConfig(
|
|
858
|
+
answers,
|
|
859
|
+
options.force ?? false,
|
|
860
|
+
isDryRun,
|
|
861
|
+
commandCwd
|
|
862
|
+
);
|
|
709
863
|
} else {
|
|
710
864
|
const errors = validateConsumerFlags(options);
|
|
711
865
|
if (errors.length > 0) {
|
|
@@ -759,11 +913,16 @@ async function runNonInteractiveInit(options, isDryRun) {
|
|
|
759
913
|
repo: options.repo,
|
|
760
914
|
files
|
|
761
915
|
};
|
|
762
|
-
await createConsumerConfig(
|
|
916
|
+
await createConsumerConfig(
|
|
917
|
+
answers,
|
|
918
|
+
options.force ?? false,
|
|
919
|
+
isDryRun,
|
|
920
|
+
commandCwd
|
|
921
|
+
);
|
|
763
922
|
}
|
|
764
923
|
}
|
|
765
|
-
async function createProducerConfig(answers, force, dryRun) {
|
|
766
|
-
const configPath = ".clafoutis/producer.json";
|
|
924
|
+
async function createProducerConfig(answers, force, dryRun, commandCwd) {
|
|
925
|
+
const configPath = resolveInCwd(commandCwd, ".clafoutis/producer.json");
|
|
767
926
|
if (!force && await fileExists(configPath)) {
|
|
768
927
|
throw new ClafoutisError(
|
|
769
928
|
"Configuration already exists",
|
|
@@ -796,7 +955,10 @@ async function createProducerConfig(answers, force, dryRun) {
|
|
|
796
955
|
];
|
|
797
956
|
const starterTokens = getAllStarterTokens();
|
|
798
957
|
for (const token of starterTokens) {
|
|
799
|
-
const tokenPath =
|
|
958
|
+
const tokenPath = resolveInCwd(
|
|
959
|
+
commandCwd,
|
|
960
|
+
path.join(answers.tokens, token.path)
|
|
961
|
+
);
|
|
800
962
|
if (!force && await fileExists(tokenPath)) {
|
|
801
963
|
continue;
|
|
802
964
|
}
|
|
@@ -807,7 +969,7 @@ async function createProducerConfig(answers, force, dryRun) {
|
|
|
807
969
|
});
|
|
808
970
|
}
|
|
809
971
|
if (answers.workflow) {
|
|
810
|
-
const workflowPath = getWorkflowPath();
|
|
972
|
+
const workflowPath = resolveInCwd(commandCwd, getWorkflowPath());
|
|
811
973
|
if (force || !await fileExists(workflowPath)) {
|
|
812
974
|
filesToCreate.push({
|
|
813
975
|
path: workflowPath,
|
|
@@ -816,22 +978,23 @@ async function createProducerConfig(answers, force, dryRun) {
|
|
|
816
978
|
});
|
|
817
979
|
}
|
|
818
980
|
}
|
|
819
|
-
|
|
981
|
+
const gitignorePath = resolveInCwd(commandCwd, ".gitignore");
|
|
982
|
+
if (force || !await fileExists(gitignorePath)) {
|
|
820
983
|
filesToCreate.push({
|
|
821
|
-
path:
|
|
984
|
+
path: gitignorePath,
|
|
822
985
|
content: getProducerGitignore(),
|
|
823
986
|
description: "Ignore build artifacts and release-assets"
|
|
824
987
|
});
|
|
825
988
|
}
|
|
826
989
|
if (dryRun) {
|
|
827
|
-
showDryRunOutput(filesToCreate);
|
|
990
|
+
showDryRunOutput(filesToCreate, commandCwd);
|
|
828
991
|
} else {
|
|
829
|
-
await writeFiles(filesToCreate);
|
|
992
|
+
await writeFiles(filesToCreate, commandCwd);
|
|
830
993
|
showNextSteps("producer", answers);
|
|
831
994
|
}
|
|
832
995
|
}
|
|
833
|
-
async function createConsumerConfig(answers, force, dryRun) {
|
|
834
|
-
const configPath = ".clafoutis/consumer.json";
|
|
996
|
+
async function createConsumerConfig(answers, force, dryRun, commandCwd) {
|
|
997
|
+
const configPath = resolveInCwd(commandCwd, ".clafoutis/consumer.json");
|
|
835
998
|
if (!force && await fileExists(configPath)) {
|
|
836
999
|
throw new ClafoutisError(
|
|
837
1000
|
"Configuration already exists",
|
|
@@ -851,10 +1014,10 @@ async function createConsumerConfig(answers, force, dryRun) {
|
|
|
851
1014
|
description: `repo: "${answers.repo}"`
|
|
852
1015
|
}
|
|
853
1016
|
];
|
|
854
|
-
const gitignorePath = ".gitignore";
|
|
1017
|
+
const gitignorePath = resolveInCwd(commandCwd, ".gitignore");
|
|
855
1018
|
const consumerIgnore = getConsumerGitignore();
|
|
856
1019
|
if (await fileExists(gitignorePath)) {
|
|
857
|
-
const existingContent = await
|
|
1020
|
+
const existingContent = await fs2.readFile(gitignorePath, "utf-8");
|
|
858
1021
|
if (!existingContent.includes(".clafoutis/cache")) {
|
|
859
1022
|
filesToCreate.push({
|
|
860
1023
|
path: gitignorePath,
|
|
@@ -870,30 +1033,30 @@ async function createConsumerConfig(answers, force, dryRun) {
|
|
|
870
1033
|
});
|
|
871
1034
|
}
|
|
872
1035
|
if (dryRun) {
|
|
873
|
-
showDryRunOutput(filesToCreate);
|
|
1036
|
+
showDryRunOutput(filesToCreate, commandCwd);
|
|
874
1037
|
} else {
|
|
875
|
-
await writeFiles(filesToCreate);
|
|
1038
|
+
await writeFiles(filesToCreate, commandCwd);
|
|
876
1039
|
showNextSteps("consumer", answers);
|
|
877
1040
|
}
|
|
878
1041
|
}
|
|
879
|
-
function showDryRunOutput(files) {
|
|
1042
|
+
function showDryRunOutput(files, commandCwd) {
|
|
880
1043
|
log3.message("");
|
|
881
1044
|
log3.step("Would create the following files:");
|
|
882
1045
|
log3.message("");
|
|
883
1046
|
for (const file of files) {
|
|
884
|
-
log3.message(` ${file.path}`);
|
|
1047
|
+
log3.message(` ${displayPath(commandCwd, file.path)}`);
|
|
885
1048
|
if (file.description) {
|
|
886
1049
|
log3.message(` \u2514\u2500 ${file.description}`);
|
|
887
1050
|
}
|
|
888
1051
|
}
|
|
889
1052
|
log3.message("");
|
|
890
1053
|
}
|
|
891
|
-
async function writeFiles(files) {
|
|
1054
|
+
async function writeFiles(files, commandCwd) {
|
|
892
1055
|
for (const file of files) {
|
|
893
1056
|
const dir = path.dirname(file.path);
|
|
894
|
-
await
|
|
895
|
-
await
|
|
896
|
-
log3.success(`Created ${file.path}`);
|
|
1057
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
1058
|
+
await fs2.writeFile(file.path, file.content);
|
|
1059
|
+
log3.success(`Created ${displayPath(commandCwd, file.path)}`);
|
|
897
1060
|
}
|
|
898
1061
|
}
|
|
899
1062
|
function showNextSteps(mode, answers) {
|
|
@@ -904,23 +1067,34 @@ function showNextSteps(mode, answers) {
|
|
|
904
1067
|
log3.message(
|
|
905
1068
|
` 1. Edit ${producerAnswers.tokens}/colors/primitives.json with your design tokens`
|
|
906
1069
|
);
|
|
907
|
-
|
|
908
|
-
|
|
1070
|
+
if (producerAnswers.workflow) {
|
|
1071
|
+
log3.message(
|
|
1072
|
+
" 2. Push to GitHub - the workflow will generate outputs and create a release automatically"
|
|
1073
|
+
);
|
|
1074
|
+
} else {
|
|
1075
|
+
log3.message(" 2. Run: npx clafoutis generate");
|
|
1076
|
+
log3.message(" 3. Push to GitHub to share your design system");
|
|
1077
|
+
}
|
|
909
1078
|
} else {
|
|
910
1079
|
log3.message(" 1. Run: npx clafoutis sync");
|
|
911
1080
|
}
|
|
912
1081
|
}
|
|
913
1082
|
|
|
914
1083
|
// src/commands/generate.ts
|
|
915
|
-
async function loadPlugin(pluginPath) {
|
|
916
|
-
const absolutePath =
|
|
1084
|
+
async function loadPlugin(pluginPath, commandCwd) {
|
|
1085
|
+
const absolutePath = resolveInCwd(commandCwd, pluginPath);
|
|
917
1086
|
if (pluginPath.endsWith(".ts")) {
|
|
918
1087
|
register();
|
|
919
1088
|
}
|
|
920
1089
|
return import(pathToFileURL(absolutePath).href);
|
|
921
1090
|
}
|
|
922
1091
|
async function generateCommand(options) {
|
|
923
|
-
|
|
1092
|
+
validateCwdOption(options.cwd);
|
|
1093
|
+
const commandCwd = resolveCommandCwd(options.cwd);
|
|
1094
|
+
const configPath = resolveInCwd(
|
|
1095
|
+
commandCwd,
|
|
1096
|
+
options.config || ".clafoutis/producer.json"
|
|
1097
|
+
);
|
|
924
1098
|
let config = await readProducerConfig(configPath);
|
|
925
1099
|
if (!config) {
|
|
926
1100
|
if (await fileExists(configPath)) {
|
|
@@ -933,7 +1107,7 @@ async function generateCommand(options) {
|
|
|
933
1107
|
if (process.stdin.isTTY) {
|
|
934
1108
|
const shouldRunWizard = await offerWizard("producer");
|
|
935
1109
|
if (shouldRunWizard) {
|
|
936
|
-
await initCommand({ producer: true });
|
|
1110
|
+
await initCommand({ producer: true, cwd: commandCwd });
|
|
937
1111
|
config = await readProducerConfig(configPath);
|
|
938
1112
|
if (!config) {
|
|
939
1113
|
throw configNotFoundError(configPath, false);
|
|
@@ -959,8 +1133,8 @@ async function generateCommand(options) {
|
|
|
959
1133
|
if (options.output) {
|
|
960
1134
|
config.output = options.output;
|
|
961
1135
|
}
|
|
962
|
-
const tokensDir =
|
|
963
|
-
const outputDir =
|
|
1136
|
+
const tokensDir = resolveInCwd(commandCwd, config.tokens || "./tokens");
|
|
1137
|
+
const outputDir = resolveInCwd(commandCwd, config.output || "./build");
|
|
964
1138
|
if (!await fileExists(tokensDir)) {
|
|
965
1139
|
throw tokensDirNotFoundError(tokensDir);
|
|
966
1140
|
}
|
|
@@ -986,7 +1160,7 @@ async function generateCommand(options) {
|
|
|
986
1160
|
let generatorModule;
|
|
987
1161
|
if (typeof value === "string") {
|
|
988
1162
|
try {
|
|
989
|
-
generatorModule = await loadPlugin(value);
|
|
1163
|
+
generatorModule = await loadPlugin(value, commandCwd);
|
|
990
1164
|
} catch (err) {
|
|
991
1165
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
992
1166
|
throw pluginLoadError(value, errorMessage);
|
|
@@ -1033,10 +1207,18 @@ async function generateCommand(options) {
|
|
|
1033
1207
|
logger.success("Generation complete");
|
|
1034
1208
|
}
|
|
1035
1209
|
var CACHE_DIR = ".clafoutis";
|
|
1036
|
-
var CACHE_FILE =
|
|
1037
|
-
|
|
1210
|
+
var CACHE_FILE = "cache";
|
|
1211
|
+
function getCachePaths(commandCwd) {
|
|
1212
|
+
const dir = path.resolve(commandCwd, CACHE_DIR);
|
|
1213
|
+
return {
|
|
1214
|
+
dir,
|
|
1215
|
+
file: path.join(dir, CACHE_FILE)
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
async function readCache(commandCwd = process.cwd()) {
|
|
1219
|
+
const { file } = getCachePaths(commandCwd);
|
|
1038
1220
|
try {
|
|
1039
|
-
return (await
|
|
1221
|
+
return (await fs2.readFile(file, "utf-8")).trim();
|
|
1040
1222
|
} catch (err) {
|
|
1041
1223
|
if (err instanceof Error && err.code === "ENOENT") {
|
|
1042
1224
|
return null;
|
|
@@ -1044,9 +1226,10 @@ async function readCache() {
|
|
|
1044
1226
|
throw err;
|
|
1045
1227
|
}
|
|
1046
1228
|
}
|
|
1047
|
-
async function writeCache(version) {
|
|
1048
|
-
|
|
1049
|
-
await
|
|
1229
|
+
async function writeCache(version, commandCwd = process.cwd()) {
|
|
1230
|
+
const { dir, file } = getCachePaths(commandCwd);
|
|
1231
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
1232
|
+
await fs2.writeFile(file, version);
|
|
1050
1233
|
}
|
|
1051
1234
|
async function downloadRelease(config) {
|
|
1052
1235
|
const token = process.env.CLAFOUTIS_REPO_TOKEN;
|
|
@@ -1112,18 +1295,23 @@ async function downloadRelease(config) {
|
|
|
1112
1295
|
}
|
|
1113
1296
|
|
|
1114
1297
|
// src/commands/sync.ts
|
|
1115
|
-
async function writeOutput(config, files) {
|
|
1298
|
+
async function writeOutput(config, files, commandCwd) {
|
|
1116
1299
|
for (const [assetName, content] of files) {
|
|
1117
1300
|
const configPath = config.files[assetName];
|
|
1118
1301
|
if (!configPath) continue;
|
|
1119
|
-
const outputPath =
|
|
1120
|
-
await
|
|
1121
|
-
await
|
|
1302
|
+
const outputPath = resolveInCwd(commandCwd, configPath);
|
|
1303
|
+
await fs2.mkdir(path.dirname(outputPath), { recursive: true });
|
|
1304
|
+
await fs2.writeFile(outputPath, content);
|
|
1122
1305
|
logger.success(`Written: ${outputPath}`);
|
|
1123
1306
|
}
|
|
1124
1307
|
}
|
|
1125
1308
|
async function syncCommand(options) {
|
|
1126
|
-
|
|
1309
|
+
validateCwdOption(options.cwd);
|
|
1310
|
+
const commandCwd = resolveCommandCwd(options.cwd);
|
|
1311
|
+
const configPath = resolveInCwd(
|
|
1312
|
+
commandCwd,
|
|
1313
|
+
options.config || ".clafoutis/consumer.json"
|
|
1314
|
+
);
|
|
1127
1315
|
let config = await readConfig(configPath);
|
|
1128
1316
|
if (!config) {
|
|
1129
1317
|
if (await fileExists(configPath)) {
|
|
@@ -1136,7 +1324,7 @@ async function syncCommand(options) {
|
|
|
1136
1324
|
if (process.stdin.isTTY) {
|
|
1137
1325
|
const shouldRunWizard = await offerWizard("consumer");
|
|
1138
1326
|
if (shouldRunWizard) {
|
|
1139
|
-
await initCommand({ consumer: true });
|
|
1327
|
+
await initCommand({ consumer: true, cwd: commandCwd });
|
|
1140
1328
|
config = await readConfig(configPath);
|
|
1141
1329
|
if (!config) {
|
|
1142
1330
|
throw configNotFoundError(configPath, true);
|
|
@@ -1149,7 +1337,7 @@ async function syncCommand(options) {
|
|
|
1149
1337
|
}
|
|
1150
1338
|
}
|
|
1151
1339
|
validateConsumerConfig(config);
|
|
1152
|
-
const cachedVersion = await readCache();
|
|
1340
|
+
const cachedVersion = await readCache(commandCwd);
|
|
1153
1341
|
const isLatest = config.version === "latest";
|
|
1154
1342
|
logger.info(`Repo: ${config.repo}`);
|
|
1155
1343
|
logger.info(`Pinned: ${config.version}`);
|
|
@@ -1163,7 +1351,7 @@ async function syncCommand(options) {
|
|
|
1163
1351
|
}
|
|
1164
1352
|
return;
|
|
1165
1353
|
}
|
|
1166
|
-
const resolveOutputPaths = () => Object.values(config.files).map((p5) =>
|
|
1354
|
+
const resolveOutputPaths = () => Object.values(config.files).map((p5) => resolveInCwd(commandCwd, p5));
|
|
1167
1355
|
if (!isLatest && !options.force && config.version === cachedVersion) {
|
|
1168
1356
|
const outputPaths = resolveOutputPaths();
|
|
1169
1357
|
const existsResults = await Promise.all(
|
|
@@ -1188,14 +1376,14 @@ async function syncCommand(options) {
|
|
|
1188
1376
|
return;
|
|
1189
1377
|
}
|
|
1190
1378
|
}
|
|
1191
|
-
await writeOutput(config, files);
|
|
1192
|
-
await writeCache(resolvedTag);
|
|
1379
|
+
await writeOutput(config, files, commandCwd);
|
|
1380
|
+
await writeCache(resolvedTag, commandCwd);
|
|
1193
1381
|
logger.success(`Synced to ${resolvedTag}`);
|
|
1194
1382
|
if (config.postSync) {
|
|
1195
|
-
await runPostSync(config.postSync);
|
|
1383
|
+
await runPostSync(config.postSync, commandCwd);
|
|
1196
1384
|
}
|
|
1197
1385
|
}
|
|
1198
|
-
async function runPostSync(command) {
|
|
1386
|
+
async function runPostSync(command, commandCwd) {
|
|
1199
1387
|
logger.info(`Running postSync: ${command}`);
|
|
1200
1388
|
const isWindows = process.platform === "win32";
|
|
1201
1389
|
const shell = isWindows ? "cmd.exe" : "/bin/sh";
|
|
@@ -1203,7 +1391,8 @@ async function runPostSync(command) {
|
|
|
1203
1391
|
return new Promise((resolve, reject) => {
|
|
1204
1392
|
const child = spawn(shell, shellArgs, {
|
|
1205
1393
|
stdio: ["inherit", "pipe", "pipe"],
|
|
1206
|
-
env: process.env
|
|
1394
|
+
env: process.env,
|
|
1395
|
+
cwd: commandCwd
|
|
1207
1396
|
});
|
|
1208
1397
|
let stdout = "";
|
|
1209
1398
|
let stderr = "";
|
|
@@ -1300,19 +1489,23 @@ program.command("generate").description("Generate platform outputs from design t
|
|
|
1300
1489
|
"-c, --config <path>",
|
|
1301
1490
|
"Path to config file",
|
|
1302
1491
|
".clafoutis/producer.json"
|
|
1303
|
-
).option("--tailwind", "Generate Tailwind output").option("--figma", "Generate Figma variables").option("-o, --output <dir>", "Output directory", "./build").option("--dry-run", "Preview changes without writing files").action(withErrorHandling(generateCommand));
|
|
1492
|
+
).option("--tailwind", "Generate Tailwind output").option("--figma", "Generate Figma variables").option("-o, --output <dir>", "Output directory", "./build").option("--cwd <path>", "Run command as if from this directory").option("--dry-run", "Preview changes without writing files").action(withErrorHandling(generateCommand));
|
|
1304
1493
|
program.command("sync").description("Sync design tokens from GitHub Release (for consumers)").option("-f, --force", "Force sync even if versions match").option(
|
|
1305
1494
|
"-c, --config <path>",
|
|
1306
1495
|
"Path to config file",
|
|
1307
1496
|
".clafoutis/consumer.json"
|
|
1308
|
-
).option("--dry-run", "Preview changes without writing files").action(withErrorHandling(syncCommand));
|
|
1497
|
+
).option("--cwd <path>", "Run command as if from this directory").option("--dry-run", "Preview changes without writing files").action(withErrorHandling(syncCommand));
|
|
1309
1498
|
program.command("init").description("Initialize Clafoutis configuration").option("--producer", "Set up as a design token producer").option("--consumer", "Set up as a design token consumer").option("-r, --repo <repo>", "GitHub repo for consumer mode (org/name)").option("-t, --tokens <path>", "Token directory path (default: ./tokens)").option("-o, --output <path>", "Output directory path (default: ./build)").option(
|
|
1310
1499
|
"-g, --generators <list>",
|
|
1311
1500
|
"Comma-separated generators: tailwind, figma"
|
|
1312
1501
|
).option("--workflow", "Create GitHub Actions workflow (default: true)").option("--no-workflow", "Skip GitHub Actions workflow").option(
|
|
1313
1502
|
"--files <mapping>",
|
|
1314
1503
|
"File mappings for consumer: asset:dest,asset:dest"
|
|
1315
|
-
).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));
|
|
1504
|
+
).option("--force", "Overwrite existing configuration").option("--dry-run", "Preview changes without writing files").option("--non-interactive", "Skip prompts, use defaults or flags").option("--cwd <path>", "Run command as if from this directory").action(withErrorHandling(initCommand));
|
|
1505
|
+
program.command("format").description("Format token JSON files for consistent formatting").option("-t, --tokens <path>", "Token directory path", "./tokens").option(
|
|
1506
|
+
"--check",
|
|
1507
|
+
"Check formatting without modifying files (fails if unformatted)"
|
|
1508
|
+
).option("--cwd <path>", "Run command as if from this directory").option("--dry-run", "Preview changes without writing files").action(withErrorHandling(formatCommand));
|
|
1316
1509
|
program.parse();
|
|
1317
1510
|
//# sourceMappingURL=index.js.map
|
|
1318
1511
|
//# sourceMappingURL=index.js.map
|