figma-token 0.1.0 → 0.1.2

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.
Files changed (3) hide show
  1. package/README.md +25 -13
  2. package/dist/index.js +324 -17
  3. package/package.json +10 -12
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # figma-token
2
2
 
3
- `figma-token` exports Figma Variables to design token files.
3
+ `figma-token` applies a Figma Plugin `tokens.json` export to a project directory.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,33 +8,45 @@
8
8
  npm install --global figma-token
9
9
  ```
10
10
 
11
- ## Help
11
+ The Figma Plugin can download all six formats directly. Use this CLI when a project needs those files written to a predictable folder and checked for changes.
12
+
13
+ ## Apply Plugin Tokens
12
14
 
13
15
  ```bash
14
- figma-token --help
15
- figma-token sync --help
16
+ figma-token ./figma-tokens.json
16
17
  ```
17
18
 
18
- ## Basic usage
19
+ This creates the following files in `./figma-token-output/`:
20
+
21
+ ```text
22
+ tokens.json
23
+ theme.ts
24
+ variables.css
25
+ tokens.scss
26
+ tailwind.css
27
+ tokens.dtcg.json
28
+ ```
19
29
 
20
- Export a local Figma Variables JSON file:
30
+ Choose a project directory with `--out`:
21
31
 
22
32
  ```bash
23
- figma-token sync --input ./figma-variables.json --format theme-ts --output ./theme.ts
33
+ figma-token ./figma-tokens.json --out ./src/tokens
24
34
  ```
25
35
 
26
- Preview token changes without writing output or snapshot files:
36
+ Preview all generated files without writing anything:
27
37
 
28
38
  ```bash
