Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

[bdsg 0.2.0]: https://github.com/CarlosEduJs/bdsg/releases/tag/bdsg@v0.2.0

## [bdsg-cli 0.2.0] - 2026-01-14

### Added

- **Gradient Command**: New `bdsg generate gradient` command
- Generate color gradients with OKLCH interpolation
- Easing functions: `linear`, `easeIn`, `easeOut`, `easeInOut`
- Hue direction control: `shorter`, `longer`, `increasing`, `decreasing`
- Output CSS variables and gradient strings
- JSON export with gradient metadata

### Changed

- **Dependencies**: Updated `bdsg` dependency to `^0.2.0`

[bdsg-cli 0.2.0]: https://github.com/CarlosEduJs/bdsg/releases/tag/bdsg-cli@v0.2.0

## [bdsg-cli 0.1.2] - 2026-01-12

### Fixed
Expand Down
46 changes: 46 additions & 0 deletions packages/bdsg-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ bdsg generate palette "#3B82F6" -n primary
bdsg generate typography -r golden-ratio
bdsg generate spacing -m fibonacci
bdsg generate shadows -s material
bdsg generate gradient "#FF0000" "#0000FF" -s 5

# Validate WCAG contrast
bdsg validate "#3B82F6" "#FFFFFF"
Expand Down Expand Up @@ -159,6 +160,51 @@ Options:

Available styles: `material`, `soft`, `hard`

#### generate gradient

*New in v0.2.0*

```bash
bdsg generate gradient <startColor> <endColor> [options]

Arguments:
startColor Start color in hex format (e.g., #FF0000)
endColor End color in hex format (e.g., #0000FF)

Options:
-n, --name <name> Gradient name (default: "gradient")
-s, --steps <steps> Number of color steps (default: "5")
-e, --easing <easing> Easing function (default: "linear")
-d, --direction <dir> Hue direction (default: "shorter")
-o, --output <dir> Output directory (default: "./tokens")
-f, --format <fmt> Output format: css, json (default: "css")
```

Available easing: `linear`, `easeIn`, `easeOut`, `easeInOut`

Available hue directions: `shorter`, `longer`, `increasing`, `decreasing`

**Example:**

```
$ bdsg generate gradient "#FF0000" "#0000FF" -s 5 -e easeInOut

✔ Gradient generated!

File: ./tokens/gradient.css

Gradient colors:
1: #ff0000
2: #e8007b
3: #ba00c2
4: #7a00f4
5: #0000ff

CSS Gradients:
linear: linear-gradient(90deg, #ff0000, #e8007b, #ba00c2, #7a00f4, #0000ff)
radial: radial-gradient(circle, #ff0000, #e8007b, #ba00c2, #7a00f4, #0000ff)
```

### validate

