@vue-skuilder/cli 0.1.7 → 0.1.8-0

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 (168) hide show
  1. package/README.md +122 -8
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +2 -1
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/init.d.ts.map +1 -1
  7. package/dist/commands/init.js +34 -3
  8. package/dist/commands/init.js.map +1 -1
  9. package/dist/commands/pack.d.ts +10 -0
  10. package/dist/commands/pack.d.ts.map +1 -1
  11. package/dist/commands/pack.js +1 -1
  12. package/dist/commands/pack.js.map +1 -1
  13. package/dist/commands/studio.d.ts.map +1 -1
  14. package/dist/commands/studio.js +640 -105
  15. package/dist/commands/studio.js.map +1 -1
  16. package/dist/commands/unpack.d.ts +12 -0
  17. package/dist/commands/unpack.d.ts.map +1 -1
  18. package/dist/commands/unpack.js +1 -1
  19. package/dist/commands/unpack.js.map +1 -1
  20. package/dist/express-assets/app.d.ts +6 -0
  21. package/dist/express-assets/app.d.ts.map +1 -0
  22. package/dist/express-assets/app.js +209 -0
  23. package/dist/express-assets/app.js.map +1 -0
  24. package/dist/express-assets/assets/classroomDesignDoc.js +24 -0
  25. package/dist/express-assets/assets/courseValidateDocUpdate.js +56 -0
  26. package/dist/express-assets/assets/get-tagsDesignDoc.json +9 -0
  27. package/dist/express-assets/attachment-preprocessing/index.d.ts +11 -0
  28. package/dist/express-assets/attachment-preprocessing/index.d.ts.map +1 -0
  29. package/dist/express-assets/attachment-preprocessing/index.js +204 -0
  30. package/dist/express-assets/attachment-preprocessing/index.js.map +1 -0
  31. package/dist/express-assets/attachment-preprocessing/normalize.d.ts +7 -0
  32. package/dist/express-assets/attachment-preprocessing/normalize.d.ts.map +1 -0
  33. package/dist/express-assets/attachment-preprocessing/normalize.js +90 -0
  34. package/dist/express-assets/attachment-preprocessing/normalize.js.map +1 -0
  35. package/dist/express-assets/client-requests/classroom-requests.d.ts +26 -0
  36. package/dist/express-assets/client-requests/classroom-requests.d.ts.map +1 -0
  37. package/dist/express-assets/client-requests/classroom-requests.js +171 -0
  38. package/dist/express-assets/client-requests/classroom-requests.js.map +1 -0
  39. package/dist/express-assets/client-requests/course-requests.d.ts +10 -0
  40. package/dist/express-assets/client-requests/course-requests.d.ts.map +1 -0
  41. package/dist/express-assets/client-requests/course-requests.js +135 -0
  42. package/dist/express-assets/client-requests/course-requests.js.map +1 -0
  43. package/dist/express-assets/client-requests/pack-requests.d.ts +19 -0
  44. package/dist/express-assets/client-requests/pack-requests.d.ts.map +1 -0
  45. package/dist/express-assets/client-requests/pack-requests.js +130 -0
  46. package/dist/express-assets/client-requests/pack-requests.js.map +1 -0
  47. package/dist/express-assets/client.d.ts +31 -0
  48. package/dist/express-assets/client.d.ts.map +1 -0
  49. package/dist/express-assets/client.js +70 -0
  50. package/dist/express-assets/client.js.map +1 -0
  51. package/dist/express-assets/couchdb/authentication.d.ts +4 -0
  52. package/dist/express-assets/couchdb/authentication.d.ts.map +1 -0
  53. package/dist/express-assets/couchdb/authentication.js +69 -0
  54. package/dist/express-assets/couchdb/authentication.js.map +1 -0
  55. package/dist/express-assets/couchdb/index.d.ts +18 -0
  56. package/dist/express-assets/couchdb/index.d.ts.map +1 -0
  57. package/dist/express-assets/couchdb/index.js +52 -0
  58. package/dist/express-assets/couchdb/index.js.map +1 -0
  59. package/dist/express-assets/design-docs.d.ts +63 -0
  60. package/dist/express-assets/design-docs.d.ts.map +1 -0
  61. package/dist/express-assets/design-docs.js +90 -0
  62. package/dist/express-assets/design-docs.js.map +1 -0
  63. package/dist/express-assets/logger.d.ts +3 -0
  64. package/dist/express-assets/logger.d.ts.map +1 -0
  65. package/dist/express-assets/logger.js +62 -0
  66. package/dist/express-assets/logger.js.map +1 -0
  67. package/dist/express-assets/routes/logs.d.ts +3 -0
  68. package/dist/express-assets/routes/logs.d.ts.map +1 -0
  69. package/dist/express-assets/routes/logs.js +274 -0
  70. package/dist/express-assets/routes/logs.js.map +1 -0
  71. package/dist/express-assets/utils/env.d.ts +11 -0
  72. package/dist/express-assets/utils/env.d.ts.map +1 -0
  73. package/dist/express-assets/utils/env.js +39 -0
  74. package/dist/express-assets/utils/env.js.map +1 -0
  75. package/dist/express-assets/utils/processQueue.d.ts +39 -0
  76. package/dist/express-assets/utils/processQueue.d.ts.map +1 -0
  77. package/dist/express-assets/utils/processQueue.js +175 -0
  78. package/dist/express-assets/utils/processQueue.js.map +1 -0
  79. package/dist/studio-ui-src/App.vue +132 -0
  80. package/dist/studio-ui-src/api/index.ts +30 -0
  81. package/dist/studio-ui-src/components/StudioFlush.vue +108 -0
  82. package/dist/studio-ui-src/config/development.ts +98 -0
  83. package/dist/studio-ui-src/index.html +13 -0
  84. package/dist/studio-ui-src/main.ts +148 -0
  85. package/dist/studio-ui-src/package.json +35 -0
  86. package/dist/studio-ui-src/router/index.ts +32 -0
  87. package/dist/studio-ui-src/stores/useAuthStore.ts +3 -0
  88. package/dist/studio-ui-src/tsconfig.json +28 -0
  89. package/dist/studio-ui-src/views/BrowseView.vue +82 -0
  90. package/dist/studio-ui-src/views/BulkImportView.vue +89 -0
  91. package/dist/studio-ui-src/views/CourseEditorView.vue +62 -0
  92. package/dist/studio-ui-src/views/CreateCardView.vue +140 -0
  93. package/dist/studio-ui-src/vite.config.base.js +100 -0
  94. package/dist/studio-ui-src/vite.config.ts +26 -0
  95. package/dist/templates/.skuilder/README.md +29 -0
  96. package/dist/types.d.ts +5 -0
  97. package/dist/types.d.ts.map +1 -1
  98. package/dist/types.js.map +1 -1
  99. package/dist/utils/ExpressManager.d.ts +28 -0
  100. package/dist/utils/ExpressManager.d.ts.map +1 -0
  101. package/dist/utils/ExpressManager.js +166 -0
  102. package/dist/utils/ExpressManager.js.map +1 -0
  103. package/dist/utils/NodeFileSystemAdapter.d.ts +6 -0
  104. package/dist/utils/NodeFileSystemAdapter.d.ts.map +1 -1
  105. package/dist/utils/NodeFileSystemAdapter.js +29 -1
  106. package/dist/utils/NodeFileSystemAdapter.js.map +1 -1
  107. package/dist/utils/error-reporting.d.ts +54 -0
  108. package/dist/utils/error-reporting.d.ts.map +1 -0
  109. package/dist/utils/error-reporting.js +143 -0
  110. package/dist/utils/error-reporting.js.map +1 -0
  111. package/dist/utils/pack-courses.d.ts.map +1 -1
  112. package/dist/utils/pack-courses.js +10 -27
  113. package/dist/utils/pack-courses.js.map +1 -1
  114. package/dist/utils/prompts.d.ts.map +1 -1
  115. package/dist/utils/prompts.js +24 -0
  116. package/dist/utils/prompts.js.map +1 -1
  117. package/dist/utils/questions-hash.d.ts +22 -0
  118. package/dist/utils/questions-hash.d.ts.map +1 -0
  119. package/dist/utils/questions-hash.js +96 -0
  120. package/dist/utils/questions-hash.js.map +1 -0
  121. package/dist/utils/template.d.ts +1 -1
  122. package/dist/utils/template.d.ts.map +1 -1
  123. package/dist/utils/template.js +202 -25
  124. package/dist/utils/template.js.map +1 -1
  125. package/eslint.config.mjs +1 -1
  126. package/package.json +30 -11
  127. package/src/cli.ts +3 -1
  128. package/src/commands/init.ts +48 -3
  129. package/src/commands/pack.ts +1 -1
  130. package/src/commands/studio.ts +850 -121
  131. package/src/commands/unpack.ts +1 -1
  132. package/src/types.ts +5 -0
  133. package/src/utils/ExpressManager.ts +210 -0
  134. package/src/utils/NodeFileSystemAdapter.ts +46 -2
  135. package/src/utils/error-reporting.ts +192 -0
  136. package/src/utils/pack-courses.ts +11 -36
  137. package/src/utils/prompts.ts +34 -0
  138. package/src/utils/questions-hash.ts +109 -0
  139. package/src/utils/template.ts +231 -27
  140. package/templates/.skuilder/README.md +29 -0
  141. package/dist/studio-ui-assets/assets/BrowseView-BJbixGOU.js +0 -2
  142. package/dist/studio-ui-assets/assets/BrowseView-BJbixGOU.js.map +0 -1
  143. package/dist/studio-ui-assets/assets/BrowseView-CM4HBO4j.css +0 -1
  144. package/dist/studio-ui-assets/assets/BulkImportView-DB6DYDJU.js +0 -2
  145. package/dist/studio-ui-assets/assets/BulkImportView-DB6DYDJU.js.map +0 -1
  146. package/dist/studio-ui-assets/assets/BulkImportView-g4wQUfPA.css +0 -1
  147. package/dist/studio-ui-assets/assets/CourseEditorView-BIlhlhw1.js +0 -2
  148. package/dist/studio-ui-assets/assets/CourseEditorView-BIlhlhw1.js.map +0 -1
  149. package/dist/studio-ui-assets/assets/CourseEditorView-WuPNLVKp.css +0 -1
  150. package/dist/studio-ui-assets/assets/CreateCardView-CyNOKCkm.css +0 -1
  151. package/dist/studio-ui-assets/assets/CreateCardView-DPjPvzzt.js +0 -2
  152. package/dist/studio-ui-assets/assets/CreateCardView-DPjPvzzt.js.map +0 -1
  153. package/dist/studio-ui-assets/assets/edit-ui.es-DiUxqbgF.js +0 -330
  154. package/dist/studio-ui-assets/assets/edit-ui.es-DiUxqbgF.js.map +0 -1
  155. package/dist/studio-ui-assets/assets/index--zY88pg6.css +0 -14
  156. package/dist/studio-ui-assets/assets/index-BnAv1C72.js +0 -287
  157. package/dist/studio-ui-assets/assets/index-BnAv1C72.js.map +0 -1
  158. package/dist/studio-ui-assets/assets/index-DHMXQY3-.js +0 -192
  159. package/dist/studio-ui-assets/assets/index-DHMXQY3-.js.map +0 -1
  160. package/dist/studio-ui-assets/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  161. package/dist/studio-ui-assets/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  162. package/dist/studio-ui-assets/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  163. package/dist/studio-ui-assets/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  164. package/dist/studio-ui-assets/assets/vue-DZcMATiC.js +0 -28
  165. package/dist/studio-ui-assets/assets/vue-DZcMATiC.js.map +0 -1
  166. package/dist/studio-ui-assets/assets/vuetify-qg7mRxy_.js +0 -6
  167. package/dist/studio-ui-assets/assets/vuetify-qg7mRxy_.js.map +0 -1
  168. package/dist/studio-ui-assets/index.html +0 -16