29
- figma-token sync --input ./figma-variables.json --dry-run
39
+ figma-token ./figma-tokens.json --dry-run
30
40
  ```
31
41
 
32
- `--input` accepts a Figma Variables JSON response or a normalized `tokens.json` array. Without `--input`, provide `--figma-token` and `--file-key`, or set `FIGMA_TOKEN` and `FIGMA_FILE_KEY`.
42
+ Check whether generated files are current:
33
43
 
34
- ## Options
44
+ ```bash
45
+ figma-token check ./figma-tokens.json --out ./src/tokens
46
+ ```
35
47
 
36
- `sync` supports `--input`, `--output`, `--snapshot`, `--format`, `--export-name`, `--figma-token`, `--file-key`, and `--dry-run`.
48
+ ## Advanced Sync
37
49
 
38
- Supported formats are `tokens-json`, `theme-ts`, `variables-css`, `tokens-scss`, `tailwind-css`, and `tokens-dtcg-json`.
50
+ The hidden `sync` command retains the previous single-format, snapshot, and Figma REST API flow for advanced users. It accepts `--input`, `--output`, `--snapshot`, `--format`, `--export-name`, `--figma-token`, `--file-key`, and `--dry-run`.
39
51
 
40
52
  For Plugin usage and project-wide documentation, see <https://github.com/lee090626/Project-F>.
package/dist/index.js CHANGED
@@ -4,20 +4,322 @@
4
4
  import "dotenv/config";
5
5
  import { Command, InvalidArgumentError } from "commander";
6
6
 
7
+ // src/exportTokens.ts
8
+ import { mkdir, readFile, stat, writeFile } from "fs/promises";
9
+ import { join, resolve } from "path";
10
+
11
+ // ../core/dist/index.js
12
+ var supportedTypes = ["color", "spacing", "radius", "borderWidth", "size", "fontSize", "opacity"];
13
+ var dimensionTypes = ["spacing", "radius", "borderWidth", "size", "fontSize"];
14
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
15
+ var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
16
+ var isColorValue = (value) => isRecord(value) && ["r", "g", "b", "a"].every((key) => isFiniteNumber(value[key]));
17
+ var words = (value) => value.replace(/%/g, " percent ").replace(/(^|[^A-Za-z0-9])-([0-9])/g, "$1 negative $2").replace(/([0-9])\.([0-9])/g, "$1 dot $2").replace(/([a-z0-9])([A-Z])/g, "$1 $2").match(/[A-Za-z0-9]+/g) ?? [];
18
+ function isDesignTokenArray(value) {
19
+ return Array.isArray(value) && value.every(
20
+ (token) => isRecord(token) && typeof token.name === "string" && Array.isArray(token.path) && token.path.length > 0 && token.path.every((part) => typeof part === "string") && supportedTypes.includes(token.type) && (isFiniteNumber(token.value) || isColorValue(token.value))
21
+ );
22
+ }
23
+ var tokenTypeFromName = (name, resolvedType) => {
24
+ const first = name.split("/").filter(Boolean)[0]?.toLowerCase().replace(/[-_\s]/g, "");
25
+ if (resolvedType === "COLOR") return "color";
26
+ if (resolvedType !== "FLOAT") return void 0;
27
+ if (first === "spacing") return "spacing";
28
+ if (first === "radius") return "radius";
29
+ if (first === "borderwidth") return "borderWidth";
30
+ if (first === "size") return "size";
31
+ if (first === "fontsize") return "fontSize";
32
+ if (first === "opacity") return "opacity";
33
+ return void 0;
34
+ };
35
+ var tokenTypeFromCollection = (collection, resolvedType) => {
36
+ if (resolvedType !== "FLOAT" || typeof collection?.name !== "string") return void 0;
37
+ const name = words(collection.name.toLowerCase());
38
+ if (name.includes("spacing") || name.includes("gap")) return "spacing";
39
+ if (name.includes("radius") || name.includes("corner") || name.includes("shape")) return "radius";
40
+ if (name.includes("borderwidth") || name.includes("border") && name.includes("width") || name.includes("stroke")) return "borderWidth";
41
+ if (name.includes("fontsize") || name.includes("font") && name.includes("size") || name.includes("text")) return "fontSize";
42
+ if (name.includes("size")) return "size";
43
+ if (name.includes("opacity") || name.includes("alpha")) return "opacity";
44
+ return void 0;
45
+ };
46
+ var to255 = (value) => Math.round(value <= 1 ? value * 255 : value);
47
+ var clamp = (min, value, max) => Math.min(max, Math.max(min, value));
48
+ function pickModeId(collection, values, wanted) {
49
+ if (wanted && wanted in values) return wanted;
50
+ const defaultModeId = typeof collection?.defaultModeId === "string" ? collection.defaultModeId : void 0;
51
+ if (defaultModeId && defaultModeId in values) return defaultModeId;
52
+ const modes = Array.isArray(collection?.modes) ? collection.modes.filter(isRecord) : [];
53
+ const firstModeId = modes.map((mode) => mode.modeId).find((modeId) => typeof modeId === "string" && modeId in values);
54
+ return firstModeId ?? Object.keys(values)[0];
55
+ }
56
+ function resolveValue(variables, variable, modeId, onWarning, seen = /* @__PURE__ */ new Set()) {
57
+ const values = isRecord(variable.valuesByMode) ? variable.valuesByMode : {};
58
+ const name = typeof variable.name === "string" ? variable.name : modeId;
59
+ if (!(modeId in values)) {
60
+ onWarning?.(`${name} (mode: ${modeId})`, "alias-mode-mismatch");
61
+ return void 0;
62
+ }
63
+ const value = values[modeId];
64
+ if (!isRecord(value) || value.type !== "VARIABLE_ALIAS" || typeof value.id !== "string") return value;
65
+ if (seen.has(value.id)) {
66
+ onWarning?.(name, "alias-cycle");
67
+ return void 0;
68
+ }
69
+ seen.add(value.id);
70
+ const target = variables[value.id];
71
+ if (!isRecord(target)) {
72
+ onWarning?.(name, "alias-target-missing");
73
+ return void 0;
74
+ }
75
+ return resolveValue(variables, target, modeId, onWarning, seen);
76
+ }
77
+ function normalizeValue(value, type) {
78
+ if (type === "color" && isRecord(value) && ["r", "g", "b"].every((key) => isFiniteNumber(value[key])) && (value.a === void 0 || isFiniteNumber(value.a))) {
79
+ return {
80
+ r: clamp(0, to255(value.r), 255),
81
+ g: clamp(0, to255(value.g), 255),
82
+ b: clamp(0, to255(value.b), 255),
83
+ a: clamp(0, typeof value.a === "number" ? value.a : 1, 1)
84
+ };
85
+ }
86
+ if (type !== "color" && isFiniteNumber(value)) return value;
87
+ return void 0;
88
+ }
89
+ function normalizeFigmaVariables(input, options = {}) {
90
+ if (!isRecord(input)) return [];
91
+ const root = isRecord(input.meta) ? input.meta : input;
92
+ const variables = isRecord(root.variables) ? root.variables : {};
93
+ const collections = isRecord(root.variableCollections) ? root.variableCollections : {};
94
+ const result = [];
95
+ for (const variable of Object.values(variables)) {
96
+ if (!isRecord(variable) || typeof variable.name !== "string" || variable.deletedButReferenced === true) continue;
97
+ const collection = typeof variable.variableCollectionId === "string" && isRecord(collections[variable.variableCollectionId]) ? collections[variable.variableCollectionId] : void 0;
98
+ const typeFromName = tokenTypeFromName(variable.name, variable.resolvedType);
99
+ const typeFromCollection = tokenTypeFromCollection(collection, variable.resolvedType);
100
+ const type = typeFromName ?? typeFromCollection;
101
+ if (!type) {
102
+ options.onUnsupported?.(variable.name, variable.resolvedType === "FLOAT" ? "unclassified-float" : "unsupported-type", typeof collection?.name === "string" ? collection.name : void 0);
103
+ continue;
104
+ }
105
+ const path = variable.name.split("/").filter(Boolean);
106
+ if (!path.length) continue;
107
+ const values = isRecord(variable.valuesByMode) ? variable.valuesByMode : {};
108
+ const modeId = pickModeId(collection, values, options.modeId);
109
+ if (!modeId) continue;
110
+ const warnAlias = (name, reason) => options.onAliasWarning?.(name, reason, typeof collection?.name === "string" ? collection.name : void 0);
111
+ const selectedValue = values[modeId];
112
+ const rawValue = resolveValue(variables, variable, modeId, warnAlias);
113
+ const value = normalizeValue(rawValue, type);
114
+ if (value === void 0) {
115
+ if (isRecord(selectedValue) && selectedValue.type === "VARIABLE_ALIAS" && rawValue !== void 0) warnAlias(variable.name, "alias-type-mismatch");
116
+ continue;
117
+ }
118
+ const modes = Array.isArray(collection?.modes) ? collection.modes.filter(isRecord) : [];
119
+ const mode = modes.find((candidate) => candidate.modeId === modeId);
120
+ result.push({
121
+ name: variable.name,
122
+ path: typeFromName ? path : [type, ...path],
123
+ type,
124
+ value,
125
+ ...typeof collection?.name === "string" ? { collection: collection.name } : {},
126
+ ...typeof mode?.name === "string" ? { mode: mode.name } : {},
127
+ ...typeof variable.description === "string" && variable.description ? { description: variable.description } : {}
128
+ });
129
+ }
130
+ return result.sort(compareTokens);
131
+ }
132
+ var identity = (token) => `${token.path.join("/")}\0${token.collection ?? ""}\0${token.mode ?? ""}`;
133
+ var compareTokens = (a, b) => (a.collection ?? "").localeCompare(b.collection ?? "") || (a.mode ?? "").localeCompare(b.mode ?? "") || a.path.join("/").localeCompare(b.path.join("/"));
134
+ function diffTokens(previous, current) {
135
+ const before = new Map(previous.map((token) => [identity(token), token]));
136
+ const after = new Map(current.map((token) => [identity(token), token]));
137
+ const diffs = [];
138
+ for (const [key, token] of after) {
139
+ const old = before.get(key);
140
+ if (!old) diffs.push({ type: "added", token });
141
+ else if (JSON.stringify(old) !== JSON.stringify(token)) diffs.push({ type: "changed", token });
142
+ }
143
+ for (const [key, token] of before) if (!after.has(key)) diffs.push({ type: "removed", token });
144
+ const order = { added: 0, changed: 1, removed: 2 };
145
+ return diffs.sort((a, b) => compareTokens(a.token, b.token) || order[a.type] - order[b.type]);
146
+ }
147
+ var renderTokensJson = (tokens) => `${JSON.stringify(tokens, null, 2)}
148
+ `;
149
+ var camelKey = (value, fallback = "default") => {
150
+ let key = words(value).map((word, index) => index ? word[0].toUpperCase() + word.slice(1).toLowerCase() : word.toLowerCase()).join("") || fallback;
151
+ if (!/^[A-Za-z_$]/.test(key)) key = `_${key}`;
152
+ return key;
153
+ };
154
+ var kebabKey = (value) => words(value).map((word) => word.toLowerCase()).join("-");
155
+ var generateVariableName = (token) => {
156
+ const prefix = token.type === "fontSize" ? "font-size" : token.type === "borderWidth" ? "border-width" : token.type;
157
+ const first = token.path[0]?.toLowerCase().replace(/[-_\s]/g, "");
158
+ const hasPrefix = first === prefix.replace(/-/g, "");
159
+ const rest = token.path.slice(hasPrefix ? 1 : 0).map(kebabKey).filter(Boolean).join("-");
160
+ return `${prefix}${rest ? `-${rest}` : ""}`;
161
+ };
162
+ var hex = (value) => clamp(0, Math.round(value), 255).toString(16).padStart(2, "0");
163
+ var colorHex = (value) => `#${hex(value.r)}${hex(value.g)}${hex(value.b)}${value.a < 1 ? hex(value.a * 255) : ""}`;
164
+ var formatValue = (token) => {
165
+ if (token.type === "color") return colorHex(token.value);
166
+ if (dimensionTypes.includes(token.type)) return `${token.value}px`;
167
+ return String(token.value);
168
+ };
169
+ var formatThemeValue = (token) => token.type === "opacity" ? token.value : formatValue(token);
170
+ function renderCssLike(tokens, nameFor, wrap) {
171
+ const lines = tokens.map((token) => `${wrap ? " " : ""}${nameFor(token)}: ${formatValue(token)};`);
172
+ return wrap ? `${wrap[0]}
173
+ ${lines.join("\n")}
174
+ ${wrap[1]}
175
+ ` : `${lines.join("\n")}
176
+ `;
177
+ }
178
+ var cssName = (token) => `--${generateVariableName(token)}`;
179
+ var scssName = (token) => `$${generateVariableName(token)}`;
180
+ var renderCssVariables = (tokens) => renderCssLike(tokens, cssName, [":root {", "}"]);
181
+ var renderScssVariables = (tokens) => renderCssLike(tokens, scssName);
182
+ var renderTailwindTheme = (tokens) => renderCssLike(tokens, cssName, ["@theme {", "}"]);
183
+ function dtcgValue(token) {
184
+ if (token.type === "color") return { $type: "color", $value: colorHex(token.value) };
185
+ if (dimensionTypes.includes(token.type)) return { $type: "dimension", $value: { value: token.value, unit: "px" } };
186
+ return { $type: "number", $value: token.value };
187
+ }
188
+ function renderDtcgJson(tokens) {
189
+ const root = {};
190
+ for (const token of tokens) {
191
+ let cursor = root;
192
+ token.path.forEach((part, index) => {
193
+ if (index === token.path.length - 1) {
194
+ if (part in cursor) throw new Error(`Duplicate DTCG path: ${token.path.join(".")}`);
195
+ cursor[part] = dtcgValue(token);
196
+ } else {
197
+ if (part in cursor && !isRecord(cursor[part])) throw new Error(`Duplicate DTCG path: ${token.path.join(".")}`);
198
+ cursor = cursor[part] ?? (cursor[part] = {});
199
+ }
200
+ });
201
+ }
202
+ return `${JSON.stringify(root, null, 2)}
203
+ `;
204
+ }
205
+ function renderTheme(tokens, exportName = "theme") {
206
+ if (!/^[A-Za-z_$][\w$]*$/.test(exportName)) throw new Error(`Invalid TypeScript export name: ${exportName}`);
207
+ const root = {};
208
+ for (const token of tokens) {
209
+ const path = token.path.map((part) => camelKey(part, "token"));
210
+ let cursor = root;
211
+ path.forEach((part, index) => {
212
+ if (index === path.length - 1) {
213
+ if (part in cursor) throw new Error(`Duplicate theme path: ${path.join(".")}`);
214
+ cursor[part] = formatThemeValue(token);
215
+ } else {
216
+ if (part in cursor && !isRecord(cursor[part])) throw new Error(`Duplicate theme path: ${path.join(".")}`);
217
+ cursor = cursor[part] ?? (cursor[part] = {});
218
+ }
219
+ });
220
+ }
221
+ return `export const ${exportName} = ${JSON.stringify(root, null, 2)} as const;
222
+ `;
223
+ }
224
+
225
+ // src/exportTokens.ts
226
+ var tokenFileNames = ["tokens.json", "theme.ts", "variables.css", "tokens.scss", "tailwind.css", "tokens.dtcg.json"];
227
+ function defaultOutputDirectory(cwd = process.cwd()) {
228
+ return resolve(cwd, "figma-token-output");
229
+ }
230
+ var outputDirectory = (output) => resolve(output ?? defaultOutputDirectory());
231
+ async function readPluginTokens(input) {
232
+ const inputPath = resolve(input);
233
+ let inputStat;
234
+ try {
235
+ inputStat = await stat(inputPath);
236
+ } catch (error) {
237
+ if (error.code === "ENOENT") throw new Error(`Input file not found: ${inputPath}`);
238
+ throw new Error(`Cannot read input file: ${inputPath}`);
239
+ }
240
+ if (inputStat.isDirectory()) throw new Error(`Input path is a directory: ${inputPath}`);
241
+ let raw;
242
+ try {
243
+ raw = JSON.parse(await readFile(inputPath, "utf8"));
244
+ } catch (error) {
245
+ if (error instanceof SyntaxError) throw new Error(`Invalid JSON: ${inputPath}`);
246
+ throw new Error(`Cannot read input file: ${inputPath}`);
247
+ }
248
+ if (!isDesignTokenArray(raw)) throw new Error(`Unsupported input: expected Plugin tokens.json: ${inputPath}`);
249
+ return { inputPath, tokens: raw };
250
+ }
251
+ function renderAll(tokens) {
252
+ return {
253
+ "tokens.json": renderTokensJson(tokens),
254
+ "theme.ts": renderTheme(tokens),
255
+ "variables.css": renderCssVariables(tokens),
256
+ "tokens.scss": renderScssVariables(tokens),
257
+ "tailwind.css": renderTailwindTheme(tokens),
258
+ "tokens.dtcg.json": renderDtcgJson(tokens)
259
+ };
260
+ }
261
+ async function prepare(options) {
262
+ const { inputPath, tokens } = await readPluginTokens(options.input);
263
+ const outputPath = outputDirectory(options.output);
264
+ return { inputPath, outputPath, files: renderAll(tokens) };
265
+ }
266
+ async function writeAll(outputPath, files) {
267
+ try {
268
+ await mkdir(outputPath, { recursive: true });
269
+ } catch {
270
+ throw new Error(`Cannot create output directory: ${outputPath}`);
271
+ }
272
+ for (const filename of tokenFileNames) {
273
+ const path = join(outputPath, filename);
274
+ try {
275
+ await writeFile(path, files[filename]);
276
+ } catch {
277
+ throw new Error(`Cannot write token file: ${path}`);
278
+ }
279
+ }
280
+ }
281
+ async function exportTokenFiles(options, log = console.log) {
282
+ const result = await prepare(options);
283
+ if (options.dryRun) {
284
+ log(`Input: ${result.inputPath}`);
285
+ log(`Output: ${result.outputPath}`);
286
+ log("Would generate:");
287
+ tokenFileNames.forEach((filename) => log(`- ${filename}`));
288
+ return result;
289
+ }
290
+ await writeAll(result.outputPath, result.files);
291
+ log(`Generated token files in: ${result.outputPath}`);
292
+ return result;
293
+ }
294
+ async function checkTokenFiles(options, log = console.log) {
295
+ const result = await prepare(options);
296
+ const outdated = [];
297
+ for (const filename of tokenFileNames) {
298
+ const path = join(result.outputPath, filename);
299
+ let contents;
300
+ try {
301
+ contents = await readFile(path, "utf8");
302
+ } catch (error) {
303
+ if (error.code === "ENOENT") {
304
+ outdated.push(`- ${filename}: missing`);
305
+ continue;
306
+ }
307
+ throw new Error(`Cannot read token file: ${path}`);
308
+ }
309
+ if (contents !== result.files[filename]) outdated.push(`- ${filename}: changed`);
310
+ }
311
+ if (outdated.length === 0) {
312
+ log("Token files are up to date.");
313
+ return 0;
314
+ }
315
+ log("Token files are not up to date:");
316
+ outdated.forEach((message) => log(message));
317
+ return 1;
318
+ }
319
+
7
320
  // src/sync.ts
