coursecode 0.1.55 → 0.1.57

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 CHANGED
@@ -235,7 +235,7 @@ coursecode preview --export
235
235
 
236
236
  | Command | Description |
237
237
  |---------|-------------|
238
- | `coursecode create <name>` | Create a new course project; use `.` to initialize the current directory |
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
239
  | `coursecode init [name]` | Initialize the current directory, optionally with a course title |
240
240
  | `coursecode preview` | Preview your course locally |
241
241
  | `coursecode convert` | Convert PDFs, Word, PowerPoint to markdown |
package/bin/cli.js CHANGED
@@ -97,7 +97,7 @@ program
97
97
  .option('--start', 'Auto-start dev server after creation (skip prompt)')
98
98
  .action(async (name, options) => {
99
99
  const { create } = await import('../lib/create.js');
100
- await create(name || path.basename(process.cwd()), { ...options, currentDirectory: true });
100
+ await create(name || '.', { ...options, currentDirectory: true });
101
101
  });
102
102
 
103
103
  // Dev command
@@ -16,7 +16,7 @@
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 |
19
+ | `coursecode create .` | Initialize the current directory; `client-manager-course` becomes title `Client Manager Course` and package name `client-manager-course` |
20
20
  | `coursecode init [name]` | Initialize the current directory, optionally with a specific course title |
21
21
  | `coursecode clean` | Remove example files and reset config to minimal starter |
22
22
  | `coursecode new slide <id>` | Create a new slide file in `course/slides/` |
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.55",
2
+ "version": "0.1.57",
3
3
  "name": "CourseCode Framework",
4
4
  "description": "Multi-format course authoring and learner runtime framework",
5
5
  "released": "2026-07-11",
@@ -9,7 +9,7 @@
9
9
 
10
10
  import fs from 'fs';
11
11
  import path from 'path';
12
- import archiver from 'archiver';
12
+ import { ZipArchive } from 'archiver';
13
13
  import { fileURLToPath } from 'url';
14
14
  import { generateManifest } from './manifest/manifest-factory.js';
15
15
 