Validate WCAG contrast between two colors with suggestions for accessibility compliance.
Expand Down
4 changes: 2 additions & 2 deletions packages/bdsg-cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bdsg-cli",
"version": "0.1.2",
"version": "0.2.0",
"description": "CLI for design system generation",
"type": "module",
"bin": {
Expand All @@ -11,7 +11,7 @@
"dev": "tsc --watch"
},
"dependencies": {
"bdsg": "^0.1.3",
"bdsg": "^0.2.0",
"commander": "^12.0.0",
"inquirer": "^9.2.0",
"chalk": "^5.3.0",
Expand Down
89 changes: 89 additions & 0 deletions packages/bdsg-cli/src/commands/generate.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import {
EASING,
generateGradient,
generatePalette,
generateShadows,
generateSpacingScale,
generateTypographyScale,
toCssGradient,
} from "bdsg";
import chalk from "chalk";
import { Command } from "commander";
Expand Down Expand Up @@ -233,4 +236,90 @@ export const generateCommand = new Command("generate")
process.exit(1);
}
}),
)
.addCommand(
new Command("gradient")
.description("Generate a color gradient")
.argument("<startColor>", "Start color in hex format (e.g., #FF0000)")
.argument("<endColor>", "End color in hex format (e.g., #0000FF)")
.option("-n, --name <name>", "Gradient name", "gradient")
.option("-s, --steps <steps>", "Number of color steps", "5")
.option(
"-e, --easing <easing>",
"Easing function (linear, easeIn, easeOut, easeInOut)",
"linear",
)
.option(
"-d, --direction <direction>",
"Hue direction (shorter, longer, increasing, decreasing)",
"shorter",
)
.option("-o, --output <dir>", "Output directory", "./tokens")
.option("-f, --format <format>", "Output format (css, json)", "css")
.action(async (startColor, endColor, options) => {
const spinner = ora("Generating gradient...").start();

try {
const steps = Number.parseInt(options.steps, 10);
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The steps option should be validated before parsing to ensure it's a valid numeric string. If a user provides a non-numeric value, Number.parseInt will return NaN, which will cause a less user-friendly error message from the library validation. Consider adding validation to check if options.steps is a valid number string and provide a clear error message if not.

Suggested change
const steps = Number.parseInt(options.steps, 10);
const stepsRaw = options.steps;
if (!/^\d+$/.test(stepsRaw)) {
throw new Error(
`Invalid value for --steps: "${stepsRaw}". Please provide a positive integer.`,
);
}
const steps = Number.parseInt(stepsRaw, 10);

Copilot uses AI. Check for mistakes.
const easingFn =
EASING[options.easing as keyof typeof EASING] || EASING.linear;

Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The easing option validation should fail with a clear error message when an invalid value is provided, rather than silently falling back to EASING.linear. This could confuse users who make typos or provide invalid easing function names. Consider validating the easing option against the valid values (linear, easeIn, easeOut, easeInOut) and throwing an error if invalid.

Suggested change
const easingFn =
EASING[options.easing as keyof typeof EASING] || EASING.linear;
const validEasings = ["linear", "easeIn", "easeOut", "easeInOut"] as const;
if (!validEasings.includes(options.easing)) {
throw new Error(
`Invalid easing option "${options.easing}". Valid options are: ${validEasings.join(
", ",
)}.`,
);
}
const easingFn = EASING[options.easing as keyof typeof EASING];

Copilot uses AI. Check for mistakes.
const colors = generateGradient(startColor, endColor, steps, {
easing: easingFn,
hueDirection: options.direction,
});

await mkdir(options.output, { recursive: true });

if (options.format === "json") {
const gradient: Record<string, unknown> = {
name: options.name,
start: startColor,
end: endColor,
steps,
colors,
css: {
linear: toCssGradient("linear", colors, 90),
radial: toCssGradient("radial", colors),
},
};
await writeFile(
join(options.output, `${options.name}.json`),
JSON.stringify({ gradient }, null, 2),
);
} else {
Comment on lines +302 to +318
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format option should be validated to ensure it's either "css" or "json". If a user provides an invalid format value, the code currently defaults to CSS output without any warning to the user. Consider validating the format option and providing a clear error message for invalid values.

Copilot uses AI. Check for mistakes.
let css = ":root {\n";
for (let i = 0; i < colors.length; i++) {
css += ` --${options.name}-${i + 1}: ${colors[i]};\n`;
}
css += ` --${options.name}-linear: ${toCssGradient("linear", colors, 90)};\n`;
css += ` --${options.name}-radial: ${toCssGradient("radial", colors)};\n`;
css += "}\n";
await writeFile(join(options.output, `${options.name}.css`), css);
}

spinner.succeed(chalk.green("Gradient generated!"));
console.log(
chalk.dim(
`\nFile: ${options.output}/${options.name}.${options.format}`,
),
);
console.log();

// Show preview
console.log(chalk.bold("Gradient colors:"));
for (let i = 0; i < colors.length; i++) {
console.log(` ${i + 1}: ${colors[i]}`);
}
console.log();
console.log(chalk.bold("CSS Gradients:"));
console.log(` linear: ${toCssGradient("linear", colors, 90)}`);
console.log(` radial: ${toCssGradient("radial", colors)}`);
console.log();
} catch (error) {
spinner.fail(chalk.red("Failed to generate gradient"));
console.error(error);
process.exit(1);
}
}),
);
Loading