@lxpack/cli 0.1.0 → 0.2.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 +127 -0
- package/dist/cli.d.ts +5 -5
- package/dist/cli.js +124 -47
- package/package.json +5 -4
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# @lxpack/cli
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@lxpack/cli)
|
|
4
|
+
[](https://github.com/eddiethedean/lxpack/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/eddiethedean/lxpack/blob/main/LICENSE)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
|
|
8
|
+
Command-line tool for scaffolding, previewing, validating, and packaging LXPack courses.
|
|
9
|
+
|
|
10
|
+
Part of [LXPack](https://github.com/eddiethedean/lxpack) — an AI-native learning experience compiler and runtime (**v0.2.0**).
|
|
11
|
+
|
|
12
|
+
| Related | Package |
|
|
13
|
+
|---------|---------|
|
|
14
|
+
| Validation | [`@lxpack/validators`](../validators/README.md) |
|
|
15
|
+
| Browser runtime | [`@lxpack/runtime`](../runtime/README.md) |
|
|
16
|
+
| Export / ZIP | [`@lxpack/scorm`](../scorm/README.md) |
|
|
17
|
+
| Lesson widgets | [`@lxpack/components`](../components/README.md) |
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g @lxpack/cli
|
|
23
|
+
# or: pnpm add -g @lxpack/cli
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Requires Node.js 20+.
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
lxpack init my-course
|
|
32
|
+
cd my-course
|
|
33
|
+
lxpack preview # http://127.0.0.1:3847 by default
|
|
34
|
+
lxpack validate
|
|
35
|
+
lxpack build --target scorm12
|
|
36
|
+
lxpack build --target scorm2004
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Output lands in `.lxpack/` unless overridden by `-o` or `lxpack.config.json`.
|
|
40
|
+
|
|
41
|
+
## Commands
|
|
42
|
+
|
|
43
|
+
| Command | Description |
|
|
44
|
+
|---------|-------------|
|
|
45
|
+
| `init <name>` | Scaffold a new course (`-d, --dir <path>`, `-f, --force`) |
|
|
46
|
+
| `preview` | Start local preview server (`-p, --port`, `-H, --host`) |
|
|
47
|
+
| `validate` | Validate `course.yaml` and referenced files |
|
|
48
|
+
| `build` | Package for LMS or standalone export |
|
|
49
|
+
|
|
50
|
+
### `build` options
|
|
51
|
+
|
|
52
|
+
| Option | Description |
|
|
53
|
+
|--------|-------------|
|
|
54
|
+
| `-t, --target <target>` | `scorm12` (default), `scorm2004`, or `standalone` |
|
|
55
|
+
| `-o, --output <path>` | Output ZIP file or directory |
|
|
56
|
+
| `--dir` | Write an unpacked directory instead of a ZIP |
|
|
57
|
+
|
|
58
|
+
`build` and `preview` use the same validation rules: errors fail the command (exit code 1). `build` reuses the validated manifest and bakes a sanitized [assessment bundle](../validators/README.md#assessment-packaging) into the exported HTML config.
|
|
59
|
+
|
|
60
|
+
**SCORM 2004** builds produce a multi-SCO ZIP: one launch page per activity under `sco/<activityId>/index.html`, plus shared `lxpack-runtime.js` and `lxpack-components.js`.
|
|
61
|
+
|
|
62
|
+
**Preview** serves the runtime client, optional components bundle at `/runtime/components.js`, and installs SCORM API simulators (1.2 and 2004) for local testing.
|
|
63
|
+
|
|
64
|
+
### Course discovery
|
|
65
|
+
|
|
66
|
+
Commands walk up from the current working directory until they find `course.yaml`. Run them from inside your course project (or a subdirectory).
|
|
67
|
+
|
|
68
|
+
### Path safety
|
|
69
|
+
|
|
70
|
+
- `init --dir` must be a relative path that stays inside the current working directory.
|
|
71
|
+
- `lxpack.config.json` `output.dir` is resolved relative to the course root with the same containment rules.
|
|
72
|
+
|
|
73
|
+
## Course layout
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
my-course/
|
|
77
|
+
course.yaml
|
|
78
|
+
lxpack.config.json # optional: defaultTarget, output
|
|
79
|
+
lessons/
|
|
80
|
+
interactions/
|
|
81
|
+
assessments/ # authoring only — omitted from export ZIPs
|
|
82
|
+
components/ # optional widget overrides
|
|
83
|
+
assets/
|
|
84
|
+
.lxpack/ # build output (generated)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`init` scaffolds commented examples for `variables`, `flow`, and `type: component` lessons. See [branching-demo](https://github.com/eddiethedean/lxpack/tree/main/examples/branching-demo) for a full v0.2 course.
|
|
88
|
+
|
|
89
|
+
### `lxpack.config.json`
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"defaultTarget": "scorm12",
|
|
94
|
+
"output": {
|
|
95
|
+
"dir": ".lxpack"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Use `"defaultTarget": "scorm2004"` when your LMS expects SCORM 2004 4th Edition packages.
|
|
101
|
+
|
|
102
|
+
See the [root README](https://github.com/eddiethedean/lxpack#course-structure) for a full `course.yaml` example.
|
|
103
|
+
|
|
104
|
+
## Programmatic use
|
|
105
|
+
|
|
106
|
+
The CLI is built with [Commander](https://github.com/tj/commander.js). For library integration, import from the built package or depend on `@lxpack/validators`, `@lxpack/scorm`, `@lxpack/runtime`, and `@lxpack/components` directly.
|
|
107
|
+
|
|
108
|
+
## Development
|
|
109
|
+
|
|
110
|
+
From the monorepo root:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
pnpm --filter @lxpack/cli build
|
|
114
|
+
pnpm --filter @lxpack/cli test
|
|
115
|
+
pnpm --filter @lxpack/cli typecheck
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Links
|
|
119
|
+
|
|
120
|
+
- [LXPack repository](https://github.com/eddiethedean/lxpack)
|
|
121
|
+
- [Documentation index](https://github.com/eddiethedean/lxpack/blob/main/docs/README.md)
|
|
122
|
+
- [Roadmap & phases](https://github.com/eddiethedean/lxpack/blob/main/docs/ROADMAP.md)
|
|
123
|
+
- [Changelog](https://github.com/eddiethedean/lxpack/blob/main/CHANGELOG.md)
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
Apache-2.0
|
package/dist/cli.d.ts
CHANGED
|
@@ -3,11 +3,11 @@ import { z } from 'zod';
|
|
|
3
3
|
|
|
4
4
|
declare const lxpackConfigSchema: z.ZodObject<{
|
|
5
5
|
exports: z.ZodOptional<z.ZodObject<{
|
|
6
|
-
defaultTarget: z.ZodOptional<z.ZodEnum<["scorm12", "standalone"]>>;
|
|
6
|
+
defaultTarget: z.ZodOptional<z.ZodEnum<["scorm12", "scorm2004", "standalone"]>>;
|
|
7
7
|
}, "strip", z.ZodTypeAny, {
|
|
8
|
-
defaultTarget?: "scorm12" | "standalone" | undefined;
|
|
8
|
+
defaultTarget?: "scorm12" | "scorm2004" | "standalone" | undefined;
|
|
9
9
|
}, {
|
|
10
|
-
defaultTarget?: "scorm12" | "standalone" | undefined;
|
|
10
|
+
defaultTarget?: "scorm12" | "scorm2004" | "standalone" | undefined;
|
|
11
11
|
}>>;
|
|
12
12
|
output: z.ZodOptional<z.ZodObject<{
|
|
13
13
|
dir: z.ZodOptional<z.ZodString>;
|
|
@@ -18,14 +18,14 @@ declare const lxpackConfigSchema: z.ZodObject<{
|
|
|
18
18
|
}>>;
|
|
19
19
|
}, "strict", z.ZodTypeAny, {
|
|
20
20
|
exports?: {
|
|
21
|
-
defaultTarget?: "scorm12" | "standalone" | undefined;
|
|
21
|
+
defaultTarget?: "scorm12" | "scorm2004" | "standalone" | undefined;
|
|
22
22
|
} | undefined;
|
|
23
23
|
output?: {
|
|
24
24
|
dir?: string | undefined;
|
|
25
25
|
} | undefined;
|
|
26
26
|
}, {
|
|
27
27
|
exports?: {
|
|
28
|
-
defaultTarget?: "scorm12" | "standalone" | undefined;
|
|
28
|
+
defaultTarget?: "scorm12" | "scorm2004" | "standalone" | undefined;
|
|
29
29
|
} | undefined;
|
|
30
30
|
output?: {
|
|
31
31
|
dir?: string | undefined;
|
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,11 @@ import { createRequire } from "module";
|
|
|
17
17
|
import { dirname, join, resolve } from "path";
|
|
18
18
|
import { stringify as stringifyYaml } from "yaml";
|
|
19
19
|
import { z } from "zod";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
loadManifest,
|
|
22
|
+
formatErrorMessage,
|
|
23
|
+
isPathContained
|
|
24
|
+
} from "@lxpack/validators";
|
|
21
25
|
var require2 = createRequire(import.meta.url);
|
|
22
26
|
function findCourseDir(startDir = process.cwd()) {
|
|
23
27
|
let dir = resolve(startDir);
|
|
@@ -31,13 +35,6 @@ function findCourseDir(startDir = process.cwd()) {
|
|
|
31
35
|
"No course.yaml found. Run from a course directory or use lxpack init."
|
|
32
36
|
);
|
|
33
37
|
}
|
|
34
|
-
async function loadCourseManifest(courseDir) {
|
|
35
|
-
const loaded = await loadManifest(courseDir);
|
|
36
|
-
if (Array.isArray(loaded)) {
|
|
37
|
-
throw new Error(loaded.map((i) => i.message).join("; "));
|
|
38
|
-
}
|
|
39
|
-
return loaded.manifest;
|
|
40
|
-
}
|
|
41
38
|
function getRuntimeAssetsDir() {
|
|
42
39
|
return dirname(require2.resolve("@lxpack/runtime/client"));
|
|
43
40
|
}
|
|
@@ -69,9 +66,17 @@ async function readRuntimeBundle(assetsDir = getRuntimeAssetsDir()) {
|
|
|
69
66
|
}
|
|
70
67
|
return { clientJs, css };
|
|
71
68
|
}
|
|
69
|
+
async function readComponentsBundle() {
|
|
70
|
+
try {
|
|
71
|
+
const bundlePath = require2.resolve("@lxpack/components/bundle");
|
|
72
|
+
return await readFile(bundlePath, "utf-8");
|
|
73
|
+
} catch {
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
72
77
|
var lxpackConfigSchema = z.object({
|
|
73
78
|
exports: z.object({
|
|
74
|
-
defaultTarget: z.enum(["scorm12", "standalone"]).optional()
|
|
79
|
+
defaultTarget: z.enum(["scorm12", "scorm2004", "standalone"]).optional()
|
|
75
80
|
}).optional(),
|
|
76
81
|
output: z.object({
|
|
77
82
|
dir: z.string().optional()
|
|
@@ -112,6 +117,27 @@ function getCliVersion() {
|
|
|
112
117
|
const pkg = require2("../package.json");
|
|
113
118
|
return pkg.version;
|
|
114
119
|
}
|
|
120
|
+
function resolvePathInCwd(relativePath) {
|
|
121
|
+
const cwd = resolve(process.cwd());
|
|
122
|
+
if (relativePath.startsWith("/") || /^[a-zA-Z]:\\/.test(relativePath)) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
"Use a relative path for the output directory (must stay inside the current working directory)"
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
const target = resolve(cwd, relativePath);
|
|
128
|
+
if (!isPathContained(cwd, target)) {
|
|
129
|
+
throw new Error("Path must be inside the current working directory");
|
|
130
|
+
}
|
|
131
|
+
return target;
|
|
132
|
+
}
|
|
133
|
+
function resolveOutputDir(courseDir, outputDir) {
|
|
134
|
+
const root = resolve(courseDir);
|
|
135
|
+
const target = resolve(root, outputDir);
|
|
136
|
+
if (!isPathContained(root, target)) {
|
|
137
|
+
throw new Error("output.dir in lxpack.config.json must stay inside the course directory");
|
|
138
|
+
}
|
|
139
|
+
return target;
|
|
140
|
+
}
|
|
115
141
|
|
|
116
142
|
// src/commands/init.ts
|
|
117
143
|
var COURSE_YAML_TEMPLATE = `title: {{title}}
|
|
@@ -139,6 +165,17 @@ lessons:
|
|
|
139
165
|
assessments:
|
|
140
166
|
- id: final_quiz
|
|
141
167
|
file: assessments/final.yaml
|
|
168
|
+
|
|
169
|
+
# Optional Phase 2 features (see examples/branching-demo):
|
|
170
|
+
# variables:
|
|
171
|
+
# path:
|
|
172
|
+
# default: intro
|
|
173
|
+
# type: string
|
|
174
|
+
# flow:
|
|
175
|
+
# - when:
|
|
176
|
+
# variable:
|
|
177
|
+
# eq: [path, advanced]
|
|
178
|
+
# goto: component_lesson
|
|
142
179
|
`;
|
|
143
180
|
var WELCOME_MD = `# Welcome
|
|
144
181
|
|
|
@@ -234,7 +271,7 @@ function formatTitle(projectName) {
|
|
|
234
271
|
return projectName.split(/[-_\s]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
235
272
|
}
|
|
236
273
|
async function initCommand(projectName, options = {}) {
|
|
237
|
-
const targetDir = options.dir ?? projectName;
|
|
274
|
+
const targetDir = resolvePathInCwd(options.dir ?? projectName);
|
|
238
275
|
const title = formatTitle(projectName);
|
|
239
276
|
const yamlTitle = formatCourseTitleForYaml(title);
|
|
240
277
|
if (existsSync2(join2(targetDir, "course.yaml")) && !options.force) {
|
|
@@ -251,7 +288,8 @@ async function initCommand(projectName, options = {}) {
|
|
|
251
288
|
join2(targetDir, "interactions", "phishing-lab"),
|
|
252
289
|
join2(targetDir, "assets"),
|
|
253
290
|
join2(targetDir, "assessments"),
|
|
254
|
-
join2(targetDir, "theme")
|
|
291
|
+
join2(targetDir, "theme"),
|
|
292
|
+
join2(targetDir, "components")
|
|
255
293
|
];
|
|
256
294
|
for (const dir of dirs) {
|
|
257
295
|
await mkdir(dir, { recursive: true });
|
|
@@ -268,6 +306,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
268
306
|
await writeFile(join2(targetDir, "assessments", "final.yaml"), FINAL_ASSESSMENT);
|
|
269
307
|
await writeFile(join2(targetDir, "lxpack.config.json"), LXPACK_CONFIG);
|
|
270
308
|
await writeFile(join2(targetDir, "theme", ".gitkeep"), "");
|
|
309
|
+
await writeFile(join2(targetDir, "components", ".gitkeep"), "");
|
|
271
310
|
console.log(pc.green(`\u2713 Created LXPack course: ${targetDir}`));
|
|
272
311
|
console.log();
|
|
273
312
|
console.log("Next steps:");
|
|
@@ -281,11 +320,25 @@ async function initCommand(projectName, options = {}) {
|
|
|
281
320
|
import Fastify from "fastify";
|
|
282
321
|
import fastifyStatic from "@fastify/static";
|
|
283
322
|
import pc2 from "picocolors";
|
|
284
|
-
import { validateCourse } from "@lxpack/validators";
|
|
323
|
+
import { validateCourse, buildRuntimeAssessmentBundle } from "@lxpack/validators";
|
|
324
|
+
import { safeJsonForHtml } from "@lxpack/scorm";
|
|
285
325
|
async function loadPreviewStyles(assetsDir = getRuntimeAssetsDir()) {
|
|
286
326
|
return loadRuntimeStyles(assetsDir);
|
|
287
327
|
}
|
|
288
|
-
|
|
328
|
+
function buildPreviewConfig(manifest, assessmentBundle) {
|
|
329
|
+
return safeJsonForHtml({
|
|
330
|
+
manifest,
|
|
331
|
+
baseUrl: "/course",
|
|
332
|
+
mode: "preview",
|
|
333
|
+
...assessmentBundle ? {
|
|
334
|
+
assessments: assessmentBundle.assessments,
|
|
335
|
+
answerKeys: assessmentBundle.answerKeys,
|
|
336
|
+
assessmentConfigs: assessmentBundle.configs,
|
|
337
|
+
assessmentFeedback: assessmentBundle.feedback
|
|
338
|
+
} : {}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
async function createPreviewServer(courseDir, manifest, assessmentBundle) {
|
|
289
342
|
const runtimeDir = getRuntimeAssetsDir();
|
|
290
343
|
const app = Fastify({ logger: false });
|
|
291
344
|
await app.register(fastifyStatic, {
|
|
@@ -299,12 +352,14 @@ async function createPreviewServer(courseDir, manifest) {
|
|
|
299
352
|
decorateReply: false
|
|
300
353
|
});
|
|
301
354
|
const stylesCss = await loadPreviewStyles(runtimeDir);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
355
|
+
const config = buildPreviewConfig(manifest, assessmentBundle);
|
|
356
|
+
const componentsJs = await readComponentsBundle();
|
|
357
|
+
if (componentsJs) {
|
|
358
|
+
app.get("/runtime/components.js", async (_req, reply) => {
|
|
359
|
+
return reply.type("application/javascript").send(componentsJs);
|
|
307
360
|
});
|
|
361
|
+
}
|
|
362
|
+
app.get("/", async (_req, reply) => {
|
|
308
363
|
const html = `<!DOCTYPE html>
|
|
309
364
|
<html lang="en">
|
|
310
365
|
<head>
|
|
@@ -315,9 +370,11 @@ async function createPreviewServer(courseDir, manifest) {
|
|
|
315
370
|
</head>
|
|
316
371
|
<body>
|
|
317
372
|
<div id="lxpack-app"></div>
|
|
373
|
+
<script type="application/json" id="lxpack-config">${config}</script>
|
|
318
374
|
<script>
|
|
319
|
-
window.__LXPACK_CONFIG__ =
|
|
375
|
+
window.__LXPACK_CONFIG__ = JSON.parse(document.getElementById('lxpack-config').textContent);
|
|
320
376
|
</script>
|
|
377
|
+
${componentsJs ? '<script type="module" src="/runtime/components.js"></script>' : ""}
|
|
321
378
|
<script type="module" src="/runtime/client.js"></script>
|
|
322
379
|
</body>
|
|
323
380
|
</html>`;
|
|
@@ -336,13 +393,21 @@ async function startPreview(courseDir, _options = {}) {
|
|
|
336
393
|
process.exit(1);
|
|
337
394
|
}
|
|
338
395
|
if (!validation.valid) {
|
|
339
|
-
console.
|
|
396
|
+
console.error(pc2.red("Cannot preview: course validation failed"));
|
|
340
397
|
for (const issue of validation.issues) {
|
|
341
|
-
console.
|
|
398
|
+
console.error(` ${issue.path}: ${issue.message}`);
|
|
342
399
|
}
|
|
343
|
-
|
|
400
|
+
process.exit(1);
|
|
344
401
|
}
|
|
345
|
-
const
|
|
402
|
+
const assessmentBundle = await buildRuntimeAssessmentBundle(
|
|
403
|
+
courseDir,
|
|
404
|
+
validation.manifest
|
|
405
|
+
);
|
|
406
|
+
const app = await createPreviewServer(
|
|
407
|
+
courseDir,
|
|
408
|
+
validation.manifest,
|
|
409
|
+
assessmentBundle
|
|
410
|
+
);
|
|
346
411
|
return { app, validation };
|
|
347
412
|
}
|
|
348
413
|
function resolvePreviewDeps(deps) {
|
|
@@ -415,10 +480,13 @@ async function validateCommand() {
|
|
|
415
480
|
// src/commands/build.ts
|
|
416
481
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
417
482
|
import { join as join3 } from "path";
|
|
418
|
-
import { packageCourse, packageStandaloneDir } from "@lxpack/scorm";
|
|
419
|
-
import {
|
|
483
|
+
import { packageCourse, packageStandaloneDir, courseSlug } from "@lxpack/scorm";
|
|
484
|
+
import {
|
|
485
|
+
validateCourse as validateCourse3,
|
|
486
|
+
buildRuntimeAssessmentBundle as buildRuntimeAssessmentBundle2
|
|
487
|
+
} from "@lxpack/validators";
|
|
420
488
|
import pc4 from "picocolors";
|
|
421
|
-
var VALID_TARGETS = ["scorm12", "standalone"];
|
|
489
|
+
var VALID_TARGETS = ["scorm12", "scorm2004", "standalone"];
|
|
422
490
|
async function buildCommand(options) {
|
|
423
491
|
const courseDir = findCourseDir();
|
|
424
492
|
const config = await loadLxpackConfig(courseDir);
|
|
@@ -432,41 +500,50 @@ async function buildCommand(options) {
|
|
|
432
500
|
process.exit(1);
|
|
433
501
|
}
|
|
434
502
|
const validation = await validateCourse3(courseDir);
|
|
435
|
-
if (!validation.valid) {
|
|
503
|
+
if (!validation.valid || !validation.manifest) {
|
|
436
504
|
console.error(pc4.red("Cannot build: course validation failed"));
|
|
437
505
|
for (const issue of validation.issues) {
|
|
438
506
|
console.error(` ${issue.path}: ${issue.message}`);
|
|
439
507
|
}
|
|
440
508
|
process.exit(1);
|
|
441
509
|
}
|
|
442
|
-
const manifest =
|
|
443
|
-
const
|
|
444
|
-
|
|
510
|
+
const manifest = validation.manifest;
|
|
511
|
+
const assessmentBundle = await buildRuntimeAssessmentBundle2(
|
|
512
|
+
courseDir,
|
|
513
|
+
manifest
|
|
514
|
+
);
|
|
515
|
+
const [{ clientJs, css }, componentsBundleJs] = await Promise.all([
|
|
516
|
+
readRuntimeBundle(),
|
|
517
|
+
readComponentsBundle()
|
|
518
|
+
]);
|
|
519
|
+
const slug = courseSlug(manifest);
|
|
445
520
|
const outputBase = config?.output?.dir ?? ".lxpack";
|
|
446
|
-
|
|
521
|
+
const outputRoot = resolveOutputDir(courseDir, outputBase);
|
|
522
|
+
await mkdir2(outputRoot, { recursive: true });
|
|
523
|
+
const packageOptions = {
|
|
524
|
+
courseDir,
|
|
525
|
+
manifest,
|
|
526
|
+
target,
|
|
527
|
+
runtimeClientJs: clientJs,
|
|
528
|
+
runtimeCss: css,
|
|
529
|
+
componentsBundleJs,
|
|
530
|
+
assessmentBundle
|
|
531
|
+
};
|
|
447
532
|
if (options.dir) {
|
|
448
|
-
const outputDir = options.output ?? join3(
|
|
533
|
+
const outputDir = options.output ?? join3(outputRoot, target);
|
|
449
534
|
const result = await packageStandaloneDir({
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
outputDir,
|
|
453
|
-
target,
|
|
454
|
-
runtimeClientJs: clientJs,
|
|
455
|
-
runtimeCss: css
|
|
535
|
+
...packageOptions,
|
|
536
|
+
outputDir
|
|
456
537
|
});
|
|
457
538
|
console.log(pc4.green(`\u2713 Built ${target} package`));
|
|
458
539
|
console.log(` Output: ${result.outputDir}`);
|
|
459
540
|
console.log(` Files: ${result.fileCount}`);
|
|
460
541
|
} else {
|
|
461
|
-
const defaultName = target === "standalone" ? `${slug}-standalone.zip` : `${slug}-scorm12.zip`;
|
|
462
|
-
const outputPath = options.output ?? join3(
|
|
542
|
+
const defaultName = target === "standalone" ? `${slug}-standalone.zip` : target === "scorm2004" ? `${slug}-scorm2004.zip` : `${slug}-scorm12.zip`;
|
|
543
|
+
const outputPath = options.output ?? join3(outputRoot, defaultName);
|
|
463
544
|
const result = await packageCourse({
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
outputPath,
|
|
467
|
-
target,
|
|
468
|
-
runtimeClientJs: clientJs,
|
|
469
|
-
runtimeCss: css
|
|
545
|
+
...packageOptions,
|
|
546
|
+
outputPath
|
|
470
547
|
});
|
|
471
548
|
console.log(pc4.green(`\u2713 Built ${target} package`));
|
|
472
549
|
console.log(` Output: ${result.outputPath}`);
|
|
@@ -494,7 +571,7 @@ function createCliProgram() {
|
|
|
494
571
|
});
|
|
495
572
|
program.command("build").description("Build LMS-compatible package").option(
|
|
496
573
|
"-t, --target <target>",
|
|
497
|
-
"Export target: scorm12, standalone",
|
|
574
|
+
"Export target: scorm12, scorm2004, standalone",
|
|
498
575
|
void 0
|
|
499
576
|
).option("-o, --output <path>", "Output file or directory path").option("--dir", "Output as directory instead of ZIP").action(
|
|
500
577
|
async (options) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lxpack/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI for building, validating, and packaging LXPack courses",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -39,9 +39,10 @@
|
|
|
39
39
|
"picocolors": "^1.1.1",
|
|
40
40
|
"yaml": "^2.7.0",
|
|
41
41
|
"zod": "^3.24.2",
|
|
42
|
-
"@lxpack/runtime": "0.
|
|
43
|
-
"@lxpack/
|
|
44
|
-
"@lxpack/
|
|
42
|
+
"@lxpack/runtime": "0.2.0",
|
|
43
|
+
"@lxpack/components": "0.2.0",
|
|
44
|
+
"@lxpack/scorm": "0.2.0",
|
|
45
|
+
"@lxpack/validators": "0.2.0"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"@types/node": "^22.13.10",
|