@vue-skuilder/cli 0.1.11-9 → 0.1.12
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/CLAUDE.md +14 -5
- package/README.md +5 -4
- package/dist/commands/studio.d.ts.map +1 -1
- package/dist/commands/studio.js +199 -128
- package/dist/commands/studio.js.map +1 -1
- package/dist/mcp-server.d.ts +0 -1
- package/dist/mcp-server.js +8 -3
- package/dist/mcp-server.js.map +1 -1
- package/dist/standalone-ui-template/package.json +2 -2
- package/dist/studio-ui-src/api/index.ts +24 -2
- package/dist/studio-ui-src/components/StudioFlush.vue +12 -26
- package/dist/studio-ui-src/main.ts +28 -0
- package/dist/studio-ui-src/package.json +3 -2
- package/dist/studio-ui-src/utils/courseConfigRegistration.ts +86 -4
- package/dist/studio-ui-src/vite.config.ts +1 -1
- package/dist/utils/template.d.ts.map +1 -1
- package/dist/utils/template.js +11 -15
- package/dist/utils/template.js.map +1 -1
- package/package.json +12 -11
- package/src/commands/studio.ts +236 -159
- package/src/mcp-server.ts +8 -3
- package/src/utils/template.ts +11 -15
- package/dist/studio-ui-src/vite.config.base.js +0 -103
package/CLAUDE.md
CHANGED
|
@@ -16,9 +16,11 @@ npx @vue-skuilder/cli pack my-course
|
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Build System
|
|
19
|
-
- **TypeScript**: Direct compilation to
|
|
19
|
+
- **TypeScript**: Direct compilation to ES module in `dist/`
|
|
20
20
|
- **Binary Entry**: `dist/cli.js` with executable shebang
|
|
21
21
|
- **Module Type**: ES module with `.js` extension
|
|
22
|
+
- **Asset Embedding**: Templates and UI source files embedded during build
|
|
23
|
+
- **Complex Build**: Multi-step process with template copying and path adjustments
|
|
22
24
|
|
|
23
25
|
## CLI Commands
|
|
24
26
|
|
|
@@ -35,8 +37,14 @@ npx @vue-skuilder/cli pack my-course
|
|
|
35
37
|
- `inquirer` - Interactive prompts and wizards
|
|
36
38
|
- `chalk` - Terminal color output
|
|
37
39
|
|
|
38
|
-
### Vue-Skuilder Integration
|
|
40
|
+
### Vue-Skuilder Integration (All Workspace Packages)
|
|
41
|
+
- `@vue-skuilder/common` - Shared types and utilities
|
|
42
|
+
- `@vue-skuilder/common-ui` - Base UI components
|
|
43
|
+
- `@vue-skuilder/courseware` - Course content types and question implementations
|
|
39
44
|
- `@vue-skuilder/db` - Database utilities for course packaging
|
|
45
|
+
- `@vue-skuilder/edit-ui` - Course editing interface components
|
|
46
|
+
- `@vue-skuilder/express` - Express server for API endpoints
|
|
47
|
+
- `@vue-skuilder/mcp` - Model Context Protocol server integration
|
|
40
48
|
- `@vue-skuilder/standalone-ui` - Standalone player for packaged courses
|
|
41
49
|
|
|
42
50
|
### File Management
|
|
@@ -63,11 +71,12 @@ npx @vue-skuilder/cli pack my-course
|
|
|
63
71
|
- **Optimization**: Minifies and optimizes course assets
|
|
64
72
|
- **Packaging**: Creates deployable course bundle
|
|
65
73
|
|
|
66
|
-
### MCP Server
|
|
67
|
-
- **Protocol Implementation**:
|
|
74
|
+
### MCP Server Integration
|
|
75
|
+
- **Protocol Implementation**: Leverages `@vue-skuilder/mcp` package for full server functionality
|
|
68
76
|
- **Course Access**: Direct CourseDBInterface integration for real-time data
|
|
69
|
-
- **Content Generation**: AI-powered card creation and content authoring
|
|
77
|
+
- **Content Generation**: AI-powered card creation and content authoring via Claude Code
|
|
70
78
|
- **ELO Integration**: Native support for difficulty rating system
|
|
79
|
+
- **Studio Command**: Launches MCP server alongside development environment
|
|
71
80
|
|
|
72
81
|
## Utilities
|
|
73
82
|
|
package/README.md
CHANGED
|
@@ -73,10 +73,11 @@ skuilder init biology-course \
|
|
|
73
73
|
Studio mode provides a complete visual editing environment for static courses:
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
|
-
skuilder studio
|
|
77
|
-
skuilder studio ./my-course
|
|
78
|
-
skuilder studio
|
|
79
|
-
skuilder studio --
|
|
76
|
+
skuilder studio # Launch in current directory
|
|
77
|
+
skuilder studio ./my-course # Launch against specific skuilder app w/ packed course data
|
|
78
|
+
skuilder studio ./some/manifest.json # Launch against a specific packed course on disk
|
|
79
|
+
skuilder studio --port 6000 # Use custom CouchDB port
|
|
80
|
+
skuilder studio --no-browser # Don't auto-open browser
|
|
80
81
|
```
|
|
81
82
|
|
|
82
83
|
#### What Studio Mode Does
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"studio.d.ts","sourceRoot":"","sources":["../../src/commands/studio.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoDpC,wBAAgB,mBAAmB,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"studio.d.ts","sourceRoot":"","sources":["../../src/commands/studio.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoDpC,wBAAgB,mBAAmB,IAAI,OAAO,CAuC7C"}
|
package/dist/commands/studio.js
CHANGED
|
@@ -39,7 +39,7 @@ async function findAvailablePort(startPort) {
|
|
|
39
39
|
export function createStudioCommand() {
|
|
40
40
|
return new Command('studio')
|
|
41
41
|
.description('Launch studio mode: a complete course editing environment with CouchDB, Express API, and web editor')
|
|
42
|
-
.argument('[coursePath]', 'Path to static course directory', '.')
|
|
42
|
+
.argument('[coursePath]', 'Path to static course directory or manifest file', '.')
|
|
43
43
|
.option('-p, --port <port>', 'CouchDB port for studio session', '5985')
|
|
44
44
|
.option('--no-browser', 'Skip automatic browser launch')
|
|
45
45
|
.action(launchStudio)
|
|
@@ -59,8 +59,11 @@ Studio Mode creates a full editing environment for static courses:
|
|
|
59
59
|
|
|
60
60
|
Requirements:
|
|
61
61
|
• Docker (for CouchDB instance)
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
- either
|
|
63
|
+
• Valid static course project (with package.json)
|
|
64
|
+
• Course data in public/static-courses/ directory
|
|
65
|
+
- OR
|
|
66
|
+
- a valid mainfest.json
|
|
64
67
|
|
|
65
68
|
Example:
|
|
66
69
|
skuilder studio # Launch in current directory
|
|
@@ -75,74 +78,54 @@ let studioUIServer = null;
|
|
|
75
78
|
async function launchStudio(coursePath, options) {
|
|
76
79
|
try {
|
|
77
80
|
console.log(chalk.cyan(`🎨 Launching Skuilder Studio...`));
|
|
78
|
-
//
|
|
81
|
+
// Input validation and course detection
|
|
79
82
|
const resolvedPath = path.resolve(coursePath);
|
|
80
|
-
console.log(chalk.gray(`📁
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
console.log(chalk.gray(`📁 Input path: ${resolvedPath}`));
|
|
84
|
+
let isManifestMode = false;
|
|
85
|
+
let actualCoursePath = resolvedPath;
|
|
86
|
+
// Check if input is a manifest file
|
|
87
|
+
const manifestValidation = await validateManifestCourse(resolvedPath);
|
|
88
|
+
if (manifestValidation.isValid) {
|
|
89
|
+
console.log(chalk.green(`✅ Valid course manifest detected`));
|
|
90
|
+
isManifestMode = true;
|
|
91
|
+
actualCoursePath = manifestValidation.coursePath;
|
|
92
|
+
console.log(chalk.gray(`📁 Course data path: ${actualCoursePath}`));
|
|
87
93
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
await ensureCacheDirectory(resolvedPath);
|
|
97
|
-
const buildExists = studioBuildExists(resolvedPath, questionsHash);
|
|
98
|
-
const buildPath = getStudioBuildPath(resolvedPath, questionsHash);
|
|
99
|
-
console.log(chalk.gray(` Questions hash: ${questionsHash}`));
|
|
100
|
-
console.log(chalk.gray(` Cached build exists: ${buildExists ? 'Yes' : 'No'}`));
|
|
101
|
-
// Determine if we need to rebuild studio-ui
|
|
102
|
-
if (buildExists) {
|
|
103
|
-
console.log(chalk.gray(` Using cached build at: ${buildPath}`));
|
|
104
|
-
studioUIPath = buildPath;
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
console.log(chalk.cyan(`🔨 Building studio-ui with local question types...`));
|
|
108
|
-
studioUIPath = await buildStudioUIWithQuestions(resolvedPath, questionsHash);
|
|
109
|
-
console.log(chalk.green(`✅ Studio-UI build complete: ${studioUIPath}`));
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch (error) {
|
|
113
|
-
// Handle catastrophic build errors by falling back to embedded source
|
|
114
|
-
console.log(chalk.yellow(`⚠️ Unable to process questions due to ${error},\n⚠️ Using embedded studio-ui`));
|
|
115
|
-
const embeddedPath = path.join(__dirname, '..', 'studio-ui-src');
|
|
116
|
-
if (fs.existsSync(embeddedPath)) {
|
|
117
|
-
studioUIPath = embeddedPath;
|
|
118
|
-
console.log(chalk.gray(` Using embedded studio-ui source directly`));
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
console.error(chalk.red(`❌ No viable studio-ui source available`));
|
|
122
|
-
throw new Error('Critical error: Cannot locate studio-ui source');
|
|
94
|
+
else {
|
|
95
|
+
// Check if it's a traditional scaffolded course directory
|
|
96
|
+
if (!(await validateSuiCourse(resolvedPath))) {
|
|
97
|
+
console.error(chalk.red(`❌ Not a valid course directory or manifest file`));
|
|
98
|
+
console.log(chalk.yellow(`💡 Studio mode accepts either:`));
|
|
99
|
+
console.log(chalk.yellow(` - Scaffolded course directory with package.json and static-data/`));
|
|
100
|
+
console.log(chalk.yellow(` - Course manifest.json file with chunks/ and indices/ directories`));
|
|
101
|
+
process.exit(1);
|
|
123
102
|
}
|
|
103
|
+
console.log(chalk.green(`✅ Valid standalone-ui course detected`));
|
|
124
104
|
}
|
|
125
|
-
//
|
|
126
|
-
const
|
|
105
|
+
// Studio UI build preparation
|
|
106
|
+
const studioUIPath = isManifestMode
|
|
107
|
+
? await handleManifestCourse()
|
|
108
|
+
: await handleSuiCourse(resolvedPath);
|
|
109
|
+
// Start CouchDB instance
|
|
110
|
+
const studioDatabaseName = generateStudioDatabaseName(actualCoursePath);
|
|
127
111
|
console.log(chalk.cyan(`🗄️ Starting studio CouchDB instance: ${studioDatabaseName}`));
|
|
128
112
|
couchDBManager = await startStudioCouchDB(studioDatabaseName, parseInt(options.port));
|
|
129
|
-
//
|
|
113
|
+
// Load course data into database
|
|
130
114
|
console.log(chalk.cyan(`📦 Unpacking course data to studio database...`));
|
|
131
|
-
const unpackResult = await unpackCourseToStudio(
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
const expressResult = await startExpressBackend(couchDBManager.getConnectionDetails(), resolvedPath, unpackResult.databaseName);
|
|
115
|
+
const unpackResult = await unpackCourseToStudio(actualCoursePath, couchDBManager.getConnectionDetails(), isManifestMode);
|
|
116
|
+
// Start Express API server
|
|
117
|
+
const expressResult = await startExpressBackend(couchDBManager.getConnectionDetails(), unpackResult.databaseName);
|
|
135
118
|
expressServer = expressResult.server;
|
|
136
|
-
//
|
|
119
|
+
// Launch studio web interface
|
|
137
120
|
console.log(chalk.cyan(`🌐 Starting studio-ui server...`));
|
|
138
121
|
console.log(chalk.gray(` Debug: Unpack result - Database: "${unpackResult.databaseName}", Course ID: "${unpackResult.courseId}"`));
|
|
139
|
-
const studioUIPort = await startStudioUIServer(couchDBManager.getConnectionDetails(), unpackResult, studioUIPath);
|
|
122
|
+
const studioUIPort = await startStudioUIServer(couchDBManager.getConnectionDetails(), unpackResult, studioUIPath, expressResult.url);
|
|
140
123
|
console.log(chalk.green(`✅ Studio session ready!`));
|
|
141
124
|
console.log(chalk.white(`🎨 Studio URL: http://localhost:${studioUIPort}`));
|
|
142
125
|
console.log(chalk.gray(` Database: ${studioDatabaseName} on port ${options.port}`));
|
|
143
126
|
console.log(chalk.gray(` Express API: ${expressResult.url}`));
|
|
144
127
|
// Display MCP connection information
|
|
145
|
-
const mcpInfo = getMCPConnectionInfo(unpackResult, couchDBManager
|
|
128
|
+
const mcpInfo = getMCPConnectionInfo(unpackResult, couchDBManager);
|
|
146
129
|
console.log(chalk.blue(`🔗 MCP Server: ${mcpInfo.command}`));
|
|
147
130
|
console.log(chalk.gray(` Connect MCP clients using the command above`));
|
|
148
131
|
console.log(chalk.gray(` Environment variables for MCP:`));
|
|
@@ -150,7 +133,7 @@ async function launchStudio(coursePath, options) {
|
|
|
150
133
|
console.log(chalk.gray(` ${key}=${value}`));
|
|
151
134
|
});
|
|
152
135
|
// Display .mcp.json content for Claude Code integration
|
|
153
|
-
const mcpJsonContent = generateMCPJson(unpackResult, couchDBManager
|
|
136
|
+
const mcpJsonContent = generateMCPJson(unpackResult, couchDBManager);
|
|
154
137
|
console.log(chalk.blue(`📋 .mcp.json content:`));
|
|
155
138
|
console.log(chalk.gray(mcpJsonContent));
|
|
156
139
|
if (options.browser) {
|
|
@@ -158,7 +141,7 @@ async function launchStudio(coursePath, options) {
|
|
|
158
141
|
await openBrowser(`http://localhost:${studioUIPort}`);
|
|
159
142
|
}
|
|
160
143
|
console.log(chalk.gray(` Press Ctrl+C to stop studio session`));
|
|
161
|
-
//
|
|
144
|
+
// Session lifecycle management
|
|
162
145
|
process.on('SIGINT', () => {
|
|
163
146
|
void (async () => {
|
|
164
147
|
console.log(chalk.cyan(`\n🔄 Stopping studio session...`));
|
|
@@ -183,7 +166,41 @@ async function launchStudio(coursePath, options) {
|
|
|
183
166
|
}
|
|
184
167
|
}
|
|
185
168
|
/**
|
|
186
|
-
*
|
|
169
|
+
* Validate that the given path is a course manifest file
|
|
170
|
+
*/
|
|
171
|
+
async function validateManifestCourse(manifestPath) {
|
|
172
|
+
try {
|
|
173
|
+
// Check if file exists
|
|
174
|
+
if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile()) {
|
|
175
|
+
return { isValid: false };
|
|
176
|
+
}
|
|
177
|
+
// Check if it's a JSON file
|
|
178
|
+
if (!manifestPath.endsWith('.json')) {
|
|
179
|
+
return { isValid: false };
|
|
180
|
+
}
|
|
181
|
+
// Try to parse as JSON
|
|
182
|
+
const manifestContent = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
183
|
+
// Validate required manifest fields
|
|
184
|
+
if (!manifestContent.courseId || !manifestContent.courseName) {
|
|
185
|
+
return { isValid: false };
|
|
186
|
+
}
|
|
187
|
+
// Find course data directory relative to manifest
|
|
188
|
+
const manifestDir = path.dirname(manifestPath);
|
|
189
|
+
const coursePath = manifestDir; // Course data should be alongside manifest
|
|
190
|
+
// Check for required course data structure
|
|
191
|
+
const chunksPath = path.join(coursePath, 'chunks');
|
|
192
|
+
const indicesPath = path.join(coursePath, 'indices');
|
|
193
|
+
if (!fs.existsSync(chunksPath) || !fs.existsSync(indicesPath)) {
|
|
194
|
+
return { isValid: false };
|
|
195
|
+
}
|
|
196
|
+
return { isValid: true, coursePath };
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return { isValid: false };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Validate that the given path contains a standalone-ui course
|
|
187
204
|
*/
|
|
188
205
|
async function validateSuiCourse(coursePath) {
|
|
189
206
|
try {
|
|
@@ -300,7 +317,7 @@ async function stopStudioSession() {
|
|
|
300
317
|
couchDBManager = null;
|
|
301
318
|
}
|
|
302
319
|
}
|
|
303
|
-
async function startStudioUIServer(connectionDetails, unpackResult, studioPath) {
|
|
320
|
+
async function startStudioUIServer(connectionDetails, unpackResult, studioPath, expressApiUrl) {
|
|
304
321
|
// Serve from built dist directory if it exists, otherwise fallback to source
|
|
305
322
|
const distPath = path.join(studioPath, 'dist');
|
|
306
323
|
const studioSourcePath = fs.existsSync(distPath) ? distPath : studioPath;
|
|
@@ -348,6 +365,9 @@ async function startStudioUIServer(connectionDetails, unpackResult, studioPath)
|
|
|
348
365
|
name: '${unpackResult.databaseName}',
|
|
349
366
|
courseId: '${unpackResult.courseId}',
|
|
350
367
|
originalCourseId: '${unpackResult.courseId}'
|
|
368
|
+
},
|
|
369
|
+
express: {
|
|
370
|
+
url: '${expressApiUrl || 'http://localhost:3000'}'
|
|
351
371
|
}
|
|
352
372
|
};
|
|
353
373
|
</script>
|
|
@@ -375,6 +395,9 @@ async function startStudioUIServer(connectionDetails, unpackResult, studioPath)
|
|
|
375
395
|
name: '${unpackResult.databaseName}',
|
|
376
396
|
courseId: '${unpackResult.courseId}',
|
|
377
397
|
originalCourseId: '${unpackResult.courseId}'
|
|
398
|
+
},
|
|
399
|
+
express: {
|
|
400
|
+
url: '${expressApiUrl || 'http://localhost:3000'}'
|
|
378
401
|
}
|
|
379
402
|
};
|
|
380
403
|
</script>
|
|
@@ -444,7 +467,7 @@ async function openBrowser(url) {
|
|
|
444
467
|
/**
|
|
445
468
|
* Phase 9.5: Start Express backend server
|
|
446
469
|
*/
|
|
447
|
-
async function startExpressBackend(couchDbConnectionDetails,
|
|
470
|
+
async function startExpressBackend(couchDbConnectionDetails, databaseName) {
|
|
448
471
|
console.log(chalk.blue('⚡ Starting Express backend server...'));
|
|
449
472
|
// Find available port starting from 3001
|
|
450
473
|
const availablePort = await findAvailablePort(3001);
|
|
@@ -470,6 +493,8 @@ async function startExpressBackend(couchDbConnectionDetails, _projectPath, datab
|
|
|
470
493
|
},
|
|
471
494
|
};
|
|
472
495
|
try {
|
|
496
|
+
// Set NODE_ENV for studio mode authentication bypass
|
|
497
|
+
process.env.NODE_ENV = 'studio';
|
|
473
498
|
// Create Express app using factory
|
|
474
499
|
const app = createExpressApp(config);
|
|
475
500
|
// Start server
|
|
@@ -495,29 +520,37 @@ async function startExpressBackend(couchDbConnectionDetails, _projectPath, datab
|
|
|
495
520
|
/**
|
|
496
521
|
* Phase 4: Unpack course data to studio CouchDB
|
|
497
522
|
*/
|
|
498
|
-
async function unpackCourseToStudio(coursePath, connectionDetails) {
|
|
523
|
+
async function unpackCourseToStudio(coursePath, connectionDetails, isManifestMode = false) {
|
|
499
524
|
try {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
525
|
+
let courseDataPath;
|
|
526
|
+
if (isManifestMode) {
|
|
527
|
+
// For manifest mode, the coursePath already points to the course data directory
|
|
528
|
+
courseDataPath = coursePath;
|
|
529
|
+
console.log(chalk.gray(` Manifest mode: using course data at ${courseDataPath}`));
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
// Find the course data directory (static-data OR public/static-courses)
|
|
533
|
+
courseDataPath = path.join(coursePath, 'static-data');
|
|
534
|
+
if (!fs.existsSync(courseDataPath)) {
|
|
535
|
+
// Try public/static-courses directory
|
|
536
|
+
const publicStaticPath = path.join(coursePath, 'public', 'static-courses');
|
|
537
|
+
if (fs.existsSync(publicStaticPath)) {
|
|
538
|
+
// Find the first course directory inside public/static-courses
|
|
539
|
+
const courses = fs
|
|
540
|
+
.readdirSync(publicStaticPath, { withFileTypes: true })
|
|
541
|
+
.filter((dirent) => dirent.isDirectory())
|
|
542
|
+
.map((dirent) => dirent.name);
|
|
543
|
+
if (courses.length > 0) {
|
|
544
|
+
courseDataPath = path.join(publicStaticPath, courses[0]);
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
throw new Error('No course directories found in public/static-courses/');
|
|
548
|
+
}
|
|
513
549
|
}
|
|
514
550
|
else {
|
|
515
|
-
throw new Error('No course
|
|
551
|
+
throw new Error('No course data found in static-data/ or public/static-courses/');
|
|
516
552
|
}
|
|
517
553
|
}
|
|
518
|
-
else {
|
|
519
|
-
throw new Error('No course data found in static-data/ or public/static-courses/');
|
|
520
|
-
}
|
|
521
554
|
}
|
|
522
555
|
console.log(chalk.gray(` Course data path: ${courseDataPath}`));
|
|
523
556
|
console.log(chalk.gray(` Running unpack directly...`));
|
|
@@ -648,9 +681,9 @@ async function buildDefaultStudioUI(buildPath) {
|
|
|
648
681
|
console.log(chalk.gray(` Transforming workspace dependencies...`));
|
|
649
682
|
const studioPackageJsonPath = path.join(buildPath, 'package.json');
|
|
650
683
|
await transformPackageJsonForStudioBuild(studioPackageJsonPath);
|
|
651
|
-
// Fix Vite config to use npm packages
|
|
652
|
-
console.log(chalk.gray(` Updating Vite configuration for
|
|
653
|
-
await
|
|
684
|
+
// Fix Vite config to use npm packages and resolve custom questions imports
|
|
685
|
+
console.log(chalk.gray(` Updating Vite configuration for studio build...`));
|
|
686
|
+
await createStudioViteConfig(buildPath);
|
|
654
687
|
// Run Vite build process
|
|
655
688
|
console.log(chalk.gray(` Running Vite build process...`));
|
|
656
689
|
await runViteBuild(buildPath);
|
|
@@ -788,9 +821,9 @@ async function buildStudioUIWithCustomQuestions(buildPath, customQuestionsData)
|
|
|
788
821
|
console.log(chalk.gray(` Transforming workspace dependencies...`));
|
|
789
822
|
const studioPackageJsonPath = path.join(buildPath, 'package.json');
|
|
790
823
|
await transformPackageJsonForStudioBuild(studioPackageJsonPath);
|
|
791
|
-
// Step 2.5: Fix Vite config to use npm packages
|
|
792
|
-
console.log(chalk.gray(` Updating Vite configuration for
|
|
793
|
-
await
|
|
824
|
+
// Step 2.5: Fix Vite config to use npm packages and resolve custom questions imports
|
|
825
|
+
console.log(chalk.gray(` Updating Vite configuration for studio build...`));
|
|
826
|
+
await createStudioViteConfig(buildPath);
|
|
794
827
|
// Step 3: Install custom questions package
|
|
795
828
|
console.log(chalk.cyan(` Installing bundled course package: ${customQuestionsData.packageName} from ${customQuestionsData.coursePath}`));
|
|
796
829
|
const distLibPath = path.join(customQuestionsData.coursePath, 'dist-lib');
|
|
@@ -954,17 +987,17 @@ async function runViteBuild(buildPath) {
|
|
|
954
987
|
});
|
|
955
988
|
}
|
|
956
989
|
/**
|
|
957
|
-
*
|
|
990
|
+
* Create Vite configuration for studio-ui build environment
|
|
958
991
|
*/
|
|
959
|
-
async function
|
|
992
|
+
async function createStudioViteConfig(buildPath) {
|
|
960
993
|
const viteConfigPath = path.join(buildPath, 'vite.config.ts');
|
|
961
994
|
if (!fs.existsSync(viteConfigPath)) {
|
|
962
995
|
console.log(chalk.yellow(` Warning: vite.config.ts not found at ${viteConfigPath}`));
|
|
963
996
|
return;
|
|
964
997
|
}
|
|
965
|
-
// Create a clean
|
|
966
|
-
//
|
|
967
|
-
const
|
|
998
|
+
// Create a clean studio vite config for studio-ui environment
|
|
999
|
+
// Includes aliases to resolve custom questions imports
|
|
1000
|
+
const studioViteConfig = `import { defineConfig } from 'vite';
|
|
968
1001
|
import vue from '@vitejs/plugin-vue';
|
|
969
1002
|
|
|
970
1003
|
export default defineConfig({
|
|
@@ -990,9 +1023,17 @@ export default defineConfig({
|
|
|
990
1023
|
},
|
|
991
1024
|
resolve: {
|
|
992
1025
|
extensions: ['.js', '.ts', '.json', '.vue'],
|
|
1026
|
+
alias: {
|
|
1027
|
+
// Resolve @vue-skuilder packages to npm packages for custom questions import
|
|
1028
|
+
'@vue-skuilder/common': '@vue-skuilder/common',
|
|
1029
|
+
'@vue-skuilder/courseware': '@vue-skuilder/courseware',
|
|
1030
|
+
'@vue-skuilder/db': '@vue-skuilder/db',
|
|
1031
|
+
'@vue-skuilder/common-ui': '@vue-skuilder/common-ui',
|
|
1032
|
+
'@vue-skuilder/edit-ui': '@vue-skuilder/edit-ui'
|
|
1033
|
+
},
|
|
993
1034
|
dedupe: [
|
|
994
1035
|
'vue',
|
|
995
|
-
'vuetify',
|
|
1036
|
+
'vuetify',
|
|
996
1037
|
'vue-router',
|
|
997
1038
|
'pinia',
|
|
998
1039
|
'@vue-skuilder/db',
|
|
@@ -1003,52 +1044,32 @@ export default defineConfig({
|
|
|
1003
1044
|
]
|
|
1004
1045
|
}
|
|
1005
1046
|
});`;
|
|
1006
|
-
fs.writeFileSync(viteConfigPath,
|
|
1007
|
-
console.log(chalk.gray(` Vite config replaced with
|
|
1047
|
+
fs.writeFileSync(viteConfigPath, studioViteConfig);
|
|
1048
|
+
console.log(chalk.gray(` Vite config replaced with studio version`));
|
|
1008
1049
|
}
|
|
1009
1050
|
/**
|
|
1010
|
-
* Determine the correct MCP server executable path
|
|
1051
|
+
* Determine the correct MCP server executable path.
|
|
1052
|
+
* This is now greatly simplified because the bundled server is always
|
|
1053
|
+
* located relative to this `studio.ts` file.
|
|
1011
1054
|
*/
|
|
1012
|
-
function resolveMCPExecutable(
|
|
1013
|
-
//
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
return {
|
|
1017
|
-
command: './packages/cli/dist/mcp-server.js',
|
|
1018
|
-
args: [],
|
|
1019
|
-
isNpx: false,
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
// Check if @vue-skuilder/cli is installed as a dependency
|
|
1023
|
-
const scaffoldedCliPath = path.join(projectPath, 'node_modules', '@vue-skuilder', 'cli', 'dist', 'mcp-server.js');
|
|
1024
|
-
if (fs.existsSync(scaffoldedCliPath)) {
|
|
1025
|
-
return {
|
|
1026
|
-
command: './node_modules/@vue-skuilder/cli/dist/mcp-server.js',
|
|
1027
|
-
args: [],
|
|
1028
|
-
isNpx: false,
|
|
1029
|
-
};
|
|
1030
|
-
}
|
|
1031
|
-
// Fallback to npx approach
|
|
1055
|
+
function resolveMCPExecutable() {
|
|
1056
|
+
// Resolve the path to the bundled server relative to the current file.
|
|
1057
|
+
// __dirname is the `dist/commands` directory.
|
|
1058
|
+
const serverPath = path.resolve(__dirname, '..', 'mcp-server.js');
|
|
1032
1059
|
return {
|
|
1033
|
-
command: '
|
|
1034
|
-
args: [
|
|
1035
|
-
isNpx: true,
|
|
1060
|
+
command: 'node',
|
|
1061
|
+
args: [serverPath],
|
|
1036
1062
|
};
|
|
1037
1063
|
}
|
|
1038
1064
|
/**
|
|
1039
1065
|
* Generate MCP connection information for studio session
|
|
1040
1066
|
*/
|
|
1041
|
-
function getMCPConnectionInfo(unpackResult, couchDBManager
|
|
1067
|
+
function getMCPConnectionInfo(unpackResult, couchDBManager) {
|
|
1042
1068
|
const couchDetails = couchDBManager.getConnectionDetails();
|
|
1043
|
-
const executable = resolveMCPExecutable(
|
|
1069
|
+
const executable = resolveMCPExecutable();
|
|
1070
|
+
const port = couchDetails.port || 5985;
|
|
1044
1071
|
// Build command string for display
|
|
1045
|
-
|
|
1046
|
-
if (executable.isNpx) {
|
|
1047
|
-
commandStr = `${executable.command} ${executable.args.join(' ')} ${unpackResult.databaseName} ${couchDetails.port}`;
|
|
1048
|
-
}
|
|
1049
|
-
else {
|
|
1050
|
-
commandStr = `node ${executable.command} ${unpackResult.databaseName} ${couchDetails.port}`;
|
|
1051
|
-
}
|
|
1072
|
+
const commandStr = `${executable.command} ${executable.args.join(' ')} ${unpackResult.databaseName} ${port}`;
|
|
1052
1073
|
return {
|
|
1053
1074
|
command: commandStr,
|
|
1054
1075
|
env: {
|
|
@@ -1062,10 +1083,10 @@ function getMCPConnectionInfo(unpackResult, couchDBManager, projectPath) {
|
|
|
1062
1083
|
/**
|
|
1063
1084
|
* Generate .mcp.json content for Claude Code integration
|
|
1064
1085
|
*/
|
|
1065
|
-
function generateMCPJson(unpackResult, couchDBManager,
|
|
1086
|
+
function generateMCPJson(unpackResult, couchDBManager, serverName = 'vue-skuilder-studio') {
|
|
1066
1087
|
const couchDetails = couchDBManager.getConnectionDetails();
|
|
1067
1088
|
const port = couchDetails.port || 5985;
|
|
1068
|
-
const executable = resolveMCPExecutable(
|
|
1089
|
+
const executable = resolveMCPExecutable();
|
|
1069
1090
|
const mcpConfig = {
|
|
1070
1091
|
mcpServers: {
|
|
1071
1092
|
[serverName]: {
|
|
@@ -1082,4 +1103,54 @@ function generateMCPJson(unpackResult, couchDBManager, projectPath, serverName =
|
|
|
1082
1103
|
};
|
|
1083
1104
|
return JSON.stringify(mcpConfig, null, 2);
|
|
1084
1105
|
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Handle SUI course build process - extract questions hashing and UI building logic
|
|
1108
|
+
*/
|
|
1109
|
+
async function handleSuiCourse(coursePath) {
|
|
1110
|
+
console.log(chalk.cyan(`🔍 Analyzing local question types...`));
|
|
1111
|
+
try {
|
|
1112
|
+
const questionsHash = await withStudioBuildErrorHandling(() => hashQuestionsDirectory(coursePath), StudioBuildErrorType.QUESTIONS_HASH_ERROR, { coursePath: coursePath });
|
|
1113
|
+
// Ensure cache directory exists
|
|
1114
|
+
await ensureCacheDirectory(coursePath);
|
|
1115
|
+
const buildExists = studioBuildExists(coursePath, questionsHash);
|
|
1116
|
+
const buildPath = getStudioBuildPath(coursePath, questionsHash);
|
|
1117
|
+
console.log(chalk.gray(` Questions hash: ${questionsHash}`));
|
|
1118
|
+
console.log(chalk.gray(` Cached build exists: ${buildExists ? 'Yes' : 'No'}`));
|
|
1119
|
+
let studioUIPath;
|
|
1120
|
+
// Determine if we need to rebuild studio-ui
|
|
1121
|
+
if (buildExists) {
|
|
1122
|
+
console.log(chalk.gray(` Using cached build at: ${buildPath}`));
|
|
1123
|
+
studioUIPath = buildPath;
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
console.log(chalk.cyan(`🔨 Building studio-ui with local question types...`));
|
|
1127
|
+
studioUIPath = await buildStudioUIWithQuestions(coursePath, questionsHash);
|
|
1128
|
+
console.log(chalk.green(`✅ Studio-UI build complete: ${studioUIPath}`));
|
|
1129
|
+
}
|
|
1130
|
+
return studioUIPath;
|
|
1131
|
+
}
|
|
1132
|
+
catch (error) {
|
|
1133
|
+
// Handle catastrophic build errors by falling back to embedded source
|
|
1134
|
+
console.log(chalk.yellow(`⚠️ Unable to process questions due to ${error},\n⚠️ Using embedded studio-ui`));
|
|
1135
|
+
const embeddedPath = path.join(__dirname, '..', 'studio-ui-src');
|
|
1136
|
+
if (fs.existsSync(embeddedPath)) {
|
|
1137
|
+
const studioUIPath = embeddedPath;
|
|
1138
|
+
console.log(chalk.gray(` Using embedded studio-ui source directly`));
|
|
1139
|
+
return studioUIPath;
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
console.error(chalk.red(`❌ No viable studio-ui source available`));
|
|
1143
|
+
throw new Error('Critical error: Cannot locate studio-ui source');
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Handle manifest course - simple default build without custom questions
|
|
1149
|
+
*/
|
|
1150
|
+
async function handleManifestCourse() {
|
|
1151
|
+
console.log(chalk.cyan(`📋 Manifest mode: using default studio-ui build`));
|
|
1152
|
+
const manifestBuildPath = path.join(__dirname, '..', 'studio-builds', 'manifest-default');
|
|
1153
|
+
const studioUIPath = await buildDefaultStudioUI(manifestBuildPath);
|
|
1154
|
+
return studioUIPath;
|
|
1155
|
+
}
|
|
1085
1156
|
//# sourceMappingURL=studio.js.map
|