@@ -1,26 +1,52 @@
1
+ import { CouchDBManager } from '@vue-skuilder/common/docker';
2
+ import chalk from 'chalk';
1
3
  import { Command } from 'commander';
2
- import path from 'path';
3
4
  import fs from 'fs';
4
- import chalk from 'chalk';
5
- import { spawn } from 'child_process';
6
- import { fileURLToPath } from 'url';
7
- import { dirname } from 'path';
8
5
  import http from 'http';
9
- import { CouchDBManager } from '@vue-skuilder/common/docker';
10
- // TODO: Re-enable once module import issues are resolved
11
- // import { StaticToCouchDBMigrator, validateStaticCourse } from '@vue-skuilder/db';
6
+ import path, { dirname } from 'path';
7
+ import serveStatic from 'serve-static';
8
+ import { fileURLToPath } from 'url';
9
+ import { VERSION } from '../cli.js';
10
+ import { createStudioBuildError, reportStudioBuildError, StudioBuildErrorType, withStudioBuildErrorHandling, } from '../utils/error-reporting.js';
11
+ import { ExpressManager } from '../utils/ExpressManager.js';
12
+ import { ensureBuildDirectory, ensureCacheDirectory, getStudioBuildPath, hashQuestionsDirectory, studioBuildExists, } from '../utils/questions-hash.js';
12
13
  const __filename = fileURLToPath(import.meta.url);
13
14
  const __dirname = dirname(__filename);
