@lessonkit/cli 0.9.3 → 1.0.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 +20 -34
- package/dist/bin.js +80 -127
- package/dist/index.js +80 -127
- package/package.json +3 -3
- package/template/vite-react/README.md +13 -11
- package/template/vite-react/lessonkit.json +6 -1
- package/template/vite-react/package.json +6 -6
- package/template/vite-react/dist/assets/index-CP3MNJ8s.css +0 -1
- package/template/vite-react/dist/assets/index-Dg5lKLi0.js +0 -8
- package/template/vite-react/dist/index.html +0 -14
package/README.md
CHANGED
|
@@ -1,55 +1,41 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @lessonkit/cli
|
|
2
2
|
|
|
3
|
-
[](https://github.com/eddiethedean/lessonkit/actions/workflows/ci.yml)
|
|
4
|
-
[](https://lessonkit.readthedocs.io/en/latest/)
|
|
5
3
|
[](https://www.npmjs.com/package/@lessonkit/cli)
|
|
4
|
+
[](https://lessonkit.readthedocs.io/en/latest/reference/cli.html)
|
|
6
5
|
[](https://github.com/eddiethedean/lessonkit/blob/main/LICENSE)
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
**Docs:** [CLI reference](https://lessonkit.readthedocs.io/en/latest/reference/cli.html) · [Packaging & CLI guide](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/packaging-and-cli.html) · [Vibe coding: shipping to LMS](https://lessonkit.readthedocs.io/en/latest/guides/vibe-coding/shipping-to-lms.html)
|
|
7
|
+
Scaffold, develop, build, and package LessonKit courses. Node.js **18+**.
|
|
11
8
|
|
|
12
9
|
## Install
|
|
13
10
|
|
|
14
11
|
```bash
|
|
15
12
|
npm install -g @lessonkit/cli
|
|
16
|
-
# or
|
|
13
|
+
# or one-shot:
|
|
17
14
|
npx @lessonkit/cli init my-course
|
|
18
15
|
```
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
## Quick start
|
|
17
|
+
## Commands
|
|
23
18
|
|
|
24
19
|
```bash
|
|
25
|
-
lessonkit init my-course
|
|
26
|
-
|
|
27
|
-
lessonkit
|
|
28
|
-
lessonkit
|
|
29
|
-
lessonkit package --target scorm12
|
|
20
|
+
lessonkit init my-course # scaffold Vite + React project
|
|
21
|
+
lessonkit dev # Vite dev server
|
|
22
|
+
lessonkit build # production build → dist/
|
|
23
|
+
lessonkit package --target scorm12 # LMS artifact
|
|
30
24
|
```
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
|
35
|
-
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `lessonkit build` | Production Vite build |
|
|
39
|
-
| `lessonkit package --target <target>` | Build or package for web / LMS |
|
|
40
|
-
| `lessonkit publish` | Stub — see [`RELEASING.md`](https://github.com/eddiethedean/lessonkit/blob/main/RELEASING.md) |
|
|
41
|
-
|
|
42
|
-
### Package targets
|
|
26
|
+
| Target | Output |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `react-vite` | Vite build only |
|
|
29
|
+
| `scorm12`, `scorm2004` | SCORM package |
|
|
30
|
+
| `standalone` | Self-contained web bundle |
|
|
31
|
+
| `xapi`, `cmi5` | xAPI / cmi5 packages |
|
|
43
32
|
|
|
44
|
-
|
|
45
|
-
- `scorm12`, `scorm2004`, `xapi`, `cmi5`, `standalone` — via `@lessonkit/lxpack`
|
|
33
|
+
Every project includes a root `lessonkit.json` manifest (`schemaVersion: 1`).
|
|
46
34
|
|
|
47
|
-
##
|
|
35
|
+
## Docs
|
|
48
36
|
|
|
49
|
-
|
|
37
|
+
[CLI reference](https://lessonkit.readthedocs.io/en/latest/reference/cli.html) · [Packaging guide](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/packaging-and-cli.html) · [Template source](https://github.com/eddiethedean/lessonkit/tree/main/templates/vite-react)
|
|
50
38
|
|
|
51
|
-
##
|
|
39
|
+
## License
|
|
52
40
|
|
|
53
|
-
-
|
|
54
|
-
- [React quickstart](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/quickstart.html)
|
|
55
|
-
- [`templates/vite-react`](https://github.com/eddiethedean/lessonkit/tree/main/templates/vite-react) — starter template
|
|
41
|
+
Apache-2.0
|
package/dist/bin.js
CHANGED
|
@@ -209,18 +209,13 @@ async function runInit(opts, logger) {
|
|
|
209
209
|
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
210
210
|
import { readFile as readFile2 } from "fs/promises";
|
|
211
211
|
import { dirname as dirname2, join as join2, parse, resolve as resolve2 } from "path";
|
|
212
|
-
import {
|
|
212
|
+
import { parseLessonkitManifest } from "@lessonkit/lxpack";
|
|
213
213
|
var LESSONKIT_JSON = "lessonkit.json";
|
|
214
214
|
var PACKAGE_JSON = "package.json";
|
|
215
|
-
var DEFAULT_PATHS = {
|
|
216
|
-
spaDistDir: "dist",
|
|
217
|
-
lxpackOutDir: ".lxpack/course",
|
|
218
|
-
outputBaseDir: ".lxpack/out"
|
|
219
|
-
};
|
|
220
215
|
function isProjectManifest(configPath) {
|
|
221
216
|
try {
|
|
222
217
|
const raw = JSON.parse(readFileSync(configPath, "utf8"));
|
|
223
|
-
return raw.schemaVersion === 1 && typeof raw.name === "string" && raw.course !== null && typeof raw.course === "object";
|
|
218
|
+
return raw.schemaVersion === 1 && typeof raw.name === "string" && raw.course !== null && typeof raw.course === "object" && !Array.isArray(raw.course);
|
|
224
219
|
} catch {
|
|
225
220
|
return false;
|
|
226
221
|
}
|
|
@@ -253,124 +248,66 @@ async function loadLessonkitJson(projectRoot) {
|
|
|
253
248
|
exitCode: EXIT_INVALID_PROJECT
|
|
254
249
|
});
|
|
255
250
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
exitCode: EXIT_INVALID_PROJECT
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
const config = raw;
|
|
263
|
-
const schemaVersion = config.schemaVersion;
|
|
264
|
-
if (schemaVersion !== 1) {
|
|
265
|
-
throw new CliError(`${configPath}: schemaVersion must be 1 (got ${String(schemaVersion)}).`, {
|
|
266
|
-
code: "INVALID_PROJECT",
|
|
267
|
-
exitCode: EXIT_INVALID_PROJECT
|
|
268
|
-
});
|
|
251
|
+
const parsed = parseLessonkitManifest(raw, configPath, projectRoot);
|
|
252
|
+
if (!parsed.ok) {
|
|
253
|
+
throwManifestCliError(configPath, parsed.issues);
|
|
269
254
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
255
|
+
return {
|
|
256
|
+
root: projectRoot,
|
|
257
|
+
schemaVersion: 1,
|
|
258
|
+
name: parsed.manifest.name,
|
|
259
|
+
course: parsed.manifest.course,
|
|
260
|
+
paths: parsed.manifest.paths
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function throwManifestCliError(configPath, issues) {
|
|
264
|
+
const layoutIssue = issues.find((i) => i.path === "course.layout");
|
|
265
|
+
if (layoutIssue?.message.includes("per-lesson-spa")) {
|
|
266
|
+
throw new CliError(
|
|
267
|
+
`${configPath}: per-lesson-spa layout is not supported by lessonkit package yet. Use single-spa or package via @lessonkit/lxpack directly.`,
|
|
268
|
+
{ code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT }
|
|
269
|
+
);
|
|
276
270
|
}
|
|
277
|
-
const
|
|
278
|
-
if (
|
|
279
|
-
throw new CliError(`${configPath}: "course" must be an
|
|
271
|
+
const lessonsIssue = issues.find((i) => i.path === "course.lessons");
|
|
272
|
+
if (lessonsIssue) {
|
|
273
|
+
throw new CliError(`${configPath}: "course.lessons" must be an array.`, {
|
|
280
274
|
code: "INVALID_PROJECT",
|
|
281
275
|
exitCode: EXIT_INVALID_PROJECT
|
|
282
276
|
});
|
|
283
277
|
}
|
|
284
|
-
const
|
|
285
|
-
if (
|
|
286
|
-
throw new CliError(`${configPath}: "
|
|
278
|
+
const spaDistTypeIssue = issues.find((i) => i.path === "paths.spaDistDir");
|
|
279
|
+
if (spaDistTypeIssue && spaDistTypeIssue.message.includes("non-empty string")) {
|
|
280
|
+
throw new CliError(`${configPath}: "paths.spaDistDir" must be a non-empty string.`, {
|
|
287
281
|
code: "INVALID_PROJECT",
|
|
288
282
|
exitCode: EXIT_INVALID_PROJECT
|
|
289
283
|
});
|
|
290
284
|
}
|
|
291
|
-
|
|
292
|
-
|
|
285
|
+
const courseSpaIssue = issues.find((i) => i.path === "course.spaDistDir");
|
|
286
|
+
if (courseSpaIssue) {
|
|
287
|
+
throw new CliError(`${configPath}: ${courseSpaIssue.message}`, {
|
|
293
288
|
code: "INVALID_PROJECT",
|
|
294
289
|
exitCode: EXIT_INVALID_PROJECT
|
|
295
290
|
});
|
|
296
291
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
throw new CliError(`${configPath}: invalid course descriptor.`, {
|
|
292
|
+
if (issues.some((i) => i.path.startsWith("paths."))) {
|
|
293
|
+
throw new CliError(`${configPath}: invalid paths.`, {
|
|
300
294
|
code: "INVALID_PROJECT",
|
|
301
295
|
exitCode: EXIT_INVALID_PROJECT,
|
|
302
|
-
issues:
|
|
303
|
-
path: i.path,
|
|
304
|
-
message: i.message
|
|
305
|
-
}))
|
|
296
|
+
issues: issues.map((i) => ({ path: i.path, message: i.message }))
|
|
306
297
|
});
|
|
307
298
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
{ code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT }
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
const pathsRaw = config.paths;
|
|
315
|
-
const paths = { ...DEFAULT_PATHS };
|
|
316
|
-
if (pathsRaw !== void 0 && (typeof pathsRaw !== "object" || pathsRaw === null)) {
|
|
317
|
-
throw new CliError(`${configPath}: "paths" must be an object.`, {
|
|
299
|
+
const schemaIssue = issues.find((i) => i.path === "schemaVersion");
|
|
300
|
+
if (schemaIssue) {
|
|
301
|
+
throw new CliError(`${configPath}: schemaVersion must be 1 (got ${schemaIssue.message.replace(/^must be 1 \(got /, "").replace(/\)$/, "")}).`, {
|
|
318
302
|
code: "INVALID_PROJECT",
|
|
319
303
|
exitCode: EXIT_INVALID_PROJECT
|
|
320
304
|
});
|
|
321
305
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
code: "INVALID_PROJECT",
|
|
328
|
-
exitCode: EXIT_INVALID_PROJECT
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
paths.spaDistDir = p.spaDistDir;
|
|
332
|
-
}
|
|
333
|
-
if (p.lxpackOutDir !== void 0) {
|
|
334
|
-
if (typeof p.lxpackOutDir !== "string" || !p.lxpackOutDir.trim()) {
|
|
335
|
-
throw new CliError(`${configPath}: "paths.lxpackOutDir" must be a non-empty string.`, {
|
|
336
|
-
code: "INVALID_PROJECT",
|
|
337
|
-
exitCode: EXIT_INVALID_PROJECT
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
paths.lxpackOutDir = p.lxpackOutDir;
|
|
341
|
-
}
|
|
342
|
-
if (p.outputBaseDir !== void 0) {
|
|
343
|
-
if (typeof p.outputBaseDir !== "string" || !p.outputBaseDir.trim()) {
|
|
344
|
-
throw new CliError(`${configPath}: "paths.outputBaseDir" must be a non-empty string.`, {
|
|
345
|
-
code: "INVALID_PROJECT",
|
|
346
|
-
exitCode: EXIT_INVALID_PROJECT
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
paths.outputBaseDir = p.outputBaseDir;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
const courseSpaDistDir = validation.descriptor.spaDistDir?.trim();
|
|
353
|
-
if (courseSpaDistDir && courseSpaDistDir !== paths.spaDistDir) {
|
|
354
|
-
throw new CliError(
|
|
355
|
-
`${configPath}: "course.spaDistDir" (${courseSpaDistDir}) differs from "paths.spaDistDir" (${paths.spaDistDir}). Use paths.spaDistDir for CLI build and package.`,
|
|
356
|
-
{ code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT }
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
const pathIssues = validateProjectPaths(projectRoot, paths);
|
|
360
|
-
if (pathIssues.length) {
|
|
361
|
-
throw new CliError(`${configPath}: invalid paths.`, {
|
|
362
|
-
code: "INVALID_PROJECT",
|
|
363
|
-
exitCode: EXIT_INVALID_PROJECT,
|
|
364
|
-
issues: pathIssues
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
return {
|
|
368
|
-
root: projectRoot,
|
|
369
|
-
schemaVersion: 1,
|
|
370
|
-
name,
|
|
371
|
-
course: validation.descriptor,
|
|
372
|
-
paths
|
|
373
|
-
};
|
|
306
|
+
throw new CliError(`${configPath}: invalid lessonkit manifest.`, {
|
|
307
|
+
code: "INVALID_PROJECT",
|
|
308
|
+
exitCode: EXIT_INVALID_PROJECT,
|
|
309
|
+
issues: issues.map((i) => ({ path: i.path, message: i.message }))
|
|
310
|
+
});
|
|
374
311
|
}
|
|
375
312
|
async function loadProject(cwd = process.cwd()) {
|
|
376
313
|
const root = findProjectRoot(cwd);
|
|
@@ -388,7 +325,7 @@ async function readPackageJson(projectRoot) {
|
|
|
388
325
|
}
|
|
389
326
|
}
|
|
390
327
|
function assertViteProject(pkg, projectRoot) {
|
|
391
|
-
const vite = pkg.devDependencies?.vite ?? pkg.dependencies?.vite ?? (existsSync2(join2(projectRoot, "node_modules", ".bin", "vite")) || existsSync2(join2(projectRoot, "node_modules", ".bin", "vite.cmd")) ? "present" : void 0);
|
|
328
|
+
const vite = pkg.devDependencies?.vite ?? pkg.dependencies?.vite ?? (existsSync2(join2(projectRoot, "node_modules", "vite", "bin", "vite.js")) || existsSync2(join2(projectRoot, "node_modules", ".bin", "vite")) || existsSync2(join2(projectRoot, "node_modules", ".bin", "vite.cmd")) ? "present" : void 0);
|
|
392
329
|
if (!vite) {
|
|
393
330
|
throw new CliError(
|
|
394
331
|
`No Vite dependency found in ${join2(projectRoot, PACKAGE_JSON)}. LessonKit projects require Vite.`,
|
|
@@ -396,28 +333,25 @@ function assertViteProject(pkg, projectRoot) {
|
|
|
396
333
|
);
|
|
397
334
|
}
|
|
398
335
|
}
|
|
399
|
-
function
|
|
336
|
+
function resolveViteJs(projectRoot) {
|
|
400
337
|
let dir = resolve2(projectRoot);
|
|
401
338
|
const fsRoot = parse(dir).root;
|
|
402
339
|
while (true) {
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
if (existsSync2(bin)) return bin;
|
|
406
|
-
const binCmd = join2(binDir, "vite.cmd");
|
|
407
|
-
if (existsSync2(binCmd)) return binCmd;
|
|
340
|
+
const viteJs = join2(dir, "node_modules", "vite", "bin", "vite.js");
|
|
341
|
+
if (existsSync2(viteJs)) return viteJs;
|
|
408
342
|
if (dir === fsRoot) break;
|
|
409
343
|
dir = dirname2(dir);
|
|
410
344
|
}
|
|
411
345
|
throw new CliError(
|
|
412
|
-
`Vite
|
|
346
|
+
`Vite not found near ${projectRoot}. Run npm install in the project first.`,
|
|
413
347
|
{ code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT }
|
|
414
348
|
);
|
|
415
349
|
}
|
|
416
|
-
function
|
|
350
|
+
function assertNode18ForLxpack() {
|
|
417
351
|
const major = Number(process.versions.node.split(".")[0]);
|
|
418
|
-
if (major <
|
|
352
|
+
if (major < 18) {
|
|
419
353
|
throw new CliError(
|
|
420
|
-
`LMS packaging requires Node.js
|
|
354
|
+
`LMS packaging requires Node.js 18+ (current: ${process.versions.node}). See docs/PACKAGING.md.`,
|
|
421
355
|
{ code: "NODE_VERSION", exitCode: EXIT_INVALID_PROJECT }
|
|
422
356
|
);
|
|
423
357
|
}
|
|
@@ -479,17 +413,19 @@ async function runDev(opts) {
|
|
|
479
413
|
const project = await loadProject(opts.cwd ?? process.cwd());
|
|
480
414
|
const pkg = await readPackageJson(project.root);
|
|
481
415
|
assertViteProject(pkg, project.root);
|
|
482
|
-
const
|
|
483
|
-
await runCommand(
|
|
416
|
+
const viteJs = resolveViteJs(project.root);
|
|
417
|
+
await runCommand(process.execPath, [viteJs, ...opts.viteArgs ?? []], { cwd: project.root });
|
|
484
418
|
return { ok: true, command: "dev", projectRoot: project.root };
|
|
485
419
|
}
|
|
486
420
|
async function runBuild(opts) {
|
|
487
421
|
const project = await loadProject(opts.cwd ?? process.cwd());
|
|
488
422
|
const pkg = await readPackageJson(project.root);
|
|
489
423
|
assertViteProject(pkg, project.root);
|
|
490
|
-
const
|
|
424
|
+
const viteJs = resolveViteJs(project.root);
|
|
491
425
|
const buildArgs = resolveViteBuildArgs(project);
|
|
492
|
-
await runCommand(
|
|
426
|
+
await runCommand(process.execPath, [viteJs, ...buildArgs, ...opts.viteArgs ?? []], {
|
|
427
|
+
cwd: project.root
|
|
428
|
+
});
|
|
493
429
|
return { ok: true, command: "build", projectRoot: project.root };
|
|
494
430
|
}
|
|
495
431
|
|
|
@@ -524,7 +460,7 @@ async function runPackage(opts) {
|
|
|
524
460
|
}
|
|
525
461
|
return { ok: true, target, projectRoot: project.root, distDir };
|
|
526
462
|
}
|
|
527
|
-
|
|
463
|
+
assertNode18ForLxpack();
|
|
528
464
|
if (!opts.noBuild || !existsSync3(distDir)) {
|
|
529
465
|
await runBuild({ cwd: project.root, json: opts.json });
|
|
530
466
|
}
|
|
@@ -598,7 +534,10 @@ async function handleCommand(fn, logger, json) {
|
|
|
598
534
|
function createProgram(baseLogger = console) {
|
|
599
535
|
const program = new Command();
|
|
600
536
|
program.name("lessonkit").description("LessonKit CLI").version(version);
|
|
601
|
-
program.command("init").description("Initialize a LessonKit project from the Vite + React template").argument("[name]", "Project directory name").option("--here", "Initialize in the current directory").option("--skip-install", "Skip npm install").option(
|
|
537
|
+
program.command("init").description("Initialize a LessonKit project from the Vite + React template").argument("[name]", "Project directory name").option("--here", "Initialize in the current directory").option("--skip-install", "Skip npm install").option(
|
|
538
|
+
"--force",
|
|
539
|
+
"With --here, allow init when the directory is empty or contains only dotfiles"
|
|
540
|
+
).option("--json", "Emit structured JSON result").action(async (name, opts) => {
|
|
602
541
|
const logger = createLogger({ json: opts.json });
|
|
603
542
|
await handleCommand(
|
|
604
543
|
() => runInit({ name, here: opts.here, skipInstall: opts.skipInstall, force: opts.force, json: opts.json }, logger),
|
|
@@ -632,13 +571,27 @@ function createProgram(baseLogger = console) {
|
|
|
632
571
|
program.command("package").description("Build or package for web / LMS delivery").requiredOption("--target <target>", `Export target (${PACKAGE_TARGETS.join(", ")})`).option("--cwd <dir>", "Project root directory").option("--no-build", "Skip implicit Vite build for LMS targets").option("--out <path>", "Override output artifact path").option("--json", "Emit structured JSON result").action(async (opts) => {
|
|
633
572
|
const logger = createLogger({ json: opts.json });
|
|
634
573
|
await handleCommand(
|
|
635
|
-
() =>
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
574
|
+
async () => {
|
|
575
|
+
const result = await runPackage({
|
|
576
|
+
target: opts.target,
|
|
577
|
+
cwd: opts.cwd,
|
|
578
|
+
noBuild: opts.build === false,
|
|
579
|
+
out: opts.out,
|
|
580
|
+
json: opts.json
|
|
581
|
+
});
|
|
582
|
+
if (!opts.json && result.ok) {
|
|
583
|
+
if (result.target === "react-vite" && "distDir" in result) {
|
|
584
|
+
logger.log(`Built react-vite \u2192 ${result.distDir}`);
|
|
585
|
+
} else if ("outputPath" in result || "outputDir" in result) {
|
|
586
|
+
const dest = result.outputPath ?? result.outputDir;
|
|
587
|
+
const count = "fileCount" in result ? result.fileCount : void 0;
|
|
588
|
+
logger.log(
|
|
589
|
+
`Packaged ${result.target}${dest ? ` \u2192 ${dest}` : ""}${count != null ? ` (${count} files)` : ""}`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return result;
|
|
594
|
+
},
|
|
642
595
|
logger,
|
|
643
596
|
Boolean(opts.json)
|
|
644
597
|
);
|