@advantacode/brander 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.
@@ -0,0 +1,46 @@
1
+ import { converter } from "culori";
2
+ const toOklch = converter("oklch");
3
+ export const scaleSteps = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
4
+ const lightnessByStep = {
5
+ 50: 0.97,
6
+ 100: 0.93,
7
+ 200: 0.87,
8
+ 300: 0.8,
9
+ 400: 0.72,
10
+ 500: 0.65,
11
+ 600: 0.57,
12
+ 700: 0.49,
13
+ 800: 0.4,
14
+ 900: 0.32,
15
+ 950: 0.24
16
+ };
17
+ export function generatePrimitivePalettes(baseColors) {
18
+ return Object.fromEntries(Object.entries(baseColors).map(([colorName, colorValue]) => [colorName, generatePrimitiveScale(colorValue)]));
19
+ }
20
+ export function resolvePrimitiveReference(palettes, reference) {
21
+ const [colorName, rawStep] = reference.split(".");
22
+ const step = Number(rawStep);
23
+ return palettes[colorName][step];
24
+ }
25
+ function generatePrimitiveScale(baseColorValue) {
26
+ const baseColor = toOklch(baseColorValue);
27
+ if (!baseColor) {
28
+ throw new Error(`Unable to convert color "${baseColorValue}" to oklch.`);
29
+ }
30
+ return Object.fromEntries(scaleSteps.map((step) => [step, formatOklchColor({ l: lightnessByStep[step], c: baseColor.c, h: baseColor.h })]));
31
+ }
32
+ function formatOklchColor(color) {
33
+ const lightness = roundComponent(color.l, 6) ?? "none";
34
+ const chroma = roundComponent(color.c, 6) ?? "none";
35
+ const hue = roundComponent(color.h, 3) ?? "none";
36
+ const alpha = roundComponent(color.alpha, 3);
37
+ return alpha !== undefined && alpha < 1
38
+ ? `oklch(${lightness} ${chroma} ${hue} / ${alpha})`
39
+ : `oklch(${lightness} ${chroma} ${hue})`;
40
+ }
41
+ function roundComponent(value, precision) {
42
+ if (value === undefined) {
43
+ return undefined;
44
+ }
45
+ return Number(value.toFixed(precision));
46
+ }
@@ -0,0 +1,145 @@
1
+ import { converter } from "culori";
2
+ import { resolvePrimitiveReference } from "./palette.js";
3
+ const toRgb = converter("rgb");
4
+ export const themeNames = ["light", "dark"];
5
+ export const semanticTokenNames = [
6
+ "background",
7
+ "surface",
8
+ "text",
9
+ "muted",
10
+ "muted-foreground",
11
+ "card",
12
+ "card-foreground",
13
+ "popover",
14
+ "popover-foreground",
15
+ "border",
16
+ "input",
17
+ "ring",
18
+ "primary",
19
+ "primary-foreground",
20
+ "secondary",
21
+ "secondary-foreground",
22
+ "accent",
23
+ "accent-foreground",
24
+ "info",
25
+ "info-foreground",
26
+ "success",
27
+ "success-foreground",
28
+ "warning",
29
+ "warning-foreground",
30
+ "danger",
31
+ "danger-foreground"
32
+ ];
33
+ export function buildThemeReferences(palettes) {
34
+ const lightPrimary = "primary.600";
35
+ const darkPrimary = "primary.500";
36
+ const lightSecondary = "secondary.600";
37
+ const darkSecondary = "secondary.500";
38
+ const lightAccent = "accent.600";
39
+ const darkAccent = "accent.500";
40
+ const lightInfo = "info.600";
41
+ const darkInfo = "info.500";
42
+ const lightSuccess = "success.600";
43
+ const darkSuccess = "success.500";
44
+ const lightWarning = "warning.600";
45
+ const darkWarning = "warning.500";
46
+ const lightDanger = "danger.600";
47
+ const darkDanger = "danger.500";
48
+ return {
49
+ light: {
50
+ background: "neutral.50",
51
+ surface: "neutral.100",
52
+ text: "neutral.950",
53
+ muted: "neutral.100",
54
+ "muted-foreground": "neutral.700",
55
+ card: "neutral.50",
56
+ "card-foreground": "neutral.950",
57
+ popover: "neutral.50",
58
+ "popover-foreground": "neutral.950",
59
+ border: "neutral.200",
60
+ input: "neutral.200",
61
+ ring: "primary.500",
62
+ primary: lightPrimary,
63
+ "primary-foreground": pickForegroundReference(palettes, lightPrimary),
64
+ secondary: lightSecondary,
65
+ "secondary-foreground": pickForegroundReference(palettes, lightSecondary),
66
+ accent: lightAccent,
67
+ "accent-foreground": pickForegroundReference(palettes, lightAccent),
68
+ info: lightInfo,
69
+ "info-foreground": pickForegroundReference(palettes, lightInfo),
70
+ success: lightSuccess,
71
+ "success-foreground": pickForegroundReference(palettes, lightSuccess),
72
+ warning: lightWarning,
73
+ "warning-foreground": pickForegroundReference(palettes, lightWarning),
74
+ danger: lightDanger,
75
+ "danger-foreground": pickForegroundReference(palettes, lightDanger)
76
+ },
77
+ dark: {
78
+ background: "neutral.950",
79
+ surface: "neutral.900",
80
+ text: "neutral.50",
81
+ muted: "neutral.900",
82
+ "muted-foreground": "neutral.300",
83
+ card: "neutral.900",
84
+ "card-foreground": "neutral.50",
85
+ popover: "neutral.900",
86
+ "popover-foreground": "neutral.50",
87
+ border: "neutral.800",
88
+ input: "neutral.800",
89
+ ring: "primary.400",
90
+ primary: darkPrimary,
91
+ "primary-foreground": pickForegroundReference(palettes, darkPrimary),
92
+ secondary: darkSecondary,
93
+ "secondary-foreground": pickForegroundReference(palettes, darkSecondary),
94
+ accent: darkAccent,
95
+ "accent-foreground": pickForegroundReference(palettes, darkAccent),
96
+ info: darkInfo,
97
+ "info-foreground": pickForegroundReference(palettes, darkInfo),
98
+ success: darkSuccess,
99
+ "success-foreground": pickForegroundReference(palettes, darkSuccess),
100
+ warning: darkWarning,
101
+ "warning-foreground": pickForegroundReference(palettes, darkWarning),
102
+ danger: darkDanger,
103
+ "danger-foreground": pickForegroundReference(palettes, darkDanger)
104
+ }
105
+ };
106
+ }
107
+ export function resolveThemeValues(palettes, references) {
108
+ return Object.fromEntries(themeNames.map((themeName) => [
109
+ themeName,
110
+ Object.fromEntries(semanticTokenNames.map((semanticTokenName) => [
111
+ semanticTokenName,
112
+ resolvePrimitiveReference(palettes, references[themeName][semanticTokenName])
113
+ ]))
114
+ ]));
115
+ }
116
+ function pickForegroundReference(palettes, backgroundReference) {
117
+ const background = resolvePrimitiveReference(palettes, backgroundReference);
118
+ const lightForeground = "neutral.50";
119
+ const darkForeground = "neutral.950";
120
+ const lightContrast = getContrastRatio(background, resolvePrimitiveReference(palettes, lightForeground));
121
+ const darkContrast = getContrastRatio(background, resolvePrimitiveReference(palettes, darkForeground));
122
+ return lightContrast >= darkContrast ? lightForeground : darkForeground;
123
+ }
124
+ function getContrastRatio(leftColor, rightColor) {
125
+ const left = toRgb(leftColor);
126
+ const right = toRgb(rightColor);
127
+ if (!left || !right) {
128
+ return 1;
129
+ }
130
+ const leftLuminance = getRelativeLuminance(left.r, left.g, left.b);
131
+ const rightLuminance = getRelativeLuminance(right.r, right.g, right.b);
132
+ const lightest = Math.max(leftLuminance, rightLuminance);
133
+ const darkest = Math.min(leftLuminance, rightLuminance);
134
+ return (lightest + 0.05) / (darkest + 0.05);
135
+ }
136
+ function getRelativeLuminance(red, green, blue) {
137
+ const [r, g, b] = [red, green, blue].map(toLinearValue);
138
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
139
+ }
140
+ function toLinearValue(channel) {
141
+ if (channel <= 0.04045) {
142
+ return channel / 12.92;
143
+ }
144
+ return ((channel + 0.055) / 1.055) ** 2.4;
145
+ }
@@ -0,0 +1,25 @@
1
+ import { generatePrimitivePalettes } from "./palette.js";
2
+ import { buildThemeReferences, resolveThemeValues } from "./semantics.js";
3
+ export function createTokenModel(baseColors) {
4
+ const primitivePalettes = generatePrimitivePalettes(baseColors);
5
+ const themeReferences = buildThemeReferences(primitivePalettes);
6
+ const themeValues = resolveThemeValues(primitivePalettes, themeReferences);
7
+ return {
8
+ color: {
9
+ primitive: primitivePalettes,
10
+ semantic: {
11
+ light: mapThemeTokens(themeReferences.light, themeValues.light),
12
+ dark: mapThemeTokens(themeReferences.dark, themeValues.dark)
13
+ }
14
+ }
15
+ };
16
+ }
17
+ function mapThemeTokens(references, values) {
18
+ return Object.fromEntries(Object.entries(references).map(([tokenName, reference]) => [
19
+ tokenName,
20
+ {
21
+ ref: reference,
22
+ value: values[tokenName]
23
+ }
24
+ ]));
25
+ }
@@ -0,0 +1,220 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { pathToFileURL, fileURLToPath } from "url";
4
+ import { config as loadDotEnv } from "dotenv";
5
+ import { writeCssArtifacts } from "./adapters/css.js";
6
+ import { writeFigmaAdapter } from "./adapters/figma.js";
7
+ import { writeMetadataJson, writeTokenModelJson } from "./adapters/json.js";
8
+ import { writeScssArtifacts } from "./adapters/scss.js";
9
+ import { writeTailwindAdapter } from "./adapters/tailwind.js";
10
+ import { writeTypeScriptArtifacts } from "./adapters/typescript.js";
11
+ import { normalizeVariablePrefix } from "./adapters/variables.js";
12
+ import { baseColorNames, resolveBaseColors } from "./engine/color-parser.js";
13
+ import { createTokenModel } from "./engine/themes.js";
14
+ export const supportedFormats = [
15
+ "all",
16
+ "css",
17
+ "json",
18
+ "typescript",
19
+ "scss",
20
+ "tailwind",
21
+ "bootstrap",
22
+ "figma"
23
+ ];
24
+ export async function generateTokens(options = {}) {
25
+ const defaultOutputDir = resolveDefaultOutputDir();
26
+ const outputDir = options.outputDir ? path.resolve(process.cwd(), options.outputDir) : defaultOutputDir;
27
+ const theme = options.theme ?? "both";
28
+ const formats = resolveFormats(options.formats);
29
+ loadDotEnv({ path: path.resolve(process.cwd(), ".env"), quiet: true });
30
+ const brandConfig = await loadBrandConfig();
31
+ const prefix = resolveCssPrefix(options.prefix, brandConfig.css?.prefix, process.env.CSS_PREFIX);
32
+ const baseColors = resolveBaseColors(brandConfig.colors ?? {});
33
+ const tokenModel = createTokenModel(baseColors);
34
+ fs.mkdirSync(outputDir, { recursive: true });
35
+ if (outputDir === defaultOutputDir) {
36
+ removeLegacyGeneratedFiles();
37
+ }
38
+ removeManagedArtifacts(outputDir);
39
+ const writtenArtifacts = [];
40
+ const writtenAdapters = [];
41
+ const variableOptions = { prefix };
42
+ if (formats.has("css")) {
43
+ writtenArtifacts.push(...writeCssArtifacts(outputDir, tokenModel, theme, variableOptions));
44
+ }
45
+ if (formats.has("json")) {
46
+ writtenArtifacts.push(...writeTokenModelJson(outputDir, tokenModel));
47
+ }
48
+ if (formats.has("scss") || formats.has("bootstrap")) {
49
+ writtenArtifacts.push(...writeScssArtifacts(outputDir, tokenModel, {
50
+ includeTokensScss: formats.has("scss"),
51
+ includeBootstrapAdapter: formats.has("bootstrap"),
52
+ variableOptions
53
+ }));
54
+ }
55
+ if (formats.has("tailwind")) {
56
+ writtenArtifacts.push(...writeTailwindAdapter(outputDir, tokenModel, variableOptions));
57
+ writtenAdapters.push("tailwind");
58
+ }
59
+ if (formats.has("bootstrap")) {
60
+ writtenAdapters.push("bootstrap");
61
+ }
62
+ if (formats.has("figma")) {
63
+ writtenArtifacts.push(...writeFigmaAdapter(outputDir, tokenModel));
64
+ writtenAdapters.push("figma");
65
+ }
66
+ const plannedArtifacts = [...new Set([...writtenArtifacts, ...(formats.has("typescript") ? ["tokens.ts"] : []), "metadata.json"])];
67
+ const metadata = createGeneratedMetadata(theme, writtenAdapters, plannedArtifacts, prefix);
68
+ if (formats.has("typescript")) {
69
+ writtenArtifacts.push(...writeTypeScriptArtifacts(outputDir, tokenModel, metadata));
70
+ }
71
+ writtenArtifacts.push(...writeMetadataJson(outputDir, metadata));
72
+ console.log(`✔ AdvantaCode tokens generated in ${path.relative(process.cwd(), outputDir) || "."}!`);
73
+ }
74
+ async function loadBrandConfig() {
75
+ const configPath = findBrandConfigPath();
76
+ if (!configPath) {
77
+ return {};
78
+ }
79
+ try {
80
+ const importedConfig = await import(pathToFileURL(configPath).href);
81
+ return parseBrandConfig(importedConfig.default ?? importedConfig, configPath);
82
+ }
83
+ catch (error) {
84
+ throw new Error(`Failed to load ${path.basename(configPath)}.\n${String(error)}`);
85
+ }
86
+ }
87
+ function findBrandConfigPath() {
88
+ const candidateFiles = [
89
+ "brand.config.ts",
90
+ "brand.config.mts",
91
+ "brand.config.cts",
92
+ "brand.config.js",
93
+ "brand.config.mjs",
94
+ "brand.config.cjs"
95
+ ];
96
+ for (const candidateFile of candidateFiles) {
97
+ const candidatePath = path.resolve(process.cwd(), candidateFile);
98
+ if (fs.existsSync(candidatePath)) {
99
+ return candidatePath;
100
+ }
101
+ }
102
+ return undefined;
103
+ }
104
+ function parseBrandConfig(rawConfig, configPath) {
105
+ if (typeof rawConfig !== "object" || rawConfig === null || Array.isArray(rawConfig)) {
106
+ throw new Error(`Expected ${path.basename(configPath)} to export an object.`);
107
+ }
108
+ const config = rawConfig;
109
+ const parsedConfig = {};
110
+ if ("name" in config && config.name !== undefined) {
111
+ if (typeof config.name !== "string") {
112
+ throw new Error(`Expected "name" in ${path.basename(configPath)} to be a string.`);
113
+ }
114
+ parsedConfig.name = config.name;
115
+ }
116
+ if ("colors" in config && config.colors !== undefined) {
117
+ if (typeof config.colors !== "object" || config.colors === null || Array.isArray(config.colors)) {
118
+ throw new Error(`Expected "colors" in ${path.basename(configPath)} to be an object.`);
119
+ }
120
+ const colorEntries = Object.entries(config.colors);
121
+ const parsedColors = {};
122
+ for (const [colorName, colorValue] of colorEntries) {
123
+ if (!baseColorNames.includes(colorName)) {
124
+ throw new Error(`Unsupported color token "${colorName}" in ${path.basename(configPath)}.`);
125
+ }
126
+ if (typeof colorValue !== "string") {
127
+ throw new Error(`Expected color "${colorName}" in ${path.basename(configPath)} to be a string.`);
128
+ }
129
+ parsedColors[colorName] = colorValue;
130
+ }
131
+ parsedConfig.colors = parsedColors;
132
+ }
133
+ if ("css" in config && config.css !== undefined) {
134
+ if (typeof config.css !== "object" || config.css === null || Array.isArray(config.css)) {
135
+ throw new Error(`Expected "css" in ${path.basename(configPath)} to be an object.`);
136
+ }
137
+ const cssConfig = config.css;
138
+ const parsedCssConfig = {};
139
+ if ("prefix" in cssConfig && cssConfig.prefix !== undefined) {
140
+ if (typeof cssConfig.prefix !== "string") {
141
+ throw new Error(`Expected "css.prefix" in ${path.basename(configPath)} to be a string.`);
142
+ }
143
+ parsedCssConfig.prefix = cssConfig.prefix;
144
+ }
145
+ parsedConfig.css = parsedCssConfig;
146
+ }
147
+ return parsedConfig;
148
+ }
149
+ function resolveFormats(formats) {
150
+ const resolvedFormats = new Set();
151
+ if (!formats || formats.length === 0 || formats.includes("all")) {
152
+ resolvedFormats.add("css");
153
+ resolvedFormats.add("json");
154
+ resolvedFormats.add("typescript");
155
+ resolvedFormats.add("scss");
156
+ resolvedFormats.add("tailwind");
157
+ resolvedFormats.add("bootstrap");
158
+ resolvedFormats.add("figma");
159
+ return resolvedFormats;
160
+ }
161
+ for (const format of formats) {
162
+ if (format === "all") {
163
+ continue;
164
+ }
165
+ resolvedFormats.add(format);
166
+ }
167
+ return resolvedFormats;
168
+ }
169
+ function removeLegacyGeneratedFiles() {
170
+ const legacyFiles = [
171
+ path.resolve(process.cwd(), "dist", "tokens.css"),
172
+ path.resolve(process.cwd(), "dist", "tokens.ts"),
173
+ path.resolve(process.cwd(), "dist", "tokens.scss"),
174
+ path.resolve(process.cwd(), "dist", "tokens.json"),
175
+ path.resolve(process.cwd(), "dist", "metadata.json"),
176
+ path.resolve(process.cwd(), "dist", "tailwind-preset.ts"),
177
+ path.resolve(process.cwd(), "dist", "generated", "tailwind-preset.ts")
178
+ ];
179
+ for (const legacyFile of legacyFiles) {
180
+ fs.rmSync(legacyFile, { force: true });
181
+ }
182
+ removeManagedArtifacts(path.resolve(process.cwd(), "dist", "generated"));
183
+ }
184
+ function resolveDefaultOutputDir() {
185
+ return path.resolve(process.cwd(), "dist", "brander");
186
+ }
187
+ function removeManagedArtifacts(outputDir) {
188
+ const managedFiles = [
189
+ "tokens.css",
190
+ "tokens.json",
191
+ "tokens.ts",
192
+ "tokens.scss",
193
+ "metadata.json",
194
+ path.join("themes", "light.css"),
195
+ path.join("themes", "dark.css"),
196
+ path.join("adapters", "tailwind.preset.ts"),
197
+ path.join("adapters", "bootstrap.variables.scss"),
198
+ path.join("adapters", "figma.tokens.json")
199
+ ];
200
+ for (const managedFile of managedFiles) {
201
+ fs.rmSync(path.join(outputDir, managedFile), { force: true });
202
+ }
203
+ }
204
+ function createGeneratedMetadata(theme, adapters, artifacts, cssPrefix) {
205
+ const packageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url));
206
+ const packageVersion = fs.existsSync(packageJsonPath)
207
+ ? (JSON.parse(fs.readFileSync(packageJsonPath, "utf8")).version ?? "0.0.0")
208
+ : "0.0.0";
209
+ return {
210
+ version: packageVersion,
211
+ generated: new Date().toISOString(),
212
+ themes: theme === "both" ? ["light", "dark"] : [theme],
213
+ adapters: [...new Set(adapters)].sort(),
214
+ artifacts: [...new Set(artifacts)].sort(),
215
+ cssPrefix
216
+ };
217
+ }
218
+ function resolveCssPrefix(cliPrefix, configPrefix, envPrefix) {
219
+ return normalizeVariablePrefix(cliPrefix ?? envPrefix ?? configPrefix ?? "");
220
+ }
package/dist/index.js ADDED
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env -S node --import tsx/esm
2
+ import fs from "fs";
3
+ import { fileURLToPath, pathToFileURL } from "url";
4
+ import { generateTokens, supportedFormats } from "./generate-tokens.js";
5
+ import { setupProject } from "./setup.js";
6
+ const packageVersion = loadPackageVersion();
7
+ if (isCliEntryPoint()) {
8
+ process.exit(await runCli(process.argv.slice(2)));
9
+ }
10
+ export async function runCli(args) {
11
+ try {
12
+ if (args.includes("--version") || args.includes("-v")) {
13
+ console.log(packageVersion);
14
+ return 0;
15
+ }
16
+ const command = resolveCommand(args);
17
+ const commandArgs = command === "generate" ? args : args.slice(1);
18
+ if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
19
+ console.log(getHelpText(command));
20
+ return 0;
21
+ }
22
+ if (command === "generate") {
23
+ await generateTokens(parseGenerateArgs(commandArgs));
24
+ return 0;
25
+ }
26
+ await setupProject(parseSetupArgs(command, commandArgs));
27
+ return 0;
28
+ }
29
+ catch (error) {
30
+ const message = error instanceof Error ? error.message : String(error);
31
+ console.error(`Error: ${message}`);
32
+ return 1;
33
+ }
34
+ }
35
+ function isCliEntryPoint() {
36
+ const entryPoint = process.argv[1];
37
+ return Boolean(entryPoint) && pathToFileURL(entryPoint).href === import.meta.url;
38
+ }
39
+ function loadPackageVersion() {
40
+ const packageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url));
41
+ if (!fs.existsSync(packageJsonPath)) {
42
+ return "0.0.0";
43
+ }
44
+ return (JSON.parse(fs.readFileSync(packageJsonPath, "utf8")).version ?? "0.0.0");
45
+ }
46
+ function resolveCommand(args) {
47
+ const firstArg = args[0];
48
+ if (!firstArg || firstArg.startsWith("-")) {
49
+ return "generate";
50
+ }
51
+ if (firstArg === "setup" || firstArg === "init") {
52
+ return firstArg;
53
+ }
54
+ throw new Error(`Unknown command "${firstArg}". Use --help to see supported commands.`);
55
+ }
56
+ function parseGenerateArgs(args) {
57
+ const options = {};
58
+ for (let index = 0; index < args.length; index += 1) {
59
+ const arg = args[index];
60
+ if (arg === "--out") {
61
+ options.outputDir = getNextArgValue(arg, args, index);
62
+ index += 1;
63
+ continue;
64
+ }
65
+ if (arg === "--format") {
66
+ options.formats = getNextArgValue(arg, args, index)
67
+ .split(",")
68
+ .map((value) => normalizeFormat(value.trim()));
69
+ index += 1;
70
+ continue;
71
+ }
72
+ if (arg === "--theme") {
73
+ const themeValue = getNextArgValue(arg, args, index);
74
+ if (!["light", "dark", "both"].includes(themeValue)) {
75
+ throw new Error('Invalid value for --theme. Use "light", "dark", or "both".');
76
+ }
77
+ options.theme = themeValue;
78
+ index += 1;
79
+ continue;
80
+ }
81
+ if (arg === "--prefix") {
82
+ options.prefix = getNextArgValue(arg, args, index);
83
+ index += 1;
84
+ continue;
85
+ }
86
+ if (arg.startsWith("-")) {
87
+ throw new Error(`Unknown option "${arg}". Use --help to see supported flags.`);
88
+ }
89
+ }
90
+ return options;
91
+ }
92
+ function parseSetupArgs(command, args) {
93
+ const generateOptions = parseGenerateArgs(args.filter((arg) => !["--style", "--script-name", "--skip-imports", "--skip-script", "--skip-config", "--skip-generate"].includes(arg)));
94
+ const options = {
95
+ command,
96
+ ...generateOptions
97
+ };
98
+ for (let index = 0; index < args.length; index += 1) {
99
+ const arg = args[index];
100
+ if (arg === "--out" || arg === "--format" || arg === "--theme" || arg === "--prefix") {
101
+ index += 1;
102
+ continue;
103
+ }
104
+ if (arg === "--style") {
105
+ options.stylePath = getNextArgValue(arg, args, index);
106
+ index += 1;
107
+ continue;
108
+ }
109
+ if (arg === "--script-name") {
110
+ options.scriptName = getNextArgValue(arg, args, index);
111
+ index += 1;
112
+ continue;
113
+ }
114
+ if (arg === "--skip-imports") {
115
+ options.skipImports = true;
116
+ continue;
117
+ }
118
+ if (arg === "--skip-script") {
119
+ options.skipScript = true;
120
+ continue;
121
+ }
122
+ if (arg === "--skip-config") {
123
+ options.skipConfig = true;
124
+ continue;
125
+ }
126
+ if (arg === "--skip-generate") {
127
+ options.skipGenerate = true;
128
+ continue;
129
+ }
130
+ if (arg.startsWith("-")) {
131
+ throw new Error(`Unknown option "${arg}". Use --help to see supported flags.`);
132
+ }
133
+ }
134
+ return options;
135
+ }
136
+ function getNextArgValue(flag, args, index) {
137
+ const nextArg = args[index + 1];
138
+ if (nextArg === undefined) {
139
+ throw new Error(`Missing value for ${flag}.`);
140
+ }
141
+ return nextArg;
142
+ }
143
+ function normalizeFormat(value) {
144
+ if (value === "ts") {
145
+ return "typescript";
146
+ }
147
+ if (supportedFormats.includes(value)) {
148
+ return value;
149
+ }
150
+ throw new Error(`Unknown format "${value}". Use --help to see supported formats.`);
151
+ }
152
+ function getHelpText(command) {
153
+ if (command === "setup" || command === "init") {
154
+ return `AdvantaCode Brander
155
+
156
+ Usage:
157
+ advantacode-brander ${command} [options]
158
+
159
+ Setup options:
160
+ --out <dir> Output directory (default: src/brander)
161
+ --style <path> Stylesheet file to patch with token imports
162
+ --script-name <name> package.json script to create (default: brand:generate)
163
+ --skip-imports Do not patch a stylesheet with token imports
164
+ --skip-script Do not add a package.json script
165
+ --skip-config Do not create brand.config.ts when missing
166
+ --skip-generate Do not run token generation after setup
167
+
168
+ Generation options:
169
+ --format <list> Comma-separated formats: all, css, json, typescript|ts, scss, tailwind, bootstrap, figma
170
+ --theme <value> Theme CSS output: light, dark, or both (default: both)
171
+ --prefix <value> CSS variable prefix. Use "" or omit for no prefix
172
+
173
+ Examples:
174
+ advantacode-brander ${command}
175
+ advantacode-brander ${command} --out src/brander
176
+ advantacode-brander ${command} --style src/style.css
177
+ advantacode-brander ${command} --skip-imports --skip-generate
178
+ `;
179
+ }
180
+ return `AdvantaCode Brander
181
+
182
+ Usage:
183
+ advantacode-brander [options]
184
+ advantacode-brander setup [options]
185
+ advantacode-brander init [options]
186
+
187
+ Commands:
188
+ setup Configure an existing app to use Brander
189
+ init Initialize a new app for Brander-driven tokens
190
+
191
+ Options:
192
+ -h, --help Show this help output
193
+ -v, --version Show the installed package version
194
+ --out <dir> Output directory (default: dist/brander)
195
+ --format <list> Comma-separated formats: all, css, json, typescript|ts, scss, tailwind, bootstrap, figma
196
+ --theme <value> Theme CSS output: light, dark, or both (default: both)
197
+ --prefix <value> CSS variable prefix. Use "" or omit for no prefix
198
+
199
+ Examples:
200
+ advantacode-brander
201
+ advantacode-brander --out src/tokens
202
+ advantacode-brander setup --out src/brander --style src/style.css
203
+ advantacode-brander init --out resources/brander --skip-imports
204
+ `;
205
+ }