coursecode 0.1.54 → 0.1.56
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 +103 -19
- 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; `create .` derives a title-cased course title and hyphenated npm name from the current folder |
|
|
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 || '.', { ...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; `client-manager-course` becomes title `Client Manager Course` and package name `client-manager-course` |
|
|
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.56",
|
|
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
|
@@ -21,6 +21,32 @@ export function toProjectDirectoryName(name) {
|
|
|
21
21
|
.replace(/^_+|_+$/g, '');
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function directoryNameWords(name) {
|
|
25
|
+
return String(name || '')
|
|
26
|
+
.trim()
|
|
27
|
+
.normalize('NFKD')
|
|
28
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
29
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
30
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
31
|
+
.replace(/['’]/g, '')
|
|
32
|
+
.replace(/[^a-zA-Z0-9]+/g, ' ')
|
|
33
|
+
.trim()
|
|
34
|
+
.split(/\s+/)
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function toCurrentDirectoryCourseTitle(name) {
|
|
39
|
+
return directoryNameWords(name)
|
|
40
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
41
|
+
.join(' ');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function toCurrentDirectoryPackageName(name) {
|
|
45
|
+
return directoryNameWords(name)
|
|
46
|
+
.map(word => word.toLowerCase())
|
|
47
|
+
.join('-');
|
|
48
|
+
}
|
|
49
|
+
|
|
24
50
|
function escapeSingleQuotedValue(value) {
|
|
25
51
|
return String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
26
52
|
}
|
|
@@ -77,6 +103,42 @@ function copyDir(src, dest, options = {}) {
|
|
|
77
103
|
}
|
|
78
104
|
}
|
|
79
105
|
|
|
106
|
+
function mergeProjectControlFile(sourcePath, destinationPath) {
|
|
107
|
+
const source = fs.readFileSync(sourcePath, 'utf-8').trimEnd();
|
|
108
|
+
if (!fs.existsSync(destinationPath)) {
|
|
109
|
+
fs.writeFileSync(destinationPath, `${source}\n`, 'utf-8');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const current = fs.readFileSync(destinationPath, 'utf-8');
|
|
114
|
+
const existingLines = new Set(current.split(/\r?\n/).map(line => line.trim()));
|
|
115
|
+
const missingRules = source
|
|
116
|
+
.split(/\r?\n/)
|
|
117
|
+
.filter(line => line.trim() && !line.trim().startsWith('#') && !existingLines.has(line.trim()));
|
|
118
|
+
|
|
119
|
+
if (missingRules.length === 0) return;
|
|
120
|
+
|
|
121
|
+
const separator = current.endsWith('\n') ? '\n' : '\n\n';
|
|
122
|
+
fs.appendFileSync(
|
|
123
|
+
destinationPath,
|
|
124
|
+
`${separator}# CourseCode project defaults\n${missingRules.join('\n')}\n`,
|
|
125
|
+
'utf-8'
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function assertCurrentDirectoryCanBeInitialized(targetDir, templateDir) {
|
|
130
|
+
const managedPaths = fs.readdirSync(templateDir)
|
|
131
|
+
.filter(name => !['.DS_Store', 'gitignore', 'gitattributes'].includes(name))
|
|
132
|
+
.concat(['framework', 'schemas', 'lib', '.coursecoderc.json']);
|
|
133
|
+
const conflicts = managedPaths.filter(name => fs.existsSync(path.join(targetDir, name)));
|
|
134
|
+
|
|
135
|
+
if (conflicts.length > 0) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Cannot initialize the current directory because CourseCode-managed paths already exist: ${conflicts.join(', ')}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
80
142
|
/**
|
|
81
143
|
* Run npm install in directory
|
|
82
144
|
*/
|
|
@@ -123,8 +185,16 @@ function gitInit(cwd) {
|
|
|
123
185
|
}
|
|
124
186
|
|
|
125
187
|
export async function create(name, options = {}) {
|
|
126
|
-
const
|
|
127
|
-
const
|
|
188
|
+
const requestedName = String(name || '').trim();
|
|
189
|
+
const currentDirectory = options.currentDirectory === true || requestedName === '.';
|
|
190
|
+
const inferFromCurrentDirectory = currentDirectory && requestedName === '.';
|
|
191
|
+
const currentDirectoryName = path.basename(process.cwd());
|
|
192
|
+
const displayName = inferFromCurrentDirectory
|
|
193
|
+
? toCurrentDirectoryCourseTitle(currentDirectoryName)
|
|
194
|
+
: requestedName;
|
|
195
|
+
const directoryName = inferFromCurrentDirectory
|
|
196
|
+
? toCurrentDirectoryPackageName(currentDirectoryName)
|
|
197
|
+
: toProjectDirectoryName(displayName);
|
|
128
198
|
|
|
129
199
|
if (!displayName) {
|
|
130
200
|
console.error('\n❌ Course name is required.\n');
|
|
@@ -136,16 +206,18 @@ export async function create(name, options = {}) {
|
|
|
136
206
|
process.exit(1);
|
|
137
207
|
}
|
|
138
208
|
|
|
139
|
-
const targetDir = path.resolve(process.cwd(), directoryName);
|
|
209
|
+
const targetDir = currentDirectory ? path.resolve(process.cwd()) : path.resolve(process.cwd(), directoryName);
|
|
210
|
+
const templateDir = path.join(PACKAGE_ROOT, 'template');
|
|
140
211
|
|
|
141
|
-
|
|
142
|
-
|
|
212
|
+
if (currentDirectory) {
|
|
213
|
+
assertCurrentDirectoryCanBeInitialized(targetDir, templateDir);
|
|
214
|
+
} else if (fs.existsSync(targetDir)) {
|
|
143
215
|
console.error(`\n❌ Directory "${directoryName}" already exists.\n`);
|
|
144
216
|
process.exit(1);
|
|
145
217
|
}
|
|
146
218
|
|
|
147
219
|
console.log(`\n🚀 Creating CourseCode project: ${displayName}\n`);
|
|
148
|
-
if (directoryName !== displayName) {
|
|
220
|
+
if (!currentDirectory && directoryName !== displayName) {
|
|
149
221
|
console.log(` Project folder: ${directoryName}`);
|
|
150
222
|
}
|
|
151
223
|
|
|
@@ -153,11 +225,12 @@ export async function create(name, options = {}) {
|
|
|
153
225
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
154
226
|
|
|
155
227
|
// Copy template files (course directory and vite config)
|
|
156
|
-
const templateDir = path.join(PACKAGE_ROOT, 'template');
|
|
157
228
|
console.log(' Copying template files...');
|
|
158
229
|
copyDir(templateDir, targetDir, {
|
|
159
|
-
exclude: [/^\.DS_Store$/, /^node_modules$/]
|
|
230
|
+
exclude: [/^\.DS_Store$/, /^node_modules$/, /^gitignore$/, /^gitattributes$/]
|
|
160
231
|
});
|
|
232
|
+
mergeProjectControlFile(path.join(templateDir, 'gitignore'), path.join(targetDir, '.gitignore'));
|
|
233
|
+
mergeProjectControlFile(path.join(templateDir, 'gitattributes'), path.join(targetDir, '.gitattributes'));
|
|
161
234
|
|
|
162
235
|
// Copy framework
|
|
163
236
|
const frameworkSrc = path.join(PACKAGE_ROOT, 'framework');
|
|
@@ -189,14 +262,19 @@ export async function create(name, options = {}) {
|
|
|
189
262
|
console.log(' Copying proxy packaging templates...');
|
|
190
263
|
copyDir(proxyTemplatesSrc, proxyTemplatesDest);
|
|
191
264
|
|
|
192
|
-
// Read and customize package.json
|
|
265
|
+
// Read and customize package.json. Pin the authoring/build CLI to the
|
|
266
|
+
// version that created the project so an isolated checkout can build.
|
|
267
|
+
const frameworkPkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
|
|
193
268
|
const pkgPath = path.join(targetDir, 'package.json');
|
|
194
269
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
195
270
|
pkg.name = directoryName;
|
|
271
|
+
pkg.devDependencies = {
|
|
272
|
+
...pkg.devDependencies,
|
|
273
|
+
coursecode: `^${frameworkPkg.version}`
|
|
274
|
+
};
|
|
196
275
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
197
276
|
|
|
198
277
|
// Create .coursecoderc.json to track framework version
|
|
199
|
-
const frameworkPkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
|
|
200
278
|
const coursecoderc = {
|
|
201
279
|
frameworkVersion: frameworkPkg.version,
|
|
202
280
|
createdAt: new Date().toISOString(),
|
|
@@ -212,7 +290,7 @@ export async function create(name, options = {}) {
|
|
|
212
290
|
// If --blank, remove example files and reset config
|
|
213
291
|
if (options.blank) {
|
|
214
292
|
const { clean } = await import('./scaffold.js');
|
|
215
|
-
clean({ basePath: targetDir });
|
|
293
|
+
clean({ basePath: targetDir, blank: true });
|
|
216
294
|
}
|
|
217
295
|
|
|
218
296
|
writeCourseTitle(targetDir, displayName);
|
|
@@ -229,12 +307,16 @@ export async function create(name, options = {}) {
|
|
|
229
307
|
}
|
|
230
308
|
|
|
231
309
|
// Initialize git repository
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
console.log('
|
|
236
|
-
|
|
237
|
-
|
|
310
|
+
if (fs.existsSync(path.join(targetDir, '.git'))) {
|
|
311
|
+
console.log('\n ✅ Existing Git repository preserved');
|
|
312
|
+
} else {
|
|
313
|
+
console.log('\n Initializing git repository...');
|
|
314
|
+
try {
|
|
315
|
+
await gitInit(targetDir);
|
|
316
|
+
console.log(' ✅ Git repository initialized');
|
|
317
|
+
} catch (_error) {
|
|
318
|
+
console.warn(' ⚠️ Git init failed. You can run "git init" manually.');
|
|
319
|
+
}
|
|
238
320
|
}
|
|
239
321
|
|
|
240
322
|
// Print success message
|
|
@@ -287,12 +369,14 @@ export async function create(name, options = {}) {
|
|
|
287
369
|
console.log(` cd ${directoryName} && coursecode dev\n`);
|
|
288
370
|
});
|
|
289
371
|
} else {
|
|
290
|
-
|
|
372
|
+
const changeDirectory = currentDirectory ? '' : `cd ${directoryName}\n `;
|
|
373
|
+
console.log(`\n To start developing:\n\n ${changeDirectory}coursecode dev\n`);
|
|
291
374
|
}
|
|
292
375
|
|
|
293
376
|
return {
|
|
294
377
|
displayName,
|
|
295
378
|
directoryName,
|
|
296
|
-
targetDir
|
|
379
|
+
targetDir,
|
|
380
|
+
currentDirectory
|
|
297
381
|
};
|
|
298
382
|
}
|
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.56",
|
|
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
|