@@ -35,7 +35,7 @@ function withClientCredentials(externalUrl, clientId, token) {
35
35
  function zipDirectory(sourceDir, zipFilePath) {
36
36
  return new Promise((resolve, reject) => {
37
37
  const output = fs.createWriteStream(zipFilePath);
38
- const archive = archiver('zip', { zlib: { level: 9 } });
38
+ const archive = new ZipArchive({ zlib: { level: 9 } });
39
39
 
40
40
  output.on('close', () => resolve(archive.pointer()));
41
41
  archive.on('error', reject);
package/lib/create.js CHANGED
@@ -9,6 +9,16 @@ import { spawn } from 'child_process';
9
9
 
10
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
11
  const PACKAGE_ROOT = path.join(__dirname, '..');
12
+ const REQUIRED_PROJECT_DIRECTORIES = [
13
+ {
14
+ path: path.join('course', 'references'),
15
+ placeholder: '# Place source documents here (docx, pptx, pdf)\n# Run `coursecode convert` to convert them to markdown\n'
16
+ },
17
+ {
18
+ path: path.join('course', 'references', 'converted'),
19
+ placeholder: '# Converted markdown files will be placed here\n# These can be used as source material for AI-assisted course creation\n'
20
+ }
21
+ ];
12
22
 
13
23
  export function toProjectDirectoryName(name) {
14
24
  return String(name || '')
@@ -21,6 +31,32 @@ export function toProjectDirectoryName(name) {
21
31
  .replace(/^_+|_+$/g, '');
22
32
  }
23
33
 
34
+ function directoryNameWords(name) {
35
+ return String(name || '')
36
+ .trim()
37
+ .normalize('NFKD')
38
+ .replace(/[\u0300-\u036f]/g, '')
39
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
40
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
41
+ .replace(/['’]/g, '')
42
+ .replace(/[^a-zA-Z0-9]+/g, ' ')
43
+ .trim()
44
+ .split(/\s+/)
45
+ .filter(Boolean);
46
+ }
47
+
48
+ export function toCurrentDirectoryCourseTitle(name) {
49
+ return directoryNameWords(name)
50
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
51
+ .join(' ');
52
+ }
53
+
54
+ export function toCurrentDirectoryPackageName(name) {
55
+ return directoryNameWords(name)
56
+ .map(word => word.toLowerCase())
57
+ .join('-');
58
+ }
59
+
24
60
  function escapeSingleQuotedValue(value) {
25
61
  return String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'");
26
62
  }
@@ -77,6 +113,18 @@ function copyDir(src, dest, options = {}) {
77
113
  }
78
114
  }
79
115
 
116
+ function ensureRequiredProjectDirectories(projectDir) {
117
+ for (const directory of REQUIRED_PROJECT_DIRECTORIES) {
118
+ const directoryPath = path.join(projectDir, directory.path);
119
+ const placeholderPath = path.join(directoryPath, '.gitkeep');
120
+ fs.mkdirSync(directoryPath, { recursive: true });
121
+
122
+ if (!fs.existsSync(placeholderPath)) {
123
+ fs.writeFileSync(placeholderPath, directory.placeholder, 'utf-8');
124
+ }
125
+ }
126
+ }
127
+
80
128
  function mergeProjectControlFile(sourcePath, destinationPath) {
81
129
  const source = fs.readFileSync(sourcePath, 'utf-8').trimEnd();
82
130
  if (!fs.existsSync(destinationPath)) {
@@ -161,9 +209,14 @@ function gitInit(cwd) {
161
209
  export async function create(name, options = {}) {
162
210
  const requestedName = String(name || '').trim();
163
211
  const currentDirectory = options.currentDirectory === true || requestedName === '.';
164
- const inferredName = path.basename(process.cwd()).replace(/[-_]+/g, ' ');
165
- const displayName = currentDirectory && requestedName === '.' ? inferredName : requestedName;
166
- const directoryName = toProjectDirectoryName(displayName);
212
+ const inferFromCurrentDirectory = currentDirectory && requestedName === '.';
213
+ const currentDirectoryName = path.basename(process.cwd());
214
+ const displayName = inferFromCurrentDirectory
215
+ ? toCurrentDirectoryCourseTitle(currentDirectoryName)
216
+ : requestedName;
217
+ const directoryName = inferFromCurrentDirectory
218
+ ? toCurrentDirectoryPackageName(currentDirectoryName)
219
+ : toProjectDirectoryName(displayName);
167
220
 
168
221
  if (!displayName) {
169
222
  console.error('\n❌ Course name is required.\n');
@@ -200,6 +253,7 @@ export async function create(name, options = {}) {
200
253
  });
201
254
  mergeProjectControlFile(path.join(templateDir, 'gitignore'), path.join(targetDir, '.gitignore'));
202
255
  mergeProjectControlFile(path.join(templateDir, 'gitattributes'), path.join(targetDir, '.gitattributes'));
256
+ ensureRequiredProjectDirectories(targetDir);
203
257
 
204
258
  // Copy framework
205
259
  const frameworkSrc = path.join(PACKAGE_ROOT, 'framework');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coursecode",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
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": {
@@ -104,7 +104,7 @@
104
104
  "dependencies": {
105
105
  "@modelcontextprotocol/sdk": "^1.29.0",
106
106
  "acorn": "^8.17.0",
107
- "archiver": "^7.0.1",
107
+ "archiver": "^8.0.0",
108
108
  "commander": "^14.0.3",
109
109
  "lz-string": "^1.5.0",
110
110
  "mammoth": "^1.12.0",
@@ -21,7 +21,7 @@
21
21
  "marked-gfm-heading-id": "^4.1.4"
22
22
  },
23
23
  "devDependencies": {
24
- "archiver": "^7.0.1",
24
+ "archiver": "^8.0.0",
25
25
  "eslint": "^9.39.4",
26
26
  "vite": "~8.1.4",
27
27
  "vite-plugin-static-copy": "^4.1.1"