14
15
  export function createStudioCommand() {
15
16
  return new Command('studio')
16
- .description('Launch studio mode for editing a static course')
17
+ .description('Launch studio mode: a complete course editing environment with CouchDB, Express API, and web editor')
17
18
  .argument('[coursePath]', 'Path to static course directory', '.')
18
19
  .option('-p, --port <port>', 'CouchDB port for studio session', '5985')
19
20
  .option('--no-browser', 'Skip automatic browser launch')
20
- .action(launchStudio);
21
+ .action(launchStudio)
22
+ .addHelpText('after', `
23
+ Studio Mode creates a full editing environment for static courses:
24
+
25
+ Services Started:
26
+ • CouchDB instance (Docker) on port 5985+ for temporary editing
27
+ • Express API server on port 3001+ for backend operations
28
+ • Studio web interface on port 7174+ for visual editing
29
+
30
+ Workflow:
31
+ 1. Loads course data from public/static-courses/ into CouchDB
32
+ 2. Opens web editor for visual course content editing
33
+ 3. Use "Flush to Static" to save changes back to your course files
34
+ 4. Studio mode overwrites source files - backup before major edits
35
+
36
+ Requirements:
37
+ • Docker (for CouchDB instance)
38
+ • Valid static course project (with package.json)
39
+ • Course data in public/static-courses/ directory
40
+
41
+ Example:
42
+ skuilder studio # Launch in current directory
43
+ skuilder studio ./my-course # Launch for specific course
44
+ skuilder studio --port 6000 # Use custom CouchDB port
45
+ skuilder studio --no-browser # Don't auto-open browser`);
21
46
  }
22
47
  // Global references for cleanup
23
48
  let couchDBManager = null;
49
+ let expressManager = null;
24
50
  let studioUIServer = null;
