@styleframe/cli 4.0.0 → 4.1.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/CHANGELOG.md +43 -0
- package/dist/build-CBdsifMN.js +47 -0
- package/dist/build-D7cinF0l.cjs +50 -0
- package/dist/build-dtcg-BpbjBRCf.js +429 -0
- package/dist/build-dtcg-vuGHy-Sl.cjs +434 -0
- package/dist/chunk-D6vf50IK.cjs +28 -0
- package/dist/dtcg-D1-iITOr.js +14 -0
- package/dist/dtcg-D84AfyzO.cjs +13 -0
- package/dist/export-CBdPGGEq.js +66 -0
- package/dist/export-DmPAU9Wh.cjs +69 -0
- package/dist/export-ONk9eKoZ.cjs +86 -0
- package/dist/export-suUS16eO.js +83 -0
- package/dist/figma-BvXoqRPU.cjs +13 -0
- package/dist/figma-D2RJh56T.js +14 -0
- package/dist/import-BQrcHNjK.cjs +126 -0
- package/dist/import-Bll_uBvJ.js +123 -0
- package/dist/import-MqLYxb8d.js +114 -0
- package/dist/import-ibQc_GXm.cjs +117 -0
- package/dist/index.cjs +16 -16
- package/dist/index.d.cts +4 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.js +16 -17
- package/dist/init-CAO0mA_w.js +262 -0
- package/dist/init-CaJoUVv2.cjs +265 -0
- package/package.json +22 -20
- package/dist/build-BIWOTFZD.cjs +0 -49
- package/dist/build-CANA04j1.js +0 -49
- package/dist/export-BMneJTdq.cjs +0 -517
- package/dist/export-Cx6awh55.js +0 -517
- package/dist/import-BLbc2zWU.cjs +0 -90
- package/dist/import-a5DtGEAY.js +0 -90
- package/dist/index-BTHfb82h.js +0 -14
- package/dist/index-jMzviwjD.cjs +0 -14
- package/dist/init-CKeTXHp5.js +0 -234
- package/dist/init-CO7VnQKe.cjs +0 -234
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-D6vf50IK.cjs");
|
|
2
|
+
const require_build_dtcg = require("./build-dtcg-vuGHy-Sl.cjs");
|
|
3
|
+
let citty = require("citty");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
node_path = require_chunk.__toESM(node_path, 1);
|
|
6
|
+
let _styleframe_loader = require("@styleframe/loader");
|
|
7
|
+
let consola = require("consola");
|
|
8
|
+
consola = require_chunk.__toESM(consola, 1);
|
|
9
|
+
let node_fs_promises = require("node:fs/promises");
|
|
10
|
+
let _styleframe_figma = require("@styleframe/figma");
|
|
11
|
+
//#region src/commands/figma/export.ts
|
|
12
|
+
var export_default = (0, citty.defineCommand)({
|
|
13
|
+
meta: {
|
|
14
|
+
name: "export",
|
|
15
|
+
description: "Export Styleframe variables to Figma-compatible DTCG format (per-mode files)"
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
config: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Path to the Styleframe config file",
|
|
21
|
+
default: "styleframe.config.ts",
|
|
22
|
+
alias: ["c"],
|
|
23
|
+
valueHint: "path"
|
|
24
|
+
},
|
|
25
|
+
output: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Output directory for per-mode .tokens.json files",
|
|
28
|
+
default: ".",
|
|
29
|
+
alias: ["o"],
|
|
30
|
+
valueHint: "path"
|
|
31
|
+
},
|
|
32
|
+
collection: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Collection name embedded in the export",
|
|
35
|
+
default: "Design Tokens",
|
|
36
|
+
alias: ["n", "name"]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
async run({ args }) {
|
|
40
|
+
const configPath = node_path.default.resolve(args.config);
|
|
41
|
+
const outputDir = node_path.default.resolve(args.output);
|
|
42
|
+
consola.default.info(`Loading configuration from "${node_path.default.relative(process.cwd(), configPath)}"...`);
|
|
43
|
+
const root = (await (0, _styleframe_loader.loadConfiguration)({ entry: configPath })).root;
|
|
44
|
+
consola.default.info("Building DTCG document...");
|
|
45
|
+
const { tokens, resolver, diagnostics: buildDiags, emittedCount, fluidNormalisedCount, maxViewport } = require_build_dtcg.buildDTCG(root, { collectionName: args.collection });
|
|
46
|
+
consola.default.info("Flattening to Figma-compatible format...");
|
|
47
|
+
const { modes, diagnostics: flattenDiags } = await (0, _styleframe_figma.flattenToFigmaDTCG)(tokens, resolver);
|
|
48
|
+
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
49
|
+
for (const [modeName, modeDoc] of Object.entries(modes)) {
|
|
50
|
+
const filename = `${modeName}.tokens.json`;
|
|
51
|
+
const filePath = node_path.default.join(outputDir, filename);
|
|
52
|
+
await (0, node_fs_promises.writeFile)(filePath, `${JSON.stringify(modeDoc, null, 2)}\n`);
|
|
53
|
+
const tokenCount = countTokens(modeDoc);
|
|
54
|
+
consola.default.info(`Wrote "${node_path.default.relative(process.cwd(), filePath)}" (${tokenCount} tokens)`);
|
|
55
|
+
}
|
|
56
|
+
if (fluidNormalisedCount > 0) consola.default.info(`Normalised ${fluidNormalisedCount} fluid token(s) using max viewport (${maxViewport}px).`);
|
|
57
|
+
const allDiags = [...buildDiags.filter((d) => d.level === "warn"), ...flattenDiags];
|
|
58
|
+
if (allDiags.length > 0) {
|
|
59
|
+
consola.default.warn(`${allDiags.length} token(s) needed special handling`);
|
|
60
|
+
for (const d of allDiags.slice(0, 10)) {
|
|
61
|
+
const name = "name" in d ? d.name : d.path;
|
|
62
|
+
const reason = "reason" in d ? d.reason : "";
|
|
63
|
+
consola.default.info(` - ${name}: ${reason}`);
|
|
64
|
+
}
|
|
65
|
+
if (allDiags.length > 10) consola.default.info(` ... and ${allDiags.length - 10} more`);
|
|
66
|
+
}
|
|
67
|
+
consola.default.success(`Exported ${emittedCount} tokens as ${Object.keys(modes).length} Figma-compatible mode file(s) to "${node_path.default.relative(process.cwd(), outputDir)}"`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
function countTokens(doc) {
|
|
71
|
+
let count = 0;
|
|
72
|
+
for (const [key, value] of Object.entries(doc)) {
|
|
73
|
+
if (key === "$root") {
|
|
74
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && "$value" in value) count++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (key.startsWith("$")) continue;
|
|
78
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
79
|
+
if ("$value" in value) count++;
|
|
80
|
+
count += countTokens(value);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return count;
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
exports.default = export_default;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { t as buildDTCG } from "./build-dtcg-BpbjBRCf.js";
|
|
2
|
+
import { defineCommand } from "citty";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { loadConfiguration } from "@styleframe/loader";
|
|
5
|
+
import consola from "consola";
|
|
6
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
7
|
+
import { flattenToFigmaDTCG } from "@styleframe/figma";
|
|
8
|
+
//#region src/commands/figma/export.ts
|
|
9
|
+
var export_default = defineCommand({
|
|
10
|
+
meta: {
|
|
11
|
+
name: "export",
|
|
12
|
+
description: "Export Styleframe variables to Figma-compatible DTCG format (per-mode files)"
|
|
13
|
+
},
|
|
14
|
+
args: {
|
|
15
|
+
config: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "Path to the Styleframe config file",
|
|
18
|
+
default: "styleframe.config.ts",
|
|
19
|
+
alias: ["c"],
|
|
20
|
+
valueHint: "path"
|
|
21
|
+
},
|
|
22
|
+
output: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Output directory for per-mode .tokens.json files",
|
|
25
|
+
default: ".",
|
|
26
|
+
alias: ["o"],
|
|
27
|
+
valueHint: "path"
|
|
28
|
+
},
|
|
29
|
+
collection: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Collection name embedded in the export",
|
|
32
|
+
default: "Design Tokens",
|
|
33
|
+
alias: ["n", "name"]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
async run({ args }) {
|
|
37
|
+
const configPath = path.resolve(args.config);
|
|
38
|
+
const outputDir = path.resolve(args.output);
|
|
39
|
+
consola.info(`Loading configuration from "${path.relative(process.cwd(), configPath)}"...`);
|
|
40
|
+
const root = (await loadConfiguration({ entry: configPath })).root;
|
|
41
|
+
consola.info("Building DTCG document...");
|
|
42
|
+
const { tokens, resolver, diagnostics: buildDiags, emittedCount, fluidNormalisedCount, maxViewport } = buildDTCG(root, { collectionName: args.collection });
|
|
43
|
+
consola.info("Flattening to Figma-compatible format...");
|
|
44
|
+
const { modes, diagnostics: flattenDiags } = await flattenToFigmaDTCG(tokens, resolver);
|
|
45
|
+
await mkdir(outputDir, { recursive: true });
|
|
46
|
+
for (const [modeName, modeDoc] of Object.entries(modes)) {
|
|
47
|
+
const filename = `${modeName}.tokens.json`;
|
|
48
|
+
const filePath = path.join(outputDir, filename);
|
|
49
|
+
await writeFile(filePath, `${JSON.stringify(modeDoc, null, 2)}\n`);
|
|
50
|
+
const tokenCount = countTokens(modeDoc);
|
|
51
|
+
consola.info(`Wrote "${path.relative(process.cwd(), filePath)}" (${tokenCount} tokens)`);
|
|
52
|
+
}
|
|
53
|
+
if (fluidNormalisedCount > 0) consola.info(`Normalised ${fluidNormalisedCount} fluid token(s) using max viewport (${maxViewport}px).`);
|
|
54
|
+
const allDiags = [...buildDiags.filter((d) => d.level === "warn"), ...flattenDiags];
|
|
55
|
+
if (allDiags.length > 0) {
|
|
56
|
+
consola.warn(`${allDiags.length} token(s) needed special handling`);
|
|
57
|
+
for (const d of allDiags.slice(0, 10)) {
|
|
58
|
+
const name = "name" in d ? d.name : d.path;
|
|
59
|
+
const reason = "reason" in d ? d.reason : "";
|
|
60
|
+
consola.info(` - ${name}: ${reason}`);
|
|
61
|
+
}
|
|
62
|
+
if (allDiags.length > 10) consola.info(` ... and ${allDiags.length - 10} more`);
|
|
63
|
+
}
|
|
64
|
+
consola.success(`Exported ${emittedCount} tokens as ${Object.keys(modes).length} Figma-compatible mode file(s) to "${path.relative(process.cwd(), outputDir)}"`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
function countTokens(doc) {
|
|
68
|
+
let count = 0;
|
|
69
|
+
for (const [key, value] of Object.entries(doc)) {
|
|
70
|
+
if (key === "$root") {
|
|
71
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && "$value" in value) count++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (key.startsWith("$")) continue;
|
|
75
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
76
|
+
if ("$value" in value) count++;
|
|
77
|
+
count += countTokens(value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return count;
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { export_default as default };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/commands/figma/index.ts
|
|
2
|
+
var figma_default = (0, require("citty").defineCommand)({
|
|
3
|
+
meta: {
|
|
4
|
+
name: "figma",
|
|
5
|
+
description: "Export and import Figma-compatible DTCG format (per-mode files)"
|
|
6
|
+
},
|
|
7
|
+
subCommands: {
|
|
8
|
+
export: () => Promise.resolve().then(() => require("./export-ONk9eKoZ.cjs")).then((m) => m.default),
|
|
9
|
+
import: () => Promise.resolve().then(() => require("./import-BQrcHNjK.cjs")).then((m) => m.default)
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
//#endregion
|
|
13
|
+
exports.default = figma_default;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
//#region src/commands/figma/index.ts
|
|
3
|
+
var figma_default = defineCommand({
|
|
4
|
+
meta: {
|
|
5
|
+
name: "figma",
|
|
6
|
+
description: "Export and import Figma-compatible DTCG format (per-mode files)"
|
|
7
|
+
},
|
|
8
|
+
subCommands: {
|
|
9
|
+
export: () => import("./export-suUS16eO.js").then((m) => m.default),
|
|
10
|
+
import: () => import("./import-Bll_uBvJ.js").then((m) => m.default)
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
//#endregion
|
|
14
|
+
export { figma_default as default };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-D6vf50IK.cjs");
|
|
2
|
+
let citty = require("citty");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
node_path = require_chunk.__toESM(node_path, 1);
|
|
5
|
+
let consola = require("consola");
|
|
6
|
+
consola = require_chunk.__toESM(consola, 1);
|
|
7
|
+
let node_fs_promises = require("node:fs/promises");
|
|
8
|
+
let _styleframe_figma = require("@styleframe/figma");
|
|
9
|
+
//#region src/commands/figma/import.ts
|
|
10
|
+
var import_default = (0, citty.defineCommand)({
|
|
11
|
+
meta: {
|
|
12
|
+
name: "import",
|
|
13
|
+
description: "Generate Styleframe code from Figma-compatible DTCG per-mode files"
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
input: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "Input .tokens.json file or directory of per-mode files",
|
|
19
|
+
required: true,
|
|
20
|
+
alias: ["i"],
|
|
21
|
+
valueHint: "path"
|
|
22
|
+
},
|
|
23
|
+
output: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Output Styleframe TypeScript file path",
|
|
26
|
+
default: "tokens.styleframe.ts",
|
|
27
|
+
alias: ["o"],
|
|
28
|
+
valueHint: "path"
|
|
29
|
+
},
|
|
30
|
+
composables: {
|
|
31
|
+
type: "boolean",
|
|
32
|
+
description: "Use @styleframe/theme composables (useColor, useSpacing, etc.)",
|
|
33
|
+
default: true
|
|
34
|
+
},
|
|
35
|
+
rem: {
|
|
36
|
+
type: "boolean",
|
|
37
|
+
description: "Use rem units for dimensions instead of px",
|
|
38
|
+
default: false
|
|
39
|
+
},
|
|
40
|
+
baseFontSize: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: "Base font size for rem conversion (in pixels)",
|
|
43
|
+
default: "16"
|
|
44
|
+
},
|
|
45
|
+
instanceName: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "Name for the Styleframe instance variable",
|
|
48
|
+
default: "s"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
async run({ args }) {
|
|
52
|
+
const inputPath = node_path.default.resolve(args.input);
|
|
53
|
+
const outputPath = node_path.default.resolve(args.output);
|
|
54
|
+
const baseFontSize = Number.parseInt(args.baseFontSize, 10) || 16;
|
|
55
|
+
consola.default.info(`Reading from "${node_path.default.relative(process.cwd(), inputPath)}"...`);
|
|
56
|
+
let data;
|
|
57
|
+
try {
|
|
58
|
+
if ((await (0, node_fs_promises.stat)(inputPath)).isDirectory()) {
|
|
59
|
+
const tokenFiles = (await (0, node_fs_promises.readdir)(inputPath)).filter((f) => f.endsWith(".tokens.json")).sort();
|
|
60
|
+
if (tokenFiles.length === 0) {
|
|
61
|
+
consola.default.error(`No .tokens.json files found in "${node_path.default.relative(process.cwd(), inputPath)}"`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
consola.default.info(`Found ${tokenFiles.length} mode file(s): ${tokenFiles.join(", ")}`);
|
|
65
|
+
const modeFormats = [];
|
|
66
|
+
for (const file of tokenFiles) {
|
|
67
|
+
const content = await (0, node_fs_promises.readFile)(node_path.default.join(inputPath, file), "utf-8");
|
|
68
|
+
const rawDoc = JSON.parse(content);
|
|
69
|
+
const modeData = (0, _styleframe_figma.fromDTCG)(rawDoc, { defaultModeName: extractModeName(rawDoc, file) });
|
|
70
|
+
modeFormats.push(modeData);
|
|
71
|
+
}
|
|
72
|
+
data = mergeModeFormats(modeFormats);
|
|
73
|
+
} else {
|
|
74
|
+
const content = await (0, node_fs_promises.readFile)(inputPath, "utf-8");
|
|
75
|
+
data = (0, _styleframe_figma.fromDTCG)(JSON.parse(content));
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
consola.default.error(`Failed to read or parse input: ${error instanceof Error ? error.message : error}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
consola.default.info(`Generating Styleframe code for ${data.variables.length} variables...`);
|
|
82
|
+
const result = (0, _styleframe_figma.generateStyleframeCode)(data, {
|
|
83
|
+
useComposables: args.composables,
|
|
84
|
+
useRem: args.rem,
|
|
85
|
+
baseFontSize,
|
|
86
|
+
instanceName: args.instanceName
|
|
87
|
+
});
|
|
88
|
+
consola.default.info(`Writing to "${node_path.default.relative(process.cwd(), outputPath)}"...`);
|
|
89
|
+
await (0, node_fs_promises.writeFile)(outputPath, result.code);
|
|
90
|
+
consola.default.success("Generated Styleframe code with:");
|
|
91
|
+
consola.default.info(` - ${result.variables.length} variables`);
|
|
92
|
+
consola.default.info(` - ${result.themes.length} theme(s)`);
|
|
93
|
+
if (args.composables && result.imports.length > 1) consola.default.info(` - Using composables: ${result.imports.slice(1).join(", ")}`);
|
|
94
|
+
consola.default.info(`\nOutput: ${node_path.default.relative(process.cwd(), outputPath)}`);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
function extractModeName(doc, filename) {
|
|
98
|
+
const ext = doc.$extensions;
|
|
99
|
+
if (ext && typeof ext["com.figma.modeName"] === "string") return ext["com.figma.modeName"];
|
|
100
|
+
const sfExt = ext?.["dev.styleframe"];
|
|
101
|
+
if (sfExt && typeof sfExt.modeName === "string") return sfExt.modeName;
|
|
102
|
+
return filename.replace(/\.tokens\.json$/, "");
|
|
103
|
+
}
|
|
104
|
+
function mergeModeFormats(formats) {
|
|
105
|
+
if (formats.length === 1) return formats[0];
|
|
106
|
+
const allModes = [];
|
|
107
|
+
const variableMap = /* @__PURE__ */ new Map();
|
|
108
|
+
for (const format of formats) {
|
|
109
|
+
for (const mode of format.modes) if (!allModes.includes(mode)) allModes.push(mode);
|
|
110
|
+
for (const v of format.variables) {
|
|
111
|
+
const existing = variableMap.get(v.name);
|
|
112
|
+
if (existing) Object.assign(existing.values, v.values);
|
|
113
|
+
else variableMap.set(v.name, {
|
|
114
|
+
...v,
|
|
115
|
+
values: { ...v.values }
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
collection: formats[0].collection,
|
|
121
|
+
modes: allModes,
|
|
122
|
+
variables: [...variableMap.values()]
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
exports.default = import_default;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import { readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import { fromDTCG, generateStyleframeCode } from "@styleframe/figma";
|
|
6
|
+
//#region src/commands/figma/import.ts
|
|
7
|
+
var import_default = defineCommand({
|
|
8
|
+
meta: {
|
|
9
|
+
name: "import",
|
|
10
|
+
description: "Generate Styleframe code from Figma-compatible DTCG per-mode files"
|
|
11
|
+
},
|
|
12
|
+
args: {
|
|
13
|
+
input: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Input .tokens.json file or directory of per-mode files",
|
|
16
|
+
required: true,
|
|
17
|
+
alias: ["i"],
|
|
18
|
+
valueHint: "path"
|
|
19
|
+
},
|
|
20
|
+
output: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Output Styleframe TypeScript file path",
|
|
23
|
+
default: "tokens.styleframe.ts",
|
|
24
|
+
alias: ["o"],
|
|
25
|
+
valueHint: "path"
|
|
26
|
+
},
|
|
27
|
+
composables: {
|
|
28
|
+
type: "boolean",
|
|
29
|
+
description: "Use @styleframe/theme composables (useColor, useSpacing, etc.)",
|
|
30
|
+
default: true
|
|
31
|
+
},
|
|
32
|
+
rem: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "Use rem units for dimensions instead of px",
|
|
35
|
+
default: false
|
|
36
|
+
},
|
|
37
|
+
baseFontSize: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "Base font size for rem conversion (in pixels)",
|
|
40
|
+
default: "16"
|
|
41
|
+
},
|
|
42
|
+
instanceName: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Name for the Styleframe instance variable",
|
|
45
|
+
default: "s"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
async run({ args }) {
|
|
49
|
+
const inputPath = path.resolve(args.input);
|
|
50
|
+
const outputPath = path.resolve(args.output);
|
|
51
|
+
const baseFontSize = Number.parseInt(args.baseFontSize, 10) || 16;
|
|
52
|
+
consola.info(`Reading from "${path.relative(process.cwd(), inputPath)}"...`);
|
|
53
|
+
let data;
|
|
54
|
+
try {
|
|
55
|
+
if ((await stat(inputPath)).isDirectory()) {
|
|
56
|
+
const tokenFiles = (await readdir(inputPath)).filter((f) => f.endsWith(".tokens.json")).sort();
|
|
57
|
+
if (tokenFiles.length === 0) {
|
|
58
|
+
consola.error(`No .tokens.json files found in "${path.relative(process.cwd(), inputPath)}"`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
consola.info(`Found ${tokenFiles.length} mode file(s): ${tokenFiles.join(", ")}`);
|
|
62
|
+
const modeFormats = [];
|
|
63
|
+
for (const file of tokenFiles) {
|
|
64
|
+
const content = await readFile(path.join(inputPath, file), "utf-8");
|
|
65
|
+
const rawDoc = JSON.parse(content);
|
|
66
|
+
const modeData = fromDTCG(rawDoc, { defaultModeName: extractModeName(rawDoc, file) });
|
|
67
|
+
modeFormats.push(modeData);
|
|
68
|
+
}
|
|
69
|
+
data = mergeModeFormats(modeFormats);
|
|
70
|
+
} else {
|
|
71
|
+
const content = await readFile(inputPath, "utf-8");
|
|
72
|
+
data = fromDTCG(JSON.parse(content));
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
consola.error(`Failed to read or parse input: ${error instanceof Error ? error.message : error}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
consola.info(`Generating Styleframe code for ${data.variables.length} variables...`);
|
|
79
|
+
const result = generateStyleframeCode(data, {
|
|
80
|
+
useComposables: args.composables,
|
|
81
|
+
useRem: args.rem,
|
|
82
|
+
baseFontSize,
|
|
83
|
+
instanceName: args.instanceName
|
|
84
|
+
});
|
|
85
|
+
consola.info(`Writing to "${path.relative(process.cwd(), outputPath)}"...`);
|
|
86
|
+
await writeFile(outputPath, result.code);
|
|
87
|
+
consola.success("Generated Styleframe code with:");
|
|
88
|
+
consola.info(` - ${result.variables.length} variables`);
|
|
89
|
+
consola.info(` - ${result.themes.length} theme(s)`);
|
|
90
|
+
if (args.composables && result.imports.length > 1) consola.info(` - Using composables: ${result.imports.slice(1).join(", ")}`);
|
|
91
|
+
consola.info(`\nOutput: ${path.relative(process.cwd(), outputPath)}`);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
function extractModeName(doc, filename) {
|
|
95
|
+
const ext = doc.$extensions;
|
|
96
|
+
if (ext && typeof ext["com.figma.modeName"] === "string") return ext["com.figma.modeName"];
|
|
97
|
+
const sfExt = ext?.["dev.styleframe"];
|
|
98
|
+
if (sfExt && typeof sfExt.modeName === "string") return sfExt.modeName;
|
|
99
|
+
return filename.replace(/\.tokens\.json$/, "");
|
|
100
|
+
}
|
|
101
|
+
function mergeModeFormats(formats) {
|
|
102
|
+
if (formats.length === 1) return formats[0];
|
|
103
|
+
const allModes = [];
|
|
104
|
+
const variableMap = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const format of formats) {
|
|
106
|
+
for (const mode of format.modes) if (!allModes.includes(mode)) allModes.push(mode);
|
|
107
|
+
for (const v of format.variables) {
|
|
108
|
+
const existing = variableMap.get(v.name);
|
|
109
|
+
if (existing) Object.assign(existing.values, v.values);
|
|
110
|
+
else variableMap.set(v.name, {
|
|
111
|
+
...v,
|
|
112
|
+
values: { ...v.values }
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
collection: formats[0].collection,
|
|
118
|
+
modes: allModes,
|
|
119
|
+
variables: [...variableMap.values()]
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
export { import_default as default };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import { readFile, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import { fromDTCG, fromDTCGResolver, generateStyleframeCode } from "@styleframe/figma";
|
|
6
|
+
//#region src/commands/dtcg/import.ts
|
|
7
|
+
var import_default = defineCommand({
|
|
8
|
+
meta: {
|
|
9
|
+
name: "import",
|
|
10
|
+
description: "Generate Styleframe code from DTCG format JSON"
|
|
11
|
+
},
|
|
12
|
+
args: {
|
|
13
|
+
input: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Input DTCG JSON file or directory containing tokens.json",
|
|
16
|
+
required: true,
|
|
17
|
+
alias: ["i"],
|
|
18
|
+
valueHint: "path"
|
|
19
|
+
},
|
|
20
|
+
output: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Output Styleframe TypeScript file path",
|
|
23
|
+
default: "tokens.styleframe.ts",
|
|
24
|
+
alias: ["o"],
|
|
25
|
+
valueHint: "path"
|
|
26
|
+
},
|
|
27
|
+
composables: {
|
|
28
|
+
type: "boolean",
|
|
29
|
+
description: "Use @styleframe/theme composables (useColor, useSpacing, etc.)",
|
|
30
|
+
default: true
|
|
31
|
+
},
|
|
32
|
+
rem: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "Use rem units for dimensions instead of px",
|
|
35
|
+
default: false
|
|
36
|
+
},
|
|
37
|
+
baseFontSize: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "Base font size for rem conversion (in pixels)",
|
|
40
|
+
default: "16"
|
|
41
|
+
},
|
|
42
|
+
instanceName: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Name for the Styleframe instance variable",
|
|
45
|
+
default: "s"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
async run({ args }) {
|
|
49
|
+
const inputPath = path.resolve(args.input);
|
|
50
|
+
const outputPath = path.resolve(args.output);
|
|
51
|
+
const baseFontSize = Number.parseInt(args.baseFontSize, 10) || 16;
|
|
52
|
+
consola.info(`Reading from "${path.relative(process.cwd(), inputPath)}"...`);
|
|
53
|
+
let data;
|
|
54
|
+
try {
|
|
55
|
+
if ((await stat(inputPath)).isDirectory()) data = await readFromDirectory(inputPath);
|
|
56
|
+
else data = await readFromFile(inputPath);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
consola.error(`Failed to read or parse input: ${error instanceof Error ? error.message : error}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
consola.info(`Generating Styleframe code for ${data.variables.length} variables...`);
|
|
62
|
+
const result = generateStyleframeCode(data, {
|
|
63
|
+
useComposables: args.composables,
|
|
64
|
+
useRem: args.rem,
|
|
65
|
+
baseFontSize,
|
|
66
|
+
instanceName: args.instanceName
|
|
67
|
+
});
|
|
68
|
+
consola.info(`Writing to "${path.relative(process.cwd(), outputPath)}"...`);
|
|
69
|
+
await writeFile(outputPath, result.code);
|
|
70
|
+
consola.success(`Generated Styleframe code with:`);
|
|
71
|
+
consola.info(` - ${result.variables.length} variables`);
|
|
72
|
+
consola.info(` - ${result.themes.length} theme(s)`);
|
|
73
|
+
if (args.composables && result.imports.length > 1) consola.info(` - Using composables: ${result.imports.slice(1).join(", ")}`);
|
|
74
|
+
consola.info(`\nOutput: ${path.relative(process.cwd(), outputPath)}`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
async function readFromDirectory(dirPath) {
|
|
78
|
+
const content = await readFile(path.join(dirPath, "tokens.json"), "utf-8");
|
|
79
|
+
const tokensDoc = JSON.parse(content);
|
|
80
|
+
const resolverPath = path.join(dirPath, "tokens.resolver.json");
|
|
81
|
+
const resolverDoc = await tryReadJson(resolverPath);
|
|
82
|
+
if (resolverDoc) {
|
|
83
|
+
consola.info(`Found resolver: "${path.relative(process.cwd(), resolverPath)}"`);
|
|
84
|
+
return fromDTCGResolver(resolverDoc, { fileLoader: async (ref) => {
|
|
85
|
+
if (ref === "tokens.json") return tokensDoc;
|
|
86
|
+
throw new Error(`Resolver references "${ref}" but only "tokens.json" was found`);
|
|
87
|
+
} });
|
|
88
|
+
}
|
|
89
|
+
return fromDTCG(tokensDoc);
|
|
90
|
+
}
|
|
91
|
+
async function readFromFile(filePath) {
|
|
92
|
+
const content = await readFile(filePath, "utf-8");
|
|
93
|
+
const tokensDoc = JSON.parse(content);
|
|
94
|
+
const resolverPath = filePath.replace(/\.json$/, ".resolver.json");
|
|
95
|
+
const resolverDoc = await tryReadJson(resolverPath);
|
|
96
|
+
if (resolverDoc) {
|
|
97
|
+
consola.info(`Auto-detected resolver: "${path.relative(process.cwd(), resolverPath)}"`);
|
|
98
|
+
return fromDTCGResolver(resolverDoc, { fileLoader: async (ref) => {
|
|
99
|
+
if (ref === path.basename(filePath)) return tokensDoc;
|
|
100
|
+
throw new Error(`Resolver references "${ref}" but only "${path.basename(filePath)}" was provided`);
|
|
101
|
+
} });
|
|
102
|
+
}
|
|
103
|
+
return fromDTCG(tokensDoc);
|
|
104
|
+
}
|
|
105
|
+
async function tryReadJson(filePath) {
|
|
106
|
+
try {
|
|
107
|
+
const content = await readFile(filePath, "utf-8");
|
|
108
|
+
return JSON.parse(content);
|
|
109
|
+
} catch {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { import_default as default };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-D6vf50IK.cjs");
|
|
2
|
+
let citty = require("citty");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
node_path = require_chunk.__toESM(node_path, 1);
|
|
5
|
+
let consola = require("consola");
|
|
6
|
+
consola = require_chunk.__toESM(consola, 1);
|
|
7
|
+
let node_fs_promises = require("node:fs/promises");
|
|
8
|
+
let _styleframe_figma = require("@styleframe/figma");
|
|
9
|
+
//#region src/commands/dtcg/import.ts
|
|
10
|
+
var import_default = (0, citty.defineCommand)({
|
|
11
|
+
meta: {
|
|
12
|
+
name: "import",
|
|
13
|
+
description: "Generate Styleframe code from DTCG format JSON"
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
input: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "Input DTCG JSON file or directory containing tokens.json",
|
|
19
|
+
required: true,
|
|
20
|
+
alias: ["i"],
|
|
21
|
+
valueHint: "path"
|
|
22
|
+
},
|
|
23
|
+
output: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Output Styleframe TypeScript file path",
|
|
26
|
+
default: "tokens.styleframe.ts",
|
|
27
|
+
alias: ["o"],
|
|
28
|
+
valueHint: "path"
|
|
29
|
+
},
|
|
30
|
+
composables: {
|
|
31
|
+
type: "boolean",
|
|
32
|
+
description: "Use @styleframe/theme composables (useColor, useSpacing, etc.)",
|
|
33
|
+
default: true
|
|
34
|
+
},
|
|
35
|
+
rem: {
|
|
36
|
+
type: "boolean",
|
|
37
|
+
description: "Use rem units for dimensions instead of px",
|
|
38
|
+
default: false
|
|
39
|
+
},
|
|
40
|
+
baseFontSize: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: "Base font size for rem conversion (in pixels)",
|
|
43
|
+
default: "16"
|
|
44
|
+
},
|
|
45
|
+
instanceName: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "Name for the Styleframe instance variable",
|
|
48
|
+
default: "s"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
async run({ args }) {
|
|
52
|
+
const inputPath = node_path.default.resolve(args.input);
|
|
53
|
+
const outputPath = node_path.default.resolve(args.output);
|
|
54
|
+
const baseFontSize = Number.parseInt(args.baseFontSize, 10) || 16;
|
|
55
|
+
consola.default.info(`Reading from "${node_path.default.relative(process.cwd(), inputPath)}"...`);
|
|
56
|
+
let data;
|
|
57
|
+
try {
|
|
58
|
+
if ((await (0, node_fs_promises.stat)(inputPath)).isDirectory()) data = await readFromDirectory(inputPath);
|
|
59
|
+
else data = await readFromFile(inputPath);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
consola.default.error(`Failed to read or parse input: ${error instanceof Error ? error.message : error}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
consola.default.info(`Generating Styleframe code for ${data.variables.length} variables...`);
|
|
65
|
+
const result = (0, _styleframe_figma.generateStyleframeCode)(data, {
|
|
66
|
+
useComposables: args.composables,
|
|
67
|
+
useRem: args.rem,
|
|
68
|
+
baseFontSize,
|
|
69
|
+
instanceName: args.instanceName
|
|
70
|
+
});
|
|
71
|
+
consola.default.info(`Writing to "${node_path.default.relative(process.cwd(), outputPath)}"...`);
|
|
72
|
+
await (0, node_fs_promises.writeFile)(outputPath, result.code);
|
|
73
|
+
consola.default.success(`Generated Styleframe code with:`);
|
|
74
|
+
consola.default.info(` - ${result.variables.length} variables`);
|
|
75
|
+
consola.default.info(` - ${result.themes.length} theme(s)`);
|
|
76
|
+
if (args.composables && result.imports.length > 1) consola.default.info(` - Using composables: ${result.imports.slice(1).join(", ")}`);
|
|
77
|
+
consola.default.info(`\nOutput: ${node_path.default.relative(process.cwd(), outputPath)}`);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
async function readFromDirectory(dirPath) {
|
|
81
|
+
const content = await (0, node_fs_promises.readFile)(node_path.default.join(dirPath, "tokens.json"), "utf-8");
|
|
82
|
+
const tokensDoc = JSON.parse(content);
|
|
83
|
+
const resolverPath = node_path.default.join(dirPath, "tokens.resolver.json");
|
|
84
|
+
const resolverDoc = await tryReadJson(resolverPath);
|
|
85
|
+
if (resolverDoc) {
|
|
86
|
+
consola.default.info(`Found resolver: "${node_path.default.relative(process.cwd(), resolverPath)}"`);
|
|
87
|
+
return (0, _styleframe_figma.fromDTCGResolver)(resolverDoc, { fileLoader: async (ref) => {
|
|
88
|
+
if (ref === "tokens.json") return tokensDoc;
|
|
89
|
+
throw new Error(`Resolver references "${ref}" but only "tokens.json" was found`);
|
|
90
|
+
} });
|
|
91
|
+
}
|
|
92
|
+
return (0, _styleframe_figma.fromDTCG)(tokensDoc);
|
|
93
|
+
}
|
|
94
|
+
async function readFromFile(filePath) {
|
|
95
|
+
const content = await (0, node_fs_promises.readFile)(filePath, "utf-8");
|
|
96
|
+
const tokensDoc = JSON.parse(content);
|
|
97
|
+
const resolverPath = filePath.replace(/\.json$/, ".resolver.json");
|
|
98
|
+
const resolverDoc = await tryReadJson(resolverPath);
|
|
99
|
+
if (resolverDoc) {
|
|
100
|
+
consola.default.info(`Auto-detected resolver: "${node_path.default.relative(process.cwd(), resolverPath)}"`);
|
|
101
|
+
return (0, _styleframe_figma.fromDTCGResolver)(resolverDoc, { fileLoader: async (ref) => {
|
|
102
|
+
if (ref === node_path.default.basename(filePath)) return tokensDoc;
|
|
103
|
+
throw new Error(`Resolver references "${ref}" but only "${node_path.default.basename(filePath)}" was provided`);
|
|
104
|
+
} });
|
|
105
|
+
}
|
|
106
|
+
return (0, _styleframe_figma.fromDTCG)(tokensDoc);
|
|
107
|
+
}
|
|
108
|
+
async function tryReadJson(filePath) {
|
|
109
|
+
try {
|
|
110
|
+
const content = await (0, node_fs_promises.readFile)(filePath, "utf-8");
|
|
111
|
+
return JSON.parse(content);
|
|
112
|
+
} catch {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
exports.default = import_default;
|