@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
@@ -31,7 +31,7 @@ interface UnpackOptions {
31
31
  cleanupOnError: boolean;
32
32
  }
33
33
 
34
- async function unpackCourse(coursePath: string, options: UnpackOptions) {
34
+ export async function unpackCourse(coursePath: string, options: UnpackOptions) {
35
35
  // Store original ENV values for cleanup
36
36
  const originalEnv = {
37
37
  COUCHDB_SERVER_PROTOCOL: ENV.COUCHDB_SERVER_PROTOCOL,
package/src/types.ts CHANGED
@@ -4,6 +4,11 @@ export interface CliOptions {
4
4
  interactive: boolean;
5
5
  couchdbUrl?: string;
6
6
  courseId?: string;
7
+ importCourseData?: boolean;
8
+ importServerUrl?: string;
9
+ importUsername?: string;
10
+ importPassword?: string;
11
+ importCourseIds?: string;
7
12
  }
8
13
 
9
14
  export interface ProjectConfig {
@@ -0,0 +1,210 @@
1
+ import { spawn, ChildProcess } from 'child_process';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, join } from 'path';
4
+ import fs from 'fs';
5
+ import { createRequire } from 'module';
6
+
7
+ const require = createRequire(import.meta.url);
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
12
+ export interface ExpressManagerOptions {
13
+ port: number;
14
+ couchdbUrl: string;
15
+ couchdbUsername: string;
16
+ couchdbPassword: string;
17
+ projectPath?: string; // Path to the project for studio mode
18
+ }
19
+
20
+ export interface ExpressManagerCallbacks {
21
+ onLog?: (message: string) => void;
22
+ onError?: (error: string) => void;
23
+ }
24
+
25
+ export class ExpressManager {
26
+ private process: ChildProcess | null = null;
27
+ private readonly options: ExpressManagerOptions;
28
+ private readonly callbacks: ExpressManagerCallbacks;
29
+
30
+ constructor(options: ExpressManagerOptions, callbacks: ExpressManagerCallbacks = {}) {
31
+ this.options = options;
32
+ this.callbacks = callbacks;
33
+ }
34
+
35
+ async start(): Promise<void> {
36
+ if (this.process) {
37
+ throw new Error('Express server is already running');
38
+ }
39
+
40
+ const expressAssetsPath = join(__dirname, '..', 'express-assets');
41
+
42
+ if (!fs.existsSync(expressAssetsPath)) {
43
+ throw new Error('Express assets not found. Please rebuild the CLI package.');
44
+ }
45
+
46
+ const expressMainPath = join(expressAssetsPath, 'app.js');
47
+
48
+ if (!fs.existsSync(expressMainPath)) {
49
+ throw new Error('Express main file not found at: ' + expressMainPath);
50
+ }
51
+
52
+ // Find available port starting from requested port
53
+ const availablePort = await this.findAvailablePort(this.options.port);
54
+
55
+ // Get version from package.json
56
+ let version = '0.0.0';
57
+ try {
58
+ const packageJsonPath = join(__dirname, '..', '..', 'package.json');
59
+ const packageJson = require(packageJsonPath);
60
+ version = packageJson.version || '0.0.0';
61
+ } catch (error) {
62
+ console.warn('Could not read package.json for version:', error);
63
+ // Fallback version if package.json not found
64
+ version = '0.0.0';
65
+ }
66
+
67
+ // Set environment variables for express
68
+ const env = {
69
+ ...process.env,
70
+ PORT: availablePort.toString(),
71
+ COUCHDB_SERVER: this.extractServerFromUrl(this.options.couchdbUrl),
72
+ COUCHDB_PROTOCOL: this.extractProtocolFromUrl(this.options.couchdbUrl),
73
+ COUCHDB_ADMIN: this.options.couchdbUsername,
74
+ COUCHDB_PASSWORD: this.options.couchdbPassword,
75
+ VERSION: version,
76
+ NODE_ENV: 'studio',
77
+ PROJECT_PATH: this.options.projectPath || process.cwd(),
78
+ };
79
+
80
+ return new Promise((resolve, reject) => {
81
+ this.process = spawn('node', [expressMainPath], {
82
+ env,
83
+ stdio: ['pipe', 'pipe', 'pipe'],
84
+ cwd: expressAssetsPath,
85
+ });
86
+
87
+ if (!this.process) {
88
+ reject(new Error('Failed to start Express process'));
89
+ return;
90
+ }
91
+
92
+ let started = false;
93
+
94
+ // Handle stdout
95
+ this.process.stdout?.on('data', (data: Buffer) => {
96
+ const message = data.toString().trim();
97
+ if (message.includes('listening on port') && !started) {
98
+ started = true;
99
+ this.options.port = availablePort; // Update with actual port
100
+ resolve();
101
+ }
102
+ this.callbacks.onLog?.(message);
103
+ });
104
+
105
+ // Handle stderr
106
+ this.process.stderr?.on('data', (data: Buffer) => {
107
+ const error = data.toString().trim();
108
+ this.callbacks.onError?.(error);
109
+ });
110
+
111
+ // Handle process exit
112
+ this.process.on('exit', (code) => {
113
+ this.process = null;
114
+ if (code !== 0 && code !== null) {
115
+ this.callbacks.onError?.(`Express process exited with code ${code}`);
116
+ }
117
+ });
118
+
119
+ // Handle process errors
120
+ this.process.on('error', (error) => {
121
+ this.process = null;
122
+ if (!started) {
123
+ reject(error);
124
+ } else {
125
+ this.callbacks.onError?.(`Express process error: ${error.message}`);
126
+ }
127
+ });
128
+
129
+ // Timeout if server doesn't start within 10 seconds
130
+ setTimeout(() => {
131
+ if (!started) {
132
+ void this.stop();
133
+ reject(new Error('Express server failed to start within timeout'));
134
+ }
135
+ }, 10000);
136
+ });
137
+ }
138
+
139
+ async stop(): Promise<void> {
140
+ if (!this.process) {
141
+ return;
142
+ }
143
+
144
+ return new Promise((resolve) => {
145
+ if (!this.process) {
146
+ resolve();
147
+ return;
148
+ }
149
+
150
+ this.process.on('exit', () => {
151
+ this.process = null;
152
+ resolve();
153
+ });
154
+
155
+ // Try graceful shutdown first
156
+ this.process.kill('SIGTERM');
157
+
158
+ // Force kill after 5 seconds if still running
159
+ setTimeout(() => {
160
+ if (this.process) {
161
+ this.process.kill('SIGKILL');
162
+ this.process = null;
163
+ resolve();
164
+ }
165
+ }, 5000);
166
+ });
167
+ }
168
+
169
+ getConnectionDetails() {
170
+ return {
171
+ url: `http://localhost:${this.options.port}`,
172
+ port: this.options.port,
173
+ };
174
+ }
175
+
176
+ private async findAvailablePort(startPort: number): Promise<number> {
177
+ const net = await import('net');
178
+
179
+ for (let port = startPort; port < startPort + 100; port++) {
180
+ if (await this.isPortAvailable(port, net)) {
181
+ return port;
182
+ }
183
+ }
184
+
185
+ throw new Error(`No available port found starting from ${startPort}`);
186
+ }
187
+
188
+ private isPortAvailable(port: number, net: typeof import('net')): Promise<boolean> {
189
+ return new Promise((resolve) => {
190
+ const server = net.createServer();
191
+
192
+ server.listen(port, '127.0.0.1', () => {
193
+ server.close(() => resolve(true));
194
+ });
195
+
196
+ server.on('error', () => resolve(false));
197
+ });
198
+ }
199
+
200
+ private extractServerFromUrl(url: string): string {
201
+ // Extract hostname:port from URL like "http://localhost:5984"
202
+ const match = url.match(/https?:\/\/([^/]+)/);
203
+ return match ? match[1] : 'localhost:5984';
204
+ }
205
+
206
+ private extractProtocolFromUrl(url: string): string {
207
+ // Extract protocol from URL like "http://localhost:5984"
208
+ return url.startsWith('https') ? 'https' : 'http';
209
+ }
210
+ }
@@ -1,6 +1,7 @@
1
1
  // packages/cli/src/utils/NodeFileSystemAdapter.ts
2
2
 
3
3
  import fs from 'fs';
4
+ import fse from 'fs-extra';
4
5
  import path from 'path';
5
6
  import { FileSystemAdapter, FileStats, FileSystemError } from '@vue-skuilder/db';
6
7
 
@@ -50,7 +51,7 @@ export class NodeFileSystemAdapter implements FileSystemAdapter {
50
51
  return {
51
52
  isDirectory: () => stats.isDirectory(),
52
53
  isFile: () => stats.isFile(),
53
- size: stats.size
54
+ size: stats.size,
54
55
  };
55
56
  } catch (error) {
56
57
  throw new FileSystemError(
@@ -62,11 +63,54 @@ export class NodeFileSystemAdapter implements FileSystemAdapter {
62
63
  }
63
64
  }
64
65
 
66
+ async writeFile(filePath: string, data: string | Buffer): Promise<void> {
67
+ try {
68
+ await fse.writeFile(filePath, data);
69
+ } catch (error) {
70
+ throw new FileSystemError(
71
+ `Failed to write file: ${error instanceof Error ? error.message : String(error)}`,
72
+ 'writeFile',
73
+ filePath,
74
+ error instanceof Error ? error : undefined
75
+ );
76
+ }
77
+ }
78
+
79
+ async writeJson(filePath: string, data: unknown, options?: { spaces?: number }): Promise<void> {
80
+ try {
81
+ await fse.writeJson(filePath, data, options);
82
+ } catch (error) {
83
+ throw new FileSystemError(
84
+ `Failed to write JSON file: ${error instanceof Error ? error.message : String(error)}`,
85
+ 'writeJson',
86
+ filePath,
87
+ error instanceof Error ? error : undefined
88
+ );
89
+ }
90
+ }
91
+
92
+ async ensureDir(dirPath: string): Promise<void> {
93
+ try {
94
+ await fse.ensureDir(dirPath);
95
+ } catch (error) {
96
+ throw new FileSystemError(
97
+ `Failed to ensure directory: ${error instanceof Error ? error.message : String(error)}`,
98
+ 'ensureDir',
99
+ dirPath,
100
+ error instanceof Error ? error : undefined
101
+ );
102
+ }
103
+ }
104
+
65
105
  joinPath(...segments: string[]): string {
66
106
  return path.join(...segments);
67
107
  }
68
108
 
109
+ dirname(filePath: string): string {
110
+ return path.dirname(filePath);
111
+ }
112
+
69
113
  isAbsolute(filePath: string): boolean {
70
114
  return path.isAbsolute(filePath);
71
115
  }
72
- }
116
+ }
@@ -0,0 +1,192 @@
1
+ import chalk from 'chalk';
2
+
3
+ /**
4
+ * Error categories for studio-ui build failures
5
+ */
6
+ export enum StudioBuildErrorType {
7
+ QUESTIONS_HASH_ERROR = 'questions-hash-error',
8
+ BUILD_FAILURE = 'build-failure',
9
+ COPY_FAILURE = 'copy-failure',
10
+ MISSING_SOURCE = 'missing-source',
11
+ MISSING_DEPENDENCIES = 'missing-dependencies',
12
+ VITE_BUILD_ERROR = 'vite-build-error',
13
+ TYPESCRIPT_ERROR = 'typescript-error',
14
+ CRITICAL_ERROR = 'critical-error'
15
+ }
16
+
17
+ /**
18
+ * Structured error information for studio-ui build failures
19
+ */
20
+ export interface StudioBuildError {
21
+ type: StudioBuildErrorType;
22
+ message: string;
23
+ cause?: Error;
24
+ context?: Record<string, unknown>;
25
+ recoverable: boolean;
26
+ fallbackAvailable: boolean;
27
+ }
28
+
29
+ /**
30
+ * Create a structured studio build error
31
+ */
32
+ export function createStudioBuildError(
33
+ type: StudioBuildErrorType,
34
+ message: string,
35
+ options: {
36
+ cause?: Error;
37
+ context?: Record<string, unknown>;
38
+ recoverable?: boolean;
39
+ fallbackAvailable?: boolean;
40
+ } = {}
41
+ ): StudioBuildError {
42
+ return {
43
+ type,
44
+ message,
45
+ cause: options.cause,
46
+ context: options.context,
47
+ recoverable: options.recoverable ?? true,
48
+ fallbackAvailable: options.fallbackAvailable ?? true
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Report a studio build error with appropriate formatting and guidance
54
+ */
55
+ export function reportStudioBuildError(error: StudioBuildError): void {
56
+ // Main error message
57
+ console.error(chalk.red(`āŒ Studio-UI Build Error: ${error.message}`));
58
+
59
+ // Error type context
60
+ console.error(chalk.gray(` Type: ${error.type}`));
61
+
62
+ // Underlying cause if available
63
+ if (error.cause) {
64
+ console.error(chalk.gray(` Cause: ${error.cause.message}`));
65
+ }
66
+
67
+ // Context information
68
+ if (error.context) {
69
+ for (const [key, value] of Object.entries(error.context)) {
70
+ console.error(chalk.gray(` ${key}: ${String(value)}`));
71
+ }
72
+ }
73
+
74
+ // Recovery guidance
75
+ if (error.recoverable) {
76
+ if (error.fallbackAvailable) {
77
+ console.log(chalk.yellow(` Fallback: Using default studio-ui (without local questions)`));
78
+ } else {
79
+ console.log(chalk.yellow(` Recovery: Manual intervention required`));
80
+ }
81
+ } else {
82
+ console.error(chalk.red(` Critical: Studio session cannot continue`));
83
+ }
84
+
85
+ // Type-specific guidance
86
+ provideBuildErrorGuidance(error.type);
87
+ }
88
+
89
+ /**
90
+ * Provide specific guidance based on error type
91
+ */
92
+ function provideBuildErrorGuidance(errorType: StudioBuildErrorType): void {
93
+ switch (errorType) {
94
+ case StudioBuildErrorType.QUESTIONS_HASH_ERROR:
95
+ console.log(chalk.cyan(` šŸ’” Check that src/questions/ directory is accessible`));
96
+ console.log(chalk.cyan(` šŸ’” Verify file permissions and disk space`));
97
+ break;
98
+
99
+ case StudioBuildErrorType.BUILD_FAILURE:
100
+ console.log(chalk.cyan(` šŸ’” Check that Vite build dependencies are installed`));
101
+ console.log(chalk.cyan(` šŸ’” Verify TypeScript configuration is valid`));
102
+ break;
103
+
104
+ case StudioBuildErrorType.COPY_FAILURE:
105
+ console.log(chalk.cyan(` šŸ’” Check write permissions for .skuilder/studio-builds/`));
106
+ console.log(chalk.cyan(` šŸ’” Verify sufficient disk space available`));
107
+ break;
108
+
109
+ case StudioBuildErrorType.MISSING_SOURCE:
110
+ console.log(chalk.cyan(` šŸ’” Rebuild CLI package: yarn workspace @vue-skuilder/cli build`));
111
+ console.log(chalk.cyan(` šŸ’” Verify studio-ui source was embedded during build`));
112
+ break;
113
+
114
+ case StudioBuildErrorType.MISSING_DEPENDENCIES:
115
+ console.log(chalk.cyan(` šŸ’” Install dependencies: yarn install`));
116
+ console.log(chalk.cyan(` šŸ’” Check that required packages are in package.json`));
117
+ break;
118
+
119
+ case StudioBuildErrorType.VITE_BUILD_ERROR:
120
+ console.log(chalk.cyan(` šŸ’” Check question type implementations for syntax errors`));
121
+ console.log(chalk.cyan(` šŸ’” Verify all imports and dependencies are available`));
122
+ break;
123
+
124
+ case StudioBuildErrorType.TYPESCRIPT_ERROR:
125
+ console.log(chalk.cyan(` šŸ’” Check TypeScript configuration in tsconfig.json`));
126
+ console.log(chalk.cyan(` šŸ’” Verify all question types have proper type definitions`));
127
+ break;
128
+
129
+ case StudioBuildErrorType.CRITICAL_ERROR:
130
+ console.log(chalk.cyan(` šŸ’” Report this issue to the vue-skuilder team`));
131
+ console.log(chalk.cyan(` šŸ’” Include the full error output in your report`));
132
+ break;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Wrap a studio build operation with error handling
138
+ */
139
+ export async function withStudioBuildErrorHandling<T>(
140
+ operation: () => Promise<T>,
141
+ errorType: StudioBuildErrorType,
142
+ context?: Record<string, unknown>
143
+ ): Promise<T> {
144
+ try {
145
+ return await operation();
146
+ } catch (error) {
147
+ const buildError = createStudioBuildError(
148
+ errorType,
149
+ error instanceof Error ? error.message : String(error),
150
+ {
151
+ cause: error instanceof Error ? error : undefined,
152
+ context,
153
+ recoverable: true,
154
+ fallbackAvailable: true
155
+ }
156
+ );
157
+
158
+ reportStudioBuildError(buildError);
159
+ throw buildError;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Check if error is a recoverable studio build error
165
+ */
166
+ export function isRecoverableStudioBuildError(error: unknown): boolean {
167
+ return error instanceof Error &&
168
+ 'type' in error &&
169
+ 'recoverable' in error &&
170
+ Boolean(error.recoverable);
171
+ }
172
+
173
+ /**
174
+ * Extract error details for logging
175
+ */
176
+ export function extractErrorDetails(error: unknown): {
177
+ message: string;
178
+ stack?: string;
179
+ code?: string;
180
+ } {
181
+ if (error instanceof Error) {
182
+ return {
183
+ message: error.message,
184
+ stack: error.stack,
185
+ code: 'code' in error ? String(error.code) : undefined
186
+ };
187
+ }
188
+
189
+ return {
190
+ message: String(error)
191
+ };
192
+ }
@@ -1,10 +1,6 @@
1
- import { execFile } from 'child_process';
2
- import { promisify } from 'util';
3
1
  import chalk from 'chalk';
4
2
  import path from 'path';
5
3
 
6
- const execFileAsync = promisify(execFile);
7
-
8
4
  export interface PackCoursesOptions {
9
5
  server: string;
10
6
  username?: string;
@@ -25,44 +21,23 @@ export async function packCourses(options: PackCoursesOptions): Promise<void> {
25
21
 
26
22
  console.log(chalk.cyan(`\nšŸ“¦ Packing ${courseIds.length} course(s) to ${outputDir}...`));
27
23
 
28
- for (const courseId of courseIds) {
29
- const args = ['pack', courseId, '--server', server, '--output', outputDir];
30
-
31
- if (username) {
32
- args.push('--username', username);
33
- }
34
-
35
- if (password) {
36
- args.push('--password', password);
37
- }
38
-
39
- const cliPath = path.join(process.cwd(), 'dist', 'cli.js');
40
- const commandArgs = ['pack', courseId, '--server', server, '--output', outputDir];
41
-
42
- if (username) {
43
- commandArgs.push('--username', username);
44
- }
45
-
46
- if (password) {
47
- commandArgs.push('--password', password);
48
- }
24
+ // Import the pack command function
25
+ const { packCourse } = await import('../commands/pack.js');
49
26
 
27
+ for (const courseId of courseIds) {
50
28
  try {
51
29
  console.log(chalk.gray(`šŸ”„ Packing course: ${courseId}`));
52
30
 
53
- const { stdout, stderr } = await execFileAsync('node', [cliPath, ...commandArgs], {
54
- cwd: process.cwd(),
55
- maxBuffer: 1024 * 1024 * 10, // 10MB buffer for large outputs
31
+ // Call the existing pack command directly instead of subprocess
32
+ await packCourse(courseId, {
33
+ server,
34
+ username,
35
+ password,
36
+ output: outputDir,
37
+ chunkSize: '1000',
38
+ noAttachments: false
56
39
  });
57
40
 
58
- if (stdout) {
59
- console.log(stdout);
60
- }
61
-
62
- if (stderr) {
63
- console.error(chalk.yellow(stderr));
64
- }
65
-
66
41
  console.log(chalk.green(`āœ… Successfully packed course: ${courseId}`));
67
42
  } catch (error: unknown) {
68
43
  console.error(chalk.red(`āŒ Failed to pack course ${courseId}:`));
@@ -389,14 +389,48 @@ export async function gatherProjectConfig(
389
389
  couchdbUrl: options.couchdbUrl,
390
390
  course: options.courseId,
391
391
  theme: PREDEFINED_THEMES[options.theme],
392
+ // Handle import options for static data layer
393
+ importCourseData: options.importCourseData,
394
+ importServerUrl: options.importServerUrl,
395
+ importUsername: options.importUsername,
396
+ importPassword: options.importPassword,
392
397
  };
393
398
 
399
+ // Parse comma-separated course IDs if provided
400
+ if (options.importCourseIds) {
401
+ config.importCourseIds = options.importCourseIds
402
+ .split(',')
403
+ .map((id: string) => id.trim())
404
+ .filter((id: string) => id.length > 0);
405
+ }
406
+
394
407
  // Validate required fields for non-interactive mode
395
408
  if (config.dataLayerType === 'couch' && !config.couchdbUrl) {
396
409
  throw new Error(
397
410
  'CouchDB URL is required when using dynamic data layer. Use --couchdb-url option.'
398
411
  );
399
412
  }
413
+
414
+ // Validate required fields for static data layer course import
415
+ if (config.importCourseData) {
416
+ if (config.dataLayerType !== 'static') {
417
+ throw new Error(
418
+ 'Course import is only available for static data layer. Use --data-layer static.'
419
+ );
420
+ }
421
+
422
+ if (!config.importServerUrl) {
423
+ throw new Error(
424
+ 'Import server URL is required when importing course data. Use --import-server-url option.'
425
+ );
426
+ }
427
+
428
+ if (!config.importCourseIds || config.importCourseIds.length === 0) {
429
+ throw new Error(
430
+ 'Course IDs are required when importing course data. Use --import-course-ids option.'
431
+ );
432
+ }
433
+ }
400
434
  }
401
435
 
402
436
  return config as ProjectConfig;