@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.
- package/README.md +122 -8
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +34 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pack.d.ts +10 -0
- package/dist/commands/pack.d.ts.map +1 -1
- package/dist/commands/pack.js +1 -1
- package/dist/commands/pack.js.map +1 -1
- package/dist/commands/studio.d.ts.map +1 -1
- package/dist/commands/studio.js +640 -105
- package/dist/commands/studio.js.map +1 -1
- package/dist/commands/unpack.d.ts +12 -0
- package/dist/commands/unpack.d.ts.map +1 -1
- package/dist/commands/unpack.js +1 -1
- package/dist/commands/unpack.js.map +1 -1
- package/dist/express-assets/app.d.ts +6 -0
- package/dist/express-assets/app.d.ts.map +1 -0
- package/dist/express-assets/app.js +209 -0
- package/dist/express-assets/app.js.map +1 -0
- package/dist/express-assets/assets/classroomDesignDoc.js +24 -0
- package/dist/express-assets/assets/courseValidateDocUpdate.js +56 -0
- package/dist/express-assets/assets/get-tagsDesignDoc.json +9 -0
- package/dist/express-assets/attachment-preprocessing/index.d.ts +11 -0
- package/dist/express-assets/attachment-preprocessing/index.d.ts.map +1 -0
- package/dist/express-assets/attachment-preprocessing/index.js +204 -0
- package/dist/express-assets/attachment-preprocessing/index.js.map +1 -0
- package/dist/express-assets/attachment-preprocessing/normalize.d.ts +7 -0
- package/dist/express-assets/attachment-preprocessing/normalize.d.ts.map +1 -0
- package/dist/express-assets/attachment-preprocessing/normalize.js +90 -0
- package/dist/express-assets/attachment-preprocessing/normalize.js.map +1 -0
- package/dist/express-assets/client-requests/classroom-requests.d.ts +26 -0
- package/dist/express-assets/client-requests/classroom-requests.d.ts.map +1 -0
- package/dist/express-assets/client-requests/classroom-requests.js +171 -0
- package/dist/express-assets/client-requests/classroom-requests.js.map +1 -0
- package/dist/express-assets/client-requests/course-requests.d.ts +10 -0
- package/dist/express-assets/client-requests/course-requests.d.ts.map +1 -0
- package/dist/express-assets/client-requests/course-requests.js +135 -0
- package/dist/express-assets/client-requests/course-requests.js.map +1 -0
- package/dist/express-assets/client-requests/pack-requests.d.ts +19 -0
- package/dist/express-assets/client-requests/pack-requests.d.ts.map +1 -0
- package/dist/express-assets/client-requests/pack-requests.js +130 -0
- package/dist/express-assets/client-requests/pack-requests.js.map +1 -0
- package/dist/express-assets/client.d.ts +31 -0
- package/dist/express-assets/client.d.ts.map +1 -0
- package/dist/express-assets/client.js +70 -0
- package/dist/express-assets/client.js.map +1 -0
- package/dist/express-assets/couchdb/authentication.d.ts +4 -0
- package/dist/express-assets/couchdb/authentication.d.ts.map +1 -0
- package/dist/express-assets/couchdb/authentication.js +69 -0
- package/dist/express-assets/couchdb/authentication.js.map +1 -0
- package/dist/express-assets/couchdb/index.d.ts +18 -0
- package/dist/express-assets/couchdb/index.d.ts.map +1 -0
- package/dist/express-assets/couchdb/index.js +52 -0
- package/dist/express-assets/couchdb/index.js.map +1 -0
- package/dist/express-assets/design-docs.d.ts +63 -0
- package/dist/express-assets/design-docs.d.ts.map +1 -0
- package/dist/express-assets/design-docs.js +90 -0
- package/dist/express-assets/design-docs.js.map +1 -0
- package/dist/express-assets/logger.d.ts +3 -0
- package/dist/express-assets/logger.d.ts.map +1 -0
- package/dist/express-assets/logger.js +62 -0
- package/dist/express-assets/logger.js.map +1 -0
- package/dist/express-assets/routes/logs.d.ts +3 -0
- package/dist/express-assets/routes/logs.d.ts.map +1 -0
- package/dist/express-assets/routes/logs.js +274 -0
- package/dist/express-assets/routes/logs.js.map +1 -0
- package/dist/express-assets/utils/env.d.ts +11 -0
- package/dist/express-assets/utils/env.d.ts.map +1 -0
- package/dist/express-assets/utils/env.js +39 -0
- package/dist/express-assets/utils/env.js.map +1 -0
- package/dist/express-assets/utils/processQueue.d.ts +39 -0
- package/dist/express-assets/utils/processQueue.d.ts.map +1 -0
- package/dist/express-assets/utils/processQueue.js +175 -0
- package/dist/express-assets/utils/processQueue.js.map +1 -0
- package/dist/studio-ui-src/App.vue +132 -0
- package/dist/studio-ui-src/api/index.ts +30 -0
- package/dist/studio-ui-src/components/StudioFlush.vue +108 -0
- package/dist/studio-ui-src/config/development.ts +98 -0
- package/dist/studio-ui-src/index.html +13 -0
- package/dist/studio-ui-src/main.ts +148 -0
- package/dist/studio-ui-src/package.json +35 -0
- package/dist/studio-ui-src/router/index.ts +32 -0
- package/dist/studio-ui-src/stores/useAuthStore.ts +3 -0
- package/dist/studio-ui-src/tsconfig.json +28 -0
- package/dist/studio-ui-src/views/BrowseView.vue +82 -0
- package/dist/studio-ui-src/views/BulkImportView.vue +89 -0
- package/dist/studio-ui-src/views/CourseEditorView.vue +62 -0
- package/dist/studio-ui-src/views/CreateCardView.vue +140 -0
- package/dist/studio-ui-src/vite.config.base.js +100 -0
- package/dist/studio-ui-src/vite.config.ts +26 -0
- package/dist/templates/.skuilder/README.md +29 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/ExpressManager.d.ts +28 -0
- package/dist/utils/ExpressManager.d.ts.map +1 -0
- package/dist/utils/ExpressManager.js +166 -0
- package/dist/utils/ExpressManager.js.map +1 -0
- package/dist/utils/NodeFileSystemAdapter.d.ts +6 -0
- package/dist/utils/NodeFileSystemAdapter.d.ts.map +1 -1
- package/dist/utils/NodeFileSystemAdapter.js +29 -1
- package/dist/utils/NodeFileSystemAdapter.js.map +1 -1
- package/dist/utils/error-reporting.d.ts +54 -0
- package/dist/utils/error-reporting.d.ts.map +1 -0
- package/dist/utils/error-reporting.js +143 -0
- package/dist/utils/error-reporting.js.map +1 -0
- package/dist/utils/pack-courses.d.ts.map +1 -1
- package/dist/utils/pack-courses.js +10 -27
- package/dist/utils/pack-courses.js.map +1 -1
- package/dist/utils/prompts.d.ts.map +1 -1
- package/dist/utils/prompts.js +24 -0
- package/dist/utils/prompts.js.map +1 -1
- package/dist/utils/questions-hash.d.ts +22 -0
- package/dist/utils/questions-hash.d.ts.map +1 -0
- package/dist/utils/questions-hash.js +96 -0
- package/dist/utils/questions-hash.js.map +1 -0
- package/dist/utils/template.d.ts +1 -1
- package/dist/utils/template.d.ts.map +1 -1
- package/dist/utils/template.js +202 -25
- package/dist/utils/template.js.map +1 -1
- package/eslint.config.mjs +1 -1
- package/package.json +30 -11
- package/src/cli.ts +3 -1
- package/src/commands/init.ts +48 -3
- package/src/commands/pack.ts +1 -1
- package/src/commands/studio.ts +850 -121
- package/src/commands/unpack.ts +1 -1
- package/src/types.ts +5 -0
- package/src/utils/ExpressManager.ts +210 -0
- package/src/utils/NodeFileSystemAdapter.ts +46 -2
- package/src/utils/error-reporting.ts +192 -0
- package/src/utils/pack-courses.ts +11 -36
- package/src/utils/prompts.ts +34 -0
- package/src/utils/questions-hash.ts +109 -0
- package/src/utils/template.ts +231 -27
- package/templates/.skuilder/README.md +29 -0
- package/dist/studio-ui-assets/assets/BrowseView-BJbixGOU.js +0 -2
- package/dist/studio-ui-assets/assets/BrowseView-BJbixGOU.js.map +0 -1
- package/dist/studio-ui-assets/assets/BrowseView-CM4HBO4j.css +0 -1
- package/dist/studio-ui-assets/assets/BulkImportView-DB6DYDJU.js +0 -2
- package/dist/studio-ui-assets/assets/BulkImportView-DB6DYDJU.js.map +0 -1
- package/dist/studio-ui-assets/assets/BulkImportView-g4wQUfPA.css +0 -1
- package/dist/studio-ui-assets/assets/CourseEditorView-BIlhlhw1.js +0 -2
- package/dist/studio-ui-assets/assets/CourseEditorView-BIlhlhw1.js.map +0 -1
- package/dist/studio-ui-assets/assets/CourseEditorView-WuPNLVKp.css +0 -1
- package/dist/studio-ui-assets/assets/CreateCardView-CyNOKCkm.css +0 -1
- package/dist/studio-ui-assets/assets/CreateCardView-DPjPvzzt.js +0 -2
- package/dist/studio-ui-assets/assets/CreateCardView-DPjPvzzt.js.map +0 -1
- package/dist/studio-ui-assets/assets/edit-ui.es-DiUxqbgF.js +0 -330
- package/dist/studio-ui-assets/assets/edit-ui.es-DiUxqbgF.js.map +0 -1
- package/dist/studio-ui-assets/assets/index--zY88pg6.css +0 -14
- package/dist/studio-ui-assets/assets/index-BnAv1C72.js +0 -287
- package/dist/studio-ui-assets/assets/index-BnAv1C72.js.map +0 -1
- package/dist/studio-ui-assets/assets/index-DHMXQY3-.js +0 -192
- package/dist/studio-ui-assets/assets/index-DHMXQY3-.js.map +0 -1
- package/dist/studio-ui-assets/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
- package/dist/studio-ui-assets/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
- package/dist/studio-ui-assets/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
- package/dist/studio-ui-assets/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
- package/dist/studio-ui-assets/assets/vue-DZcMATiC.js +0 -28
- package/dist/studio-ui-assets/assets/vue-DZcMATiC.js.map +0 -1
- package/dist/studio-ui-assets/assets/vuetify-qg7mRxy_.js +0 -6
- package/dist/studio-ui-assets/assets/vuetify-qg7mRxy_.js.map +0 -1
- package/dist/studio-ui-assets/index.html +0 -16
package/src/commands/unpack.ts
CHANGED
|
@@ -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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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}:`));
|
package/src/utils/prompts.ts
CHANGED
|
@@ -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;
|