@vue-skuilder/cli 0.1.4 → 0.1.6

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.
@@ -13,7 +13,7 @@ const __dirname = path.dirname(__filename);
13
13
  export async function findStandaloneUiPath(): Promise<string> {
14
14
  // Start from CLI package root and work upward
15
15
  let currentDir = path.join(__dirname, '..', '..');
16
-
16
+
17
17
  while (currentDir !== path.dirname(currentDir)) {
18
18
  const nodeModulesPath = path.join(currentDir, 'node_modules', '@vue-skuilder', 'standalone-ui');
19
19
  if (existsSync(nodeModulesPath)) {
@@ -21,8 +21,10 @@ export async function findStandaloneUiPath(): Promise<string> {
21
21
  }
22
22
  currentDir = path.dirname(currentDir);
23
23
  }
24
-
25
- throw new Error('Could not find @vue-skuilder/standalone-ui package. Please ensure it is installed.');
24
+
25
+ throw new Error(
26
+ 'Could not find @vue-skuilder/standalone-ui package. Please ensure it is installed.'
27
+ );
26
28
  }
27
29
 
28
30
  /**
@@ -34,18 +36,18 @@ export async function copyDirectory(
34
36
  excludePatterns: string[] = ['node_modules', 'dist', '.git', 'cypress']
35
37
  ): Promise<void> {
36
38
  const entries = await fs.readdir(source, { withFileTypes: true });
37
-
39
+
38
40
  await fs.mkdir(destination, { recursive: true });
39
-
41
+
40
42
  for (const entry of entries) {
41
43
  const sourcePath = path.join(source, entry.name);
42
44
  const destPath = path.join(destination, entry.name);
43
-
45
+
44
46
  // Skip excluded patterns
45
- if (excludePatterns.some(pattern => entry.name.includes(pattern))) {
47
+ if (excludePatterns.some((pattern) => entry.name.includes(pattern))) {
46
48
  continue;
47
49
  }
48
-
50
+
49
51
  if (entry.isDirectory()) {
50
52
  await copyDirectory(sourcePath, destPath, excludePatterns);
51
53
  } else {
@@ -64,12 +66,12 @@ export async function transformPackageJson(
64
66
  ): Promise<void> {
65
67
  const content = await fs.readFile(packageJsonPath, 'utf-8');
66
68
  const packageJson = JSON.parse(content);
67
-
69
+
68
70
  // Update basic project info
69
71
  packageJson.name = projectName;
70
72
  packageJson.description = `Skuilder course application: ${projectName}`;
71
73
  packageJson.version = '1.0.0';
72
-
74
+
73
75
  // Transform workspace dependencies to published versions
74
76
  if (packageJson.dependencies) {
75
77
  for (const [depName, version] of Object.entries(packageJson.dependencies)) {
@@ -79,21 +81,21 @@ export async function transformPackageJson(
79
81
  }
80
82
  }
81
83
  }
82
-
84
+
83
85
  // Add missing terser devDependency for build minification
84
86
  if (packageJson.devDependencies && !packageJson.devDependencies['terser']) {
85
87
  packageJson.devDependencies['terser'] = '^5.39.0';
86
88
  }
87
-
89
+
88
90
  // Remove CLI-specific fields that don't belong in generated projects
89
91
  delete packageJson.publishConfig;
90
-
92
+
91
93
  await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
92
94
  }
93
95
 
94
96
  /**
95
97
  * Create a vite.config.ts to work with published packages instead of workspace sources
96
- *
98
+ *
97
99
  * // [ ] This should be revised so that it works from the existing vite.config.ts in standalone-ui. As is, it recreates 95% of the same config.
98
100
  */
99
101
  export async function createViteConfig(viteConfigPath: string): Promise<void> {
@@ -109,7 +111,7 @@ export default defineConfig({
109
111
  alias: {
110
112
  // Alias for internal src paths
111
113
  '@': fileURLToPath(new URL('./src', import.meta.url)),
112
-
114
+
113
115
  // Add events alias if needed (often required by dependencies)
114
116
  events: 'events',
115
117
  },
@@ -156,7 +158,7 @@ export default defineConfig({
156
158
  },
157
159
  });
158
160
  `;
159
-
161
+
160
162
  await fs.writeFile(viteConfigPath, transformedContent);
161
163
  }
162
164
 
@@ -169,24 +171,66 @@ export async function generateSkuilderConfig(
169
171
  ): Promise<void> {
170
172
  const skuilderConfig: SkuilderConfig = {
171
173
  title: config.title,
172
- dataLayerType: config.dataLayerType
174
+ dataLayerType: config.dataLayerType,
173
175
  };
174
-
175
- if (config.course) {
176
+
177
+ // For dynamic data layer, use the specified course ID
178
+ if (config.dataLayerType === 'couch' && config.course) {
176
179
  skuilderConfig.course = config.course;
177
180
  }
178
-
181
+
182
+ // For static data layer with imported courses, use the first course as primary
183
+ if (config.dataLayerType === 'static' && config.importCourseIds && config.importCourseIds.length > 0) {
184
+ skuilderConfig.course = config.importCourseIds[0];
185
+ }
186
+
179
187
  if (config.couchdbUrl) {
180
188
  skuilderConfig.couchdbUrl = config.couchdbUrl;
181
189
  }
182
-
190
+
183
191
  if (config.theme) {
184
192
  skuilderConfig.theme = config.theme;
185
193
  }
186
-
194
+
187
195
  await fs.writeFile(configPath, JSON.stringify(skuilderConfig, null, 2));
188
196
  }
189
197
 
198
+ /**
199
+ * Transform tsconfig.json to be standalone (remove base config reference)
200
+ */
201
+ export async function transformTsConfig(tsconfigPath: string): Promise<void> {
202
+ const content = await fs.readFile(tsconfigPath, 'utf-8');
203
+ const tsconfig = JSON.parse(content);
204
+
205
+ // Remove the extends reference to the monorepo base config
206
+ delete tsconfig.extends;
207
+
208
+ // Merge in the essential settings from the base config that scaffolded apps need
209
+ tsconfig.compilerOptions = {
210
+ ...tsconfig.compilerOptions,
211
+ // Essential TypeScript settings from base config
212
+ strict: true,
213
+ skipLibCheck: true,
214
+ forceConsistentCasingInFileNames: true,
215
+ esModuleInterop: true,
216
+ allowSyntheticDefaultImports: true,
217
+ // Keep existing Vue/Vite-specific settings
218
+ target: tsconfig.compilerOptions.target || 'ESNext',
219
+ useDefineForClassFields: tsconfig.compilerOptions.useDefineForClassFields,
220
+ module: tsconfig.compilerOptions.module || 'ESNext',
221
+ moduleResolution: tsconfig.compilerOptions.moduleResolution || 'bundler',
222
+ jsx: tsconfig.compilerOptions.jsx || 'preserve',
223
+ resolveJsonModule: tsconfig.compilerOptions.resolveJsonModule,
224
+ isolatedModules: tsconfig.compilerOptions.isolatedModules,
225
+ lib: tsconfig.compilerOptions.lib || ['ESNext', 'DOM'],
226
+ noEmit: tsconfig.compilerOptions.noEmit,
227
+ baseUrl: tsconfig.compilerOptions.baseUrl || '.',
228
+ types: tsconfig.compilerOptions.types || ['vite/client'],
229
+ };
230
+
231
+ await fs.writeFile(tsconfigPath, JSON.stringify(tsconfig, null, 2));
232
+ }
233
+
190
234
  /**
191
235
  * Generate .gitignore file for the project
192
236
  */
@@ -313,14 +357,20 @@ Thumbs.db
313
357
  /**
314
358
  * Generate project README.md
315
359
  */
316
- export async function generateReadme(
317
- readmePath: string,
318
- config: ProjectConfig
319
- ): Promise<void> {
320
- const dataLayerInfo = config.dataLayerType === 'static'
321
- ? 'This project uses a static data layer with JSON files.'
322
- : `This project connects to CouchDB at: ${config.couchdbUrl || '[URL not specified]'}`;
360
+ export async function generateReadme(readmePath: string, config: ProjectConfig): Promise<void> {
361
+ let dataLayerInfo = '';
362
+
363
+ if (config.dataLayerType === 'static') {
364
+ dataLayerInfo = 'This project uses a static data layer with JSON files.';
323
365
 
366
+ if (config.importCourseIds && config.importCourseIds.length > 0) {
367
+ const courseList = config.importCourseIds.map(id => `- ${id}`).join('\n');
368
+ dataLayerInfo += `\n\n**Imported Courses:**\n${courseList}\n\nCourse data is stored in \`public/static-courses/\` and loaded automatically.`;
369
+ }
370
+ } else {
371
+ dataLayerInfo = `This project connects to CouchDB at: ${config.couchdbUrl || '[URL not specified]'}`;
372
+ }
373
+
324
374
  const readme = `# ${config.title}
325
375
 
326
376
  A Skuilder course application built with Vue 3, Vuetify, and Pinia.
@@ -356,10 +406,42 @@ Course configuration is managed in \`skuilder.config.json\`. You can modify:
356
406
 
357
407
  ## Theme
358
408
 
359
- Current theme: **${config.theme.name}**
360
- - Primary: ${config.theme.colors.primary}
361
- - Secondary: ${config.theme.colors.secondary}
362
- - Accent: ${config.theme.colors.accent}
409
+ Current theme: **${config.theme.name}** (${config.theme.defaultMode} mode)
410
+ - Primary: ${config.theme.light.colors.primary}
411
+ - Secondary: ${config.theme.light.colors.secondary}
412
+ - Accent: ${config.theme.light.colors.accent}
413
+
414
+ This theme includes both light and dark variants. The application will use the ${config.theme.defaultMode} theme by default, but users can toggle between light and dark modes in their settings.
415
+
416
+ ### Theme Customization
417
+
418
+ To customize the theme colors, edit the \`theme\` section in \`skuilder.config.json\`:
419
+
420
+ \`\`\`json
421
+ {
422
+ "theme": {
423
+ "name": "custom",
424
+ "defaultMode": "light",
425
+ "light": {
426
+ "dark": false,
427
+ "colors": {
428
+ "primary": "#your-color",
429
+ "secondary": "#your-color",
430
+ "accent": "#your-color"
431
+ // ... other semantic colors
432
+ }
433
+ },
434
+ "dark": {
435
+ "dark": true,
436
+ "colors": {
437
+ // ... dark variant colors
438
+ }
439
+ }
440
+ }
441
+ }
442
+ \`\`\`
443
+
444
+ The theme system supports all Vuetify semantic colors including error, success, warning, info, background, surface, and text colors. Changes to the configuration file are applied automatically on restart.
363
445
 
364
446
  ## Testing
365
447
 
@@ -391,31 +473,37 @@ export async function processTemplate(
391
473
  ): Promise<void> {
392
474
  console.log(chalk.blue('📦 Locating standalone-ui template...'));
393
475
  const templatePath = await findStandaloneUiPath();
394
-
476
+
395
477
  console.log(chalk.blue('📂 Copying project files...'));
396
478
  await copyDirectory(templatePath, projectPath);
397
-
479
+
398
480
  console.log(chalk.blue('⚙️ Configuring package.json...'));
399
481
  const packageJsonPath = path.join(projectPath, 'package.json');
400
482
  await transformPackageJson(packageJsonPath, config.projectName, cliVersion);
401
-
483
+
402
484
  console.log(chalk.blue('🔧 Creating vite.config.ts...'));
403
485
  const viteConfigPath = path.join(projectPath, 'vite.config.ts');
404
486
  if (existsSync(viteConfigPath)) {
405
487
  await createViteConfig(viteConfigPath);
406
488
  }
407
-
489
+
490
+ console.log(chalk.blue('🔧 Transforming tsconfig.json...'));
491
+ const tsconfigPath = path.join(projectPath, 'tsconfig.json');
492
+ if (existsSync(tsconfigPath)) {
493
+ await transformTsConfig(tsconfigPath);
494
+ }
495
+
408
496
  console.log(chalk.blue('🔧 Generating configuration...'));
409
497
  const configPath = path.join(projectPath, 'skuilder.config.json');
410
498
  await generateSkuilderConfig(configPath, config);
411
-
499
+
412
500
  console.log(chalk.blue('📝 Creating README...'));
413
501
  const readmePath = path.join(projectPath, 'README.md');
414
502
  await generateReadme(readmePath, config);
415
-
503
+
416
504
  console.log(chalk.blue('📄 Generating .gitignore...'));
417
505
  const gitignorePath = path.join(projectPath, '.gitignore');
418
506
  await generateGitignore(gitignorePath);
419
-
507
+
420
508
  console.log(chalk.green('✅ Template processing complete!'));
421
- }
509
+ }