skuilder 0.1.2 → 0.1.4

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.
Files changed (90) hide show
  1. package/README.md +162 -2
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +35 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/init.d.ts +3 -0
  7. package/dist/commands/init.d.ts.map +1 -0
  8. package/dist/commands/init.js +70 -0
  9. package/dist/commands/init.js.map +1 -0
  10. package/dist/types.d.ts +35 -0
  11. package/dist/types.d.ts.map +1 -0
  12. package/dist/types.js +35 -0
  13. package/dist/types.js.map +1 -0
  14. package/dist/utils/prompts.d.ts +5 -0
  15. package/dist/utils/prompts.d.ts.map +1 -0
  16. package/dist/utils/prompts.js +185 -0
  17. package/dist/utils/prompts.js.map +1 -0
  18. package/dist/utils/template.d.ts +36 -0
  19. package/dist/utils/template.d.ts.map +1 -0
  20. package/dist/utils/template.js +369 -0
  21. package/dist/utils/template.js.map +1 -0
  22. package/eslint.config.mjs +21 -0
  23. package/package.json +41 -36
  24. package/src/cli.ts +42 -0
  25. package/src/commands/init.ts +83 -0
  26. package/src/types.ts +72 -0
  27. package/src/utils/prompts.ts +204 -0
  28. package/src/utils/template.ts +421 -0
  29. package/tsconfig.json +12 -21
  30. package/.npmignore +0 -56
  31. package/android/app/BUCK +0 -65
  32. package/android/app/build.gradle +0 -139
  33. package/android/app/proguard-rules.pro +0 -66
  34. package/android/app/src/main/AndroidManifest.xml +0 -32
  35. package/android/app/src/main/java/com/rxphelloworld/MainActivity.java +0 -15
  36. package/android/app/src/main/java/com/rxphelloworld/MainApplication.java +0 -40
  37. package/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  38. package/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  39. package/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  40. package/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  41. package/android/app/src/main/res/values/strings.xml +0 -3
  42. package/android/app/src/main/res/values/styles.xml +0 -8
  43. package/android/build.gradle +0 -24
  44. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  45. package/android/gradle/wrapper/gradle-wrapper.properties +0 -6
  46. package/android/gradle.properties +0 -20
  47. package/android/gradlew +0 -164
  48. package/android/gradlew.bat +0 -90
  49. package/android/keystores/BUCK +0 -8
  50. package/android/keystores/debug.keystore.properties +0 -4
  51. package/android/settings.gradle +0 -3
  52. package/img/fingerCounter/1.PNG +0 -0
  53. package/img/fingerCounter/10.PNG +0 -0
  54. package/img/fingerCounter/2.PNG +0 -0
  55. package/img/fingerCounter/3.PNG +0 -0
  56. package/img/fingerCounter/4.PNG +0 -0
  57. package/img/fingerCounter/5.PNG +0 -0
  58. package/img/fingerCounter/6.PNG +0 -0
  59. package/img/fingerCounter/7.PNG +0 -0
  60. package/img/fingerCounter/8.PNG +0 -0
  61. package/img/fingerCounter/9.PNG +0 -0
  62. package/index.android.js +0 -1
  63. package/index.html +0 -30
  64. package/index.ios.js +0 -1
  65. package/ios/RXPHelloWorld/AppDelegate.h +0 -16
  66. package/ios/RXPHelloWorld/AppDelegate.m +0 -37
  67. package/ios/RXPHelloWorld/Base.lproj/LaunchScreen.xib +0 -42
  68. package/ios/RXPHelloWorld/Images.xcassets/AppIcon.appiconset/Contents.json +0 -38
  69. package/ios/RXPHelloWorld/Info.plist +0 -56
  70. package/ios/RXPHelloWorld/main.m +0 -18
  71. package/ios/RXPHelloWorld.xcodeproj/project.pbxproj +0 -1251
  72. package/ios/RXPHelloWorld.xcodeproj/xcshareddata/xcschemes/RXPHelloWorld-tvOS.xcscheme +0 -129
  73. package/ios/RXPHelloWorld.xcodeproj/xcshareddata/xcschemes/RXPHelloWorld.xcscheme +0 -129
  74. package/ios/RXPHelloWorldTests/Info.plist +0 -24
  75. package/ios/RXPHelloWorldTests/RXPHelloWorldTests.m +0 -70
  76. package/src/App.tsx +0 -206
  77. package/src/appUtilities/Grader.ts +0 -72
  78. package/src/appUtilities/Keybinder.ts +0 -28
  79. package/src/appUtilities/Recorder.ts +0 -73
  80. package/src/cloudantFiles/_users._design._auth.validate_doc_update.js +0 -136
  81. package/src/components/ProgressChart.tsx +0 -155
  82. package/src/components/fingerCounter/fingerCounter.tsx +0 -38
  83. package/src/components/fingerCounter/resources/hands.svg +0 -512
  84. package/src/components/numpad.tsx +0 -146
  85. package/src/components/sessionReport.tsx +0 -87
  86. package/src/index.tsx +0 -8
  87. package/src/styles/answerStyles.css +0 -22
  88. package/src/typings/react.d.ts +0 -964
  89. package/webpack.config.ts +0 -26
  90. package/yarn.lock +0 -6208
