@vue-skuilder/cli 0.1.4 → 0.1.5

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,163 @@
1
+ import { Command } from 'commander';
2
+ import PouchDB from 'pouchdb';
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+ import chalk from 'chalk';
6
+ import { CouchDBToStaticPacker } from '@vue-skuilder/db/packer';
7
+
8
+ export function createPackCommand(): Command {
9
+ return new Command('pack')
10
+ .description('Pack a CouchDB course into static files')
11
+ .argument('<courseId>', 'Course ID to pack')
12
+ .option('-s, --server <url>', 'CouchDB server URL', 'http://localhost:5984')
13
+ .option('-u, --username <username>', 'CouchDB username')
14
+ .option('-p, --password <password>', 'CouchDB password')
15
+ .option('-o, --output <dir>', 'Output directory', './static-courses')
16
+ .option('-c, --chunk-size <size>', 'Documents per chunk', '1000')
17
+ .option('--no-attachments', 'Exclude attachments')
18
+ .action(packCourse);
19
+ }
20
+
21
+ interface PackOptions {
22
+ server: string;
23
+ username?: string;
24
+ password?: string;
25
+ output: string;
26
+ chunkSize: string;
27
+ noAttachments: boolean;
28
+ }
29
+
30
+ async function packCourse(courseId: string, options: PackOptions) {
31
+ try {
32
+ console.log(chalk.cyan(`šŸ”§ Packing course: ${courseId}`));
33
+
34
+ // Validate courseId
35
+ if (!courseId || courseId.trim() === '') {
36
+ throw new Error('Course ID is required');
37
+ }
38
+
39
+ // Connect to CouchDB
40
+ const dbUrl = `${options.server}/coursedb-${courseId}`;
41
+ const dbOptions: Record<string, unknown> = {};
42
+
43
+ if (options.username && options.password) {
44
+ dbOptions.auth = {
45
+ username: options.username,
46
+ password: options.password,
47
+ };
48
+ }
49
+
50
+ console.log(chalk.gray(`šŸ“” Connecting to: ${dbUrl}`));
51
+ const sourceDB = new PouchDB(dbUrl, dbOptions);
52
+
53
+ // Test connection
54
+ try {
55
+ await sourceDB.info();
56
+ console.log(chalk.green('āœ… Connected to database'));
57
+ } catch (error: unknown) {
58
+ let errorMessage = 'Unknown error';
59
+ if (error instanceof Error) {
60
+ errorMessage = error.message;
61
+ } else if (typeof error === 'string') {
62
+ errorMessage = error;
63
+ } else if (error && typeof error === 'object' && 'message' in error) {
64
+ errorMessage = String((error as { message: unknown }).message);
65
+ }
66
+ throw new Error(`Failed to connect to database: ${errorMessage}`);
67
+ }
68
+
69
+ // Configure packer (data transformation only)
70
+ const packerConfig = {
71
+ chunkSize: parseInt(options.chunkSize),
72
+ includeAttachments: !options.noAttachments,
73
+ };
74
+
75
+ console.log(chalk.gray(`šŸ“¦ Chunk size: ${packerConfig.chunkSize} documents`));
76
+ console.log(chalk.gray(`šŸ“Ž Include attachments: ${packerConfig.includeAttachments}`));
77
+
78
+ // Pack the course (data transformation)
79
+ console.log(chalk.cyan('šŸ”„ Processing course data...'));
80
+ const packer = new CouchDBToStaticPacker(packerConfig);
81
+ const packedData = await packer.packCourse(sourceDB, courseId);
82
+
83
+ // Create output directory
84
+ const outputDir = path.resolve(options.output, courseId);
85
+ await fs.ensureDir(outputDir);
86
+ console.log(chalk.gray(`šŸ“ Output directory: ${outputDir}`));
87
+
88
+ // Write files
89
+ await writePackedData(packedData, outputDir);
90
+
91
+ // Success summary
92
+ console.log(chalk.green('\nāœ… Successfully packed course!'));
93
+ console.log(chalk.white(`šŸ“Š Course: ${packedData.manifest.courseName}`));
94
+ console.log(chalk.white(`šŸ“„ Documents: ${packedData.manifest.documentCount}`));
95
+ console.log(chalk.white(`šŸ—‚ļø Chunks: ${packedData.manifest.chunks.length}`));
96
+ console.log(chalk.white(`šŸ—ƒļø Indices: ${packedData.manifest.indices.length}`));
97
+ console.log(chalk.white(`šŸ“ Location: ${outputDir}`));
98
+
99
+ } catch (error: unknown) {
100
+ console.error(chalk.red('\nāŒ Packing failed:'));
101
+ let errorMessage = 'Unknown error';
102
+ if (error instanceof Error) {
103
+ errorMessage = error.message;
104
+ } else if (typeof error === 'string') {
105
+ errorMessage = error;
106
+ } else if (error && typeof error === 'object' && 'message' in error) {
107
+ errorMessage = String((error as { message: unknown }).message);
108
+ }
109
+ console.error(chalk.red(errorMessage));
110
+ process.exit(1);
111
+ }
112
+ }
113
+
114
+ interface PackedData {
115
+ manifest: {
116
+ version: string;
117
+ courseId: string;
118
+ courseName: string;
119
+ lastUpdated: string;
120
+ documentCount: number;
121
+ chunks: unknown[];
122
+ indices: unknown[];
123
+ designDocs: unknown[];
124
+ };
125
+ chunks: Map<string, unknown[]>;
126
+ indices: Map<string, unknown>;
127
+ }
128
+
129
+ async function writePackedData(
130
+ packedData: PackedData,
131
+ outputDir: string
132
+ ) {
133
+ console.log(chalk.cyan('šŸ’¾ Writing files...'));
134
+
135
+ // Write manifest
136
+ const manifestPath = path.join(outputDir, 'manifest.json');
137
+ await fs.writeJson(manifestPath, packedData.manifest, { spaces: 2 });
138
+ console.log(chalk.gray(`šŸ“‹ Wrote manifest: ${manifestPath}`));
139
+
140
+ // Create directories
141
+ const chunksDir = path.join(outputDir, 'chunks');
142
+ const indicesDir = path.join(outputDir, 'indices');
143
+ await fs.ensureDir(chunksDir);
144
+ await fs.ensureDir(indicesDir);
145
+
146
+ // Write chunks
147
+ let chunkCount = 0;
148
+ for (const [chunkId, chunkData] of packedData.chunks) {
149
+ const chunkPath = path.join(chunksDir, `${chunkId}.json`);
150
+ await fs.writeJson(chunkPath, chunkData);
151
+ chunkCount++;
152
+ }
153
+ console.log(chalk.gray(`šŸ“¦ Wrote ${chunkCount} chunks`));
154
+
155
+ // Write indices
156
+ let indexCount = 0;
157
+ for (const [indexName, indexData] of packedData.indices) {
158
+ const indexPath = path.join(indicesDir, `${indexName}.json`);
159
+ await fs.writeJson(indexPath, indexData, { spaces: 2 });
160
+ indexCount++;
161
+ }
162
+ console.log(chalk.gray(`šŸ—ƒļø Wrote ${indexCount} indices`));
163
+ }
package/src/types.ts CHANGED
@@ -15,15 +15,45 @@ export interface ProjectConfig {
15
15
  theme: ThemeConfig;
16
16
  }
