willow-cli
Version:
CLI for installing Willow Design System components
508 lines (463 loc) • 14 kB
JavaScript
import chalk from 'chalk';
import path from 'path';
import { createDirectory, writeFileContent } from './fileSystem.js';
import { loadTemplate } from './templateLoader.js';
/**
* Creates all the essential design system files including tokens, theme utilities, and hooks
*/
export async function createDesignSystemFiles(libDir, projectType) {
console.log(chalk.blue('🎨 Creating design system files...'));
try {
// Create tokens file
await createTokensFile(libDir, projectType);
// Create theme-colors file
await createThemeColorsFile(libDir, projectType);
// Create theme-utils file
await createThemeUtilsFile(libDir, projectType);
// Create hooks directory
const hooksDir = path.join(libDir, 'hooks');
await createDirectory(hooksDir);
console.log(chalk.green(`✅ Created directory: ${hooksDir}`));
console.log(chalk.green('✅ All design system files created successfully'));
}
catch (error) {
console.error(chalk.red('❌ Failed to create design system files:'), error);
throw error;
}
}
/**
* Creates tokens file from template
*/
export async function createTokensFile(libDir, projectType) {
const extension = projectType.isOnlineIDE ? '.js' : '.ts';
const tokensPath = path.join(libDir, `tokens${extension}`);
console.log(chalk.blue('🎨 Creating tokens file...'));
console.log(chalk.gray(` Extension: ${extension}`));
console.log(chalk.gray(` Creating tokens file: ${tokensPath}`));
let content;
try {
content = await loadTemplate('lib/tokens.ts', {
projectType,
isOnlineIDE: projectType.isOnlineIDE
});
console.log(chalk.green(' ✅ Loaded tokens from template'));
}
catch (error) {
console.log(chalk.yellow(' ⚠️ Template not found, using built-in tokens'));
content = `/**
* Willow Design System Tokens
*
* This file contains all design tokens for the Willow Design System.
* These tokens should be the single source of truth for all design values.
*/
// ============================================
// COLOR TOKENS
// ============================================
export const colors = {
// Brand Colors - Willow Primary
willow: {
50: '#F4F3FF',
100: '#E8E6FF',
200: '#D1CCFF',
300: '#BBB3FF',
400: '#A499FF',
500: '#8D80FF',
600: '#7666FF',
700: '#5F4DFF',
800: '#4833FF',
900: '#311AFF',
950: '#230E67',
},
// Neutral Colors
neutral: {
0: '#FFFFFF',
50: '#F3F7F8',
100: '#E0E9ED',
200: '#CDD9DE',
300: '#B9C9D0',
400: '#A6B9C2',
500: '#93A9B4',
600: '#7F99A6',
700: '#6C8998',
800: '#587989',
900: '#45697B',
950: '#31596D',
},
// Info Blue
info: {
50: '#F1F8FE',
100: '#E1EFFD',
200: '#BDDFFA',
300: '#62B6F4',
400: '#41A8EF',
500: '#188DDF',
600: '#0B6FBE',
700: '#0A589A',
800: '#0D4B7F',
900: '#10406A',
950: '#0B2846',
},
// Success Green
success: {
50: '#E0FAEC',
100: '#C8F5DD',
200: '#91EBBA',
300: '#5AE198',
400: '#23D775',
500: '#1FC16B',
600: '#199B55',
700: '#147540',
800: '#0E4F2A',
900: '#082A15',
},
// Warning Orange
warning: {
50: '#FFF1EB',
100: '#FFE3D6',
200: '#FFC7AD',
300: '#FFAB85',
400: '#FF8F5C',
500: '#FF8447',
600: '#E66A2E',
700: '#B35124',
800: '#803919',
900: '#4D210F',
},
// Error/Danger Red
error: {
50: '#FFEBEC',
100: '#FFD6D9',
200: '#FFADB3',
300: '#FF858C',
400: '#FF5C66',
500: '#FB3748',
600: '#E01E2F',
700: '#AB1723',
800: '#751018',
900: '#400A0C',
},
};
// ============================================
// TYPOGRAPHY TOKENS
// ============================================
export const typography = {
fontFamily: {
sans: ['Codec Pro', 'Inter', 'system-ui', '-apple-system', 'sans-serif'],
mono: ['ui-monospace', 'SFMono-Regular', 'Consolas', 'monospace'],
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1.125rem' }],
sm: ['0.875rem', { lineHeight: '1.25rem' }],
base: ['1rem', { lineHeight: '1.5rem' }],
lg: ['1.125rem', { lineHeight: '1.75rem' }],
xl: ['1.25rem', { lineHeight: '1.875rem' }],
'2xl': ['1.5rem', { lineHeight: '2rem' }],
'3xl': ['1.875rem', { lineHeight: '2.375rem' }],
'4xl': ['2.25rem', { lineHeight: '2.75rem' }],
},
fontWeight: {
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
},
};
// ============================================
// SPACING TOKENS
// ============================================
export const spacing = {
0: '0px',
px: '1px',
0.5: '0.125rem',
1: '0.25rem',
2: '0.5rem',
3: '0.75rem',
4: '1rem',
5: '1.25rem',
6: '1.5rem',
8: '2rem',
10: '2.5rem',
12: '3rem',
16: '4rem',
20: '5rem',
24: '6rem',
};
// ============================================
// SHADOW TOKENS
// ============================================
export const shadows = {
button: {
primary: {
DEFAULT: '0px 1px 3px 0px rgba(37,62,167,0.2)',
hover: '0px 2px 5px 0px rgba(37,62,167,0.3)',
active: 'inset 0px 1px 3px 0px rgba(37,62,167,0.4)',
},
secondary: {
DEFAULT: '0px 1px 2px 0px rgba(10,13,20,0.03)',
hover: '0px 1px 3px 0px rgba(10,13,20,0.05)',
active: 'inset 0px 1px 2px 0px rgba(10,13,20,0.05)',
},
},
};
// ============================================
// BORDER RADIUS TOKENS
// ============================================
export const borderRadius = {
none: '0',
sm: '0.125rem',
DEFAULT: '0.25rem',
md: '0.375rem',
lg: '0.5rem',
xl: '0.75rem',
'2xl': '1rem',
'3xl': '1.5rem',
full: '9999px',
};
// ============================================
// COLLECTED TOKENS
// ============================================
export const tokens = {
colors,
typography,
spacing,
shadows,
borderRadius,
};
export default tokens;`;
}
await writeFileContent(tokensPath, content);
console.log(chalk.green(` ✅ Created: ${tokensPath}`));
}
/**
* Creates theme-colors file from template
*/
export async function createThemeColorsFile(libDir, projectType) {
const extension = projectType.isOnlineIDE ? '.js' : '.ts';
const themeColorsPath = path.join(libDir, `theme-colors${extension}`);
console.log(chalk.gray(` Creating theme-colors file: ${themeColorsPath}`));
let content;
try {
content = await loadTemplate('lib/theme-colors.ts', {
projectType,
isOnlineIDE: projectType.isOnlineIDE
});
}
catch (error) {
content = `/**
* Willow Design System Theme Colors
*
* Centralized color definitions that map to CSS custom properties
* for dynamic theming support.
*/
export const themeColors = {
light: {
background: 'hsl(0, 0%, 100%)',
foreground: 'hsl(222.2, 84%, 4.9%)',
primary: 'hsl(262, 100%, 70%)',
'primary-foreground': 'hsl(0, 0%, 100%)',
secondary: 'hsl(210, 40%, 96.1%)',
'secondary-foreground': 'hsl(222.2, 47.4%, 11.2%)',
muted: 'hsl(210, 40%, 96.1%)',
'muted-foreground': 'hsl(215.4, 16.3%, 46.9%)',
accent: 'hsl(210, 40%, 96.1%)',
'accent-foreground': 'hsl(222.2, 47.4%, 11.2%)',
destructive: 'hsl(0, 84.2%, 60.2%)',
'destructive-foreground': 'hsl(210, 40%, 98%)',
border: 'hsl(214.3, 31.8%, 91.4%)',
input: 'hsl(214.3, 31.8%, 91.4%)',
ring: 'hsl(262, 100%, 70%)',
},
dark: {
background: 'hsl(222.2, 84%, 4.9%)',
foreground: 'hsl(210, 40%, 98%)',
primary: 'hsl(262, 83%, 58%)',
'primary-foreground': 'hsl(222.2, 47.4%, 11.2%)',
secondary: 'hsl(217.2, 32.6%, 17.5%)',
'secondary-foreground': 'hsl(210, 40%, 98%)',
muted: 'hsl(217.2, 32.6%, 17.5%)',
'muted-foreground': 'hsl(215, 20.2%, 65.1%)',
accent: 'hsl(217.2, 32.6%, 17.5%)',
'accent-foreground': 'hsl(210, 40%, 98%)',
destructive: 'hsl(0, 62.8%, 30.6%)',
'destructive-foreground': 'hsl(210, 40%, 98%)',
border: 'hsl(217.2, 32.6%, 17.5%)',
input: 'hsl(217.2, 32.6%, 17.5%)',
ring: 'hsl(262, 83%, 58%)',
},
} as const;
export type ThemeMode = keyof typeof themeColors;
export type ThemeColorKey = keyof typeof themeColors.light;`;
}
await writeFileContent(themeColorsPath, content);
console.log(chalk.green(` ✅ Created: ${themeColorsPath}`));
}
/**
* Creates theme-utils file from template
*/
export async function createThemeUtilsFile(libDir, projectType) {
const extension = projectType.isOnlineIDE ? '.js' : '.ts';
const themeUtilsPath = path.join(libDir, `theme-utils${extension}`);
console.log(chalk.gray(` Creating theme-utils file: ${themeUtilsPath}`));
let content;
try {
content = await loadTemplate('lib/theme-utils.ts', {
projectType,
isOnlineIDE: projectType.isOnlineIDE
});
}
catch (error) {
content = `/**
* Willow Design System Theme Utilities
*
* Utilities for managing theme state, applying theme colors,
* and handling light/dark mode transitions.
*/
import { themeColors, type ThemeMode, type ThemeColorKey } from './theme-colors${projectType.isOnlineIDE ? '.js' : ''}';
export type Theme = "dark" | "light" | "system";
/**
* Get the current theme from localStorage
*/
export function getTheme(): Theme {
if (typeof window === "undefined") return "system";
const stored = localStorage.getItem("willow-theme") as Theme;
if (stored && ["dark", "light", "system"].includes(stored)) {
return stored;
}
return "system";
}
/**
* Set and apply a theme
*/
export function setTheme(theme: Theme): void {
if (typeof window === "undefined") return;
localStorage.setItem("willow-theme", theme);
const root = window.document.documentElement;
root.classList.remove("light", "dark");
if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light";
root.classList.add(systemTheme);
applyThemeColors(systemTheme);
} else {
root.classList.add(theme);
applyThemeColors(theme as ThemeMode);
}
}
/**
* Apply theme colors to CSS custom properties
*/
function applyThemeColors(mode: ThemeMode): void {
if (typeof window === "undefined") return;
const colors = themeColors[mode];
const root = window.document.documentElement;
Object.entries(colors).forEach(([key, value]) => {
root.style.setProperty(\`--\${key}\`, value);
});
}
/**
* Initialize theme on page load
*/
export function initializeTheme(): void {
if (typeof window === "undefined") return;
const theme = getTheme();
setTheme(theme);
// Listen for system theme changes
if (theme === "system") {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handleChange = () => {
if (getTheme() === "system") {
setTheme("system");
}
};
mediaQuery.addEventListener("change", handleChange);
}
}
/**
* Get a theme color value
*/
export function getThemeColor(colorKey: ThemeColorKey, mode?: ThemeMode): string {
const currentMode = mode || (typeof window !== "undefined" &&
window.document.documentElement.classList.contains("dark") ? "dark" : "light");
return themeColors[currentMode][colorKey];
}
/**
* Check if dark mode is currently active
*/
export function isDarkMode(): boolean {
if (typeof window === "undefined") return false;
return window.document.documentElement.classList.contains("dark");
}
/**
* Toggle between light and dark mode
*/
export function toggleTheme(): void {
const currentTheme = getTheme();
if (currentTheme === "system") {
setTheme(isDarkMode() ? "light" : "dark");
} else {
setTheme(currentTheme === "light" ? "dark" : "light");
}
}`;
}
await writeFileContent(themeUtilsPath, content);
console.log(chalk.green(` ✅ Created: ${themeUtilsPath}`));
}
/**
* Creates a modular utility function for writing core lib files
*/
export async function createCoreLibFiles(libDir, projectType) {
const extension = projectType.isOnlineIDE ? '.js' : '.ts';
console.log(chalk.blue('📚 Creating core lib files...'));
// Create utils.ts from template or fallback
const utilsPath = `${libDir}/utils${extension}`;
let utilsContent;
try {
utilsContent = await loadTemplate('lib/utils.ts', {
projectType,
isOnlineIDE: projectType.isOnlineIDE
});
}
catch (error) {
// Use JavaScript-compatible syntax for online IDEs
if (projectType.isOnlineIDE) {
utilsContent = `import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
/**
* Core utility for combining class names with Tailwind CSS
* Handles conditional classes, removes duplicates, and resolves conflicts
*/
export function cn(...inputs) {
return twMerge(clsx(inputs));
}`;
}
else {
utilsContent = `import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
/**
* Core utility for combining class names with Tailwind CSS
* Handles conditional classes, removes duplicates, and resolves conflicts
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}`;
}
}
await writeFileContent(utilsPath, utilsContent);
console.log(chalk.green(`✅ Created: ${utilsPath}`));
}
/**
* Creates version tracking file for willow installations
*/
export async function createVersionFile(version = '0.6.26') {
const versionData = {
version,
installed: new Date().toISOString().split('T')[0],
timestamp: new Date().toISOString(),
components: [],
lastUpdated: new Date().toISOString(),
};
await writeFileContent('.willow-version.json', JSON.stringify(versionData, null, 2));
console.log(chalk.green('✅ Created: .willow-version.json'));
}