@@ -0,0 +1,369 @@
1
+ import { promises as fs, existsSync } from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import chalk from 'chalk';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ /**
8
+ * Find the standalone-ui package in node_modules
9
+ */
10
+ export async function findStandaloneUiPath() {
11
+ // Start from CLI package root and work upward
12
+ let currentDir = path.join(__dirname, '..', '..');
13
+ while (currentDir !== path.dirname(currentDir)) {
14
+ const nodeModulesPath = path.join(currentDir, 'node_modules', '@vue-skuilder', 'standalone-ui');
15
+ if (existsSync(nodeModulesPath)) {
16
+ return nodeModulesPath;
17
+ }
18
+ currentDir = path.dirname(currentDir);
19
+ }
20
+ throw new Error('Could not find @vue-skuilder/standalone-ui package. Please ensure it is installed.');
21
+ }
22
+ /**
23
+ * Copy directory recursively, excluding certain files/directories
24
+ */
25
+ export async function copyDirectory(source, destination, excludePatterns = ['node_modules', 'dist', '.git', 'cypress']) {
26
+ const entries = await fs.readdir(source, { withFileTypes: true });
27
+ await fs.mkdir(destination, { recursive: true });
28
+ for (const entry of entries) {
29
+ const sourcePath = path.join(source, entry.name);
30
+ const destPath = path.join(destination, entry.name);
31
+ // Skip excluded patterns
32
+ if (excludePatterns.some(pattern => entry.name.includes(pattern))) {
33
+ continue;
34
+ }
35
+ if (entry.isDirectory()) {
36
+ await copyDirectory(sourcePath, destPath, excludePatterns);
37
+ }
38
+ else {
39
+ await fs.copyFile(sourcePath, destPath);
40
+ }
41
+ }
42
+ }
43
+ /**
44
+ * Transform package.json to use published dependencies instead of workspace references
45
+ */
46
+ export async function transformPackageJson(packageJsonPath, projectName, cliVersion) {
47
+ const content = await fs.readFile(packageJsonPath, 'utf-8');
48
+ const packageJson = JSON.parse(content);
49
+ // Update basic project info
50
+ packageJson.name = projectName;
51
+ packageJson.description = `Skuilder course application: ${projectName}`;
52
+ packageJson.version = '1.0.0';
53
+ // Transform workspace dependencies to published versions
54
+ if (packageJson.dependencies) {
55
+ for (const [depName, version] of Object.entries(packageJson.dependencies)) {
56
+ if (typeof version === 'string' && version.startsWith('workspace:')) {
57
+ // Replace workspace references with CLI's version
58
+ packageJson.dependencies[depName] = `^${cliVersion}`;
59
+ }
60
+ }
61
+ }
62
+ // Add missing terser devDependency for build minification
63
+ if (packageJson.devDependencies && !packageJson.devDependencies['terser']) {
64
+ packageJson.devDependencies['terser'] = '^5.39.0';
65
+ }
66
+ // Remove CLI-specific fields that don't belong in generated projects
67
+ delete packageJson.publishConfig;
68
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
69
+ }
70
+ /**
71
+ * Create a vite.config.ts to work with published packages instead of workspace sources
72
+ *
73
+ * // [ ] 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.
74
+ */
75
+ export async function createViteConfig(viteConfigPath) {
76
+ // Create a clean vite config for standalone projects
77
+ const transformedContent = `// packages/standalone-ui/vite.config.ts
78
+ import { defineConfig } from 'vite';
79
+ import vue from '@vitejs/plugin-vue';
80
+ import { fileURLToPath, URL } from 'node:url';
81
+
82
+ export default defineConfig({
83
+ plugins: [vue()],
84
+ resolve: {
85
+ alias: {
86
+ // Alias for internal src paths
87
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
88
+
89
+ // Add events alias if needed (often required by dependencies)
90
+ events: 'events',
91
+ },
92
+ extensions: ['.js', '.ts', '.json', '.vue'],
93
+ dedupe: [
94
+ // Ensure single instances of core libs and published packages
95
+ 'vue',
96
+ 'vuetify',
97
+ 'pinia',
98
+ 'vue-router',
99
+ '@vue-skuilder/db',
100
+ '@vue-skuilder/common',
101
+ '@vue-skuilder/common-ui',
102
+ '@vue-skuilder/courses',
103
+ ],
104
+ },
105
+ // --- Dependencies optimization ---
106
+ optimizeDeps: {
107
+ // Help Vite pre-bundle dependencies from published packages
108
+ include: [
109
+ '@vue-skuilder/common-ui',
110
+ '@vue-skuilder/db',
111
+ '@vue-skuilder/common',
112
+ '@vue-skuilder/courses',
113
+ ],
114
+ },
115
+ server: {
116
+ port: 5173, // Use standard Vite port for standalone projects
117
+ },
118
+ build: {
119
+ sourcemap: true,
120
+ target: 'es2020',
121
+ minify: 'terser',
122
+ terserOptions: {
123
+ keep_classnames: true,
124
+ },
125
+ },
126
+ // Add define block for process polyfills
127
+ define: {
128
+ global: 'window',
129
+ 'process.env': process.env,
130
+ 'process.browser': true,
131
+ 'process.version': JSON.stringify(process.version),
132
+ },
133
+ });
134
+ `;
135
+ await fs.writeFile(viteConfigPath, transformedContent);
136
+ }
137
+ /**
138
+ * Generate skuilder.config.json based on project configuration
139
+ */
140
+ export async function generateSkuilderConfig(configPath, config) {
141
+ const skuilderConfig = {
142
+ title: config.title,
143
+ dataLayerType: config.dataLayerType
144
+ };
145
+ if (config.course) {
146
+ skuilderConfig.course = config.course;
147
+ }
148
+ if (config.couchdbUrl) {
149
+ skuilderConfig.couchdbUrl = config.couchdbUrl;
150
+ }
151
+ if (config.theme) {
152
+ skuilderConfig.theme = config.theme;
153
+ }
154
+ await fs.writeFile(configPath, JSON.stringify(skuilderConfig, null, 2));
155
+ }
156
+ /**
157
+ * Generate .gitignore file for the project
158
+ */
159
+ export async function generateGitignore(gitignorePath) {
160
+ const gitignoreContent = `# Dependencies
161
+ node_modules/
162
+ /.pnp
163
+ .pnp.js
164
+
165
+ # Production builds
166
+ /dist
167
+ /build
168
+
169
+ # Local env files
170
+ .env
171
+ .env.local
172
+ .env.development.local
173
+ .env.test.local
174
+ .env.production.local
175
+
176
+ # Log files
177
+ npm-debug.log*
178
+ yarn-debug.log*
179
+ yarn-error.log*
180
+ pnpm-debug.log*
181
+ lerna-debug.log*
182
+
183
+ # Runtime data
184
+ pids
185
+ *.pid
186
+ *.seed
187
+ *.pid.lock
188
+
189
+ # Coverage directory used by tools like istanbul
190
+ coverage/
191
+ *.lcov
192
+
193
+ # nyc test coverage
194
+ .nyc_output
195
+
196
+ # Dependency directories
197
+ jspm_packages/
198
+
199
+ # TypeScript cache
200
+ *.tsbuildinfo
201
+
202
+ # Optional npm cache directory
203
+ .npm
204
+
205
+ # Optional eslint cache
206
+ .eslintcache
207
+
208
+ # Microbundle cache
209
+ .rpt2_cache/
210
+ .rts2_cache_cjs/
211
+ .rts2_cache_es/
212
+ .rts2_cache_umd/
213
+
214
+ # Optional REPL history
215
+ .node_repl_history
216
+
217
+ # Output of 'npm pack'
218
+ *.tgz
219
+
220
+ # Yarn Integrity file
221
+ .yarn-integrity
222
+
223
+ # parcel-bundler cache (https://parceljs.org/)
224
+ .cache
225
+ .parcel-cache
226
+
227
+ # Next.js build output
228
+ .next
229
+
230
+ # Nuxt.js build / generate output
231
+ .nuxt
232
+ dist
233
+
234
+ # Gatsby files
235
+ .cache/
236
+ public
237
+
238
+ # Storybook build outputs
239
+ .out
240
+ .storybook-out
241
+
242
+ # Temporary folders
243
+ tmp/
244
+ temp/
245
+
246
+ # Editor directories and files
247
+ .vscode/
248
+ .idea
249
+ .DS_Store
250
+ *.suo
251
+ *.ntvs*
252
+ *.njsproj
253
+ *.sln
254
+ *.sw?
255
+
256
+ # OS generated files
257
+ Thumbs.db
258
+
259
+ # Cypress
260
+ /cypress/videos/
261
+ /cypress/screenshots/
262
+
263
+ # Local development
264
+ .env.development
265
+ .env.production
266
+
267
+ # Package manager lockfiles (uncomment if you want to ignore them)
268
+ # package-lock.json
269
+ # yarn.lock
270
+ # pnpm-lock.yaml
271
+
272
+ # Skuilder specific
273
+ /src/data/local-*.json
274
+ `;
275
+ await fs.writeFile(gitignorePath, gitignoreContent);
276
+ }
277
+ /**
278
+ * Generate project README.md
279
+ */
280
+ export async function generateReadme(readmePath, config) {
281
+ const dataLayerInfo = config.dataLayerType === 'static'
282
+ ? 'This project uses a static data layer with JSON files.'
283
+ : `This project connects to CouchDB at: ${config.couchdbUrl || '[URL not specified]'}`;
284
+ const readme = `# ${config.title}
285
+
286
+ A Skuilder course application built with Vue 3, Vuetify, and Pinia.
287
+
288
+ ## Data Layer
289
+
290
+ ${dataLayerInfo}
291
+
292
+ ## Development
293
+
294
+ Install dependencies:
295
+ \`\`\`bash
296
+ npm install
297
+ \`\`\`
298
+
299
+ Start the development server:
300
+ \`\`\`bash
301
+ npm run dev
302
+ \`\`\`
303
+
304
+ Build for production:
305
+ \`\`\`bash
306
+ npm run build
307
+ \`\`\`
308
+
309
+ ## Configuration
310
+
311
+ Course configuration is managed in \`skuilder.config.json\`. You can modify:
312
+ - Course title
313
+ - Data layer settings
314
+ - Theme customization
315
+ - Database connection details (for dynamic data layer)
316
+
317
+ ## Theme
318
+
319
+ Current theme: **${config.theme.name}**
320
+ - Primary: ${config.theme.colors.primary}
321
+ - Secondary: ${config.theme.colors.secondary}
322
+ - Accent: ${config.theme.colors.accent}
323
+
324
+ ## Testing
325
+
326
+ Run end-to-end tests:
327
+ \`\`\`bash
328
+ npm run test:e2e
329
+ \`\`\`
330
+
331
+ Run tests in headless mode:
332
+ \`\`\`bash
333
+ npm run test:e2e:headless
334
+ \`\`\`
335
+
336
+ ## Learn More
337
+
338
+ Visit the [Skuilder documentation](https://github.com/NiloCK/vue-skuilder) for more information about building course applications.
339
+ `;
340
+ await fs.writeFile(readmePath, readme);
341
+ }
342
+ /**
343
+ * Copy and transform the standalone-ui template to create a new project
344
+ */
345
+ export async function processTemplate(projectPath, config, cliVersion) {
346
+ console.log(chalk.blue('📦 Locating standalone-ui template...'));
347
+ const templatePath = await findStandaloneUiPath();
348
+ console.log(chalk.blue('📂 Copying project files...'));
349
+ await copyDirectory(templatePath, projectPath);
350
+ console.log(chalk.blue('⚙️ Configuring package.json...'));
351
+ const packageJsonPath = path.join(projectPath, 'package.json');
352
+ await transformPackageJson(packageJsonPath, config.projectName, cliVersion);
353
+ console.log(chalk.blue('🔧 Creating vite.config.ts...'));
354
+ const viteConfigPath = path.join(projectPath, 'vite.config.ts');
355
+ if (existsSync(viteConfigPath)) {
356
+ await createViteConfig(viteConfigPath);
357
+ }
358
+ console.log(chalk.blue('🔧 Generating configuration...'));
359
+ const configPath = path.join(projectPath, 'skuilder.config.json');
360
+ await generateSkuilderConfig(configPath, config);
361
+ console.log(chalk.blue('📝 Creating README...'));
362
+ const readmePath = path.join(projectPath, 'README.md');
363
+ await generateReadme(readmePath, config);
364
+ console.log(chalk.blue('📄 Generating .gitignore...'));
365
+ const gitignorePath = path.join(projectPath, '.gitignore');
366
+ await generateGitignore(gitignorePath);
367
+ console.log(chalk.green('✅ Template processing complete!'));
368
+ }
369
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/utils/template.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import backendConfig from '../../eslint.config.backend.mjs';
2
+
3
+ export default [
4
+ ...backendConfig,
5
+ {
6
+ ignores: ['node_modules/**', 'dist/**', 'eslint.config.mjs', 'tsconfig.json'],
7
+ },
8
+ {
9
+ languageOptions: {
10
+ parserOptions: {
11
+ project: './tsconfig.json',
12
+ tsconfigRootDir: import.meta.dirname,
13
+ },
14
+ },
15
+ rules: {
16
+ // CLI-specific rules - CLI tools need console output
17
+ 'no-console': 'off',
18
+ '@typescript-eslint/no-console': 'off',
19
+ },
20
+ },
21
+ ];
package/package.json CHANGED
@@ -1,44 +1,49 @@
1
1
  {
2
2
  "name": "skuilder",
3
- "version": "0.1.2",
4
- "main": "index.js",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://www.github.com/nilock/skuilder"
3
+ "version": "0.1.4",
4
+ "type": "module",
5
+ "description": "CLI tool for scaffolding Skuilder course applications",
6
+ "bin": {
7
+ "skuilder": "./dist/cli.js"
8
8
  },
9
- "author": {
10
- "name": "Colin Kennedy",
11
- "email": "colink@mun.ca",
12
- "url": "https://www.github.com/nilock"
9
+ "main": "dist/cli.js",
10
+ "types": "dist/cli.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/cli.d.ts",
14
+ "import": "./dist/cli.js"
15
+ }
13
16
  },
