coursecode 0.1.54 → 0.1.55
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 +2 -1
- package/bin/cli.js +13 -1
- package/framework/docs/COURSE_AUTHORING_GUIDE.md +5 -1
- package/framework/docs/FRAMEWORK_GUIDE.md +1 -1
- package/framework/version.json +2 -2
- package/lib/create.js +71 -18
- package/lib/scaffold.js +33 -4
- package/package.json +3 -3
- package/template/gitattributes +44 -0
- package/template/gitignore +38 -0
- package/template/vite.config.js +11 -1
package/README.md
CHANGED
|
@@ -235,7 +235,8 @@ coursecode preview --export
|
|
|
235
235
|
|
|
236
236
|
| Command | Description |
|
|
237
237
|
|---------|-------------|
|
|
238
|
-
| `coursecode create <name>` | Create a new course project |
|
|
238
|
+
| `coursecode create <name>` | Create a new course project; use `.` to initialize the current directory |
|
|
239
|
+
| `coursecode init [name]` | Initialize the current directory, optionally with a course title |
|
|
239
240
|
| `coursecode preview` | Preview your course locally |
|
|
240
241
|
| `coursecode convert` | Convert PDFs, Word, PowerPoint to markdown |
|
|
241
242
|
| `coursecode mcp` | Start the MCP server for AI integration |
|
package/bin/cli.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Commands:
|
|
7
7
|
* coursecode create <name> - Create a new course project
|
|
8
|
+
* coursecode init [name] - Initialize the current directory
|
|
8
9
|
* coursecode dev - Start development server
|
|
9
10
|
* coursecode build - Build course package
|
|
10
11
|
* coursecode upgrade - Upgrade framework in current project
|
|
@@ -79,7 +80,7 @@ program
|
|
|
79
80
|
// Create command
|
|
80
81
|
program
|
|
81
82
|
.command('create <name>')
|
|
82
|
-
.description('Create a new course project')
|
|
83
|
+
.description('Create a new course project (use "." for the current directory)')
|
|
83
84
|
.option('--blank', 'Create without example slides (clean starter)')
|
|
84
85
|
.option('--no-install', 'Skip npm install')
|
|
85
86
|
.option('--start', 'Auto-start dev server after creation (skip prompt)')
|
|
@@ -88,6 +89,17 @@ program
|
|
|
88
89
|
await create(name, options);
|
|
89
90
|
});
|
|
90
91
|
|
|
92
|
+
program
|
|
93
|
+
.command('init [name]')
|
|
94
|
+
.description('Initialize the current directory as a CourseCode project')
|
|
95
|
+
.option('--blank', 'Create without example content (clean starter)')
|
|
96
|
+
.option('--no-install', 'Skip npm install')
|
|
97
|
+
.option('--start', 'Auto-start dev server after creation (skip prompt)')
|
|
98
|
+
.action(async (name, options) => {
|
|
99
|
+
const { create } = await import('../lib/create.js');
|
|
100
|
+
await create(name || path.basename(process.cwd()), { ...options, currentDirectory: true });
|
|
101
|
+
});
|
|
102
|
+
|
|
91
103
|
// Dev command
|
|
92
104
|
program
|
|
93
105
|
.command('dev')
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
|---------|-------------|
|
|
17
17
|
| `coursecode create <name>` | Create a new course project (includes example slides) |
|
|
18
18
|
| `coursecode create <name> --blank` | Create a blank project (no example content) |
|
|
19
|
+
| `coursecode create .` | Initialize the current directory using its folder name as the course title |
|
|
20
|
+
| `coursecode init [name]` | Initialize the current directory, optionally with a specific course title |
|
|
19
21
|
| `coursecode clean` | Remove example files and reset config to minimal starter |
|
|
20
22
|
| `coursecode new slide <id>` | Create a new slide file in `course/slides/` |
|
|
21
23
|
| `coursecode new assessment <id>` | Create a new assessment file in `course/slides/` |
|
|
@@ -253,7 +255,7 @@ coursecode export-content --format json -o content.json
|
|
|
253
255
|
3. **Create slide** in `course/slides/intro.js`:
|
|
254
256
|
```javascript
|
|
255
257
|
export const slide = {
|
|
256
|
-
render(
|
|
258
|
+
render(_root, _context) {
|
|
257
259
|
const container = document.createElement('div');
|
|
258
260
|
container.innerHTML = `<h1>Welcome</h1><p>Content here</p>`;
|
|
259
261
|
return container; // Must return a DOM element
|
|
@@ -261,6 +263,8 @@ coursecode export-content --format json -o content.json
|
|
|
261
263
|
};
|
|
262
264
|
```
|
|
263
265
|
|
|
266
|
+
The first render argument is reserved and currently receives `null`. Keep it as `_root` for API compatibility. Create and return a new root `HTMLElement`; use the second argument only when the slide needs navigation or assessment context.
|
|
267
|
+
|
|
264
268
|
> **⚠️ No import statements.** Components, interactions, CSS classes, and icons are all globally available at runtime. The only valid `import` is for local assets (images, SVGs). Access framework APIs via `const { createXxxQuestion } = CourseCode;` (destructure from global, **not** an import).
|
|
265
269
|
|
|
266
270
|
4. **Add styles** to `course/theme.css` (only for custom branding - use framework utility classes first, see CSS Quick Reference section below)
|
|
@@ -310,7 +310,7 @@ Separates data, presentation, logic (used in `app/`, `navigation/`):
|
|
|
310
310
|
|
|
311
311
|
- **Single ViewManager** in `main.js` controls slide navigation
|
|
312
312
|
- **No caching** - views render fresh each `showView()` to prevent stale data
|
|
313
|
-
- Slide signature: `render(
|
|
313
|
+
- Slide signature: `render(_root, context)`. The first argument is reserved and currently `null`; slides create and return their own root `HTMLElement`. The second argument carries navigation/render context.
|
|
314
314
|
- Components with sub-views (assessments) create own ViewManager
|
|
315
315
|
|
|
316
316
|
### Event Delegation
|
package/framework/version.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.
|
|
2
|
+
"version": "0.1.55",
|
|
3
3
|
"name": "CourseCode Framework",
|
|
4
4
|
"description": "Multi-format course authoring and learner runtime framework",
|
|
5
|
-
"released": "2026-07-
|
|
5
|
+
"released": "2026-07-11",
|
|
6
6
|
"formats": [
|
|
7
7
|
"SCORM 2004 4th Edition",
|
|
8
8
|
"SCORM 1.2",
|
package/lib/create.js
CHANGED
|
@@ -77,6 +77,42 @@ function copyDir(src, dest, options = {}) {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
function mergeProjectControlFile(sourcePath, destinationPath) {
|
|
81
|
+
const source = fs.readFileSync(sourcePath, 'utf-8').trimEnd();
|
|
82
|
+
if (!fs.existsSync(destinationPath)) {
|
|
83
|
+
fs.writeFileSync(destinationPath, `${source}\n`, 'utf-8');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const current = fs.readFileSync(destinationPath, 'utf-8');
|
|
88
|
+
const existingLines = new Set(current.split(/\r?\n/).map(line => line.trim()));
|
|
89
|
+
const missingRules = source
|
|
90
|
+
.split(/\r?\n/)
|
|
91
|
+
.filter(line => line.trim() && !line.trim().startsWith('#') && !existingLines.has(line.trim()));
|
|
92
|
+
|
|
93
|
+
if (missingRules.length === 0) return;
|
|
94
|
+
|
|
95
|
+
const separator = current.endsWith('\n') ? '\n' : '\n\n';
|
|
96
|
+
fs.appendFileSync(
|
|
97
|
+
destinationPath,
|
|
98
|
+
`${separator}# CourseCode project defaults\n${missingRules.join('\n')}\n`,
|
|
99
|
+
'utf-8'
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function assertCurrentDirectoryCanBeInitialized(targetDir, templateDir) {
|
|
104
|
+
const managedPaths = fs.readdirSync(templateDir)
|
|
105
|
+
.filter(name => !['.DS_Store', 'gitignore', 'gitattributes'].includes(name))
|
|
106
|
+
.concat(['framework', 'schemas', 'lib', '.coursecoderc.json']);
|
|
107
|
+
const conflicts = managedPaths.filter(name => fs.existsSync(path.join(targetDir, name)));
|
|
108
|
+
|
|
109
|
+
if (conflicts.length > 0) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Cannot initialize the current directory because CourseCode-managed paths already exist: ${conflicts.join(', ')}`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
80
116
|
/**
|
|
81
117
|
* Run npm install in directory
|
|
82
118
|
*/
|
|
@@ -123,7 +159,10 @@ function gitInit(cwd) {
|
|
|
123
159
|
}
|
|
124
160
|
|
|
125
161
|
export async function create(name, options = {}) {
|
|
126
|
-
const
|
|
162
|
+
const requestedName = String(name || '').trim();
|
|
163
|
+
const currentDirectory = options.currentDirectory === true || requestedName === '.';
|
|
164
|
+
const inferredName = path.basename(process.cwd()).replace(/[-_]+/g, ' ');
|
|
165
|
+
const displayName = currentDirectory && requestedName === '.' ? inferredName : requestedName;
|
|
127
166
|
const directoryName = toProjectDirectoryName(displayName);
|
|
128
167
|
|
|
129
168
|
if (!displayName) {
|
|
@@ -136,16 +175,18 @@ export async function create(name, options = {}) {
|
|
|
136
175
|
process.exit(1);
|
|
137
176
|
}
|
|
138
177
|
|
|
139
|
-
const targetDir = path.resolve(process.cwd(), directoryName);
|
|
178
|
+
const targetDir = currentDirectory ? path.resolve(process.cwd()) : path.resolve(process.cwd(), directoryName);
|
|
179
|
+
const templateDir = path.join(PACKAGE_ROOT, 'template');
|
|
140
180
|
|
|
141
|
-
|
|
142
|
-
|
|
181
|
+
if (currentDirectory) {
|
|
182
|
+
assertCurrentDirectoryCanBeInitialized(targetDir, templateDir);
|
|
183
|
+
} else if (fs.existsSync(targetDir)) {
|
|
143
184
|
console.error(`\n❌ Directory "${directoryName}" already exists.\n`);
|
|
144
185
|
process.exit(1);
|
|
145
186
|
}
|
|
146
187
|
|
|
147
188
|
console.log(`\n🚀 Creating CourseCode project: ${displayName}\n`);
|
|
148
|
-
if (directoryName !== displayName) {
|
|
189
|
+
if (!currentDirectory && directoryName !== displayName) {
|
|
149
190
|
console.log(` Project folder: ${directoryName}`);
|
|
150
191
|
}
|
|
151
192
|
|
|
@@ -153,11 +194,12 @@ export async function create(name, options = {}) {
|
|
|
153
194
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
154
195
|
|
|
155
196
|
// Copy template files (course directory and vite config)
|
|
156
|
-
const templateDir = path.join(PACKAGE_ROOT, 'template');
|
|
157
197
|
console.log(' Copying template files...');
|
|
158
198
|
copyDir(templateDir, targetDir, {
|
|
159
|
-
exclude: [/^\.DS_Store$/, /^node_modules$/]
|
|
199
|
+
exclude: [/^\.DS_Store$/, /^node_modules$/, /^gitignore$/, /^gitattributes$/]
|
|
160
200
|
});
|
|
201
|
+
mergeProjectControlFile(path.join(templateDir, 'gitignore'), path.join(targetDir, '.gitignore'));
|
|
202
|
+
mergeProjectControlFile(path.join(templateDir, 'gitattributes'), path.join(targetDir, '.gitattributes'));
|
|
161
203
|
|
|
162
204
|
// Copy framework
|
|
163
205
|
const frameworkSrc = path.join(PACKAGE_ROOT, 'framework');
|
|
@@ -189,14 +231,19 @@ export async function create(name, options = {}) {
|
|
|
189
231
|
console.log(' Copying proxy packaging templates...');
|
|
190
232
|
copyDir(proxyTemplatesSrc, proxyTemplatesDest);
|
|
191
233
|
|
|
192
|
-
// Read and customize package.json
|
|
234
|
+
// Read and customize package.json. Pin the authoring/build CLI to the
|
|
235
|
+
// version that created the project so an isolated checkout can build.
|
|
236
|
+
const frameworkPkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
|
|
193
237
|
const pkgPath = path.join(targetDir, 'package.json');
|
|
194
238
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
195
239
|
pkg.name = directoryName;
|
|
240
|
+
pkg.devDependencies = {
|
|
241
|
+
...pkg.devDependencies,
|
|
242
|
+
coursecode: `^${frameworkPkg.version}`
|
|
243
|
+
};
|
|
196
244
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
197
245
|
|
|
198
246
|
// Create .coursecoderc.json to track framework version
|
|
199
|
-
const frameworkPkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
|
|
200
247
|
const coursecoderc = {
|
|
201
248
|
frameworkVersion: frameworkPkg.version,
|
|
202
249
|
createdAt: new Date().toISOString(),
|
|
@@ -212,7 +259,7 @@ export async function create(name, options = {}) {
|
|
|
212
259
|
// If --blank, remove example files and reset config
|
|
213
260
|
if (options.blank) {
|
|
214
261
|
const { clean } = await import('./scaffold.js');
|
|
215
|
-
clean({ basePath: targetDir });
|
|
262
|
+
clean({ basePath: targetDir, blank: true });
|
|
216
263
|
}
|
|
217
264
|
|
|
218
265
|
writeCourseTitle(targetDir, displayName);
|
|
@@ -229,12 +276,16 @@ export async function create(name, options = {}) {
|
|
|
229
276
|
}
|
|
230
277
|
|
|
231
278
|
// Initialize git repository
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
console.log('
|
|
236
|
-
|
|
237
|
-
|
|
279
|
+
if (fs.existsSync(path.join(targetDir, '.git'))) {
|
|
280
|
+
console.log('\n ✅ Existing Git repository preserved');
|
|
281
|
+
} else {
|
|
282
|
+
console.log('\n Initializing git repository...');
|
|
283
|
+
try {
|
|
284
|
+
await gitInit(targetDir);
|
|
285
|
+
console.log(' ✅ Git repository initialized');
|
|
286
|
+
} catch (_error) {
|
|
287
|
+
console.warn(' ⚠️ Git init failed. You can run "git init" manually.');
|
|
288
|
+
}
|
|
238
289
|
}
|
|
239
290
|
|
|
240
291
|
// Print success message
|
|
@@ -287,12 +338,14 @@ export async function create(name, options = {}) {
|
|
|
287
338
|
console.log(` cd ${directoryName} && coursecode dev\n`);
|
|
288
339
|
});
|
|
289
340
|
} else {
|
|
290
|
-
|
|
341
|
+
const changeDirectory = currentDirectory ? '' : `cd ${directoryName}\n `;
|
|
342
|
+
console.log(`\n To start developing:\n\n ${changeDirectory}coursecode dev\n`);
|
|
291
343
|
}
|
|
292
344
|
|
|
293
345
|
return {
|
|
294
346
|
displayName,
|
|
295
347
|
directoryName,
|
|
296
|
-
targetDir
|
|
348
|
+
targetDir,
|
|
349
|
+
currentDirectory
|
|
297
350
|
};
|
|
298
351
|
}
|
package/lib/scaffold.js
CHANGED
|
@@ -34,13 +34,24 @@ const MINIMAL_CONFIG = `export const courseConfig = {
|
|
|
34
34
|
};
|
|
35
35
|
`;
|
|
36
36
|
|
|
37
|
+
const DEMO_ASSETS = [
|
|
38
|
+
'docs/example_md_1.md',
|
|
39
|
+
'docs/example_md_2.md',
|
|
40
|
+
'docs/example_pdf_1_thumbnail.png',
|
|
41
|
+
'docs/example_pdf_2.pdf',
|
|
42
|
+
'images/course-architecture.svg',
|
|
43
|
+
'images/logo.svg',
|
|
44
|
+
'widgets/counter-demo.html',
|
|
45
|
+
'widgets/gravity-painter.html'
|
|
46
|
+
];
|
|
47
|
+
|
|
37
48
|
/**
|
|
38
49
|
* Minimal slide template
|
|
39
50
|
*/
|
|
40
51
|
function slideTemplate(id) {
|
|
41
52
|
const title = id.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
42
53
|
return `export const slide = {
|
|
43
|
-
render(
|
|
54
|
+
render(_root, _context) {
|
|
44
55
|
const container = document.createElement('div');
|
|
45
56
|
container.innerHTML = \`
|
|
46
57
|
<h1>${title}</h1>
|
|
@@ -83,9 +94,11 @@ const questions = [
|
|
|
83
94
|
];
|
|
84
95
|
|
|
85
96
|
export const slide = {
|
|
86
|
-
render(
|
|
87
|
-
const
|
|
88
|
-
|
|
97
|
+
render(_root, context = {}) {
|
|
98
|
+
const container = document.createElement('div');
|
|
99
|
+
const assessment = AssessmentManager.createAssessment({ ...config, questions });
|
|
100
|
+
assessment.render(container, context);
|
|
101
|
+
return container;
|
|
89
102
|
}
|
|
90
103
|
};
|
|
91
104
|
`;
|
|
@@ -128,6 +141,22 @@ export function clean(options = {}) {
|
|
|
128
141
|
}
|
|
129
142
|
}
|
|
130
143
|
|
|
144
|
+
// Remove the known demo documents, images, and widgets without touching
|
|
145
|
+
// any author-created assets that may already exist in the project.
|
|
146
|
+
const assetsDir = path.join(coursePath, 'assets');
|
|
147
|
+
for (const relativePath of DEMO_ASSETS) {
|
|
148
|
+
const assetPath = path.join(assetsDir, relativePath);
|
|
149
|
+
if (fs.existsSync(assetPath)) {
|
|
150
|
+
fs.rmSync(assetPath, { force: true });
|
|
151
|
+
removed++;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// A freshly blank scaffold must not inherit the example narration cache.
|
|
156
|
+
if (options.blank) {
|
|
157
|
+
fs.rmSync(path.join(basePath, '.narration-cache.json'), { force: true });
|
|
158
|
+
}
|
|
159
|
+
|
|
131
160
|
// Rewrite course-config.js to minimal starter
|
|
132
161
|
const configPath = path.join(coursePath, 'course-config.js');
|
|
133
162
|
if (fs.existsSync(configPath)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coursecode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.55",
|
|
4
4
|
"description": "Multi-format course authoring framework with CLI tools (SCORM 2004, SCORM 1.2, cmi5, LTI 1.3)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
"!framework/dist/**",
|
|
15
15
|
"schemas",
|
|
16
16
|
"template",
|
|
17
|
-
"!template/.gitattributes",
|
|
18
17
|
"!template/**/.gitkeep",
|
|
19
18
|
"README.md",
|
|
20
19
|
"THIRD_PARTY_NOTICES.md"
|
|
@@ -51,6 +50,7 @@
|
|
|
51
50
|
"lint:responsive:structure": "node scripts/check-responsive-structure-scoping.mjs",
|
|
52
51
|
"lint:fix": "eslint . --fix",
|
|
53
52
|
"smoke:responsive": "node scripts/responsive-visual-smoke.mjs",
|
|
53
|
+
"smoke:packed-create": "node scripts/smoke-packed-create.mjs",
|
|
54
54
|
"build": "npx vite build --config vite.framework-dev.config.js",
|
|
55
55
|
"build:dev": "npx vite build --config vite.framework-dev.config.js",
|
|
56
56
|
"build:scorm2004": "LMS_FORMAT=scorm2004 npx vite build --config vite.framework-dev.config.js",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"test:cloud": "vitest run --config tests/vitest.cloud.config.js",
|
|
70
70
|
"test:watch": "vitest --config tests/vitest.config.js",
|
|
71
71
|
"test:coverage": "vitest run --config tests/vitest.config.js --coverage",
|
|
72
|
-
"prerelease:check": "npm run lint && npm run lint:responsive && npm run lint:responsive:structure && npm run test:coverage && npm run build",
|
|
72
|
+
"prerelease:check": "npm run lint && npm run lint:responsive && npm run lint:responsive:structure && npm run test:coverage && npm run build && npm run smoke:packed-create",
|
|
73
73
|
"prerelease:check:full": "npm run prerelease:check && npm run test:e2e && npm run test:drivers && npm run smoke:responsive -- --profile=expanded",
|
|
74
74
|
"prepublishOnly": "npm run prerelease:check"
|
|
75
75
|
},
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Auto detect text files and perform LF normalization
|
|
2
|
+
* text=auto
|
|
3
|
+
|
|
4
|
+
# Force LF line endings for text files
|
|
5
|
+
*.js text eol=lf
|
|
6
|
+
*.mjs text eol=lf
|
|
7
|
+
*.cjs text eol=lf
|
|
8
|
+
*.json text eol=lf
|
|
9
|
+
*.css text eol=lf
|
|
10
|
+
*.html text eol=lf
|
|
11
|
+
*.htm text eol=lf
|
|
12
|
+
*.xml text eol=lf
|
|
13
|
+
*.xsd text eol=lf
|
|
14
|
+
*.dtd text eol=lf
|
|
15
|
+
*.md text eol=lf
|
|
16
|
+
*.txt text eol=lf
|
|
17
|
+
*.yml text eol=lf
|
|
18
|
+
*.yaml text eol=lf
|
|
19
|
+
*.env text eol=lf
|
|
20
|
+
|
|
21
|
+
# Binary files
|
|
22
|
+
*.png binary
|
|
23
|
+
*.jpg binary
|
|
24
|
+
*.jpeg binary
|
|
25
|
+
*.gif binary
|
|
26
|
+
*.ico binary
|
|
27
|
+
*.webp binary
|
|
28
|
+
*.svg binary
|
|
29
|
+
*.mp3 binary
|
|
30
|
+
*.mp4 binary
|
|
31
|
+
*.wav binary
|
|
32
|
+
*.ogg binary
|
|
33
|
+
*.webm binary
|
|
34
|
+
*.woff binary
|
|
35
|
+
*.woff2 binary
|
|
36
|
+
*.ttf binary
|
|
37
|
+
*.eot binary
|
|
38
|
+
*.otf binary
|
|
39
|
+
*.pdf binary
|
|
40
|
+
*.zip binary
|
|
41
|
+
|
|
42
|
+
# Linguist overrides for GitHub stats
|
|
43
|
+
*.css linguist-detectable=false
|
|
44
|
+
*.xsd linguist-detectable=false
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
dist/
|
|
6
|
+
*.zip
|
|
7
|
+
|
|
8
|
+
# Preview export (comment this line to include in repo for GitHub Pages deployment)
|
|
9
|
+
course-preview/
|
|
10
|
+
|
|
11
|
+
# Environment
|
|
12
|
+
.env
|
|
13
|
+
.env.local
|
|
14
|
+
|
|
15
|
+
# Google Cloud service account credentials (NEVER commit these!)
|
|
16
|
+
**/service-account*.json
|
|
17
|
+
**/*-credentials.json
|
|
18
|
+
**/*-keyfile.json
|
|
19
|
+
|
|
20
|
+
# System files
|
|
21
|
+
.DS_Store
|
|
22
|
+
Thumbs.db
|
|
23
|
+
|
|
24
|
+
# IDE
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
*.swp
|
|
28
|
+
*.swo
|
|
29
|
+
|
|
30
|
+
# CourseCode Cloud (project binding)
|
|
31
|
+
.coursecode/
|
|
32
|
+
|
|
33
|
+
# Narration cache
|
|
34
|
+
.narration-cache.json
|
|
35
|
+
|
|
36
|
+
# Logs
|
|
37
|
+
*.log
|
|
38
|
+
npm-debug.log*
|
package/template/vite.config.js
CHANGED
|
@@ -16,6 +16,13 @@ const ROOT_DIR = path.resolve(__dirname);
|
|
|
16
16
|
const DIST_DIR = path.join(ROOT_DIR, 'dist');
|
|
17
17
|
const SUPPORTED_BROWSER_TARGETS = ['chrome111', 'edge111', 'firefox114', 'safari16.4'];
|
|
18
18
|
|
|
19
|
+
function directoryContainsFiles(directory) {
|
|
20
|
+
if (!fs.existsSync(directory)) return false;
|
|
21
|
+
return fs.readdirSync(directory, { withFileTypes: true }).some(entry => (
|
|
22
|
+
entry.isFile() || (entry.isDirectory() && directoryContainsFiles(path.join(directory, entry.name)))
|
|
23
|
+
));
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
async function loadBuildUtils() {
|
|
20
27
|
if (
|
|
21
28
|
generateManifest &&
|
|
@@ -382,7 +389,10 @@ export default defineConfig(async ({ mode }) => {
|
|
|
382
389
|
{ src: 'schemas/common/*', dest: 'common', rename: { stripBase: 2 } },
|
|
383
390
|
// Publish runtime assets only. Never ship authoring references,
|
|
384
391
|
// configuration source, assessment source, or hidden project files.
|
|
385
|
-
|
|
392
|
+
// A blank course is valid and may not contain any assets yet.
|
|
393
|
+
...(directoryContainsFiles(path.join(ROOT_DIR, 'course', 'assets'))
|
|
394
|
+
? [{ src: 'course/assets', dest: 'course', rename: { stripBase: 1 } }]
|
|
395
|
+
: []),
|
|
386
396
|
{ src: 'framework/js/vendor/**/*', dest: 'js/vendor', rename: { stripBase: 3 } }
|
|
387
397
|
],
|
|
388
398
|
watch: isDev ? { rerun: true } : undefined
|