17
17
 
18
- export interface ThemeConfig {
19
- name: string;
18
+ export interface VuetifyThemeDefinition {
19
+ dark: boolean;
20
20
  colors: {
21
+ // Core semantic colors
21
22
  primary: string;
22
23
  secondary: string;
23
24
  accent: string;
25
+ error: string;
26
+ info: string;
27
+ success: string;
28
+ warning: string;
29
+
30
+ // Surface colors
31
+ background: string;
32
+ surface: string;
33
+ 'surface-bright': string;
34
+ 'surface-light': string;
35
+ 'surface-variant': string;
36
+ 'on-surface-variant': string;
37
+
38
+ // Derived colors
39
+ 'primary-darken-1': string;
40
+ 'secondary-darken-1': string;
41
+
42
+ // Text colors
43
+ 'on-primary': string;
44
+ 'on-secondary': string;
45
+ 'on-background': string;
46
+ 'on-surface': string;
24
47
  };
25
48
  }
26
49
 
50
+ export interface ThemeConfig {
51
+ name: string;
52
+ light: VuetifyThemeDefinition;
53
+ dark: VuetifyThemeDefinition;
54
+ defaultMode: 'light' | 'dark';
55
+ }
56
+
27
57
  export interface SkuilderConfig {
28
58
  title: string;
29
59
  course?: string;
@@ -39,34 +69,210 @@ export interface InitCommandOptions extends CliOptions {
39
69
  export const PREDEFINED_THEMES: Record<string, ThemeConfig> = {
40
70
  default: {
41
71
  name: 'default',
42
- colors: {
43
- primary: '#1976D2',
44
- secondary: '#424242',
45
- accent: '#82B1FF'
72
+ defaultMode: 'light',
73
+ light: {
74
+ dark: false,
75
+ colors: {
76
+ primary: '#1976D2',
77
+ secondary: '#424242',
78
+ accent: '#82B1FF',
79
+ error: '#F44336',
80
+ info: '#2196F3',
81
+ success: '#4CAF50',
82
+ warning: '#FF9800',
83
+ background: '#FFFFFF',
84
+ surface: '#FFFFFF',
85
+ 'surface-bright': '#FFFFFF',
86
+ 'surface-light': '#EEEEEE',
87
+ 'surface-variant': '#E3F2FD',
88
+ 'on-surface-variant': '#1976D2',
89
+ 'primary-darken-1': '#1565C0',
90
+ 'secondary-darken-1': '#212121',
91
+ 'on-primary': '#FFFFFF',
92
+ 'on-secondary': '#FFFFFF',
93
+ 'on-background': '#212121',
94
+ 'on-surface': '#212121',
95
+ }
96
+ },
97
+ dark: {
98
+ dark: true,
99
+ colors: {
100
+ primary: '#2196F3',
101
+ secondary: '#90A4AE',
102
+ accent: '#82B1FF',
103
+ error: '#FF5252',
104
+ info: '#2196F3',
105
+ success: '#4CAF50',
106
+ warning: '#FFC107',
107
+ background: '#121212',
108
+ surface: '#1E1E1E',
109
+ 'surface-bright': '#2C2C2C',
110
+ 'surface-light': '#2C2C2C',
111
+ 'surface-variant': '#1A237E',
112
+ 'on-surface-variant': '#82B1FF',
113
+ 'primary-darken-1': '#1976D2',
114
+ 'secondary-darken-1': '#546E7A',
115
+ 'on-primary': '#000000',
116
+ 'on-secondary': '#000000',
117
+ 'on-background': '#FFFFFF',
118
+ 'on-surface': '#FFFFFF',
119
+ }
46
120
  }
47
121
  },
48
122
  medical: {
49
123
  name: 'medical',
50
- colors: {
51
- primary: '#2E7D32',
52
- secondary: '#558B2F',
53
- accent: '#66BB6A'
124
+ defaultMode: 'light',
125
+ light: {
126
+ dark: false,
127
+ colors: {
128
+ primary: '#2E7D32',
129
+ secondary: '#558B2F',
130
+ accent: '#66BB6A',
131
+ error: '#D32F2F',
132
+ info: '#1976D2',
133
+ success: '#388E3C',
134
+ warning: '#F57C00',
135
+ background: '#FAFAFA',
136
+ surface: '#FFFFFF',
137
+ 'surface-bright': '#FFFFFF',
138
+ 'surface-light': '#F5F5F5',
139
+ 'surface-variant': '#E8F5E8',
140
+ 'on-surface-variant': '#2E7D32',
141
+ 'primary-darken-1': '#1B5E20',
142
+ 'secondary-darken-1': '#33691E',
143
+ 'on-primary': '#FFFFFF',
144
+ 'on-secondary': '#FFFFFF',
145
+ 'on-background': '#212121',
146
+ 'on-surface': '#212121',
147
+ }
148
+ },
149
+ dark: {
150
+ dark: true,
151
+ colors: {
152
+ primary: '#4CAF50',
153
+ secondary: '#8BC34A',
154
+ accent: '#81C784',
155
+ error: '#F44336',
156
+ info: '#2196F3',
157
+ success: '#4CAF50',
158
+ warning: '#FF9800',
159
+ background: '#121212',
160
+ surface: '#1E1E1E',
161
+ 'surface-bright': '#2C2C2C',
162
+ 'surface-light': '#2C2C2C',
163
+ 'surface-variant': '#1B2E1B',
164
+ 'on-surface-variant': '#81C784',
165
+ 'primary-darken-1': '#388E3C',
166
+ 'secondary-darken-1': '#689F38',
167
+ 'on-primary': '#000000',
168
+ 'on-secondary': '#000000',
169
+ 'on-background': '#FFFFFF',
170
+ 'on-surface': '#FFFFFF',
171
+ }
54
172
  }
55
173
  },
56
174
  educational: {
57
175
  name: 'educational',
58
- colors: {
59
- primary: '#F57C00',
60
- secondary: '#FF9800',
61
- accent: '#FFB74D'
176
+ defaultMode: 'light',
177
+ light: {
178
+ dark: false,
179
+ colors: {
180
+ primary: '#F57C00',
181
+ secondary: '#FF9800',
182
+ accent: '#FFB74D',
183
+ error: '#F44336',
184
+ info: '#2196F3',
185
+ success: '#4CAF50',
186
+ warning: '#FF9800',
187
+ background: '#FFFEF7',
188
+ surface: '#FFFFFF',
189
+ 'surface-bright': '#FFFFFF',
190
+ 'surface-light': '#FFF8E1',
191
+ 'surface-variant': '#FFF3E0',
192
+ 'on-surface-variant': '#F57C00',
193
+ 'primary-darken-1': '#E65100',
194
+ 'secondary-darken-1': '#F57C00',
195
+ 'on-primary': '#FFFFFF',
196
+ 'on-secondary': '#000000',
197
+ 'on-background': '#212121',
198
+ 'on-surface': '#212121',
199
+ }
200
+ },
201
+ dark: {
202
+ dark: true,
203
+ colors: {
204
+ primary: '#FF9800',
205
+ secondary: '#FFB74D',
206
+ accent: '#FFCC02',
207
+ error: '#FF5252',
208
+ info: '#2196F3',
209
+ success: '#4CAF50',
210
+ warning: '#FF9800',
211
+ background: '#121212',
212
+ surface: '#1E1E1E',
213
+ 'surface-bright': '#2C2C2C',
214
+ 'surface-light': '#2C2C2C',
215
+ 'surface-variant': '#2E1A00',
216
+ 'on-surface-variant': '#FFCC02',
217
+ 'primary-darken-1': '#F57C00',
218
+ 'secondary-darken-1': '#FF9800',
219
+ 'on-primary': '#000000',
220
+ 'on-secondary': '#000000',
221
+ 'on-background': '#FFFFFF',
222
+ 'on-surface': '#FFFFFF',
223
+ }
62
224
  }
63
225
  },
64
226
  corporate: {
65
227
  name: 'corporate',
66
- colors: {
67
- primary: '#37474F',
68
- secondary: '#546E7A',
69
- accent: '#78909C'
228
+ defaultMode: 'light',
229
+ light: {
230
+ dark: false,
231
+ colors: {
232
+ primary: '#37474F',
233
+ secondary: '#546E7A',
234
+ accent: '#78909C',
235
+ error: '#F44336',
236
+ info: '#2196F3',
237
+ success: '#4CAF50',
238
+ warning: '#FF9800',
239
+ background: '#FAFAFA',
240
+ surface: '#FFFFFF',
241
+ 'surface-bright': '#FFFFFF',
242
+ 'surface-light': '#F5F5F5',
243
+ 'surface-variant': '#ECEFF1',
244
+ 'on-surface-variant': '#37474F',
245
+ 'primary-darken-1': '#263238',
246
+ 'secondary-darken-1': '#455A64',
247
+ 'on-primary': '#FFFFFF',
248
+ 'on-secondary': '#FFFFFF',
249
+ 'on-background': '#212121',
250
+ 'on-surface': '#212121',
251
+ }
252
+ },
253
+ dark: {
254
+ dark: true,
255
+ colors: {
256
+ primary: '#607D8B',
257
+ secondary: '#78909C',
258
+ accent: '#90A4AE',
259
+ error: '#FF5252',
260
+ info: '#2196F3',
261
+ success: '#4CAF50',
262
+ warning: '#FFC107',
263
+ background: '#121212',
264
+ surface: '#1E1E1E',
265
+ 'surface-bright': '#2C2C2C',
266
+ 'surface-light': '#2C2C2C',
267
+ 'surface-variant': '#1C2429',
268
+ 'on-surface-variant': '#90A4AE',
269
+ 'primary-darken-1': '#455A64',
270
+ 'secondary-darken-1': '#546E7A',
271
+ 'on-primary': '#FFFFFF',
272
+ 'on-secondary': '#FFFFFF',
273
+ 'on-background': '#FFFFFF',
274
+ 'on-surface': '#FFFFFF',
275
+ }
70
276
  }
71
277
  }
72
278
  };
@@ -1,6 +1,78 @@
1
1
  import inquirer from 'inquirer';
2
2
  import chalk from 'chalk';
3
- import { CliOptions, ProjectConfig, PREDEFINED_THEMES, ThemeConfig } from '../types.js';
3
+ import { CliOptions, ProjectConfig, PREDEFINED_THEMES } from '../types.js';
4
+
5
+ /**
6
+ * Convert hex color to closest ANSI color code
7
+ */
8
+ function hexToAnsi(hex: string): string {
9
+ // Remove # if present
10
+ hex = hex.replace('#', '');
11
+
12
+ // Convert hex to RGB
13
+ const r = parseInt(hex.substr(0, 2), 16);
14
+ const g = parseInt(hex.substr(2, 2), 16);
15
+ const b = parseInt(hex.substr(4, 2), 16);
16
+
17
+ // Convert to 256-color ANSI
18
+ const ansiCode = 16 + (36 * Math.round(r / 255 * 5)) + (6 * Math.round(g / 255 * 5)) + Math.round(b / 255 * 5);
19
+ return `\x1b[48;5;${ansiCode}m`;
20
+ }
21
+
22
+ /**
23
+ * Create a color swatch for terminal display
24
+ */
25
+ function createColorSwatch(hex: string, label: string): string {
26
+ const colorCode = hexToAnsi(hex);
27
+ const reset = '\x1b[0m';
28
+ return `${colorCode} ${reset} ${label}`;
29
+ }
30
+
31
+ /**
32
+ * Create theme preview with color swatches
33
+ */
34
+ function createThemePreview(themeName: string): string {
35
+ const theme = PREDEFINED_THEMES[themeName];
36
+ const lightColors = theme.light.colors;
37
+
38
+ const primarySwatch = createColorSwatch(lightColors.primary, 'Primary');
39
+ const secondarySwatch = createColorSwatch(lightColors.secondary, 'Secondary');
40
+ const accentSwatch = createColorSwatch(lightColors.accent, 'Accent');
41
+
42
+ return `${primarySwatch} ${secondarySwatch} ${accentSwatch}`;
43
+ }
44
+
45
+ /**
46
+ * Display comprehensive theme preview after selection
47
+ */
48
+ export function displayThemePreview(themeName: string): void {
49
+ const theme = PREDEFINED_THEMES[themeName];
50
+
51
+ console.log(chalk.cyan('\nšŸŽØ Theme Color Palette:'));
52
+ console.log(chalk.white(` ${theme.name.toUpperCase()} THEME`));
53
+
54
+ // Light theme colors
55
+ console.log(chalk.white('\n Light Mode:'));
56
+ const lightColors = theme.light.colors;
57
+ console.log(` ${createColorSwatch(lightColors.primary, `Primary: ${lightColors.primary}`)}`);
58
+ console.log(` ${createColorSwatch(lightColors.secondary, `Secondary: ${lightColors.secondary}`)}`);
59
+ console.log(` ${createColorSwatch(lightColors.accent, `Accent: ${lightColors.accent}`)}`);
60
+ console.log(` ${createColorSwatch(lightColors.success, `Success: ${lightColors.success}`)}`);
61
+ console.log(` ${createColorSwatch(lightColors.warning, `Warning: ${lightColors.warning}`)}`);
62
+ console.log(` ${createColorSwatch(lightColors.error, `Error: ${lightColors.error}`)}`);
63
+
64
+ // Dark theme colors
65
+ console.log(chalk.white('\n Dark Mode:'));
66
+ const darkColors = theme.dark.colors;
67
+ console.log(` ${createColorSwatch(darkColors.primary, `Primary: ${darkColors.primary}`)}`);
68
+ console.log(` ${createColorSwatch(darkColors.secondary, `Secondary: ${darkColors.secondary}`)}`);
69
+ console.log(` ${createColorSwatch(darkColors.accent, `Accent: ${darkColors.accent}`)}`);
70
+ console.log(` ${createColorSwatch(darkColors.success, `Success: ${darkColors.success}`)}`);
71
+ console.log(` ${createColorSwatch(darkColors.warning, `Warning: ${darkColors.warning}`)}`);
72
+ console.log(` ${createColorSwatch(darkColors.error, `Error: ${darkColors.error}`)}`);
73
+
74
+ console.log(chalk.gray(`\n Default mode: ${theme.defaultMode}`));
75
+ }
4
76
 
5
77
  export async function gatherProjectConfig(
6
78
  projectName: string,
@@ -65,19 +137,19 @@ export async function gatherProjectConfig(
65
137
  message: 'Select theme:',
66
138
  choices: [
67
139
  {
68
- name: 'Default (Material Blue)',
140
+ name: `Default (Material Blue) ${createThemePreview('default')}`,
69
141
  value: 'default'
70
142
  },
71
143
  {
72
- name: 'Medical (Healthcare Green)',
144
+ name: `Medical (Healthcare Green) ${createThemePreview('medical')}`,
73
145
  value: 'medical'
74
146
  },
75
147
  {
76
- name: 'Educational (Academic Orange)',
148
+ name: `Educational (Academic Orange) ${createThemePreview('educational')}`,
77
149
  value: 'educational'
78
150
  },
79
151
  {
80
- name: 'Corporate (Professional Gray)',
152
+ name: `Corporate (Professional Gray) ${createThemePreview('corporate')}`,
81
153
  value: 'corporate'
82
154
  }
83
155
  ],
@@ -93,6 +165,9 @@ export async function gatherProjectConfig(
93
165
  course: answers.courseId,
94
166
  theme: PREDEFINED_THEMES[answers.themeName]
95
167
  };
168
+
169
+ // Show comprehensive theme preview
170
+ displayThemePreview(answers.themeName);
96
171
  } else {
97
172
  // Non-interactive mode: use provided options
98
173
  config = {
@@ -130,7 +205,7 @@ export async function confirmProjectCreation(
130
205
  console.log(` Course ID: ${chalk.white(config.course)}`);
131
206
  }
132
207
 
133
- console.log(` Theme: ${chalk.white(config.theme.name)}`);
208
+ console.log(` Theme: ${chalk.white(config.theme.name)} ${createThemePreview(config.theme.name)}`);
134
209
  console.log(` Directory: ${chalk.white(projectPath)}`);
135
210
 
136
211
  const { confirmed } = await inquirer.prompt([
@@ -145,48 +220,7 @@ export async function confirmProjectCreation(
145
220
  return confirmed;
146
221
  }
147
222
 
148
- export async function promptForCustomTheme(): Promise<ThemeConfig> {
149
- console.log(chalk.cyan('\nšŸŽØ Custom Theme Configuration\n'));
150
-
151
- const answers = await inquirer.prompt([
152
- {
153
- type: 'input',
154
- name: 'name',
155
- message: 'Theme name:',
156
- validate: (input: string) => input.trim().length > 0 || 'Theme name is required'
157
- },
158
- {
159
- type: 'input',
160
- name: 'primary',
161
- message: 'Primary color (hex):',
162
- default: '#1976D2',
163
- validate: validateHexColor
164
- },
165
- {
166
- type: 'input',
167
- name: 'secondary',
168
- message: 'Secondary color (hex):',
169
- default: '#424242',
170
- validate: validateHexColor
171
- },
172
- {
173
- type: 'input',
174
- name: 'accent',
175
- message: 'Accent color (hex):',
176
- default: '#82B1FF',
177
- validate: validateHexColor
178
- }
179
- ]);
180
223
 
181
- return {
182
- name: answers.name,
183
- colors: {
184
- primary: answers.primary,
185
- secondary: answers.secondary,
186
- accent: answers.accent
187
- }
188
- };
189
- }
190
224
 
191
225
  function formatProjectName(projectName: string): string {
192
226
  return projectName
@@ -195,10 +229,3 @@ function formatProjectName(projectName: string): string {
195
229
  .join(' ');
196
230
  }
197
231
 
198
- function validateHexColor(input: string): boolean | string {
199
- const hexColorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
200
- if (!hexColorRegex.test(input)) {
201
- return 'Please enter a valid hex color (e.g., #1976D2)';
202
- }
203
- return true;
204
- }