14
17
  "scripts": {
15
- "web-watch": "webpack --progress --colors --watch",
16
- "rn-watch": "tsc --watch",
17
- "start": "node node_modules/react-native/local-cli/cli.js start"
18
+ "build": "rm -rf dist && tsc",
19
+ "dev": "tsc --watch",
20
+ "lint": "npx eslint .",
21
+ "lint:fix": "npx eslint . --fix",
22
+ "lint:check": "npx eslint . --max-warnings 0"
23
+ },
24
+ "keywords": [
25
+ "cli",
26
+ "scaffolding",
27
+ "vue",
28
+ "skuilder",
29
+ "education",
30
+ "course"
31
+ ],
32
+ "dependencies": {
33
+ "@vue-skuilder/standalone-ui": "^0.1.4",
34
+ "chalk": "^5.3.0",
35
+ "commander": "^11.0.0",
36
+ "fs-extra": "^11.2.0",
37
+ "inquirer": "^9.2.0"
18
38
  },
19
39
  "devDependencies": {
20
- "@types/node": "^7.0.12",
21
- "@types/webpack": "^2.2.14",
22
- "awesome-typescript-loader": "3.1.2",
23
- "image-webpack-loader": "^3.3.1",
24
- "source-map-loader": "^0.1.6",
25
- "ts-node": "^3.0.2",
26
- "typescript": "2.1.6",
27
- "webpack": "2.2.1"
40
+ "@types/fs-extra": "^11.0.0",
41
+ "@types/inquirer": "^9.0.0",
42
+ "@types/node": "^20.0.0",
43
+ "typescript": "~5.7.2"
28
44
  },