25
51
  async function launchStudio(coursePath, options) {
26
52
  try {
@@ -36,6 +62,42 @@ async function launchStudio(coursePath, options) {
36
62
  process.exit(1);
37
63
  }
38
64
  console.log(chalk.green(`✅ Valid standalone-ui course detected`));
65
+ // Phase 0.5: Hash questions directory to determine studio-ui build needs
66
+ console.log(chalk.cyan(`🔍 Analyzing local question types...`));
67
+ let questionsHash;
68
+ let studioUIPath;
69
+ try {
70
+ questionsHash = await withStudioBuildErrorHandling(() => hashQuestionsDirectory(resolvedPath), StudioBuildErrorType.QUESTIONS_HASH_ERROR, { coursePath: resolvedPath });
71
+ // Ensure cache directory exists
72
+ await ensureCacheDirectory(resolvedPath);
73
+ const buildExists = studioBuildExists(resolvedPath, questionsHash);
74
+ const buildPath = getStudioBuildPath(resolvedPath, questionsHash);
75
+ console.log(chalk.gray(` Questions hash: ${questionsHash}`));
76
+ console.log(chalk.gray(` Cached build exists: ${buildExists ? 'Yes' : 'No'}`));
77
+ // Determine if we need to rebuild studio-ui
78
+ if (buildExists) {
79
+ console.log(chalk.gray(` Using cached build at: ${buildPath}`));
80
+ studioUIPath = buildPath;
81
+ }
82
+ else {
83
+ console.log(chalk.cyan(`🔨 Building studio-ui with local question types...`));
84
+ studioUIPath = await buildStudioUIWithQuestions(resolvedPath, questionsHash);
85
+ console.log(chalk.green(`✅ Studio-UI build complete: ${studioUIPath}`));
86
+ }
87
+ }
88
+ catch (error) {
89
+ // Handle catastrophic build errors by falling back to embedded source
90
+ console.log(chalk.yellow(`⚠️ Unable to process questions due to ${error},\n⚠️ Using embedded studio-ui`));
91
+ const embeddedPath = path.join(__dirname, '..', 'studio-ui-src');
92
+ if (fs.existsSync(embeddedPath)) {
93
+ studioUIPath = embeddedPath;
94
+ console.log(chalk.gray(` Using embedded studio-ui source directly`));
95
+ }
96
+ else {
97
+ console.error(chalk.red(`❌ No viable studio-ui source available`));
98
+ throw new Error('Critical error: Cannot locate studio-ui source');
99
+ }
100
+ }
39
101
  // Phase 1: CouchDB Management
40
102
  const studioDatabaseName = generateStudioDatabaseName(resolvedPath);
41
103
  console.log(chalk.cyan(`🗄️ Starting studio CouchDB instance: ${studioDatabaseName}`));
@@ -43,13 +105,17 @@ async function launchStudio(coursePath, options) {
43
105
  // Phase 4: Populate CouchDB with course data
44
106
  console.log(chalk.cyan(`📦 Unpacking course data to studio database...`));
45
107
  const unpackResult = await unpackCourseToStudio(resolvedPath, couchDBManager.getConnectionDetails());
108
+ // Phase 9.5: Launch Express backend
109
+ console.log(chalk.cyan(`⚡ Starting Express backend server...`));
110
+ expressManager = await startExpressBackend(couchDBManager.getConnectionDetails(), resolvedPath);
46
111
  // Phase 7: Launch studio-ui server
47
112
  console.log(chalk.cyan(`🌐 Starting studio-ui server...`));
48
113
  console.log(chalk.gray(` Debug: Unpack result - Database: "${unpackResult.databaseName}", Course ID: "${unpackResult.courseId}"`));
49
- const studioUIPort = await startStudioUIServer(couchDBManager.getConnectionDetails(), unpackResult);
114
+ const studioUIPort = await startStudioUIServer(couchDBManager.getConnectionDetails(), unpackResult, studioUIPath);
50
115
  console.log(chalk.green(`✅ Studio session ready!`));
51
116
  console.log(chalk.white(`🎨 Studio URL: http://localhost:${studioUIPort}`));
52
117
  console.log(chalk.gray(` Database: ${studioDatabaseName} on port ${options.port}`));
118
+ console.log(chalk.gray(` Express API: ${expressManager.getConnectionDetails().url}`));
53
119
  if (options.browser) {
54
120
  console.log(chalk.cyan(`🌐 Opening browser...`));
55
121
  await openBrowser(`http://localhost:${studioUIPort}`);
@@ -153,7 +219,7 @@ async function startStudioCouchDB(_databaseName, port) {
153
219
  }
154
220
  }
155
221
  /**
156
- * Stop entire studio session (CouchDB + UI server)
222
+ * Stop entire studio session (CouchDB + Express + UI server)
157
223
  */
158
224
  async function stopStudioSession() {
159
225
  // Stop studio-ui server
@@ -168,6 +234,18 @@ async function stopStudioSession() {
168
234
  }
169
235
  studioUIServer = null;
170
236
  }
237
+ // Stop Express backend
238
+ if (expressManager) {
239
+ try {
240
+ await expressManager.stop();
241
+ console.log(chalk.green(`✅ Express backend stopped`));
242
+ }
243
+ catch (error) {
244
+ const errorMessage = error instanceof Error ? error.message : String(error);
245
+ console.error(chalk.red(`Error stopping Express backend: ${errorMessage}`));
246
+ }
247
+ expressManager = null;
248
+ }
171
249
  // Stop CouchDB
172
250
  if (couchDBManager) {
173
251
  try {
@@ -181,10 +259,30 @@ async function stopStudioSession() {
181
259
  couchDBManager = null;
182
260
  }
183
261
  }
184
- async function startStudioUIServer(connectionDetails, unpackResult) {
185
- const studioAssetsPath = path.join(__dirname, '..', 'studio-ui-assets');
186
- if (!fs.existsSync(studioAssetsPath)) {
187
- throw new Error('Studio-UI assets not found. Please rebuild the CLI package.');
262
+ async function startStudioUIServer(connectionDetails, unpackResult, studioPath) {
263
+ // Serve from built dist directory if it exists, otherwise fallback to source
264
+ const distPath = path.join(studioPath, 'dist');
265
+ const studioSourcePath = fs.existsSync(distPath) ? distPath : studioPath;
266
+ console.log(chalk.gray(` Serving studio-ui from: ${studioSourcePath}`));
267
+ const serve = serveStatic(studioSourcePath, {
268
+ index: ['index.html'],
269
+ setHeaders: (res, path) => {
270
+ if (path.endsWith('.woff2')) {
271
+ res.setHeader('Content-Type', 'font/woff2');
272
+ }
273
+ else if (path.endsWith('.woff')) {
274
+ res.setHeader('Content-Type', 'font/woff');
275
+ }
276
+ else if (path.endsWith('.ttf')) {
277
+ res.setHeader('Content-Type', 'font/ttf');
278
+ }
279
+ else if (path.endsWith('.eot')) {
280
+ res.setHeader('Content-Type', 'application/vnd.ms-fontobject');
281
+ }
282
+ },
283
+ });
284
+ if (!fs.existsSync(studioSourcePath)) {
285
+ throw new Error('Studio-UI source not found. Please rebuild the CLI package.');
188
286
  }
189
287
  // Find available port starting from 7174
190
288
  let port = 7174;
@@ -192,34 +290,38 @@ async function startStudioUIServer(connectionDetails, unpackResult) {
192
290
  try {
193
291
  await new Promise((resolve, reject) => {
194
292
  const server = http.createServer((req, res) => {
195
- let filePath = path.join(studioAssetsPath, req.url === '/' ? 'index.html' : req.url || '');
196
- // Security: prevent directory traversal
197
- if (!filePath.startsWith(studioAssetsPath)) {
198
- res.writeHead(403);
199
- res.end('Forbidden');
293
+ const url = new URL(req.url || '/', `http://${req.headers.host}`);
294
+ // Inject config for index.html
295
+ if (url.pathname === '/' || url.pathname === '/index.html') {
296
+ const indexPath = path.join(studioSourcePath, 'index.html');
297
+ let html = fs.readFileSync(indexPath, 'utf8');
298
+ const connectionScript = `
299
+ <script>
300
+ window.STUDIO_CONFIG = {
301
+ couchdb: {
302
+ url: '${connectionDetails.url}',
303
+ username: '${connectionDetails.username}',
304
+ password: '${connectionDetails.password}'
305
+ },
306
+ database: {
307
+ name: '${unpackResult.databaseName}',
308
+ courseId: '${unpackResult.courseId}',
309
+ originalCourseId: '${unpackResult.courseId}'
310
+ }
311
+ };
312
+ </script>
313
+ `;
314
+ html = html.replace('</head>', connectionScript + '</head>');
315
+ res.setHeader('Content-Type', 'text/html');
316
+ res.end(html);
200
317
  return;
201
318
  }
202
- // Check if file exists
203
- if (!fs.existsSync(filePath)) {
204
- // If it's not a file, serve index.html for SPA routing
205
- filePath = path.join(studioAssetsPath, 'index.html');
206
- }
207
- // Determine content type
208
- const ext = path.extname(filePath);
209
- const contentType = {
210
- '.html': 'text/html',
211
- '.js': 'text/javascript',
212
- '.css': 'text/css',
213
- '.woff2': 'font/woff2',
214
- '.woff': 'font/woff',
215
- '.ttf': 'font/ttf',
216
- '.eot': 'application/vnd.ms-fontobject',
217
- }[ext] || 'application/octet-stream';
218
- res.writeHead(200, { 'Content-Type': contentType });
219
- // For HTML files, inject CouchDB connection details
220
- if (ext === '.html') {
221
- let html = fs.readFileSync(filePath, 'utf8');
222
- // Inject connection details as script tag before </head>
319
+ // Fallback to serve-static for all other assets
320
+ serve(req, res, () => {
321
+ // If serve-static doesn't find the file, it calls next().
322
+ // We can treat this as a 404, but for SPAs, we should serve index.html.
323
+ const indexPath = path.join(studioSourcePath, 'index.html');
324
+ let html = fs.readFileSync(indexPath, 'utf8');
223
325
  const connectionScript = `
224
326
  <script>
225
327
  window.STUDIO_CONFIG = {
@@ -230,19 +332,16 @@ async function startStudioUIServer(connectionDetails, unpackResult) {
230
332
  },
231
333
  database: {
232
334
  name: '${unpackResult.databaseName}',
233
- courseId: '${unpackResult.courseId}'
335
+ courseId: '${unpackResult.courseId}',
336
+ originalCourseId: '${unpackResult.courseId}'
234
337
  }
235
338
  };
236
339
  </script>
237
340
  `;
238
341
  html = html.replace('</head>', connectionScript + '</head>');
342
+ res.setHeader('Content-Type', 'text/html');
239
343
  res.end(html);
240
- }
241
- else {
242
- // Serve static files
243
- const stream = fs.createReadStream(filePath);
244
- stream.pipe(res);
245
- }
344
+ });
246
345
  });
247
346
  server.listen(port, '127.0.0.1', () => {
248
347
  studioUIServer = server;
@@ -301,11 +400,39 @@ async function openBrowser(url) {
301
400
  console.log(chalk.yellow(`⚠️ Could not automatically open browser. Please visit: ${url}`));
302
401
  }
303
402
  }
403
+ /**
404
+ * Phase 9.5: Start Express backend server
405
+ */
406
+ async function startExpressBackend(couchDbConnectionDetails, projectPath) {
407
+ const expressManager = new ExpressManager({
408
+ port: 3001, // Start from 3001 to avoid conflicts
409
+ couchdbUrl: couchDbConnectionDetails.url,
410
+ couchdbUsername: couchDbConnectionDetails.username,
411
+ couchdbPassword: couchDbConnectionDetails.password,
412
+ projectPath: projectPath,
413
+ }, {
414
+ onLog: (message) => console.log(chalk.gray(` Express: ${message}`)),
415
+ onError: (error) => console.error(chalk.red(` Express Error: ${error}`)),
416
+ });
417
+ try {
418
+ await expressManager.start();
419
+ const connectionDetails = expressManager.getConnectionDetails();
420
+ console.log(chalk.green(`✅ Express backend ready`));
421
+ console.log(chalk.gray(` URL: ${connectionDetails.url}`));
422
+ console.log(chalk.gray(` Port: ${connectionDetails.port}`));
423
+ return expressManager;
424
+ }
425
+ catch (error) {
426
+ const errorMessage = error instanceof Error ? error.message : String(error);
427
+ console.error(chalk.red(`Failed to start Express backend: ${errorMessage}`));
428
+ throw error;
429
+ }
430
+ }
304
431
  /**
305
432
  * Phase 4: Unpack course data to studio CouchDB
306
433
  */
307
434
  async function unpackCourseToStudio(coursePath, connectionDetails) {
308
- return new Promise((resolve, reject) => {
435
+ try {
309
436
  // Find the course data directory (static-data OR public/static-courses)
310
437
  let courseDataPath = path.join(coursePath, 'static-data');
311
438
  if (!fs.existsSync(courseDataPath)) {
@@ -321,76 +448,484 @@ async function unpackCourseToStudio(coursePath, connectionDetails) {
321
448
  courseDataPath = path.join(publicStaticPath, courses[0]);
322
449
  }
323
450
  else {
324
- reject(new Error('No course directories found in public/static-courses/'));
325
- return;
451
+ throw new Error('No course directories found in public/static-courses/');
326
452
  }
327
453
  }
328
454
  else {
329
- reject(new Error('No course data found in static-data/ or public/static-courses/'));
330
- return;
455
+ throw new Error('No course data found in static-data/ or public/static-courses/');
331
456
  }
332
457
  }
333
458
  console.log(chalk.gray(` Course data path: ${courseDataPath}`));
334
- // Build the unpack command arguments
335
- const args = [
336
- 'unpack',
337
- courseDataPath,
338
- '--server',
339
- connectionDetails.url,
340
- '--username',
341
- connectionDetails.username,
342
- '--password',
343
- connectionDetails.password,
344
- ];
345
- console.log(chalk.gray(` Running: skuilder ${args.join(' ')}`));
346
- // Spawn the unpack command as a child process
347
- const unpackProcess = spawn('node', [path.join(__dirname, '..', 'cli.js'), ...args], {
348
- stdio: ['pipe', 'pipe', 'pipe'],
349
- cwd: process.cwd(),
459
+ console.log(chalk.gray(` Running unpack directly...`));
460
+ // Generate database name the same way unpack command does
461
+ const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');
462
+ const random = Math.random().toString(36).substring(2, 8);
463
+ // We need the course ID from the static course data first
464
+ const { validateStaticCourse } = await import('@vue-skuilder/db');
465
+ const { NodeFileSystemAdapter } = await import('../utils/NodeFileSystemAdapter.js');
466
+ const fileSystemAdapter = new NodeFileSystemAdapter();
467
+ const validation = await validateStaticCourse(courseDataPath, fileSystemAdapter);
468
+ if (!validation.valid) {
469
+ throw new Error('Static course validation failed');
470
+ }
471
+ const studioCourseId = `unpacked_${validation.courseId || 'unknown'}_${timestamp}_${random}`;
472
+ const targetDbName = `coursedb-${studioCourseId}`;
473
+ // Import and call the existing unpack command
474
+ const { unpackCourse } = await import('./unpack.js');
475
+ try {
476
+ await unpackCourse(courseDataPath, {
477
+ server: connectionDetails.url,
478
+ username: connectionDetails.username,
479
+ password: connectionDetails.password,
480
+ database: targetDbName,
481
+ chunkSize: '100',
482
+ validate: false,
483
+ cleanupOnError: true,
484
+ });
485
+ console.log(chalk.green(`✅ Course data unpacked successfully`));
486
+ // Return the database name and course ID for studio use
487
+ const databaseName = studioCourseId;
488
+ const courseId = validation.courseId || '';
489
+ console.log(chalk.gray(` Debug: Extracted - Full DB: "${targetDbName}", Course DB ID: "${databaseName}", Course ID: "${courseId}"`));
490
+ return { databaseName, courseId };
491
+ }
492
+ catch (innerError) {
493
+ console.error(chalk.red(`❌ Failed to unpack course: ${innerError instanceof Error ? innerError.message : String(innerError)}`));
494
+ throw innerError;
495
+ }
496
+ }
497
+ catch (error) {
498
+ console.error(chalk.red(`❌ Studio unpack failed: ${error instanceof Error ? error.message : String(error)}`));
499
+ throw error;
500
+ }
501
+ }
502
+ /**
503
+ * Build studio-ui with local question types integrated
504
+ */
505
+ async function buildStudioUIWithQuestions(coursePath, questionsHash) {
506
+ const buildPath = await ensureBuildDirectory(coursePath, questionsHash);
507
+ try {
508
+ // Handle special cases
509
+ if (questionsHash === 'no-questions') {
510
+ console.log(chalk.gray(` No local questions detected, using default studio-ui`));
511
+ return await buildDefaultStudioUI(buildPath);
512
+ }
513
+ if (questionsHash === 'empty-questions') {
514
+ console.log(chalk.gray(` Empty questions directory, using default studio-ui`));
515
+ return await buildDefaultStudioUI(buildPath);
516
+ }
517
+ if (questionsHash === 'hash-error') {
518
+ const hashError = createStudioBuildError(StudioBuildErrorType.QUESTIONS_HASH_ERROR, 'Questions directory could not be processed', {
519
+ context: { coursePath, questionsHash },
520
+ recoverable: true,
521
+ fallbackAvailable: true,
522
+ });
523
+ reportStudioBuildError(hashError);
524
+ return await buildDefaultStudioUI(buildPath);
525
+ }
526
+ // Phase 4.1 - Build custom questions library and integrate with studio-ui
527
+ console.log(chalk.cyan(` Building custom questions library...`));
528
+ const customQuestionsData = await buildCustomQuestionsLibrary(coursePath, questionsHash);
529
+ if (customQuestionsData) {
530
+ console.log(chalk.cyan(` Integrating custom questions into studio-ui...`));
531
+ return await buildStudioUIWithCustomQuestions(buildPath, customQuestionsData);
532
+ }
533
+ else {
534
+ console.log(chalk.yellow(` Failed to build custom questions, falling back to default studio-ui`));
535
+ return await buildDefaultStudioUI(buildPath);
536
+ }
537
+ }
538
+ catch (error) {
539
+ const buildError = createStudioBuildError(StudioBuildErrorType.BUILD_FAILURE, 'Studio-UI build process failed', {
540
+ cause: error instanceof Error ? error : undefined,
541
+ context: { coursePath, questionsHash, buildPath },
542
+ recoverable: true,
543
+ fallbackAvailable: true,
350
544
  });
351
- let stdout = '';
352
- let stderr = '';
353
- unpackProcess.stdout?.on('data', (data) => {
354
- const output = data.toString();
355
- stdout += output;
356
- // Forward output with indentation
357
- process.stdout.write(chalk.gray(` ${output.replace(/\n/g, '\n ')}`));
545
+ reportStudioBuildError(buildError);
546
+ // Always try fallback to default studio-ui
547
+ try {
548
+ return await buildDefaultStudioUI(buildPath);
549
+ }
550
+ catch (fallbackError) {
551
+ // If even the fallback fails, this is critical
552
+ const criticalError = createStudioBuildError(StudioBuildErrorType.CRITICAL_ERROR, 'Both primary and fallback studio-ui builds failed', {
553
+ cause: fallbackError instanceof Error ? fallbackError : undefined,
554
+ context: { coursePath, questionsHash, buildPath, originalError: error },
555
+ recoverable: false,
556
+ fallbackAvailable: false,
557
+ });
558
+ reportStudioBuildError(criticalError);
559
+ throw criticalError;
560
+ }
561
+ }
562
+ }
563
+ /**
564
+ * Build default studio-ui (without local questions integration)
565
+ */
566
+ async function buildDefaultStudioUI(buildPath) {
567
+ const studioSourcePath = path.join(__dirname, '..', 'studio-ui-src');
568
+ try {
569
+ // Verify source directory exists
570
+ if (!fs.existsSync(studioSourcePath)) {
571
+ const sourceError = createStudioBuildError(StudioBuildErrorType.MISSING_SOURCE, `Studio-UI source directory not found at ${studioSourcePath}`, {
572
+ context: { studioSourcePath, buildPath },
573
+ recoverable: true,
574
+ fallbackAvailable: true,
575
+ });
576
+ reportStudioBuildError(sourceError);
577
+ throw sourceError;
578
+ }
579
+ // Copy studio-ui source files to build directory
580
+ console.log(chalk.gray(` Copying studio-ui source to build directory...`));
581
+ const { copyDirectory } = await import('../utils/template.js');
582
+ await withStudioBuildErrorHandling(() => copyDirectory(studioSourcePath, buildPath), StudioBuildErrorType.COPY_FAILURE, { studioSourcePath, buildPath });
583
+ // Transform workspace dependencies to published versions
584
+ console.log(chalk.gray(` Transforming workspace dependencies...`));
585
+ const studioPackageJsonPath = path.join(buildPath, 'package.json');
586
+ await transformPackageJsonForStudioBuild(studioPackageJsonPath);
587
+ // Fix Vite config to use npm packages instead of monorepo paths
588
+ console.log(chalk.gray(` Updating Vite configuration for standalone build...`));
589
+ await fixViteConfigForStandaloneBuild(buildPath);
590
+ // Run Vite build process
591
+ console.log(chalk.gray(` Running Vite build process...`));
592
+ await runViteBuild(buildPath);
593
+ // Verify build output exists
594
+ const distPath = path.join(buildPath, 'dist');
595
+ const indexPath = path.join(distPath, 'index.html');
596
+ if (!fs.existsSync(indexPath)) {
597
+ const buildError = createStudioBuildError(StudioBuildErrorType.BUILD_FAILURE, `Build output missing: ${indexPath}`, {
598
+ context: { buildPath, distPath, indexPath },
599
+ recoverable: true,
600
+ fallbackAvailable: true,
601
+ });
602
+ reportStudioBuildError(buildError);
603
+ throw buildError;
604
+ }
605
+ console.log(chalk.gray(` Default studio-ui built successfully`));
606
+ return buildPath;
607
+ }
608
+ catch (error) {
609
+ // Ultimate fallback: serve directly from embedded source
610
+ if (fs.existsSync(studioSourcePath)) {
611
+ console.log(chalk.yellow(` Using embedded studio-ui source as final fallback`));
612
+ return studioSourcePath;
613
+ }
614
+ // This should never happen, but provides a last resort
615
+ const criticalError = createStudioBuildError(StudioBuildErrorType.CRITICAL_ERROR, 'No viable studio-ui source available', {
616
+ cause: error instanceof Error ? error : undefined,
617
+ context: { studioSourcePath, buildPath },
618
+ recoverable: false,
619
+ fallbackAvailable: false,
358
620
  });
359
- unpackProcess.stderr?.on('data', (data) => {
360
- const output = data.toString();
361
- stderr += output;
362
- // Forward error output with indentation
363
- process.stderr.write(chalk.gray(` ${output.replace(/\n/g, '\n ')}`));
621
+ reportStudioBuildError(criticalError);
622
+ throw criticalError;
623
+ }
624
+ }
625
+ /**
626
+ * Build custom questions library from scaffolded course
627
+ */
628
+ async function buildCustomQuestionsLibrary(coursePath, questionsHash) {
629
+ try {
630
+ // Check if this is a scaffolded course with dual build system
631
+ const packageJsonPath = path.join(coursePath, 'package.json');
632
+ if (!fs.existsSync(packageJsonPath)) {
633
+ console.log(chalk.gray(` No package.json found, skipping custom questions build`));
634
+ return null;
635
+ }
636
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
637
+ // Check if course has the dual build system (build:lib script)
638
+ if (!packageJson.scripts || !packageJson.scripts['build:lib']) {
639
+ console.log(chalk.gray(` Course does not support custom questions library build`));
640
+ return null;
641
+ }
642
+ // Check if course has questions directory with our expected structure
643
+ const questionsIndexPath = path.join(coursePath, 'src', 'questions', 'index.ts');
644
+ if (!fs.existsSync(questionsIndexPath)) {
645
+ console.log(chalk.gray(` No src/questions/index.ts found, skipping custom questions build`));
646
+ return null;
647
+ }
648
+ console.log(chalk.cyan(` Found scaffolded course with custom questions support`));
649
+ console.log(chalk.gray(` Building questions library...`));
650
+ // Build the questions library
651
+ const { spawn } = await import('child_process');
652
+ const buildProcess = spawn('npm', ['run', 'build:lib'], {
653
+ cwd: coursePath,
654
+ stdio: 'pipe',
655
+ env: { ...process.env, BUILD_MODE: 'library' },
364
656
  });
365
- unpackProcess.on('close', (code) => {
366
- if (code === 0) {
367
- console.log(chalk.green(`✅ Course data unpacked successfully`));
368
- // Parse the output to extract database name and course ID
369
- console.log(chalk.gray(` Debug: Parsing unpack output...`));
370
- const databaseMatch = stdout.match(/Database: ([\w-_]+)/);
371
- const courseIdMatch = stdout.match(/Course: .* \(([a-f0-9]+)\)/);
372
- const fullDatabaseName = databaseMatch ? databaseMatch[1] : '';
373
- const courseId = courseIdMatch ? courseIdMatch[1] : '';
374
- // Extract the course database ID by removing 'coursedb-' prefix
375
- const databaseName = fullDatabaseName.startsWith('coursedb-')
376
- ? fullDatabaseName.substring('coursedb-'.length)
377
- : fullDatabaseName;
378
- console.log(chalk.gray(` Debug: Parsed - Full DB: "${fullDatabaseName}", Course DB ID: "${databaseName}", Course ID: "${courseId}"`));
379
- if (!databaseName || !courseId) {
380
- console.warn(chalk.yellow(`⚠️ Could not parse database name or course ID from unpack output`));
381
- console.log(chalk.gray(` Raw stdout length: ${stdout.length} chars`));
657
+ let buildOutput = '';
658
+ let buildError = '';
659
+ buildProcess.stdout?.on('data', (data) => {
660
+ buildOutput += data.toString();
661
+ });
662
+ buildProcess.stderr?.on('data', (data) => {
663
+ buildError += data.toString();
664
+ });
665
+ const buildExitCode = await new Promise((resolve) => {
666
+ buildProcess.on('close', resolve);
667
+ });
668
+ if (buildExitCode !== 0) {
669
+ const buildFailError = createStudioBuildError(StudioBuildErrorType.BUILD_FAILURE, 'Custom questions library build failed', {
670
+ context: {
671
+ coursePath,
672
+ questionsHash,
673
+ exitCode: buildExitCode,
674
+ output: buildOutput,
675
+ error: buildError,
676
+ },
677
+ recoverable: true,
678
+ fallbackAvailable: true,
679
+ });
680
+ reportStudioBuildError(buildFailError);
681
+ return null;
682
+ }
683
+ console.log(chalk.green(` ✅ Questions library built successfully`));
684
+ // Check that the library build outputs exist
685
+ const libraryPath = path.join(coursePath, 'dist-lib');
686
+ const questionsLibPath = path.join(libraryPath, 'questions.mjs');
687
+ if (!fs.existsSync(questionsLibPath)) {
688
+ console.log(chalk.yellow(` Warning: Expected library output not found at ${questionsLibPath}`));
689
+ return null;
690
+ }
691
+ // Validate that the questions library was built successfully
692
+ console.log(chalk.green(` ✅ Questions library built and available for studio-ui`));
693
+ console.log(chalk.gray(` Library path: ${questionsLibPath}`));
694
+ console.log(chalk.gray(` Package name: ${packageJson.name}`));
695
+ return {
696
+ coursePath,
697
+ questionsHash,
698
+ libraryPath,
699
+ packageName: packageJson.name,
700
+ };
701
+ }
702
+ catch (error) {
703
+ const generalError = createStudioBuildError(StudioBuildErrorType.BUILD_FAILURE, 'Custom questions library build process failed', {
704
+ cause: error instanceof Error ? error : undefined,
705
+ context: { coursePath, questionsHash },
706
+ recoverable: true,
707
+ fallbackAvailable: true,
708
+ });
709
+ reportStudioBuildError(generalError);
710
+ return null;
711
+ }
712
+ }
713
+ /**
714
+ * Build studio-ui with custom questions integrated via npm install
715
+ */
716
+ async function buildStudioUIWithCustomQuestions(buildPath, customQuestionsData) {
717
+ try {
718
+ console.log(chalk.gray(` Setting up studio-ui with custom questions...`));
719
+ // Step 1: Copy studio-ui source files
720
+ const studioSourcePath = path.join(__dirname, '..', 'studio-ui-src');
721
+ const { copyDirectory } = await import('../utils/template.js');
722
+ await copyDirectory(studioSourcePath, buildPath);
723
+ // Step 2: Transform workspace dependencies to published versions
724
+ console.log(chalk.gray(` Transforming workspace dependencies...`));
725
+ const studioPackageJsonPath = path.join(buildPath, 'package.json');
726
+ await transformPackageJsonForStudioBuild(studioPackageJsonPath);
727
+ // Step 2.5: Fix Vite config to use npm packages instead of monorepo paths
728
+ console.log(chalk.gray(` Updating Vite configuration for standalone build...`));
729
+ await fixViteConfigForStandaloneBuild(buildPath);
730
+ // Step 3: Install custom questions package
731
+ console.log(chalk.cyan(` Installing bundled course package: ${customQuestionsData.packageName} from ${customQuestionsData.coursePath}`));
732
+ const distLibPath = path.join(customQuestionsData.coursePath, 'dist-lib');
733
+ if (!fs.existsSync(distLibPath)) {
734
+ throw new Error(`dist-lib directory not found at: ${distLibPath}. Run 'npm run build:lib' first.`);
735
+ }
736
+ // Ensure node_modules directory exists in studio-ui
737
+ const nodeModulesPath = path.join(buildPath, 'node_modules');
738
+ const packageInstallPath = path.join(nodeModulesPath, customQuestionsData.packageName);
739
+ if (!fs.existsSync(nodeModulesPath)) {
740
+ fs.mkdirSync(nodeModulesPath, { recursive: true });
741
+ }
742
+ if (!fs.existsSync(packageInstallPath)) {
743
+ fs.mkdirSync(packageInstallPath, { recursive: true });
744
+ }
745
+ // Copy dist-lib contents to node_modules/{packageName}
746
+ console.log(chalk.gray(` Copying dist-lib to node_modules/${customQuestionsData.packageName}...`));
747
+ await copyDirectory(distLibPath, packageInstallPath);
748
+ // Copy package.json for proper npm module structure
749
+ const originalPackageJsonPath = path.join(customQuestionsData.coursePath, 'package.json');
750
+ const targetPackageJsonPath = path.join(packageInstallPath, 'package.json');
751
+ if (fs.existsSync(originalPackageJsonPath)) {
752
+ fs.copyFileSync(originalPackageJsonPath, targetPackageJsonPath);
753
+ }
754
+ console.log(chalk.green(` ✅ Bundled package installed successfully`));
755
+ // Step 4: Create runtime configuration for custom questions
756
+ const runtimeConfigPath = path.join(buildPath, 'custom-questions-config.json');
757
+ const runtimeConfig = {
758
+ hasCustomQuestions: true,
759
+ questionsHash: customQuestionsData.questionsHash,
760
+ packageName: customQuestionsData.packageName,
761
+ importPath: './questions.mjs',
762
+ };
763
+ fs.writeFileSync(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
764
+ console.log(chalk.gray(` ✅ Custom questions configuration written for package: ${customQuestionsData.packageName}`));
765
+ // Step 5: Run Vite build process
766
+ console.log(chalk.gray(` Running Vite build process...`));
767
+ await runViteBuild(buildPath);
768
+ // Step 6: Copy config file and questions module to built dist directory
769
+ const distPath = path.join(buildPath, 'dist');
770
+ const sourceConfigPath = path.join(buildPath, 'custom-questions-config.json');
771
+ const distConfigPath = path.join(distPath, 'custom-questions-config.json');
772
+ if (fs.existsSync(sourceConfigPath)) {
773
+ fs.copyFileSync(sourceConfigPath, distConfigPath);
774
+ console.log(chalk.gray(` Custom questions config copied to dist directory`));
775
+ }
776
+ // Copy the built questions.mjs file to dist/assets for proper serving
777
+ const nodeModulesQuestionsPath = path.join(buildPath, 'node_modules', customQuestionsData.packageName, 'questions.mjs');
778
+ const distAssetsPath = path.join(distPath, 'assets');
779
+ const distQuestionsPath = path.join(distAssetsPath, 'questions.mjs');
780
+ if (fs.existsSync(nodeModulesQuestionsPath)) {
781
+ // Ensure assets directory exists
782
+ if (!fs.existsSync(distAssetsPath)) {
783
+ fs.mkdirSync(distAssetsPath, { recursive: true });
784
+ }
785
+ fs.copyFileSync(nodeModulesQuestionsPath, distQuestionsPath);
786
+ console.log(chalk.gray(` Built questions.mjs copied to dist/assets directory`));
787
+ }
788
+ else {
789
+ console.log(chalk.yellow(` Warning: questions.mjs not found at ${nodeModulesQuestionsPath}`));
790
+ }
791
+ // Step 7: Verify build output exists
792
+ const indexPath = path.join(distPath, 'index.html');
793
+ if (!fs.existsSync(indexPath)) {
794
+ throw new Error(`Build output missing: ${indexPath}`);
795
+ }
796
+ console.log(chalk.gray(` Studio-ui with custom questions built successfully`));
797
+ return buildPath;
798
+ }
799
+ catch (error) {
800
+ const integrationError = createStudioBuildError(StudioBuildErrorType.BUILD_FAILURE, 'Failed to integrate custom questions into studio-ui via npm install', {
801
+ cause: error instanceof Error ? error : undefined,
802
+ context: { buildPath, customQuestionsData },
803
+ recoverable: true,
804
+ fallbackAvailable: true,
805
+ });
806
+ reportStudioBuildError(integrationError);
807
+ // Fallback to default studio-ui
808
+ console.log(chalk.red(` Exiting`));
809
+ process.exit(1);
810
+ return await buildDefaultStudioUI(buildPath);
811
+ }
812
+ }
813
+ /**
814
+ * Transform package.json to replace workspace and file dependencies with published versions
815
+ */
816
+ async function transformPackageJsonForStudioBuild(packageJsonPath) {
817
+ const content = fs.readFileSync(packageJsonPath, 'utf-8');
818
+ const packageJson = JSON.parse(content);
819
+ // Version mappings for vue-skuilder packages
820
+ const vueSkuilderPackageVersions = {
821
+ '@vue-skuilder/common': VERSION,
822
+ '@vue-skuilder/common-ui': VERSION,
823
+ '@vue-skuilder/courses': VERSION,
824
+ '@vue-skuilder/db': VERSION,
825
+ '@vue-skuilder/edit-ui': VERSION,
826
+ '@vue-skuilder/express': VERSION,
827
+ '@vue-skuilder/cli': VERSION,
828
+ };
829
+ // Transform dependencies
830
+ if (packageJson.dependencies) {
831
+ for (const [depName, version] of Object.entries(packageJson.dependencies)) {
832
+ if (typeof version === 'string' &&
833
+ (version.startsWith('workspace:') || version.startsWith('file:'))) {
834
+ const publishedVersion = vueSkuilderPackageVersions[depName];
835
+ if (publishedVersion) {
836
+ packageJson.dependencies[depName] = `^${publishedVersion}`;
837
+ console.log(chalk.gray(` Transformed ${depName}: ${version} → ^${publishedVersion}`));
838
+ }
839
+ }
840
+ }
841
+ }
842
+ // Transform devDependencies
843
+ if (packageJson.devDependencies) {
844
+ for (const [depName, version] of Object.entries(packageJson.devDependencies)) {
845
+ if (typeof version === 'string' &&
846
+ (version.startsWith('workspace:') || version.startsWith('file:'))) {
847
+ const publishedVersion = vueSkuilderPackageVersions[depName];
848
+ if (publishedVersion) {
849
+ packageJson.devDependencies[depName] = `^${publishedVersion}`;
850
+ console.log(chalk.gray(` Transformed ${depName}: ${version} → ^${publishedVersion}`));
382
851
  }
383
- resolve({ databaseName, courseId });
852
+ }
853
+ }
854
+ }
855
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
856
+ }
857
+ /**
858
+ * Run Vite build process in the specified directory
859
+ */
860
+ async function runViteBuild(buildPath) {
861
+ const { spawn } = await import('child_process');
862
+ return new Promise((resolve, reject) => {
863
+ const buildProcess = spawn('npm', ['run', 'build'], {
864
+ cwd: buildPath,
865
+ stdio: 'pipe',
866
+ });
867
+ let buildOutput = '';
868
+ let buildError = '';
869
+ buildProcess.stdout?.on('data', (data) => {
870
+ buildOutput += data.toString();
871
+ });
872
+ buildProcess.stderr?.on('data', (data) => {
873
+ buildError += data.toString();
874
+ });
875
+ buildProcess.on('close', (code) => {
876
+ if (code === 0) {
877
+ console.log(chalk.gray(` Vite build completed successfully`));
878
+ resolve();
384
879
  }
385
880
  else {
386
- console.error(chalk.red(`❌ Failed to unpack course data (exit code: ${code})`));
387
- reject(new Error(`Unpack failed with code ${code}\nstdout: ${stdout}\nstderr: ${stderr}`));
881
+ console.log(chalk.yellow(` Vite build failed with exit code ${code}`));
882
+ console.log(chalk.yellow(` Build stdout: ${buildOutput}`));
883
+ console.log(chalk.yellow(` Build stderr: ${buildError}`));
884
+ reject(new Error(`Vite build failed with exit code ${code}: ${buildError}`));
388
885
  }
389
886
  });
390
- unpackProcess.on('error', (error) => {
391
- console.error(chalk.red(`❌ Failed to start unpack process: ${error.message}`));
392
- reject(error);
887
+ buildProcess.on('error', (error) => {
888
+ reject(new Error(`Failed to start Vite build process: ${error.message}`));
393
889
  });
394
890
  });
395
891
  }
892
+ /**
893
+ * Fix Vite configuration to work in standalone build environment
894
+ */
895
+ async function fixViteConfigForStandaloneBuild(buildPath) {
896
+ const viteConfigPath = path.join(buildPath, 'vite.config.ts');
897
+ if (!fs.existsSync(viteConfigPath)) {
898
+ console.log(chalk.yellow(` Warning: vite.config.ts not found at ${viteConfigPath}`));
899
+ return;
900
+ }
901
+ // Create a simplified vite config that uses standard npm resolution
902
+ // For custom questions builds, we need Vue bundled in the questions.mjs
903
+ const standaloneViteConfig = `import { defineConfig } from 'vite';
904
+ import vue from '@vitejs/plugin-vue';
905
+
906
+ export default defineConfig({
907
+ plugins: [vue()],
908
+ server: {
909
+ port: 7173,
910
+ host: '0.0.0.0'
911
+ },
912
+ build: {
913
+ target: 'es2020',
914
+ outDir: 'dist',
915
+ sourcemap: true,
916
+ rollupOptions: {
917
+ // Don't externalize Vue for custom questions - bundle it in
918
+ external: [],
919
+ output: {
920
+ manualChunks: {
921
+ vue: ['vue', 'vue-router', 'pinia'],
922
+ vuetify: ['vuetify']
923
+ }
924
+ }
925
+ }
926
+ }
927
+ });`;
928
+ fs.writeFileSync(viteConfigPath, standaloneViteConfig);
929
+ console.log(chalk.gray(` Vite config replaced with standalone version`));
930
+ }
396
931
  //# sourceMappingURL=studio.js.map