8
- import { mkdir, readFile, writeFile } from "fs/promises";
321
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
9
322
  import { dirname } from "path";
10
- import {
11
- diffTokens,
12
- isDesignTokenArray,
13
- normalizeFigmaVariables,
14
- renderCssVariables,
15
- renderDtcgJson,
16
- renderScssVariables,
17
- renderTailwindTheme,
18
- renderTheme,
19
- renderTokensJson
20
- } from "@lee090626/core";
21
323
 
22
324
  // src/figma/fetchFigmaVariables.ts
23
325
  var FIGMA_API = "https://api.figma.com/v1";
@@ -40,7 +342,7 @@ async function fetchFigmaVariables(fileKey, token) {
40
342
  // src/sync.ts
41
343
  var readJson = async (path) => {
42
344
  try {
43
- return JSON.parse(await readFile(path, "utf8"));
345
+ return JSON.parse(await readFile2(path, "utf8"));
44
346
  } catch (error) {
45
347
  if (error instanceof SyntaxError) throw new Error(`Invalid JSON: ${path}`);
46
348
  throw error;
@@ -57,8 +359,8 @@ var readSnapshot = async (path) => {
57
359
  }
58
360
  };
59
361
  var save = async (path, contents) => {
60
- await mkdir(dirname(path), { recursive: true });
61
- await writeFile(path, contents);
362
+ await mkdir2(dirname(path), { recursive: true });
363
+ await writeFile2(path, contents);
62
364
  };
63
365
  var render = (tokens, options) => ({
64
366
  "tokens-json": renderTokensJson,
@@ -97,8 +399,13 @@ var format = (value) => {
97
399
  if (!formats.includes(value)) throw new InvalidArgumentError(`format\uC740 ${formats.join(", ")} \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
98
400
  return value;
99
401
  };
100
- var program = new Command().name("figma-token").description("Figma Variables\uB97C \uB85C\uCEEC \uB514\uC790\uC778 \uD1A0\uD070\uC73C\uB85C \uB3D9\uAE30\uD654\uD569\uB2C8\uB2E4.");
101
- program.command("sync").option("--input <path>", "\uB85C\uCEEC Figma Variables JSON").option("--output <path>", "\uCD9C\uB825 \uD30C\uC77C", "./tokens.json").option("--snapshot <path>", "snapshot \uD30C\uC77C", ".figma-token/snapshot.json").option("--format <format>", "\uCD9C\uB825 \uD3EC\uB9F7", format, "tokens-json").option("--export-name <name>", "theme.ts export \uC774\uB984", "theme").option("--figma-token <token>", "Figma token").option("--file-key <key>", "Figma file key").option("--dry-run", "\uD30C\uC77C\uC744 \uC4F0\uC9C0 \uC54A\uACE0 diff\uB9CC \uCD9C\uB825", false).action(async (options) => sync({
402
+ var program = new Command().name("figma-token").description("Plugin tokens.json\uC744 \uD504\uB85C\uC81D\uD2B8 \uD1A0\uD070 \uD30C\uC77C\uB85C \uC801\uC6A9\uD569\uB2C8\uB2E4.").version("0.1.2").argument("<input>", "Figma Plugin\uC5D0\uC11C \uB2E4\uC6B4\uB85C\uB4DC\uD55C tokens.json").option("--out <directory>", "\uCD9C\uB825 \uD3F4\uB354 (default: ./figma-token-output)").option("--dry-run", "\uD30C\uC77C\uC744 \uC4F0\uC9C0 \uC54A\uACE0 \uC0DD\uC131 \uACB0\uACFC\uB97C \uD655\uC778").action(async (input, options) => {
403
+ await exportTokenFiles({ input, output: options.out ?? defaultOutputDirectory(), dryRun: options.dryRun });
404
+ });
405
+ program.command("check").description("\uD604\uC7AC \uD1A0\uD070 \uD30C\uC77C\uC774 Plugin tokens.json\uACFC \uC77C\uCE58\uD558\uB294\uC9C0 \uD655\uC778\uD569\uB2C8\uB2E4.").argument("<input>", "Figma Plugin\uC5D0\uC11C \uB2E4\uC6B4\uB85C\uB4DC\uD55C tokens.json").option("--out <directory>", "\uCD9C\uB825 \uD3F4\uB354 (default: ./figma-token-output)").action(async (input, options) => {
406
+ process.exitCode = await checkTokenFiles({ input, output: options.out ?? defaultOutputDirectory() });
407
+ });
408
+ program.command("sync", { hidden: true }).option("--input <path>", "\uB85C\uCEEC Figma Variables JSON").option("--output <path>", "\uCD9C\uB825 \uD30C\uC77C", "./tokens.json").option("--snapshot <path>", "snapshot \uD30C\uC77C", ".figma-token/snapshot.json").option("--format <format>", "\uCD9C\uB825 \uD3EC\uB9F7", format, "tokens-json").option("--export-name <name>", "theme.ts export \uC774\uB984", "theme").option("--figma-token <token>", "Figma token").option("--file-key <key>", "Figma file key").option("--dry-run", "\uD30C\uC77C\uC744 \uC4F0\uC9C0 \uC54A\uACE0 diff\uB9CC \uCD9C\uB825", false).action(async (options) => sync({
102
409
  ...options,
103
410
  figmaToken: options.figmaToken ?? process.env.FIGMA_TOKEN,
104
411
  fileKey: options.fileKey ?? process.env.FIGMA_FILE_KEY
package/package.json CHANGED
@@ -1,31 +1,29 @@
1
1
  {
2
2
  "name": "figma-token",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Export Figma Variables to design token files.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
- "bin": {
8
- "figma-token": "dist/index.js"
9
- },
7
+ "bin": { "figma-token": "dist/index.js" },
10
8
  "repository": {
11
9
  "type": "git",
12
- "url": "https://github.com/lee090626/Project-F.git",
10
+ "url": "git+https://github.com/lee090626/Project-F.git",
13
11
  "directory": "packages/cli"
14
12
  },
15
13
  "files": [
16
14
  "dist",
17
15
  "README.md"
18
16
  ],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "test": "vitest run"
20
+ },
19
21
  "dependencies": {
20
22
  "commander": "^14.0.0",
21
- "dotenv": "^17.0.0",
22
- "@lee090626/core": "0.1.0"
23
+ "dotenv": "^17.0.0"
23
24
  },
24
25
  "devDependencies": {
26
+ "@lee090626/core": "workspace:*",
25
27
  "tsup": "^8.5.0"
26
- },
27
- "scripts": {
28
- "build": "tsup",
29
- "test": "vitest run"
30
28
  }
31
- }
29
+ }