@vue-skuilder/cli 0.1.3 → 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.
@@ -1,24 +1,46 @@
1
- import { existsSync } from 'fs';
2
- import path from 'path';
3
1
  import chalk from 'chalk';
2
+ import { Command } from 'commander';
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import path, { dirname, join } from 'path';
5
+ import { fileURLToPath } from 'url';
4
6
  import { CliOptions } from '../types.js';
5
- import { gatherProjectConfig, confirmProjectCreation } from '../utils/prompts.js';
7
+ import {
8
+ confirmProjectCreation,
9
+ displayThemePreview,
10
+ gatherProjectConfig,
11
+ } from '../utils/prompts.js';
6
12
  import { processTemplate } from '../utils/template.js';
7
- import { readFileSync } from 'fs';
8
- import { fileURLToPath } from 'url';
9
- import { dirname, join } from 'path';
10
13
 
11
14
  const __filename = fileURLToPath(import.meta.url);
12
15
  const __dirname = dirname(__filename);
13
16
 
14
- export async function initCommand(
15
- projectName: string,
16
- options: CliOptions
17
- ): Promise<void> {
17
+ export function createInitCommand(): Command {
18
+ return new Command('init')
19
+ .argument('<project-name>', 'name of the project to create')
20
+ .description('create a new Skuilder course application')
21
+ .option('--data-layer <type>', 'data layer type (static|dynamic)', 'dynamic')
22
+ .option('--theme <name>', 'theme name (default|medical|educational|corporate)', 'default')
23
+ .option('--no-interactive', 'skip interactive prompts')
24
+ .option('--couchdb-url <url>', 'CouchDB server URL (for dynamic data layer)')
25
+ .option('--course-id <id>', 'course ID to import (for dynamic data layer)')
26
+ .action(initCommand);
27
+ }
28
+
29
+ interface InitOptions {
30
+ dataLayer: string;
31
+ theme: string;
32
+ interactive: boolean;
33
+ couchdbUrl?: string;
34
+ courseId?: string;
35
+ }
36
+
37
+ async function initCommand(projectName: string, options: InitOptions): Promise<void> {
18
38
  try {
19
39
  // Validate project name
20
40
  if (!isValidProjectName(projectName)) {
21
- console.error(chalk.red('❌ Invalid project name. Use only letters, numbers, hyphens, and underscores.'));
41
+ console.error(
42
+ chalk.red('❌ Invalid project name. Use only letters, numbers, hyphens, and underscores.')
43
+ );
22
44
  process.exit(1);
23
45
  }
24
46
 
@@ -34,11 +56,20 @@ export async function initCommand(
34
56
  const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
35
57
  const cliVersion = packageJson.version;
36
58
 
59
+ // Convert options to CliOptions format
60
+ const cliOptions: CliOptions = {
61
+ dataLayer: options.dataLayer as 'static' | 'dynamic',
62
+ theme: options.theme as 'default' | 'medical' | 'educational' | 'corporate',
63
+ interactive: options.interactive,
64
+ couchdbUrl: options.couchdbUrl,
65
+ courseId: options.courseId,
66
+ };
67
+
37
68
  // Gather project configuration
38
- const config = await gatherProjectConfig(projectName, options);
69
+ const config = await gatherProjectConfig(projectName, cliOptions);
39
70
 
40
71
  // Confirm project creation (only in interactive mode)
41
- if (options.interactive) {
72
+ if (cliOptions.interactive) {
42
73
  const confirmed = await confirmProjectCreation(config, projectPath);
43
74
  if (!confirmed) {
44
75
  console.log(chalk.yellow('Project creation cancelled.'));
@@ -53,24 +84,43 @@ export async function initCommand(
53
84
 
54
85
  // Success message
55
86
  console.log(chalk.green('\n🎉 Project created successfully!\n'));
56
- console.log(chalk.cyan('Next steps:'));
87
+
88
+ // Show theme preview
89
+ displayThemePreview(config.theme.name);
90
+
91
+ console.log(chalk.cyan('\nNext steps:'));
57
92
  console.log(` ${chalk.white('cd')} ${projectName}`);
58
93
  console.log(` ${chalk.white('npm install')}`);
59
94
  console.log(` ${chalk.white('npm run dev')}`);
60
95
  console.log('');
61
96
 
62
97
  if (config.dataLayerType === 'couch') {
63
- console.log(chalk.yellow('📝 Note: Make sure your CouchDB server is running and accessible.'));
98
+ console.log(
99
+ chalk.yellow('📝 Note: Make sure your CouchDB server is running and accessible.')
100
+ );
64
101
  if (config.course) {
65
- console.log(chalk.yellow(`📚 Course ID "${config.course}" will be loaded from the database.`));
102
+ console.log(
103
+ chalk.yellow(`📚 Course ID "${config.course}" will be loaded from the database.`)
104
+ );
66
105
  }
67
106
  } else {
68
- console.log(chalk.yellow('📝 Note: This project uses static data. Sample course data has been included.'));
107
+ console.log(
108
+ chalk.yellow(
109
+ '📝 Note: This project uses static data. Sample course data has been included.'
110
+ )
111
+ );
69
112
  }
70
-
71
- } catch (error) {
113
+ } catch (error: unknown) {
72
114
  console.error(chalk.red('\n❌ Failed to create project:'));
73
- console.error(chalk.red(error instanceof Error ? error.message : String(error)));
115
+ let errorMessage = 'Unknown error';
116
+ if (error instanceof Error) {
117
+ errorMessage = error.message;
118
+ } else if (typeof error === 'string') {
119
+ errorMessage = error;
120
+ } else if (error && typeof error === 'object' && 'message' in error) {
121
+ errorMessage = String((error as { message: unknown }).message);
122
+ }
123
+ console.error(chalk.red(errorMessage));
74
124
  process.exit(1);
75
125
  }
76
126
  }
@@ -80,4 +130,4 @@ function isValidProjectName(name: string): boolean {
80
130
  // Must start with a letter or number
81
131
  const validNameRegex = /^[a-zA-Z0-9][a-zA-Z0-9-_]*$/;
82
132
  return validNameRegex.test(name) && name.length > 0 && name.length <= 214;
83
- }
133
+ }
@@ -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
  };