styleflow 0.1.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/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # styleflow
2
+
3
+ Design-to-dev CLI for StyleFlow projects.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install --save-dev styleflow
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```bash
14
+ npx styleflow init
15
+ npx styleflow pull
16
+ npx styleflow build
17
+ ```
18
+
19
+ ## Commands
20
+
21
+ - `styleflow init`: create `styleflow.config.ts` and ensure required `.env` keys.
22
+ - `styleflow pull [all|tokens|stylesheet|figma-styles]`: pull project artifacts.
23
+ - `styleflow build`: build output files from pulled artifacts.
24
+
25
+ ## Environment
26
+
27
+ - `STYLEFLOW_TOKEN`
28
+ - `STYLEFLOW_PROJECT_ID`
29
+ - `STYLEFLOW_API_URL` (default: `https://api.styleflow.app`)
30
+ - `STYLEFLOW_PROJECT_VERSION_ID` (optional)
@@ -0,0 +1,14 @@
1
+ import type { ResolvedStyleflowRuntimeConfig } from "./types.js";
2
+ type BuildOutputFile = {
3
+ type: string;
4
+ format: string;
5
+ path: string;
6
+ };
7
+ export interface BuildResult {
8
+ rawDirectory: string;
9
+ outputFiles: BuildOutputFile[];
10
+ warnings: string[];
11
+ }
12
+ export declare function buildStyleflowOutputs(runtime: ResolvedStyleflowRuntimeConfig): Promise<BuildResult>;
13
+ export {};
14
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,8BAA8B,EAG/B,MAAM,YAAY,CAAC;AAEpB,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAyED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,8BAA8B,GACtC,OAAO,CAAC,WAAW,CAAC,CA4ItB"}
package/dist/build.js ADDED
@@ -0,0 +1,185 @@
1
+ import path from "node:path";
2
+ import { ensureDirectory, readJsonFile, writeJsonFile, writeTextFile } from "./fs.js";
3
+ import { exportCSS, exportSCSS, exportStyleDictionary, exportTailwindPreset, } from "./generators/export.js";
4
+ function normalizeTokenFormat(format) {
5
+ const normalized = String(format || "css")
6
+ .trim()
7
+ .toLowerCase();
8
+ if (normalized === "css")
9
+ return "css";
10
+ if (normalized === "scss")
11
+ return "scss";
12
+ if (normalized === "json")
13
+ return "json";
14
+ if (normalized === "style-dictionary" || normalized === "style_dictionary") {
15
+ return "style-dictionary";
16
+ }
17
+ if (normalized === "tailwind")
18
+ return "tailwind";
19
+ throw new Error(`Unsupported token output format "${String(format)}". Use css|scss|json|style-dictionary|tailwind.`);
20
+ }
21
+ function getTokenOutputPath(runtime) {
22
+ const configured = runtime.config.output?.tokens?.path;
23
+ if (typeof configured === "string" && configured.trim().length > 0) {
24
+ return path.resolve(runtime.cwd, configured.trim());
25
+ }
26
+ return path.resolve(runtime.cwd, ".styleflow/build/tokens.css");
27
+ }
28
+ function getTokenOutputFormat(runtime) {
29
+ return normalizeTokenFormat(runtime.config.output?.tokens?.format || "css");
30
+ }
31
+ function getRawFilePath(runtime, fileName) {
32
+ return path.join(path.resolve(runtime.cwd, runtime.rawDirectory), fileName);
33
+ }
34
+ function hasObject(value) {
35
+ return typeof value === "object" && value !== null;
36
+ }
37
+ function collectStylesheetClassMap(stylesheet, mapping, componentsConfig) {
38
+ if (!hasObject(stylesheet)) {
39
+ return {
40
+ framework: componentsConfig.framework || "react",
41
+ styling: componentsConfig.styling || "tailwind",
42
+ classes: {},
43
+ };
44
+ }
45
+ const classes = hasObject(stylesheet.classes)
46
+ ? stylesheet.classes
47
+ : {};
48
+ const classMap = {};
49
+ for (const className of Object.keys(classes).sort()) {
50
+ classMap[className] = {
51
+ mappedTo: mapping?.[className] || null,
52
+ source: "stylesheet",
53
+ };
54
+ }
55
+ return {
56
+ framework: componentsConfig.framework || "react",
57
+ styling: componentsConfig.styling || "tailwind",
58
+ classes: classMap,
59
+ };
60
+ }
61
+ export async function buildStyleflowOutputs(runtime) {
62
+ const rawDirectory = path.resolve(runtime.cwd, runtime.rawDirectory);
63
+ const outputFiles = [];
64
+ const warnings = [];
65
+ const tokenOutputPath = getTokenOutputPath(runtime);
66
+ const tokenOutputFormat = getTokenOutputFormat(runtime);
67
+ const tokensRawPath = getRawFilePath(runtime, "tokens.structured.json");
68
+ let tokensData;
69
+ try {
70
+ const rawTokens = await readJsonFile(tokensRawPath);
71
+ if (!hasObject(rawTokens)) {
72
+ throw new Error("Invalid tokens payload: expected object.");
73
+ }
74
+ const files = hasObject(rawTokens.files)
75
+ ? rawTokens.files
76
+ : {};
77
+ const collections = hasObject(rawTokens.collections)
78
+ ? rawTokens.collections
79
+ : {};
80
+ tokensData = { files, collections };
81
+ }
82
+ catch (error) {
83
+ const message = error instanceof Error ? error.message : String(error);
84
+ throw new Error(`Unable to read tokens raw data at "${tokensRawPath}". Run "styleflow pull" first. (${message})`);
85
+ }
86
+ const tokenOutputDir = path.dirname(tokenOutputPath);
87
+ await ensureDirectory(tokenOutputDir);
88
+ switch (tokenOutputFormat) {
89
+ case "css": {
90
+ const css = exportCSS(tokensData);
91
+ await writeTextFile(tokenOutputPath, css);
92
+ outputFiles.push({ type: "tokens", format: "css", path: tokenOutputPath });
93
+ break;
94
+ }
95
+ case "scss": {
96
+ const scss = exportSCSS(tokensData);
97
+ await writeTextFile(tokenOutputPath, scss);
98
+ outputFiles.push({ type: "tokens", format: "scss", path: tokenOutputPath });
99
+ break;
100
+ }
101
+ case "json": {
102
+ await writeJsonFile(tokenOutputPath, tokensData);
103
+ outputFiles.push({ type: "tokens", format: "json", path: tokenOutputPath });
104
+ break;
105
+ }
106
+ case "style-dictionary": {
107
+ const sd = exportStyleDictionary(tokensData);
108
+ await writeJsonFile(tokenOutputPath, sd);
109
+ outputFiles.push({
110
+ type: "tokens",
111
+ format: "style-dictionary",
112
+ path: tokenOutputPath,
113
+ });
114
+ break;
115
+ }
116
+ case "tailwind": {
117
+ const preset = exportTailwindPreset(tokensData);
118
+ await writeTextFile(tokenOutputPath, preset);
119
+ outputFiles.push({
120
+ type: "tokens",
121
+ format: "tailwind",
122
+ path: tokenOutputPath,
123
+ });
124
+ break;
125
+ }
126
+ }
127
+ const stylesheetOutput = runtime.config.output?.stylesheet;
128
+ if (stylesheetOutput?.path) {
129
+ const stylesheetRawPath = getRawFilePath(runtime, "stylesheet.json");
130
+ try {
131
+ const stylesheetData = await readJsonFile(stylesheetRawPath);
132
+ const resolvedOutputPath = path.resolve(runtime.cwd, stylesheetOutput.path);
133
+ await writeJsonFile(resolvedOutputPath, stylesheetData);
134
+ outputFiles.push({
135
+ type: "stylesheet",
136
+ format: "json",
137
+ path: resolvedOutputPath,
138
+ });
139
+ }
140
+ catch {
141
+ warnings.push(`Stylesheet output skipped: raw file not found at "${stylesheetRawPath}".`);
142
+ }
143
+ }
144
+ const figmaStylesOutput = runtime.config.output?.figmaStyles;
145
+ if (figmaStylesOutput?.path) {
146
+ const figmaStylesRawPath = getRawFilePath(runtime, "figma-styles.json");
147
+ try {
148
+ const figmaStylesData = await readJsonFile(figmaStylesRawPath);
149
+ const resolvedOutputPath = path.resolve(runtime.cwd, figmaStylesOutput.path);
150
+ await writeJsonFile(resolvedOutputPath, figmaStylesData);
151
+ outputFiles.push({
152
+ type: "figma-styles",
153
+ format: "json",
154
+ path: resolvedOutputPath,
155
+ });
156
+ }
157
+ catch {
158
+ warnings.push(`Figma styles output skipped: raw file not found at "${figmaStylesRawPath}".`);
159
+ }
160
+ }
161
+ const componentsOutput = runtime.config.output?.components;
162
+ if (componentsOutput?.path) {
163
+ const stylesheetRawPath = getRawFilePath(runtime, "stylesheet.json");
164
+ try {
165
+ const stylesheetData = await readJsonFile(stylesheetRawPath);
166
+ const componentDir = path.resolve(runtime.cwd, componentsOutput.path);
167
+ const mapPath = path.join(componentDir, "styleflow-components.map.json");
168
+ const mapData = collectStylesheetClassMap(stylesheetData, runtime.config.mapping?.classes, componentsOutput);
169
+ await writeJsonFile(mapPath, mapData);
170
+ outputFiles.push({
171
+ type: "components",
172
+ format: "mapping-json",
173
+ path: mapPath,
174
+ });
175
+ }
176
+ catch {
177
+ warnings.push(`Components mapping skipped: stylesheet raw file not found at "${stylesheetRawPath}".`);
178
+ }
179
+ }
180
+ return {
181
+ rawDirectory,
182
+ outputFiles,
183
+ warnings,
184
+ };
185
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync } from "node:fs";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { Command } from "commander";
6
+ import { ensureEnvKeys, getDefaultConfigTemplate, loadStyleflowConfig, resolveRuntimeConfig, } from "./config.js";
7
+ import { buildStyleflowOutputs } from "./build.js";
8
+ import { normalizePullResources, pullStyleflowData } from "./pull.js";
9
+ const CLI_VERSION = "0.1.0";
10
+ function formatError(error) {
11
+ if (error instanceof Error)
12
+ return error.message;
13
+ return String(error);
14
+ }
15
+ function logInfo(message) {
16
+ process.stdout.write(`[styleflow] ${message}\n`);
17
+ }
18
+ function logWarn(message) {
19
+ process.stderr.write(`[styleflow] ${message}\n`);
20
+ }
21
+ async function buildRuntime(options) {
22
+ const cwd = process.cwd();
23
+ const loaded = await loadStyleflowConfig(cwd, options.config);
24
+ return resolveRuntimeConfig(loaded, {
25
+ projectId: options.projectId,
26
+ projectVersionId: options.projectVersionId ?? null,
27
+ apiUrl: options.apiUrl,
28
+ token: options.token,
29
+ rawDirectory: options.rawDirectory,
30
+ });
31
+ }
32
+ function resolveInitConfigPath(configArg) {
33
+ const cwd = process.cwd();
34
+ if (configArg && configArg.trim()) {
35
+ return path.resolve(cwd, configArg.trim());
36
+ }
37
+ return path.resolve(cwd, "styleflow.config.ts");
38
+ }
39
+ async function runPull(resourceArg, options) {
40
+ const runtime = await buildRuntime(options);
41
+ const resources = normalizePullResources(resourceArg || "all");
42
+ const result = await pullStyleflowData(runtime, resources);
43
+ logInfo(`pull completed for project "${runtime.projectId}"`);
44
+ logInfo(`raw directory: ${result.rawDirectory}`);
45
+ for (const resource of result.pulledResources) {
46
+ logInfo(`- pulled ${resource}: ${result.files[resource].path}`);
47
+ }
48
+ for (const resource of result.skippedResources) {
49
+ logWarn(`- skipped ${resource}`);
50
+ }
51
+ for (const warning of result.warnings) {
52
+ logWarn(`warning: ${warning}`);
53
+ }
54
+ logInfo(`manifest: ${result.manifestPath}`);
55
+ }
56
+ async function runBuild(options) {
57
+ const runtime = await buildRuntime(options);
58
+ if (options.pull) {
59
+ const resources = normalizePullResources(options.pullResource || "all");
60
+ const pullResult = await pullStyleflowData(runtime, resources);
61
+ logInfo(`pre-build pull completed (${pullResult.pulledResources.length} resources)`);
62
+ }
63
+ const result = await buildStyleflowOutputs(runtime);
64
+ logInfo(`build completed from raw directory: ${result.rawDirectory}`);
65
+ for (const file of result.outputFiles) {
66
+ logInfo(`- ${file.type} (${file.format}): ${file.path}`);
67
+ }
68
+ for (const warning of result.warnings) {
69
+ logWarn(`warning: ${warning}`);
70
+ }
71
+ }
72
+ async function runInit(options) {
73
+ const configPath = resolveInitConfigPath(options.config);
74
+ if (existsSync(configPath) && !options.force) {
75
+ throw new Error(`Config already exists at "${configPath}". Use --force to overwrite.`);
76
+ }
77
+ const template = getDefaultConfigTemplate();
78
+ await fs.writeFile(configPath, template, "utf8");
79
+ const envResult = await ensureEnvKeys(process.cwd());
80
+ logInfo(`config created: ${configPath}`);
81
+ if (envResult.addedKeys.length > 0) {
82
+ logInfo(`env updated (${envResult.envPath}): ${envResult.addedKeys.join(", ")}`);
83
+ }
84
+ else {
85
+ logInfo(`env already configured: ${envResult.envPath}`);
86
+ }
87
+ if (options.projectId && options.projectId.trim()) {
88
+ logInfo(`set STYLEFLOW_PROJECT_ID="${options.projectId.trim()}" in ${envResult.envPath}`);
89
+ }
90
+ if (options.apiUrl && options.apiUrl.trim()) {
91
+ logInfo(`set STYLEFLOW_API_URL="${options.apiUrl.trim()}" in ${envResult.envPath}`);
92
+ }
93
+ }
94
+ async function main() {
95
+ const program = new Command();
96
+ program
97
+ .name("styleflow")
98
+ .version(CLI_VERSION)
99
+ .description("StyleFlow design-to-dev CLI");
100
+ program
101
+ .command("init")
102
+ .description("Create styleflow.config.ts and ensure required .env keys")
103
+ .option("-c, --config <path>", "Config output path")
104
+ .option("-f, --force", "Overwrite existing config", false)
105
+ .option("--project-id <id>", "Initial project id hint")
106
+ .option("--api-url <url>", "Initial API URL hint")
107
+ .action(async (options) => {
108
+ await runInit(options);
109
+ });
110
+ program
111
+ .command("pull [resource]")
112
+ .description("Pull data from StyleFlow API into raw directory")
113
+ .option("-c, --config <path>", "Path to styleflow config file")
114
+ .option("--project-id <id>", "Override project id")
115
+ .option("--project-version-id <id>", "Override project version id")
116
+ .option("--api-url <url>", "Override API URL")
117
+ .option("--token <token>", "Override API token")
118
+ .option("--raw-directory <path>", "Override raw directory")
119
+ .action(async (resource, options) => {
120
+ await runPull(resource, options);
121
+ });
122
+ program
123
+ .command("build")
124
+ .description("Build output files from pulled raw data")
125
+ .option("-c, --config <path>", "Path to styleflow config file")
126
+ .option("--project-id <id>", "Override project id")
127
+ .option("--project-version-id <id>", "Override project version id")
128
+ .option("--api-url <url>", "Override API URL")
129
+ .option("--token <token>", "Override API token")
130
+ .option("--raw-directory <path>", "Override raw directory")
131
+ .option("--pull", "Run pull before build", false)
132
+ .option("--pull-resource <resource>", "Resource to pull before build (all|tokens|stylesheet|figma-styles)")
133
+ .action(async (options) => {
134
+ await runBuild(options);
135
+ });
136
+ program
137
+ .command("get:all")
138
+ .description("Alias for: styleflow pull all")
139
+ .option("-c, --config <path>", "Path to styleflow config file")
140
+ .option("--project-id <id>", "Override project id")
141
+ .option("--project-version-id <id>", "Override project version id")
142
+ .option("--api-url <url>", "Override API URL")
143
+ .option("--token <token>", "Override API token")
144
+ .option("--raw-directory <path>", "Override raw directory")
145
+ .action(async (options) => {
146
+ await runPull("all", options);
147
+ });
148
+ program
149
+ .command("get:tokens")
150
+ .description("Alias for: styleflow pull tokens")
151
+ .option("-c, --config <path>", "Path to styleflow config file")
152
+ .option("--project-id <id>", "Override project id")
153
+ .option("--project-version-id <id>", "Override project version id")
154
+ .option("--api-url <url>", "Override API URL")
155
+ .option("--token <token>", "Override API token")
156
+ .option("--raw-directory <path>", "Override raw directory")
157
+ .action(async (options) => {
158
+ await runPull("tokens", options);
159
+ });
160
+ program
161
+ .command("get:stylesheet")
162
+ .description("Alias for: styleflow pull stylesheet")
163
+ .option("-c, --config <path>", "Path to styleflow config file")
164
+ .option("--project-id <id>", "Override project id")
165
+ .option("--project-version-id <id>", "Override project version id")
166
+ .option("--api-url <url>", "Override API URL")
167
+ .option("--token <token>", "Override API token")
168
+ .option("--raw-directory <path>", "Override raw directory")
169
+ .action(async (options) => {
170
+ await runPull("stylesheet", options);
171
+ });
172
+ program
173
+ .command("get:figma-styles")
174
+ .description("Alias for: styleflow pull figma-styles")
175
+ .option("-c, --config <path>", "Path to styleflow config file")
176
+ .option("--project-id <id>", "Override project id")
177
+ .option("--project-version-id <id>", "Override project version id")
178
+ .option("--api-url <url>", "Override API URL")
179
+ .option("--token <token>", "Override API token")
180
+ .option("--raw-directory <path>", "Override raw directory")
181
+ .action(async (options) => {
182
+ await runPull("figma-styles", options);
183
+ });
184
+ await program.parseAsync(process.argv);
185
+ }
186
+ main().catch((error) => {
187
+ logWarn(`error: ${formatError(error)}`);
188
+ process.exit(1);
189
+ });
@@ -0,0 +1,15 @@
1
+ import type { LoadedStyleflowConfig, ResolvedStyleflowRuntimeConfig } from "./types.js";
2
+ export declare function loadStyleflowConfig(cwd: string, explicitPath?: string): Promise<LoadedStyleflowConfig>;
3
+ export declare function resolveRuntimeConfig(loaded: LoadedStyleflowConfig, overrides?: {
4
+ projectId?: string;
5
+ projectVersionId?: string | null;
6
+ apiUrl?: string;
7
+ token?: string;
8
+ rawDirectory?: string;
9
+ }): ResolvedStyleflowRuntimeConfig;
10
+ export declare function getDefaultConfigTemplate(): string;
11
+ export declare function ensureEnvKeys(cwd: string): Promise<{
12
+ envPath: string;
13
+ addedKeys: string[];
14
+ }>;
15
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,qBAAqB,EACrB,8BAA8B,EAE/B,MAAM,YAAY,CAAC;AAsEpB,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,qBAAqB,CAAC,CAShC;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,qBAAqB,EAC7B,SAAS,GAAE;IACT,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CAClB,GACL,8BAA8B,CA0DhC;AAED,wBAAgB,wBAAwB,IAAI,MAAM,CA+BjD;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC,CA6BD"}
package/dist/config.js ADDED
@@ -0,0 +1,170 @@
1
+ import { existsSync } from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { createRequire } from "node:module";
5
+ import { createJiti } from "jiti";
6
+ const DEFAULT_CONFIG_FILENAMES = [
7
+ "styleflow.config.ts",
8
+ "styleflow.config.mts",
9
+ "styleflow.config.cts",
10
+ "styleflow.config.mjs",
11
+ "styleflow.config.cjs",
12
+ "styleflow.config.js",
13
+ "styleflow.config.json",
14
+ ];
15
+ const DEFAULT_API_URL = "https://api.styleflow.app";
16
+ const DEFAULT_RAW_DIRECTORY = ".styleflow/raw";
17
+ function sanitizeApiUrl(value) {
18
+ const raw = String(value || "").trim();
19
+ const candidate = raw || DEFAULT_API_URL;
20
+ return candidate.replace(/\/+$/, "");
21
+ }
22
+ function resolveConfigPath(cwd, explicitPath) {
23
+ if (explicitPath && explicitPath.trim()) {
24
+ const absolute = path.resolve(cwd, explicitPath.trim());
25
+ return existsSync(absolute) ? absolute : null;
26
+ }
27
+ for (const filename of DEFAULT_CONFIG_FILENAMES) {
28
+ const absolute = path.resolve(cwd, filename);
29
+ if (existsSync(absolute)) {
30
+ return absolute;
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+ async function readConfigModule(configPath) {
36
+ if (configPath.endsWith(".json")) {
37
+ const raw = await fs.readFile(configPath, "utf8");
38
+ return JSON.parse(raw);
39
+ }
40
+ if (configPath.endsWith(".cjs")) {
41
+ const req = createRequire(import.meta.url);
42
+ return req(configPath);
43
+ }
44
+ const jiti = createJiti(import.meta.url, {
45
+ interopDefault: true,
46
+ moduleCache: false,
47
+ });
48
+ return jiti.import(configPath);
49
+ }
50
+ function normalizeLoadedConfig(input) {
51
+ if (!input)
52
+ return {};
53
+ if (typeof input !== "object") {
54
+ throw new Error("Invalid StyleFlow config: expected object export.");
55
+ }
56
+ const record = input;
57
+ const maybeDefault = record.default;
58
+ if (maybeDefault && typeof maybeDefault === "object") {
59
+ return maybeDefault;
60
+ }
61
+ return record;
62
+ }
63
+ export async function loadStyleflowConfig(cwd, explicitPath) {
64
+ const configPath = resolveConfigPath(cwd, explicitPath);
65
+ if (!configPath) {
66
+ return { config: {}, configPath: null, cwd };
67
+ }
68
+ const loaded = await readConfigModule(configPath);
69
+ const config = normalizeLoadedConfig(loaded);
70
+ return { config, configPath, cwd };
71
+ }
72
+ export function resolveRuntimeConfig(loaded, overrides = {}) {
73
+ const env = process.env;
74
+ const config = loaded.config || {};
75
+ const projectId = (overrides.projectId && overrides.projectId.trim()) ||
76
+ (typeof config.projectId === "string" ? config.projectId.trim() : "") ||
77
+ (env.STYLEFLOW_PROJECT_ID || "").trim();
78
+ if (!projectId) {
79
+ throw new Error("Missing project id. Set STYLEFLOW_PROJECT_ID or define projectId in styleflow.config.");
80
+ }
81
+ const token = (overrides.token && overrides.token.trim()) ||
82
+ (typeof config.auth?.token === "string" ? config.auth.token.trim() : "") ||
83
+ (env.STYLEFLOW_TOKEN || "").trim();
84
+ if (!token) {
85
+ throw new Error("Missing token. Set STYLEFLOW_TOKEN or define auth.token in styleflow.config.");
86
+ }
87
+ const apiUrl = sanitizeApiUrl(overrides.apiUrl ||
88
+ config.apiUrl ||
89
+ env.STYLEFLOW_API_URL ||
90
+ DEFAULT_API_URL);
91
+ const projectVersionIdRaw = overrides.projectVersionId ??
92
+ config.projectVersionId ??
93
+ env.STYLEFLOW_PROJECT_VERSION_ID ??
94
+ null;
95
+ const projectVersionId = typeof projectVersionIdRaw === "string" && projectVersionIdRaw.trim().length > 0
96
+ ? projectVersionIdRaw.trim()
97
+ : null;
98
+ const rawDirectory = (overrides.rawDirectory && overrides.rawDirectory.trim()) ||
99
+ (typeof config.rawDirectory === "string" ? config.rawDirectory.trim() : "") ||
100
+ DEFAULT_RAW_DIRECTORY;
101
+ return {
102
+ cwd: loaded.cwd,
103
+ projectId,
104
+ projectVersionId,
105
+ apiUrl,
106
+ token,
107
+ rawDirectory,
108
+ config,
109
+ configPath: loaded.configPath,
110
+ };
111
+ }
112
+ export function getDefaultConfigTemplate() {
113
+ return `export default {
114
+ projectId: process.env.STYLEFLOW_PROJECT_ID,
115
+ apiUrl: process.env.STYLEFLOW_API_URL ?? "https://api.styleflow.app",
116
+ rawDirectory: ".styleflow/raw",
117
+ output: {
118
+ tokens: {
119
+ format: "css",
120
+ path: "src/styles/styleflow-tokens.css"
121
+ },
122
+ stylesheet: {
123
+ format: "json",
124
+ path: ".styleflow/build/stylesheet.json"
125
+ },
126
+ figmaStyles: {
127
+ format: "json",
128
+ path: ".styleflow/build/figma-styles.json"
129
+ },
130
+ components: {
131
+ framework: "react",
132
+ styling: "tailwind",
133
+ path: ".styleflow/build/components"
134
+ }
135
+ },
136
+ mapping: {
137
+ classes: {
138
+ // "btn-primary": "btn btn-primary"
139
+ }
140
+ }
141
+ };
142
+ `;
143
+ }
144
+ export async function ensureEnvKeys(cwd) {
145
+ const envPath = path.resolve(cwd, ".env");
146
+ const wanted = [
147
+ ["STYLEFLOW_API_URL", DEFAULT_API_URL],
148
+ ["STYLEFLOW_PROJECT_ID", ""],
149
+ ["STYLEFLOW_TOKEN", ""],
150
+ ];
151
+ let existing = "";
152
+ try {
153
+ existing = await fs.readFile(envPath, "utf8");
154
+ }
155
+ catch {
156
+ existing = "";
157
+ }
158
+ const addedKeys = [];
159
+ const lines = existing.length > 0 ? existing.replace(/\s+$/, "").split("\n") : [];
160
+ for (const [key, value] of wanted) {
161
+ const hasKey = lines.some((line) => line.startsWith(`${key}=`));
162
+ if (!hasKey) {
163
+ lines.push(`${key}=${value}`);
164
+ addedKeys.push(key);
165
+ }
166
+ }
167
+ const nextContent = `${lines.join("\n")}\n`;
168
+ await fs.writeFile(envPath, nextContent, "utf8");
169
+ return { envPath, addedKeys };
170
+ }
package/dist/fs.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export declare function ensureDirectory(dirPath: string): Promise<void>;
2
+ export declare function writeJsonFile(filePath: string, data: unknown): Promise<void>;
3
+ export declare function readJsonFile<T>(filePath: string): Promise<T>;
4
+ export declare function writeTextFile(filePath: string, content: string): Promise<void>;
5
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAGA,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpE;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlF;AAED,wBAAsB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAGlE;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpF"}
package/dist/fs.js ADDED
@@ -0,0 +1,17 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ export async function ensureDirectory(dirPath) {
4
+ await fs.mkdir(dirPath, { recursive: true });
5
+ }
6
+ export async function writeJsonFile(filePath, data) {
7
+ await ensureDirectory(path.dirname(filePath));
8
+ await fs.writeFile(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
9
+ }
10
+ export async function readJsonFile(filePath) {
11
+ const raw = await fs.readFile(filePath, "utf8");
12
+ return JSON.parse(raw);
13
+ }
14
+ export async function writeTextFile(filePath, content) {
15
+ await ensureDirectory(path.dirname(filePath));
16
+ await fs.writeFile(filePath, content, "utf8");
17
+ }
@@ -0,0 +1,6 @@
1
+ import type { StructuredTokens } from "../types.js";
2
+ export declare function exportCSS(tokens: StructuredTokens): string;
3
+ export declare function exportSCSS(tokens: StructuredTokens): string;
4
+ export declare function exportStyleDictionary(tokens: StructuredTokens): Record<string, unknown>;
5
+ export declare function exportTailwindPreset(tokens: StructuredTokens): string;
6
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../src/generators/export.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAc,MAAM,aAAa,CAAC;AAgHhE,wBAAgB,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CA4D1D;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CA6C3D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAwCvF;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAmCrE"}
@@ -0,0 +1,246 @@
1
+ function tokenNameToCSS(filePath, tokenName) {
2
+ const parts = filePath.split("/");
3
+ const fullParts = [...parts, tokenName];
4
+ return fullParts
5
+ .map((p) => p.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase())
6
+ .join("-")
7
+ .replace(/[^a-z0-9-]/g, "-")
8
+ .replace(/-+/g, "-")
9
+ .replace(/^-|-$/g, "");
10
+ }
11
+ function tokenNameToSD(filePath, tokenName) {
12
+ const parts = filePath.split("/");
13
+ return [...parts, tokenName].map((p) => p
14
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
15
+ .toLowerCase()
16
+ .replace(/[^a-z0-9-]/g, "-")
17
+ .replace(/-+/g, "-")
18
+ .replace(/^-|-$/g, ""));
19
+ }
20
+ function resolveValue(value, files, mode, defaultModes, visited = new Set()) {
21
+ if (value === null || value === undefined)
22
+ return "";
23
+ if (typeof value === "number")
24
+ return String(value);
25
+ if (typeof value === "boolean")
26
+ return String(value);
27
+ if (typeof value === "string")
28
+ return value;
29
+ if (typeof value === "object") {
30
+ const obj = value;
31
+ if ("cssValue" in obj && typeof obj.cssValue === "string") {
32
+ return obj.cssValue;
33
+ }
34
+ if (obj.ref && typeof obj.ref === "string") {
35
+ const refName = String(obj.ref);
36
+ if (visited.has(refName))
37
+ return `/* circular: ${refName} */`;
38
+ visited.add(refName);
39
+ const resolved = findTokenValue(refName, files, mode, defaultModes, visited);
40
+ return resolved ?? `/* unresolved: ${refName} */`;
41
+ }
42
+ if (obj.expr && typeof obj.expr === "string") {
43
+ return String(obj.expr);
44
+ }
45
+ }
46
+ return String(value);
47
+ }
48
+ function findTokenValue(refPath, files, mode, defaultModes, visited = new Set()) {
49
+ for (const [filePath, tokens] of Object.entries(files)) {
50
+ for (const [tokenName, tokenData] of Object.entries(tokens)) {
51
+ const entry = tokenData;
52
+ const fullName = `${filePath.split("/").slice(1).join("/")}/${tokenName}`.replace(/^\//, "");
53
+ const withCollection = `${filePath.split("/")[0]}/${fullName}`;
54
+ if (refPath === fullName || refPath === tokenName || refPath === withCollection) {
55
+ const collectionName = filePath.split("/")[0];
56
+ const defMode = defaultModes[collectionName] || mode;
57
+ const raw = entry.values[mode] ?? entry.values[defMode] ?? Object.values(entry.values)[0];
58
+ return resolveValue(raw, files, mode, defaultModes, visited);
59
+ }
60
+ }
61
+ }
62
+ return null;
63
+ }
64
+ function buildDefaultModes(tokens) {
65
+ const defaultModes = {};
66
+ for (const [name, meta] of Object.entries(tokens.collections)) {
67
+ defaultModes[name] = meta.defaultMode;
68
+ }
69
+ const allModes = new Set();
70
+ for (const meta of Object.values(tokens.collections)) {
71
+ for (const mode of meta.modes) {
72
+ allModes.add(mode);
73
+ }
74
+ }
75
+ const sortedModes = Array.from(allModes);
76
+ const primaryDefault = Object.values(tokens.collections)[0]?.defaultMode || sortedModes[0] || "default";
77
+ return { defaultModes, sortedModes, primaryDefault };
78
+ }
79
+ export function exportCSS(tokens) {
80
+ const { files } = tokens;
81
+ const { defaultModes, sortedModes, primaryDefault } = buildDefaultModes(tokens);
82
+ const output = [];
83
+ output.push("/* Generated by StyleFlow */");
84
+ output.push("/* https://styleflow.app */");
85
+ output.push("");
86
+ output.push(":root {");
87
+ for (const [filePath, fileTokens] of Object.entries(files)) {
88
+ const collectionName = filePath.split("/")[0];
89
+ for (const [tokenName, tokenData] of Object.entries(fileTokens)) {
90
+ const entry = tokenData;
91
+ const cssName = tokenNameToCSS(filePath, tokenName);
92
+ const defMode = defaultModes[collectionName] || primaryDefault;
93
+ const raw = entry.values[defMode] ?? Object.values(entry.values)[0];
94
+ const value = resolveValue(raw, files, defMode, defaultModes);
95
+ if (value) {
96
+ output.push(` --${cssName}: ${value};`);
97
+ }
98
+ }
99
+ }
100
+ output.push("}");
101
+ for (const mode of sortedModes) {
102
+ if (mode === primaryDefault)
103
+ continue;
104
+ const overrides = [];
105
+ for (const [filePath, fileTokens] of Object.entries(files)) {
106
+ const collectionName = filePath.split("/")[0];
107
+ const defMode = defaultModes[collectionName] || primaryDefault;
108
+ for (const [tokenName, tokenData] of Object.entries(fileTokens)) {
109
+ const entry = tokenData;
110
+ if (entry.values[mode] === undefined)
111
+ continue;
112
+ const defValue = resolveValue(entry.values[defMode] ?? Object.values(entry.values)[0], files, defMode, defaultModes);
113
+ const modeValue = resolveValue(entry.values[mode], files, mode, defaultModes);
114
+ if (modeValue && modeValue !== defValue) {
115
+ const cssName = tokenNameToCSS(filePath, tokenName);
116
+ overrides.push(` --${cssName}: ${modeValue};`);
117
+ }
118
+ }
119
+ }
120
+ if (overrides.length > 0) {
121
+ output.push("");
122
+ const selector = mode.toLowerCase().replace(/\s+/g, "-");
123
+ output.push(`[data-theme="${selector}"] {`);
124
+ output.push(...overrides);
125
+ output.push("}");
126
+ }
127
+ }
128
+ return `${output.join("\n")}\n`;
129
+ }
130
+ export function exportSCSS(tokens) {
131
+ const { files } = tokens;
132
+ const { defaultModes, sortedModes, primaryDefault } = buildDefaultModes(tokens);
133
+ const output = [];
134
+ output.push("// Generated by StyleFlow");
135
+ output.push("// https://styleflow.app");
136
+ output.push("");
137
+ output.push("// Token variables");
138
+ output.push("");
139
+ for (const [filePath, fileTokens] of Object.entries(files)) {
140
+ const collectionName = filePath.split("/")[0];
141
+ const defMode = defaultModes[collectionName] || primaryDefault;
142
+ for (const [tokenName, tokenData] of Object.entries(fileTokens)) {
143
+ const entry = tokenData;
144
+ const name = tokenNameToCSS(filePath, tokenName);
145
+ const raw = entry.values[defMode] ?? Object.values(entry.values)[0];
146
+ const value = resolveValue(raw, files, defMode, defaultModes);
147
+ if (value) {
148
+ output.push(`$${name}: ${value};`);
149
+ }
150
+ }
151
+ }
152
+ for (const mode of sortedModes) {
153
+ const mapName = mode.toLowerCase().replace(/\s+/g, "-");
154
+ output.push("");
155
+ output.push(`$tokens-${mapName}: (`);
156
+ for (const [filePath, fileTokens] of Object.entries(files)) {
157
+ for (const [tokenName, tokenData] of Object.entries(fileTokens)) {
158
+ const entry = tokenData;
159
+ if (entry.values[mode] === undefined)
160
+ continue;
161
+ const key = tokenNameToCSS(filePath, tokenName);
162
+ const value = resolveValue(entry.values[mode], files, mode, defaultModes);
163
+ if (value) {
164
+ output.push(` "${key}": ${value},`);
165
+ }
166
+ }
167
+ }
168
+ output.push(");");
169
+ }
170
+ return `${output.join("\n")}\n`;
171
+ }
172
+ export function exportStyleDictionary(tokens) {
173
+ const { files } = tokens;
174
+ const { defaultModes, primaryDefault } = buildDefaultModes(tokens);
175
+ const result = {};
176
+ for (const [filePath, fileTokens] of Object.entries(files)) {
177
+ const collectionName = filePath.split("/")[0];
178
+ const defMode = defaultModes[collectionName] || primaryDefault;
179
+ for (const [tokenName, tokenData] of Object.entries(fileTokens)) {
180
+ const entry = tokenData;
181
+ const raw = entry.values[defMode] ?? Object.values(entry.values)[0];
182
+ const value = resolveValue(raw, files, defMode, defaultModes);
183
+ if (!value)
184
+ continue;
185
+ const pathParts = tokenNameToSD(filePath, tokenName);
186
+ let current = result;
187
+ for (let i = 0; i < pathParts.length - 1; i++) {
188
+ const key = pathParts[i];
189
+ if (!current[key] || typeof current[key] !== "object") {
190
+ current[key] = {};
191
+ }
192
+ current = current[key];
193
+ }
194
+ const lastKey = pathParts[pathParts.length - 1];
195
+ let sdType = "other";
196
+ if (entry.type === "COLOR")
197
+ sdType = "color";
198
+ else if (entry.type === "FLOAT" || entry.type === "NUMBER")
199
+ sdType = "number";
200
+ else if (entry.type === "BOOLEAN")
201
+ sdType = "boolean";
202
+ else if (entry.type === "STRING")
203
+ sdType = "string";
204
+ current[lastKey] = {
205
+ value,
206
+ type: sdType,
207
+ };
208
+ }
209
+ }
210
+ return result;
211
+ }
212
+ export function exportTailwindPreset(tokens) {
213
+ const { files } = tokens;
214
+ const { defaultModes, primaryDefault } = buildDefaultModes(tokens);
215
+ const colors = {};
216
+ for (const [filePath, fileTokens] of Object.entries(files)) {
217
+ const collectionName = filePath.split("/")[0];
218
+ const defMode = defaultModes[collectionName] || primaryDefault;
219
+ for (const [tokenName, tokenData] of Object.entries(fileTokens)) {
220
+ const entry = tokenData;
221
+ if (entry.type !== "COLOR")
222
+ continue;
223
+ const raw = entry.values[defMode] ?? Object.values(entry.values)[0];
224
+ const value = resolveValue(raw, files, defMode, defaultModes);
225
+ if (!value)
226
+ continue;
227
+ const key = tokenNameToCSS(filePath, tokenName);
228
+ colors[key] = value;
229
+ }
230
+ }
231
+ const output = [];
232
+ output.push("/** Generated by StyleFlow */");
233
+ output.push("/** https://styleflow.app */");
234
+ output.push("module.exports = {");
235
+ output.push(" theme: {");
236
+ output.push(" extend: {");
237
+ output.push(" colors: {");
238
+ for (const [key, value] of Object.entries(colors).sort(([a], [b]) => a.localeCompare(b))) {
239
+ output.push(` ${JSON.stringify(key)}: ${JSON.stringify(value)},`);
240
+ }
241
+ output.push(" },");
242
+ output.push(" },");
243
+ output.push(" },");
244
+ output.push("};");
245
+ return `${output.join("\n")}\n`;
246
+ }
package/dist/http.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ export declare class ApiError extends Error {
2
+ readonly status: number;
3
+ readonly path: string;
4
+ readonly bodyText: string;
5
+ constructor(path: string, status: number, bodyText: string);
6
+ }
7
+ export declare function apiGetJson<T>(params: {
8
+ baseUrl: string;
9
+ token: string;
10
+ path: string;
11
+ query?: Record<string, string | null | undefined>;
12
+ }): Promise<T>;
13
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAEd,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;CAO3D;AAQD,wBAAsB,UAAU,CAAC,CAAC,EAChC,MAAM,EAAE;IACN,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACnD,GACA,OAAO,CAAC,CAAC,CAAC,CAqBZ"}
package/dist/http.js ADDED
@@ -0,0 +1,37 @@
1
+ export class ApiError extends Error {
2
+ status;
3
+ path;
4
+ bodyText;
5
+ constructor(path, status, bodyText) {
6
+ super(`Request failed (${status}) for ${path}`);
7
+ this.name = "ApiError";
8
+ this.status = status;
9
+ this.path = path;
10
+ this.bodyText = bodyText;
11
+ }
12
+ }
13
+ function joinUrl(baseUrl, requestPath) {
14
+ const cleanBase = baseUrl.replace(/\/+$/, "");
15
+ const cleanPath = requestPath.startsWith("/") ? requestPath : `/${requestPath}`;
16
+ return `${cleanBase}${cleanPath}`;
17
+ }
18
+ export async function apiGetJson(params) {
19
+ const url = new URL(joinUrl(params.baseUrl, params.path));
20
+ for (const [key, value] of Object.entries(params.query || {})) {
21
+ if (typeof value !== "string" || value.trim().length === 0)
22
+ continue;
23
+ url.searchParams.set(key, value.trim());
24
+ }
25
+ const response = await fetch(url, {
26
+ method: "GET",
27
+ headers: {
28
+ Authorization: `Bearer ${params.token}`,
29
+ Accept: "application/json",
30
+ },
31
+ });
32
+ if (!response.ok) {
33
+ const bodyText = await response.text();
34
+ throw new ApiError(params.path, response.status, bodyText);
35
+ }
36
+ return (await response.json());
37
+ }
@@ -0,0 +1,4 @@
1
+ import type { StyleflowConfig } from "./types.js";
2
+ export declare function defineConfig(config: StyleflowConfig): StyleflowConfig;
3
+ export type { CollectionMeta, ComponentsOutputConfig, JsonOutputConfig, LoadedStyleflowConfig, PullResource, ResolvedStyleflowRuntimeConfig, StructuredTokens, StyleflowAuthConfig, StyleflowConfig, StyleflowMappingConfig, StyleflowOutputConfig, TokenEntry, TokenOutputConfig, TokenOutputFormat, } from "./types.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAErE;AAED,YAAY,EACV,cAAc,EACd,sBAAsB,EACtB,gBAAgB,EAChB,qBAAqB,EACrB,YAAY,EACZ,8BAA8B,EAC9B,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,UAAU,EACV,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export function defineConfig(config) {
2
+ return config;
3
+ }
package/dist/pull.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { PullResource, ResolvedStyleflowRuntimeConfig } from "./types.js";
2
+ type PulledResourceMeta = {
3
+ path: string;
4
+ hash: string | null;
5
+ version: number | null;
6
+ pulled: boolean;
7
+ };
8
+ export type PullResult = {
9
+ rawDirectory: string;
10
+ pulledResources: PullResource[];
11
+ skippedResources: PullResource[];
12
+ warnings: string[];
13
+ files: Record<PullResource, PulledResourceMeta>;
14
+ manifestPath: string;
15
+ };
16
+ export declare function normalizePullResources(input?: string | null): PullResource[];
17
+ export declare function pullStyleflowData(runtime: ResolvedStyleflowRuntimeConfig, resources: PullResource[]): Promise<PullResult>;
18
+ export {};
19
+ //# sourceMappingURL=pull.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../src/pull.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,8BAA8B,EAAE,MAAM,YAAY,CAAC;AAE/E,KAAK,kBAAkB,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,YAAY,EAAE,CAAC;IAChC,gBAAgB,EAAE,YAAY,EAAE,CAAC;IACjC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC;IAChD,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAQF,wBAAgB,sBAAsB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,YAAY,EAAE,CAa5E;AAQD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,8BAA8B,EACvC,SAAS,EAAE,YAAY,EAAE,GACxB,OAAO,CAAC,UAAU,CAAC,CAmIrB"}
package/dist/pull.js ADDED
@@ -0,0 +1,145 @@
1
+ import path from "node:path";
2
+ import { writeJsonFile } from "./fs.js";
3
+ import { ApiError, apiGetJson } from "./http.js";
4
+ export function normalizePullResources(input) {
5
+ const normalized = String(input || "all").trim().toLowerCase();
6
+ if (!normalized || normalized === "all") {
7
+ return ["tokens", "stylesheet", "figmaStyles"];
8
+ }
9
+ if (normalized === "tokens")
10
+ return ["tokens"];
11
+ if (normalized === "stylesheet")
12
+ return ["stylesheet"];
13
+ if (normalized === "figma-styles" || normalized === "figmastyles")
14
+ return ["figmaStyles"];
15
+ throw new Error(`Unknown resource "${input}". Use one of: all, tokens, stylesheet, figma-styles.`);
16
+ }
17
+ function toQuery(projectVersionId) {
18
+ return {
19
+ projectVersionId,
20
+ };
21
+ }
22
+ export async function pullStyleflowData(runtime, resources) {
23
+ const rawDirectory = path.resolve(runtime.cwd, runtime.rawDirectory);
24
+ const warnings = [];
25
+ const projectParam = encodeURIComponent(runtime.projectId);
26
+ const files = {
27
+ tokens: {
28
+ path: path.join(rawDirectory, "tokens.structured.json"),
29
+ hash: null,
30
+ version: null,
31
+ pulled: false,
32
+ },
33
+ stylesheet: {
34
+ path: path.join(rawDirectory, "stylesheet.json"),
35
+ hash: null,
36
+ version: null,
37
+ pulled: false,
38
+ },
39
+ figmaStyles: {
40
+ path: path.join(rawDirectory, "figma-styles.json"),
41
+ hash: null,
42
+ version: null,
43
+ pulled: false,
44
+ },
45
+ };
46
+ const requested = new Set(resources);
47
+ if (requested.has("tokens")) {
48
+ const payload = await apiGetJson({
49
+ baseUrl: runtime.apiUrl,
50
+ token: runtime.token,
51
+ path: `/api/styleflow/projects/${projectParam}/tokens`,
52
+ query: {
53
+ ...toQuery(runtime.projectVersionId),
54
+ format: "structured",
55
+ },
56
+ });
57
+ if (!payload || typeof payload.tokens !== "object" || payload.tokens === null) {
58
+ throw new Error("Tokens endpoint returned an empty payload.");
59
+ }
60
+ await writeJsonFile(files.tokens.path, payload.tokens);
61
+ files.tokens.pulled = true;
62
+ }
63
+ if (requested.has("stylesheet")) {
64
+ try {
65
+ const payload = await apiGetJson({
66
+ baseUrl: runtime.apiUrl,
67
+ token: runtime.token,
68
+ path: `/api/styleflow/projects/${projectParam}/stylesheet`,
69
+ query: toQuery(runtime.projectVersionId),
70
+ });
71
+ await writeJsonFile(files.stylesheet.path, payload.data ?? {});
72
+ files.stylesheet.hash = typeof payload.hash === "string" ? payload.hash : null;
73
+ files.stylesheet.version =
74
+ typeof payload.version === "number" ? payload.version : null;
75
+ files.stylesheet.pulled = true;
76
+ }
77
+ catch (error) {
78
+ if (error instanceof ApiError && error.status === 404) {
79
+ warnings.push("Stylesheet not found for project/version. Skipped.");
80
+ }
81
+ else {
82
+ throw error;
83
+ }
84
+ }
85
+ }
86
+ if (requested.has("figmaStyles")) {
87
+ try {
88
+ const payload = await apiGetJson({
89
+ baseUrl: runtime.apiUrl,
90
+ token: runtime.token,
91
+ path: `/api/styleflow/projects/${projectParam}/figma-styles`,
92
+ query: toQuery(runtime.projectVersionId),
93
+ });
94
+ await writeJsonFile(files.figmaStyles.path, payload.data ?? {});
95
+ files.figmaStyles.hash = typeof payload.hash === "string" ? payload.hash : null;
96
+ files.figmaStyles.version =
97
+ typeof payload.version === "number" ? payload.version : null;
98
+ files.figmaStyles.pulled = true;
99
+ }
100
+ catch (error) {
101
+ if (error instanceof ApiError && error.status === 404) {
102
+ warnings.push("Figma styles not found for project/version. Skipped.");
103
+ }
104
+ else {
105
+ throw error;
106
+ }
107
+ }
108
+ }
109
+ const manifestPath = path.join(rawDirectory, "manifest.json");
110
+ await writeJsonFile(manifestPath, {
111
+ schemaVersion: 1,
112
+ pulledAt: new Date().toISOString(),
113
+ apiUrl: runtime.apiUrl,
114
+ projectId: runtime.projectId,
115
+ projectVersionId: runtime.projectVersionId,
116
+ resources: {
117
+ tokens: {
118
+ path: path.relative(rawDirectory, files.tokens.path),
119
+ pulled: files.tokens.pulled,
120
+ },
121
+ stylesheet: {
122
+ path: path.relative(rawDirectory, files.stylesheet.path),
123
+ pulled: files.stylesheet.pulled,
124
+ hash: files.stylesheet.hash,
125
+ version: files.stylesheet.version,
126
+ },
127
+ figmaStyles: {
128
+ path: path.relative(rawDirectory, files.figmaStyles.path),
129
+ pulled: files.figmaStyles.pulled,
130
+ hash: files.figmaStyles.hash,
131
+ version: files.figmaStyles.version,
132
+ },
133
+ },
134
+ });
135
+ const pulledResources = resources.filter((resource) => files[resource].pulled);
136
+ const skippedResources = resources.filter((resource) => !files[resource].pulled);
137
+ return {
138
+ rawDirectory,
139
+ pulledResources,
140
+ skippedResources,
141
+ warnings,
142
+ files,
143
+ manifestPath,
144
+ };
145
+ }
@@ -0,0 +1,68 @@
1
+ export type PullResource = "tokens" | "stylesheet" | "figmaStyles";
2
+ export type TokenOutputFormat = "css" | "scss" | "json" | "style-dictionary" | "tailwind";
3
+ export interface TokenOutputConfig {
4
+ format?: TokenOutputFormat;
5
+ path: string;
6
+ }
7
+ export interface JsonOutputConfig {
8
+ format?: "json";
9
+ path: string;
10
+ }
11
+ export interface ComponentsOutputConfig {
12
+ path: string;
13
+ framework?: "react" | "vue" | "svelte" | "html";
14
+ styling?: "tailwind" | "css-modules" | "styled-components";
15
+ }
16
+ export interface StyleflowOutputConfig {
17
+ language?: "typescript" | "javascript";
18
+ tokens?: TokenOutputConfig;
19
+ stylesheet?: JsonOutputConfig;
20
+ figmaStyles?: JsonOutputConfig;
21
+ components?: ComponentsOutputConfig;
22
+ }
23
+ export interface StyleflowMappingConfig {
24
+ classes?: Record<string, string>;
25
+ tokens?: Record<string, string>;
26
+ }
27
+ export interface StyleflowAuthConfig {
28
+ token?: string;
29
+ }
30
+ export interface StyleflowConfig {
31
+ projectId?: string;
32
+ projectVersionId?: string | null;
33
+ apiUrl?: string;
34
+ rawDirectory?: string;
35
+ auth?: StyleflowAuthConfig;
36
+ output?: StyleflowOutputConfig;
37
+ mapping?: StyleflowMappingConfig;
38
+ }
39
+ export interface LoadedStyleflowConfig {
40
+ config: StyleflowConfig;
41
+ configPath: string | null;
42
+ cwd: string;
43
+ }
44
+ export interface ResolvedStyleflowRuntimeConfig {
45
+ cwd: string;
46
+ projectId: string;
47
+ projectVersionId: string | null;
48
+ apiUrl: string;
49
+ token: string;
50
+ rawDirectory: string;
51
+ config: StyleflowConfig;
52
+ configPath: string | null;
53
+ }
54
+ export type TokenEntry = {
55
+ type: string;
56
+ values: Record<string, unknown>;
57
+ };
58
+ export type CollectionMeta = {
59
+ id?: string;
60
+ name: string;
61
+ defaultMode: string;
62
+ modes: string[];
63
+ };
64
+ export type StructuredTokens = {
65
+ files: Record<string, Record<string, TokenEntry>>;
66
+ collections: Record<string, CollectionMeta>;
67
+ };
68
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,YAAY,GAAG,aAAa,CAAC;AAEnE,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,kBAAkB,GAAG,UAAU,CAAC;AAE1F,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAChD,OAAO,CAAC,EAAE,UAAU,GAAG,aAAa,GAAG,mBAAmB,CAAC;CAC5D;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,CAAC;IACvC,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,UAAU,CAAC,EAAE,sBAAsB,CAAC;CACrC;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,OAAO,CAAC,EAAE,sBAAsB,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,8BAA8B;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IAClD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAC7C,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "styleflow",
3
+ "version": "0.1.0",
4
+ "description": "StyleFlow design-to-dev CLI for pulling and building tokens/stylesheets",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "styleflow": "dist/cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ },
16
+ "./types": {
17
+ "types": "./dist/types.d.ts",
18
+ "import": "./dist/types.js"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "README.md"
24
+ ],
25
+ "keywords": [
26
+ "design-tokens",
27
+ "figma",
28
+ "styleflow",
29
+ "cli"
30
+ ],
31
+ "engines": {
32
+ "node": ">=20.0.0"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "dependencies": {
38
+ "commander": "^13.1.0",
39
+ "jiti": "^2.4.2"
40
+ },
41
+ "license": "MIT"
42
+ }