29
- "dependencies": {
30
- "moment": "^2.18.1",
31
- "mousetrap": "^1.6.1",
32
- "pouchdb-authentication": "^0.5.5",
33
- "pouchdb-browser": "^6.2.0",
34
- "react": "^15.4.1",
35
- "react-addons-perf": "^15.4.1",
36
- "react-addons-test-utils": "^15.4.1",
37
- "react-dom": "^15.4.1",
38
- "react-native": "^0.42.0",
39
- "react-native-windows": "^0.33.0",
40
- "reactxp": "^0.42.0-rc.12",
41
- "skldr-course-base": "^0.1.2",
42
- "skldr-crs-math": "^0.1.2"
43
- }
44
- }
45
+ "engines": {
46
+ "node": ">=18.0.0"
47
+ },
48
+ "packageManager": "yarn@4.6.0"
49
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { readFileSync } from 'fs';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, join } from 'path';
7
+ import { initCommand } from './commands/init.js';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ // Read package.json to get version
13
+ const packagePath = join(__dirname, '..', 'package.json');
14
+ const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
15
+
16
+ const program = new Command();
17
+
18
+ program
19
+ .name('skuilder')
20
+ .description('CLI tool for scaffolding Skuilder course applications')
21
+ .version(packageJson.version);
22
+
23
+ program
24
+ .command('init')
25
+ .argument('<project-name>', 'name of the project to create')
26
+ .description('create a new Skuilder course application')
27
+ .option('--data-layer <type>', 'data layer type (static|dynamic)', 'dynamic')
28
+ .option('--theme <name>', 'theme name (default|medical|educational|corporate)', 'default')
29
+ .option('--no-interactive', 'skip interactive prompts')
30
+ .option('--couchdb-url <url>', 'CouchDB server URL (for dynamic data layer)')
31
+ .option('--course-id <id>', 'course ID to import (for dynamic data layer)')
32
+ .action(initCommand);
33
+
34
+ program.on('--help', () => {
35
+ console.log('');
36
+ console.log('Examples:');
37
+ console.log(' $ skuilder init my-anatomy-course');
38
+ console.log(' $ skuilder init biology-101 --data-layer=static --theme=medical');
39
+ console.log(' $ skuilder init physics --no-interactive --data-layer=dynamic');
40
+ });
41
+
42
+ program.parse();
@@ -0,0 +1,83 @@
1
+ import { existsSync } from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import { CliOptions } from '../types.js';
5
+ import { gatherProjectConfig, confirmProjectCreation } from '../utils/prompts.js';
6
+ import { processTemplate } from '../utils/template.js';
7
+ import { readFileSync } from 'fs';
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname, join } from 'path';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+
14
+ export async function initCommand(
15
+ projectName: string,
16
+ options: CliOptions
17
+ ): Promise<void> {
18
+ try {
19
+ // Validate project name
20
+ if (!isValidProjectName(projectName)) {
21
+ console.error(chalk.red('❌ Invalid project name. Use only letters, numbers, hyphens, and underscores.'));
22
+ process.exit(1);
23
+ }
24
+
25
+ // Check if directory already exists
26
+ const projectPath = path.resolve(process.cwd(), projectName);
27
+ if (existsSync(projectPath)) {
28
+ console.error(chalk.red(`❌ Directory "${projectName}" already exists.`));
29
+ process.exit(1);
30
+ }
31
+
32
+ // Get CLI version for dependency transformation
33
+ const packagePath = join(__dirname, '..', '..', 'package.json');
34
+ const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
35
+ const cliVersion = packageJson.version;
36
+
37
+ // Gather project configuration
38
+ const config = await gatherProjectConfig(projectName, options);
39
+
40
+ // Confirm project creation (only in interactive mode)
41
+ if (options.interactive) {
42
+ const confirmed = await confirmProjectCreation(config, projectPath);
43
+ if (!confirmed) {
44
+ console.log(chalk.yellow('Project creation cancelled.'));
45
+ return;
46
+ }
47
+ }
48
+
49
+ console.log(chalk.cyan(`\n🛠️ Creating project "${projectName}"...\n`));
50
+
51
+ // Process template and create project
52
+ await processTemplate(projectPath, config, cliVersion);
53
+
54
+ // Success message
55
+ console.log(chalk.green('\n🎉 Project created successfully!\n'));
56
+ console.log(chalk.cyan('Next steps:'));
57
+ console.log(` ${chalk.white('cd')} ${projectName}`);
58
+ console.log(` ${chalk.white('npm install')}`);
59
+ console.log(` ${chalk.white('npm run dev')}`);
60
+ console.log('');
61
+
62
+ if (config.dataLayerType === 'couch') {
63
+ console.log(chalk.yellow('📝 Note: Make sure your CouchDB server is running and accessible.'));
64
+ if (config.course) {
65
+ console.log(chalk.yellow(`📚 Course ID "${config.course}" will be loaded from the database.`));
66
+ }
67
+ } else {
68
+ console.log(chalk.yellow('📝 Note: This project uses static data. Sample course data has been included.'));
69
+ }
70
+
71
+ } catch (error) {
72
+ console.error(chalk.red('\n❌ Failed to create project:'));
73
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ function isValidProjectName(name: string): boolean {
79
+ // Allow letters, numbers, hyphens, and underscores
80
+ // Must start with a letter or number
81
+ const validNameRegex = /^[a-zA-Z0-9][a-zA-Z0-9-_]*$/;
82
+ return validNameRegex.test(name) && name.length > 0 && name.length <= 214;
83
+ }
package/src/types.ts ADDED
@@ -0,0 +1,72 @@
1
+ export interface CliOptions {
2
+ dataLayer: 'static' | 'dynamic';
3
+ theme: 'default' | 'medical' | 'educational' | 'corporate';
4
+ interactive: boolean;
5
+ couchdbUrl?: string;
6
+ courseId?: string;
7
+ }
8
+
9
+ export interface ProjectConfig {
10
+ projectName: string;
11
+ title: string;
12
+ dataLayerType: 'static' | 'couch';
13
+ course?: string;
14
+ couchdbUrl?: string;
15
+ theme: ThemeConfig;
16
+ }
17
+
18
+ export interface ThemeConfig {
19
+ name: string;
20
+ colors: {
21
+ primary: string;
22
+ secondary: string;
23
+ accent: string;
24
+ };
25
+ }
26
+
27
+ export interface SkuilderConfig {
28
+ title: string;
29
+ course?: string;
30
+ dataLayerType: 'static' | 'couch';
31
+ couchdbUrl?: string;
32
+ theme?: ThemeConfig;
33
+ }
34
+
35
+ export interface InitCommandOptions extends CliOptions {
36
+ projectName: string;
37
+ }
38
+
39
+ export const PREDEFINED_THEMES: Record<string, ThemeConfig> = {
40
+ default: {
41
+ name: 'default',
42
+ colors: {
43
+ primary: '#1976D2',
44
+ secondary: '#424242',
45
+ accent: '#82B1FF'
46
+ }
47
+ },
48
+ medical: {
49
+ name: 'medical',
50
+ colors: {
51
+ primary: '#2E7D32',
52
+ secondary: '#558B2F',
53
+ accent: '#66BB6A'
54
+ }
55
+ },
56
+ educational: {
57
+ name: 'educational',
58
+ colors: {
59
+ primary: '#F57C00',
60
+ secondary: '#FF9800',
61
+ accent: '#FFB74D'
62
+ }
63
+ },
64
+ corporate: {
65
+ name: 'corporate',
66
+ colors: {
67
+ primary: '#37474F',
68
+ secondary: '#546E7A',
69
+ accent: '#78909C'
70
+ }
71
+ }
72
+ };