@lessonkit/cli 1.5.0 → 1.6.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/dist/bin.js +258 -16
- package/dist/index.js +258 -16
- package/package.json +4 -3
- package/template/vite-react/package.json +6 -6
package/dist/bin.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { createRequire as
|
|
4
|
+
import { createRequire as createRequire3 } from "module";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/init.ts
|
|
8
8
|
import { slugifyId } from "@lessonkit/core";
|
|
9
|
-
import { cp, mkdir, readdir, readFile, rename, rm, writeFile } from "fs/promises";
|
|
9
|
+
import { cp, mkdir, readdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
|
|
10
10
|
import { existsSync } from "fs";
|
|
11
11
|
import { randomUUID } from "crypto";
|
|
12
12
|
import { basename, dirname, join, resolve } from "path";
|
|
@@ -138,6 +138,11 @@ function getTemplateDir() {
|
|
|
138
138
|
}
|
|
139
139
|
return candidates[0];
|
|
140
140
|
}
|
|
141
|
+
async function isDirEmpty(dir) {
|
|
142
|
+
if (!existsSync(dir)) return true;
|
|
143
|
+
const entries = await readdir(dir);
|
|
144
|
+
return entries.length === 0;
|
|
145
|
+
}
|
|
141
146
|
async function isDirEmptyOrDotfilesOnly(dir) {
|
|
142
147
|
if (!existsSync(dir)) return true;
|
|
143
148
|
const entries = await readdir(dir);
|
|
@@ -190,6 +195,51 @@ async function applyTemplateSubstitutions(projectDir, projectName, slug) {
|
|
|
190
195
|
appSource = appSource.replace(/\{\{courseTitle\}\}/g, escapeJsxString(projectName));
|
|
191
196
|
await writeFile(appPath, appSource, "utf8");
|
|
192
197
|
}
|
|
198
|
+
async function backupConflictingFiles(stagingDir, projectDir) {
|
|
199
|
+
const backups = /* @__PURE__ */ new Map();
|
|
200
|
+
const stagingEntries = await readdir(stagingDir, { withFileTypes: true });
|
|
201
|
+
for (const entry of stagingEntries) {
|
|
202
|
+
const destPath = join(projectDir, entry.name);
|
|
203
|
+
if (!existsSync(destPath)) continue;
|
|
204
|
+
const destStat = await stat(destPath);
|
|
205
|
+
if (destStat.isFile()) {
|
|
206
|
+
backups.set(entry.name, await readFile(destPath));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return backups;
|
|
210
|
+
}
|
|
211
|
+
async function rollbackPromotedFiles(projectDir, stagingDir, preExisting, backups) {
|
|
212
|
+
const failures = [];
|
|
213
|
+
let stagingEntries;
|
|
214
|
+
try {
|
|
215
|
+
stagingEntries = await readdir(stagingDir, { withFileTypes: true });
|
|
216
|
+
} catch {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
for (const entry of stagingEntries) {
|
|
220
|
+
if (preExisting.has(entry.name)) continue;
|
|
221
|
+
try {
|
|
222
|
+
await rm(join(projectDir, entry.name), { recursive: true, force: true });
|
|
223
|
+
} catch (err) {
|
|
224
|
+
failures.push(
|
|
225
|
+
`remove ${entry.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const [name, content] of backups) {
|
|
230
|
+
try {
|
|
231
|
+
await writeFile(join(projectDir, name), content);
|
|
232
|
+
} catch (err) {
|
|
233
|
+
failures.push(`restore ${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (failures.length > 0) {
|
|
237
|
+
throw new CliError(`Init rollback failed: ${failures.join("; ")}`, {
|
|
238
|
+
code: "RUNTIME",
|
|
239
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
193
243
|
async function promoteStagingToProjectDir(stagingDir, projectDir) {
|
|
194
244
|
await mkdir(projectDir, { recursive: true });
|
|
195
245
|
const entries = await readdir(stagingDir, { withFileTypes: true });
|
|
@@ -204,6 +254,16 @@ async function promoteStagingToProjectDir(stagingDir, projectDir) {
|
|
|
204
254
|
}
|
|
205
255
|
}
|
|
206
256
|
}
|
|
257
|
+
var __testInitHelpers = {
|
|
258
|
+
getTemplateDir,
|
|
259
|
+
isDirEmpty,
|
|
260
|
+
isDirEmptyOrDotfilesOnly,
|
|
261
|
+
escapeJsxString,
|
|
262
|
+
copyTemplate,
|
|
263
|
+
promoteStagingToProjectDir,
|
|
264
|
+
rollbackPromotedFiles,
|
|
265
|
+
backupConflictingFiles
|
|
266
|
+
};
|
|
207
267
|
async function runInit(opts, logger) {
|
|
208
268
|
const cwd = process.cwd();
|
|
209
269
|
const rawName = opts.name ?? (opts.here ? slugifyId(basename(process.cwd()) || "my-course") : void 0);
|
|
@@ -232,10 +292,13 @@ async function runInit(opts, logger) {
|
|
|
232
292
|
);
|
|
233
293
|
}
|
|
234
294
|
if (opts.here && !await isDirEmptyOrDotfilesOnly(projectDir) && !opts.force) {
|
|
235
|
-
throw new CliError(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
295
|
+
throw new CliError(
|
|
296
|
+
`Directory is not empty: ${projectDir}. Use --here --force only when the directory is empty or contains dotfiles only (e.g. .git).`,
|
|
297
|
+
{
|
|
298
|
+
code: "INVALID_PROJECT",
|
|
299
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
300
|
+
}
|
|
301
|
+
);
|
|
239
302
|
}
|
|
240
303
|
if (opts.here && opts.force && !await isDirEmptyOrDotfilesOnly(projectDir)) {
|
|
241
304
|
throw new CliError(
|
|
@@ -262,7 +325,23 @@ async function runInit(opts, logger) {
|
|
|
262
325
|
await runNpmInstall(stagingDir);
|
|
263
326
|
}
|
|
264
327
|
if (opts.here) {
|
|
265
|
-
await
|
|
328
|
+
const preExisting = new Set(await readdir(projectDir));
|
|
329
|
+
const backups = await backupConflictingFiles(stagingDir, projectDir);
|
|
330
|
+
try {
|
|
331
|
+
await __testInitHelpers.promoteStagingToProjectDir(stagingDir, projectDir);
|
|
332
|
+
} catch (promoteErr) {
|
|
333
|
+
try {
|
|
334
|
+
await rollbackPromotedFiles(projectDir, stagingDir, preExisting, backups);
|
|
335
|
+
} catch (rollbackErr) {
|
|
336
|
+
const promoteMessage = promoteErr instanceof Error ? promoteErr.message : String(promoteErr);
|
|
337
|
+
const rollbackMessage = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
|
|
338
|
+
throw new CliError(`${promoteMessage}; ${rollbackMessage}`, {
|
|
339
|
+
code: "RUNTIME",
|
|
340
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
throw promoteErr;
|
|
344
|
+
}
|
|
266
345
|
await rm(stagingDir, { recursive: true, force: true });
|
|
267
346
|
} else {
|
|
268
347
|
await rename(stagingDir, projectDir);
|
|
@@ -283,7 +362,9 @@ async function runInit(opts, logger) {
|
|
|
283
362
|
|
|
284
363
|
// src/commands/dev.ts
|
|
285
364
|
import { existsSync as existsSync3 } from "fs";
|
|
286
|
-
import {
|
|
365
|
+
import { mkdir as mkdir2 } from "fs/promises";
|
|
366
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
367
|
+
import { assertSpaDistContentsSafe } from "@lessonkit/lxpack";
|
|
287
368
|
|
|
288
369
|
// src/lib/project.ts
|
|
289
370
|
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
@@ -293,10 +374,13 @@ import { dirname as dirname2, join as join2, parse, resolve as resolve2 } from "
|
|
|
293
374
|
import { parseLessonkitManifest } from "@lessonkit/lxpack";
|
|
294
375
|
var LESSONKIT_JSON = "lessonkit.json";
|
|
295
376
|
var PACKAGE_JSON = "package.json";
|
|
377
|
+
function isSchemaVersionOne(value) {
|
|
378
|
+
return value === 1 || value === "1";
|
|
379
|
+
}
|
|
296
380
|
function isProjectManifest(configPath) {
|
|
297
381
|
try {
|
|
298
382
|
const raw = JSON.parse(readFileSync(configPath, "utf8"));
|
|
299
|
-
return raw.schemaVersion
|
|
383
|
+
return isSchemaVersionOne(raw.schemaVersion) && typeof raw.name === "string" && raw.course !== null && typeof raw.course === "object" && !Array.isArray(raw.course);
|
|
300
384
|
} catch {
|
|
301
385
|
return false;
|
|
302
386
|
}
|
|
@@ -469,7 +553,12 @@ function resolvePackageOutput(project, target, override) {
|
|
|
469
553
|
if (override) {
|
|
470
554
|
try {
|
|
471
555
|
const resolved = resolveSafePackageOutputOverride(project.root, override);
|
|
472
|
-
|
|
556
|
+
const isZipOutput = override.trim().toLowerCase().endsWith(".zip");
|
|
557
|
+
return {
|
|
558
|
+
output: resolved,
|
|
559
|
+
dir: target === "standalone" && !isZipOutput,
|
|
560
|
+
outputBaseDir
|
|
561
|
+
};
|
|
473
562
|
} catch (err) {
|
|
474
563
|
const message = err instanceof Error ? err.message : String(err);
|
|
475
564
|
throw new CliError(message, { code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT });
|
|
@@ -515,7 +604,8 @@ async function runDev(opts) {
|
|
|
515
604
|
const pkg = await readPackageJson(project.root);
|
|
516
605
|
assertViteProject(pkg, project.root);
|
|
517
606
|
const viteJs = resolveViteJs(project.root);
|
|
518
|
-
|
|
607
|
+
const devArgs = stripOutDirFromViteArgs(opts.viteArgs ?? []);
|
|
608
|
+
await runCommand(process.execPath, [viteJs, ...devArgs], {
|
|
519
609
|
cwd: project.root,
|
|
520
610
|
timeoutMs: 0
|
|
521
611
|
});
|
|
@@ -526,11 +616,15 @@ async function runBuild(opts) {
|
|
|
526
616
|
const pkg = await readPackageJson(project.root);
|
|
527
617
|
assertViteProject(pkg, project.root);
|
|
528
618
|
const viteJs = resolveViteJs(project.root);
|
|
619
|
+
const distDir = resolveDistDir(project);
|
|
620
|
+
await mkdir2(dirname3(distDir), { recursive: true });
|
|
621
|
+
if (existsSync3(distDir)) {
|
|
622
|
+
await assertSpaDistContentsSafe({ main: distDir }, project.root);
|
|
623
|
+
}
|
|
529
624
|
const buildArgs = resolveViteBuildArgv(project, opts.viteArgs);
|
|
530
625
|
await runCommand(process.execPath, [viteJs, ...buildArgs], {
|
|
531
626
|
cwd: project.root
|
|
532
627
|
});
|
|
533
|
-
const distDir = resolveDistDir(project);
|
|
534
628
|
const indexHtml = join3(distDir, "index.html");
|
|
535
629
|
if (!existsSync3(indexHtml)) {
|
|
536
630
|
throw new CliError(
|
|
@@ -609,7 +703,8 @@ async function runPackage(opts) {
|
|
|
609
703
|
output,
|
|
610
704
|
dir,
|
|
611
705
|
outputBaseDir,
|
|
612
|
-
strictParity: opts.strictParity
|
|
706
|
+
strictParity: opts.strictParity,
|
|
707
|
+
strictBuild: opts.strict
|
|
613
708
|
});
|
|
614
709
|
if (!result.ok) {
|
|
615
710
|
throw new CliError("Packaging failed.", {
|
|
@@ -641,6 +736,112 @@ async function runPackage(opts) {
|
|
|
641
736
|
};
|
|
642
737
|
}
|
|
643
738
|
|
|
739
|
+
// src/commands/export.ts
|
|
740
|
+
import { existsSync as existsSync5 } from "fs";
|
|
741
|
+
import { relative, resolve as resolve4 } from "path";
|
|
742
|
+
import { exportLkcourse, resolveSafePackageOutputOverride as resolveSafePackageOutputOverride2 } from "@lessonkit/lxpack";
|
|
743
|
+
function resolveExportOutput(projectRoot, override, defaultName) {
|
|
744
|
+
if (override) {
|
|
745
|
+
try {
|
|
746
|
+
return resolveSafePackageOutputOverride2(projectRoot, override);
|
|
747
|
+
} catch (err) {
|
|
748
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
749
|
+
throw new CliError(message, { code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT });
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return resolve4(projectRoot, `${defaultName ?? "course"}.lkcourse`);
|
|
753
|
+
}
|
|
754
|
+
async function runExport(opts) {
|
|
755
|
+
const project = await loadProject(opts.cwd ?? process.cwd());
|
|
756
|
+
const distDir = resolve4(project.root, project.paths.spaDistDir);
|
|
757
|
+
if (opts.noBuild && !existsSync5(distDir)) {
|
|
758
|
+
throw new CliError(
|
|
759
|
+
`dist directory not found at ${distDir}. Run lessonkit build before export with --no-build.`,
|
|
760
|
+
{
|
|
761
|
+
code: "INVALID_PROJECT",
|
|
762
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
763
|
+
}
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
if (!opts.noBuild) {
|
|
767
|
+
await runBuild({ cwd: project.root, json: opts.json });
|
|
768
|
+
}
|
|
769
|
+
if (!existsSync5(distDir)) {
|
|
770
|
+
throw new CliError(`dist directory not found at ${distDir}. Run lessonkit build first.`, {
|
|
771
|
+
code: "INVALID_PROJECT",
|
|
772
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
const resolvedOut = resolveExportOutput(project.root, opts.out, project.name);
|
|
776
|
+
const outRelative = relative(project.root, resolvedOut).replace(/\\/g, "/");
|
|
777
|
+
const result = await exportLkcourse({
|
|
778
|
+
projectRoot: project.root,
|
|
779
|
+
manifest: project,
|
|
780
|
+
outPath: outRelative,
|
|
781
|
+
includeBlockTree: Boolean(opts.withBlockTree)
|
|
782
|
+
});
|
|
783
|
+
if (!result.ok) {
|
|
784
|
+
throw new CliError(
|
|
785
|
+
result.issues.map((i) => `${i.path}: ${i.message}`).join("; "),
|
|
786
|
+
{ code: "EXPORT_FAILED", exitCode: EXIT_PACKAGING }
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
ok: true,
|
|
791
|
+
command: "export",
|
|
792
|
+
projectRoot: project.root,
|
|
793
|
+
archivePath: result.archivePath,
|
|
794
|
+
fileCount: result.fileCount,
|
|
795
|
+
includeBlockTree: result.includeBlockTree
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// src/commands/blocks.ts
|
|
800
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
801
|
+
import { createRequire as createRequire2 } from "module";
|
|
802
|
+
function loadBlockCatalog() {
|
|
803
|
+
const require3 = createRequire2(import.meta.url);
|
|
804
|
+
const catalogPath = require3.resolve("@lessonkit/react/block-catalog.v3.json");
|
|
805
|
+
return JSON.parse(readFileSync2(catalogPath, "utf8"));
|
|
806
|
+
}
|
|
807
|
+
function filterEntries(entries, opts) {
|
|
808
|
+
return entries.filter((entry) => {
|
|
809
|
+
if (opts.category && entry.category !== opts.category) return false;
|
|
810
|
+
if (opts.tier && entry.tier !== opts.tier) return false;
|
|
811
|
+
return true;
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
async function runBlocksList(opts) {
|
|
815
|
+
const catalog = loadBlockCatalog();
|
|
816
|
+
const entries = filterEntries(catalog.entries, opts);
|
|
817
|
+
if (!opts.json) {
|
|
818
|
+
const lines = [
|
|
819
|
+
"type category h5pMachineName",
|
|
820
|
+
...entries.map(
|
|
821
|
+
(entry) => [
|
|
822
|
+
entry.type,
|
|
823
|
+
entry.category ?? "\u2014",
|
|
824
|
+
entry.h5pMachineName ?? "\u2014"
|
|
825
|
+
].join(" ")
|
|
826
|
+
)
|
|
827
|
+
];
|
|
828
|
+
return {
|
|
829
|
+
ok: true,
|
|
830
|
+
command: "blocks list",
|
|
831
|
+
schemaVersion: catalog.schemaVersion,
|
|
832
|
+
count: entries.length,
|
|
833
|
+
text: lines.join("\n")
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
return {
|
|
837
|
+
ok: true,
|
|
838
|
+
command: "blocks list",
|
|
839
|
+
schemaVersion: catalog.schemaVersion,
|
|
840
|
+
count: entries.length,
|
|
841
|
+
entries
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
644
845
|
// src/lib/logger.ts
|
|
645
846
|
function createLogger(opts) {
|
|
646
847
|
if (opts?.json) {
|
|
@@ -655,7 +856,7 @@ function createLogger(opts) {
|
|
|
655
856
|
}
|
|
656
857
|
|
|
657
858
|
// src/index.ts
|
|
658
|
-
var require2 =
|
|
859
|
+
var require2 = createRequire3(import.meta.url);
|
|
659
860
|
var { version } = require2("../package.json");
|
|
660
861
|
async function handleCommand(fn, logger, json) {
|
|
661
862
|
try {
|
|
@@ -712,7 +913,7 @@ function createProgram(baseLogger = console) {
|
|
|
712
913
|
);
|
|
713
914
|
}
|
|
714
915
|
);
|
|
715
|
-
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("--strict-parity", "Treat React ID parity warnings as packaging errors").option("--json", "Emit structured JSON result").action(async (opts) => {
|
|
916
|
+
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("--strict-parity", "Treat React ID parity warnings as packaging errors").option("--strict", "Treat Vite build warnings as packaging failures").option("--json", "Emit structured JSON result").action(async (opts) => {
|
|
716
917
|
const logger = createLogger({ json: opts.json });
|
|
717
918
|
await handleCommand(
|
|
718
919
|
async () => {
|
|
@@ -722,7 +923,8 @@ function createProgram(baseLogger = console) {
|
|
|
722
923
|
noBuild: opts.build === false,
|
|
723
924
|
out: opts.out,
|
|
724
925
|
json: opts.json,
|
|
725
|
-
strictParity: opts.strictParity
|
|
926
|
+
strictParity: opts.strictParity,
|
|
927
|
+
strict: opts.strict
|
|
726
928
|
});
|
|
727
929
|
if (!opts.json && result.ok && result.command === "package") {
|
|
728
930
|
if (result.target === "react-vite") {
|
|
@@ -740,6 +942,46 @@ function createProgram(baseLogger = console) {
|
|
|
740
942
|
Boolean(opts.json)
|
|
741
943
|
);
|
|
742
944
|
});
|
|
945
|
+
addCwdAndJson(
|
|
946
|
+
program.command("export").description("Export a portable .lkcourse archive (manifest + interchange + dist)").option("--out <path>", "Output .lkcourse path (relative to project root)").option("--no-build", "Skip implicit Vite build").option("--with-block-tree", "Include optional block-tree.json from src scan")
|
|
947
|
+
).action(async (opts) => {
|
|
948
|
+
const logger = createLogger({ json: opts.json });
|
|
949
|
+
await handleCommand(
|
|
950
|
+
async () => {
|
|
951
|
+
const result = await runExport({
|
|
952
|
+
cwd: opts.cwd,
|
|
953
|
+
out: opts.out,
|
|
954
|
+
noBuild: opts.build === false,
|
|
955
|
+
withBlockTree: opts.withBlockTree,
|
|
956
|
+
json: opts.json
|
|
957
|
+
});
|
|
958
|
+
if (!opts.json && result.ok && result.command === "export") {
|
|
959
|
+
logger.log(`Exported .lkcourse \u2192 ${result.archivePath} (${result.fileCount} files)`);
|
|
960
|
+
}
|
|
961
|
+
return result;
|
|
962
|
+
},
|
|
963
|
+
logger,
|
|
964
|
+
Boolean(opts.json)
|
|
965
|
+
);
|
|
966
|
+
});
|
|
967
|
+
program.command("blocks").description("Block registry commands").command("list").description("List runtime blocks from block-catalog.v3.json").option("--json", "Emit structured JSON result").option("--category <category>", "Filter by category (container, assessment, content, compound)").option("--tier <tier>", "Filter by tier (A, B, C, D, E)").action(async (opts) => {
|
|
968
|
+
const logger = createLogger({ json: opts.json });
|
|
969
|
+
await handleCommand(
|
|
970
|
+
async () => {
|
|
971
|
+
const result = await runBlocksList({
|
|
972
|
+
json: opts.json,
|
|
973
|
+
category: opts.category,
|
|
974
|
+
tier: opts.tier
|
|
975
|
+
});
|
|
976
|
+
if (!opts.json && result.ok && "text" in result && typeof result.text === "string") {
|
|
977
|
+
logger.log(result.text);
|
|
978
|
+
}
|
|
979
|
+
return result;
|
|
980
|
+
},
|
|
981
|
+
logger,
|
|
982
|
+
Boolean(opts.json)
|
|
983
|
+
);
|
|
984
|
+
});
|
|
743
985
|
program.command("publish").description("[maintainers] Not implemented \u2014 use Changesets (see RELEASING.md)").action(() => {
|
|
744
986
|
baseLogger.log(
|
|
745
987
|
"lessonkit publish is not implemented. Monorepo releases use Changesets: npm run changeset && npm run version-packages && npm run release. See RELEASING.md."
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { createRequire as
|
|
2
|
+
import { createRequire as createRequire3 } from "module";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
|
|
5
5
|
// src/commands/init.ts
|
|
6
6
|
import { slugifyId } from "@lessonkit/core";
|
|
7
|
-
import { cp, mkdir, readdir, readFile, rename, rm, writeFile } from "fs/promises";
|
|
7
|
+
import { cp, mkdir, readdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
|
|
8
8
|
import { existsSync } from "fs";
|
|
9
9
|
import { randomUUID } from "crypto";
|
|
10
10
|
import { basename, dirname, join, resolve } from "path";
|
|
@@ -136,6 +136,11 @@ function getTemplateDir() {
|
|
|
136
136
|
}
|
|
137
137
|
return candidates[0];
|
|
138
138
|
}
|
|
139
|
+
async function isDirEmpty(dir) {
|
|
140
|
+
if (!existsSync(dir)) return true;
|
|
141
|
+
const entries = await readdir(dir);
|
|
142
|
+
return entries.length === 0;
|
|
143
|
+
}
|
|
139
144
|
async function isDirEmptyOrDotfilesOnly(dir) {
|
|
140
145
|
if (!existsSync(dir)) return true;
|
|
141
146
|
const entries = await readdir(dir);
|
|
@@ -188,6 +193,51 @@ async function applyTemplateSubstitutions(projectDir, projectName, slug) {
|
|
|
188
193
|
appSource = appSource.replace(/\{\{courseTitle\}\}/g, escapeJsxString(projectName));
|
|
189
194
|
await writeFile(appPath, appSource, "utf8");
|
|
190
195
|
}
|
|
196
|
+
async function backupConflictingFiles(stagingDir, projectDir) {
|
|
197
|
+
const backups = /* @__PURE__ */ new Map();
|
|
198
|
+
const stagingEntries = await readdir(stagingDir, { withFileTypes: true });
|
|
199
|
+
for (const entry of stagingEntries) {
|
|
200
|
+
const destPath = join(projectDir, entry.name);
|
|
201
|
+
if (!existsSync(destPath)) continue;
|
|
202
|
+
const destStat = await stat(destPath);
|
|
203
|
+
if (destStat.isFile()) {
|
|
204
|
+
backups.set(entry.name, await readFile(destPath));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return backups;
|
|
208
|
+
}
|
|
209
|
+
async function rollbackPromotedFiles(projectDir, stagingDir, preExisting, backups) {
|
|
210
|
+
const failures = [];
|
|
211
|
+
let stagingEntries;
|
|
212
|
+
try {
|
|
213
|
+
stagingEntries = await readdir(stagingDir, { withFileTypes: true });
|
|
214
|
+
} catch {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
for (const entry of stagingEntries) {
|
|
218
|
+
if (preExisting.has(entry.name)) continue;
|
|
219
|
+
try {
|
|
220
|
+
await rm(join(projectDir, entry.name), { recursive: true, force: true });
|
|
221
|
+
} catch (err) {
|
|
222
|
+
failures.push(
|
|
223
|
+
`remove ${entry.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
for (const [name, content] of backups) {
|
|
228
|
+
try {
|
|
229
|
+
await writeFile(join(projectDir, name), content);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
failures.push(`restore ${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (failures.length > 0) {
|
|
235
|
+
throw new CliError(`Init rollback failed: ${failures.join("; ")}`, {
|
|
236
|
+
code: "RUNTIME",
|
|
237
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
191
241
|
async function promoteStagingToProjectDir(stagingDir, projectDir) {
|
|
192
242
|
await mkdir(projectDir, { recursive: true });
|
|
193
243
|
const entries = await readdir(stagingDir, { withFileTypes: true });
|
|
@@ -202,6 +252,16 @@ async function promoteStagingToProjectDir(stagingDir, projectDir) {
|
|
|
202
252
|
}
|
|
203
253
|
}
|
|
204
254
|
}
|
|
255
|
+
var __testInitHelpers = {
|
|
256
|
+
getTemplateDir,
|
|
257
|
+
isDirEmpty,
|
|
258
|
+
isDirEmptyOrDotfilesOnly,
|
|
259
|
+
escapeJsxString,
|
|
260
|
+
copyTemplate,
|
|
261
|
+
promoteStagingToProjectDir,
|
|
262
|
+
rollbackPromotedFiles,
|
|
263
|
+
backupConflictingFiles
|
|
264
|
+
};
|
|
205
265
|
async function runInit(opts, logger) {
|
|
206
266
|
const cwd = process.cwd();
|
|
207
267
|
const rawName = opts.name ?? (opts.here ? slugifyId(basename(process.cwd()) || "my-course") : void 0);
|
|
@@ -230,10 +290,13 @@ async function runInit(opts, logger) {
|
|
|
230
290
|
);
|
|
231
291
|
}
|
|
232
292
|
if (opts.here && !await isDirEmptyOrDotfilesOnly(projectDir) && !opts.force) {
|
|
233
|
-
throw new CliError(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
293
|
+
throw new CliError(
|
|
294
|
+
`Directory is not empty: ${projectDir}. Use --here --force only when the directory is empty or contains dotfiles only (e.g. .git).`,
|
|
295
|
+
{
|
|
296
|
+
code: "INVALID_PROJECT",
|
|
297
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
298
|
+
}
|
|
299
|
+
);
|
|
237
300
|
}
|
|
238
301
|
if (opts.here && opts.force && !await isDirEmptyOrDotfilesOnly(projectDir)) {
|
|
239
302
|
throw new CliError(
|
|
@@ -260,7 +323,23 @@ async function runInit(opts, logger) {
|
|
|
260
323
|
await runNpmInstall(stagingDir);
|
|
261
324
|
}
|
|
262
325
|
if (opts.here) {
|
|
263
|
-
await
|
|
326
|
+
const preExisting = new Set(await readdir(projectDir));
|
|
327
|
+
const backups = await backupConflictingFiles(stagingDir, projectDir);
|
|
328
|
+
try {
|
|
329
|
+
await __testInitHelpers.promoteStagingToProjectDir(stagingDir, projectDir);
|
|
330
|
+
} catch (promoteErr) {
|
|
331
|
+
try {
|
|
332
|
+
await rollbackPromotedFiles(projectDir, stagingDir, preExisting, backups);
|
|
333
|
+
} catch (rollbackErr) {
|
|
334
|
+
const promoteMessage = promoteErr instanceof Error ? promoteErr.message : String(promoteErr);
|
|
335
|
+
const rollbackMessage = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
|
|
336
|
+
throw new CliError(`${promoteMessage}; ${rollbackMessage}`, {
|
|
337
|
+
code: "RUNTIME",
|
|
338
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
throw promoteErr;
|
|
342
|
+
}
|
|
264
343
|
await rm(stagingDir, { recursive: true, force: true });
|
|
265
344
|
} else {
|
|
266
345
|
await rename(stagingDir, projectDir);
|
|
@@ -281,7 +360,9 @@ async function runInit(opts, logger) {
|
|
|
281
360
|
|
|
282
361
|
// src/commands/dev.ts
|
|
283
362
|
import { existsSync as existsSync3 } from "fs";
|
|
284
|
-
import {
|
|
363
|
+
import { mkdir as mkdir2 } from "fs/promises";
|
|
364
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
365
|
+
import { assertSpaDistContentsSafe } from "@lessonkit/lxpack";
|
|
285
366
|
|
|
286
367
|
// src/lib/project.ts
|
|
287
368
|
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
@@ -291,10 +372,13 @@ import { dirname as dirname2, join as join2, parse, resolve as resolve2 } from "
|
|
|
291
372
|
import { parseLessonkitManifest } from "@lessonkit/lxpack";
|
|
292
373
|
var LESSONKIT_JSON = "lessonkit.json";
|
|
293
374
|
var PACKAGE_JSON = "package.json";
|
|
375
|
+
function isSchemaVersionOne(value) {
|
|
376
|
+
return value === 1 || value === "1";
|
|
377
|
+
}
|
|
294
378
|
function isProjectManifest(configPath) {
|
|
295
379
|
try {
|
|
296
380
|
const raw = JSON.parse(readFileSync(configPath, "utf8"));
|
|
297
|
-
return raw.schemaVersion
|
|
381
|
+
return isSchemaVersionOne(raw.schemaVersion) && typeof raw.name === "string" && raw.course !== null && typeof raw.course === "object" && !Array.isArray(raw.course);
|
|
298
382
|
} catch {
|
|
299
383
|
return false;
|
|
300
384
|
}
|
|
@@ -467,7 +551,12 @@ function resolvePackageOutput(project, target, override) {
|
|
|
467
551
|
if (override) {
|
|
468
552
|
try {
|
|
469
553
|
const resolved = resolveSafePackageOutputOverride(project.root, override);
|
|
470
|
-
|
|
554
|
+
const isZipOutput = override.trim().toLowerCase().endsWith(".zip");
|
|
555
|
+
return {
|
|
556
|
+
output: resolved,
|
|
557
|
+
dir: target === "standalone" && !isZipOutput,
|
|
558
|
+
outputBaseDir
|
|
559
|
+
};
|
|
471
560
|
} catch (err) {
|
|
472
561
|
const message = err instanceof Error ? err.message : String(err);
|
|
473
562
|
throw new CliError(message, { code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT });
|
|
@@ -513,7 +602,8 @@ async function runDev(opts) {
|
|
|
513
602
|
const pkg = await readPackageJson(project.root);
|
|
514
603
|
assertViteProject(pkg, project.root);
|
|
515
604
|
const viteJs = resolveViteJs(project.root);
|
|
516
|
-
|
|
605
|
+
const devArgs = stripOutDirFromViteArgs(opts.viteArgs ?? []);
|
|
606
|
+
await runCommand(process.execPath, [viteJs, ...devArgs], {
|
|
517
607
|
cwd: project.root,
|
|
518
608
|
timeoutMs: 0
|
|
519
609
|
});
|
|
@@ -524,11 +614,15 @@ async function runBuild(opts) {
|
|
|
524
614
|
const pkg = await readPackageJson(project.root);
|
|
525
615
|
assertViteProject(pkg, project.root);
|
|
526
616
|
const viteJs = resolveViteJs(project.root);
|
|
617
|
+
const distDir = resolveDistDir(project);
|
|
618
|
+
await mkdir2(dirname3(distDir), { recursive: true });
|
|
619
|
+
if (existsSync3(distDir)) {
|
|
620
|
+
await assertSpaDistContentsSafe({ main: distDir }, project.root);
|
|
621
|
+
}
|
|
527
622
|
const buildArgs = resolveViteBuildArgv(project, opts.viteArgs);
|
|
528
623
|
await runCommand(process.execPath, [viteJs, ...buildArgs], {
|
|
529
624
|
cwd: project.root
|
|
530
625
|
});
|
|
531
|
-
const distDir = resolveDistDir(project);
|
|
532
626
|
const indexHtml = join3(distDir, "index.html");
|
|
533
627
|
if (!existsSync3(indexHtml)) {
|
|
534
628
|
throw new CliError(
|
|
@@ -607,7 +701,8 @@ async function runPackage(opts) {
|
|
|
607
701
|
output,
|
|
608
702
|
dir,
|
|
609
703
|
outputBaseDir,
|
|
610
|
-
strictParity: opts.strictParity
|
|
704
|
+
strictParity: opts.strictParity,
|
|
705
|
+
strictBuild: opts.strict
|
|
611
706
|
});
|
|
612
707
|
if (!result.ok) {
|
|
613
708
|
throw new CliError("Packaging failed.", {
|
|
@@ -639,6 +734,112 @@ async function runPackage(opts) {
|
|
|
639
734
|
};
|
|
640
735
|
}
|
|
641
736
|
|
|
737
|
+
// src/commands/export.ts
|
|
738
|
+
import { existsSync as existsSync5 } from "fs";
|
|
739
|
+
import { relative, resolve as resolve4 } from "path";
|
|
740
|
+
import { exportLkcourse, resolveSafePackageOutputOverride as resolveSafePackageOutputOverride2 } from "@lessonkit/lxpack";
|
|
741
|
+
function resolveExportOutput(projectRoot, override, defaultName) {
|
|
742
|
+
if (override) {
|
|
743
|
+
try {
|
|
744
|
+
return resolveSafePackageOutputOverride2(projectRoot, override);
|
|
745
|
+
} catch (err) {
|
|
746
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
747
|
+
throw new CliError(message, { code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT });
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return resolve4(projectRoot, `${defaultName ?? "course"}.lkcourse`);
|
|
751
|
+
}
|
|
752
|
+
async function runExport(opts) {
|
|
753
|
+
const project = await loadProject(opts.cwd ?? process.cwd());
|
|
754
|
+
const distDir = resolve4(project.root, project.paths.spaDistDir);
|
|
755
|
+
if (opts.noBuild && !existsSync5(distDir)) {
|
|
756
|
+
throw new CliError(
|
|
757
|
+
`dist directory not found at ${distDir}. Run lessonkit build before export with --no-build.`,
|
|
758
|
+
{
|
|
759
|
+
code: "INVALID_PROJECT",
|
|
760
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
761
|
+
}
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
if (!opts.noBuild) {
|
|
765
|
+
await runBuild({ cwd: project.root, json: opts.json });
|
|
766
|
+
}
|
|
767
|
+
if (!existsSync5(distDir)) {
|
|
768
|
+
throw new CliError(`dist directory not found at ${distDir}. Run lessonkit build first.`, {
|
|
769
|
+
code: "INVALID_PROJECT",
|
|
770
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
const resolvedOut = resolveExportOutput(project.root, opts.out, project.name);
|
|
774
|
+
const outRelative = relative(project.root, resolvedOut).replace(/\\/g, "/");
|
|
775
|
+
const result = await exportLkcourse({
|
|
776
|
+
projectRoot: project.root,
|
|
777
|
+
manifest: project,
|
|
778
|
+
outPath: outRelative,
|
|
779
|
+
includeBlockTree: Boolean(opts.withBlockTree)
|
|
780
|
+
});
|
|
781
|
+
if (!result.ok) {
|
|
782
|
+
throw new CliError(
|
|
783
|
+
result.issues.map((i) => `${i.path}: ${i.message}`).join("; "),
|
|
784
|
+
{ code: "EXPORT_FAILED", exitCode: EXIT_PACKAGING }
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
return {
|
|
788
|
+
ok: true,
|
|
789
|
+
command: "export",
|
|
790
|
+
projectRoot: project.root,
|
|
791
|
+
archivePath: result.archivePath,
|
|
792
|
+
fileCount: result.fileCount,
|
|
793
|
+
includeBlockTree: result.includeBlockTree
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/commands/blocks.ts
|
|
798
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
799
|
+
import { createRequire as createRequire2 } from "module";
|
|
800
|
+
function loadBlockCatalog() {
|
|
801
|
+
const require3 = createRequire2(import.meta.url);
|
|
802
|
+
const catalogPath = require3.resolve("@lessonkit/react/block-catalog.v3.json");
|
|
803
|
+
return JSON.parse(readFileSync2(catalogPath, "utf8"));
|
|
804
|
+
}
|
|
805
|
+
function filterEntries(entries, opts) {
|
|
806
|
+
return entries.filter((entry) => {
|
|
807
|
+
if (opts.category && entry.category !== opts.category) return false;
|
|
808
|
+
if (opts.tier && entry.tier !== opts.tier) return false;
|
|
809
|
+
return true;
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
async function runBlocksList(opts) {
|
|
813
|
+
const catalog = loadBlockCatalog();
|
|
814
|
+
const entries = filterEntries(catalog.entries, opts);
|
|
815
|
+
if (!opts.json) {
|
|
816
|
+
const lines = [
|
|
817
|
+
"type category h5pMachineName",
|
|
818
|
+
...entries.map(
|
|
819
|
+
(entry) => [
|
|
820
|
+
entry.type,
|
|
821
|
+
entry.category ?? "\u2014",
|
|
822
|
+
entry.h5pMachineName ?? "\u2014"
|
|
823
|
+
].join(" ")
|
|
824
|
+
)
|
|
825
|
+
];
|
|
826
|
+
return {
|
|
827
|
+
ok: true,
|
|
828
|
+
command: "blocks list",
|
|
829
|
+
schemaVersion: catalog.schemaVersion,
|
|
830
|
+
count: entries.length,
|
|
831
|
+
text: lines.join("\n")
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
ok: true,
|
|
836
|
+
command: "blocks list",
|
|
837
|
+
schemaVersion: catalog.schemaVersion,
|
|
838
|
+
count: entries.length,
|
|
839
|
+
entries
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
|
|
642
843
|
// src/lib/logger.ts
|
|
643
844
|
function createLogger(opts) {
|
|
644
845
|
if (opts?.json) {
|
|
@@ -653,7 +854,7 @@ function createLogger(opts) {
|
|
|
653
854
|
}
|
|
654
855
|
|
|
655
856
|
// src/index.ts
|
|
656
|
-
var require2 =
|
|
857
|
+
var require2 = createRequire3(import.meta.url);
|
|
657
858
|
var { version } = require2("../package.json");
|
|
658
859
|
async function handleCommand(fn, logger, json) {
|
|
659
860
|
try {
|
|
@@ -710,7 +911,7 @@ function createProgram(baseLogger = console) {
|
|
|
710
911
|
);
|
|
711
912
|
}
|
|
712
913
|
);
|
|
713
|
-
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("--strict-parity", "Treat React ID parity warnings as packaging errors").option("--json", "Emit structured JSON result").action(async (opts) => {
|
|
914
|
+
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("--strict-parity", "Treat React ID parity warnings as packaging errors").option("--strict", "Treat Vite build warnings as packaging failures").option("--json", "Emit structured JSON result").action(async (opts) => {
|
|
714
915
|
const logger = createLogger({ json: opts.json });
|
|
715
916
|
await handleCommand(
|
|
716
917
|
async () => {
|
|
@@ -720,7 +921,8 @@ function createProgram(baseLogger = console) {
|
|
|
720
921
|
noBuild: opts.build === false,
|
|
721
922
|
out: opts.out,
|
|
722
923
|
json: opts.json,
|
|
723
|
-
strictParity: opts.strictParity
|
|
924
|
+
strictParity: opts.strictParity,
|
|
925
|
+
strict: opts.strict
|
|
724
926
|
});
|
|
725
927
|
if (!opts.json && result.ok && result.command === "package") {
|
|
726
928
|
if (result.target === "react-vite") {
|
|
@@ -738,6 +940,46 @@ function createProgram(baseLogger = console) {
|
|
|
738
940
|
Boolean(opts.json)
|
|
739
941
|
);
|
|
740
942
|
});
|
|
943
|
+
addCwdAndJson(
|
|
944
|
+
program.command("export").description("Export a portable .lkcourse archive (manifest + interchange + dist)").option("--out <path>", "Output .lkcourse path (relative to project root)").option("--no-build", "Skip implicit Vite build").option("--with-block-tree", "Include optional block-tree.json from src scan")
|
|
945
|
+
).action(async (opts) => {
|
|
946
|
+
const logger = createLogger({ json: opts.json });
|
|
947
|
+
await handleCommand(
|
|
948
|
+
async () => {
|
|
949
|
+
const result = await runExport({
|
|
950
|
+
cwd: opts.cwd,
|
|
951
|
+
out: opts.out,
|
|
952
|
+
noBuild: opts.build === false,
|
|
953
|
+
withBlockTree: opts.withBlockTree,
|
|
954
|
+
json: opts.json
|
|
955
|
+
});
|
|
956
|
+
if (!opts.json && result.ok && result.command === "export") {
|
|
957
|
+
logger.log(`Exported .lkcourse \u2192 ${result.archivePath} (${result.fileCount} files)`);
|
|
958
|
+
}
|
|
959
|
+
return result;
|
|
960
|
+
},
|
|
961
|
+
logger,
|
|
962
|
+
Boolean(opts.json)
|
|
963
|
+
);
|
|
964
|
+
});
|
|
965
|
+
program.command("blocks").description("Block registry commands").command("list").description("List runtime blocks from block-catalog.v3.json").option("--json", "Emit structured JSON result").option("--category <category>", "Filter by category (container, assessment, content, compound)").option("--tier <tier>", "Filter by tier (A, B, C, D, E)").action(async (opts) => {
|
|
966
|
+
const logger = createLogger({ json: opts.json });
|
|
967
|
+
await handleCommand(
|
|
968
|
+
async () => {
|
|
969
|
+
const result = await runBlocksList({
|
|
970
|
+
json: opts.json,
|
|
971
|
+
category: opts.category,
|
|
972
|
+
tier: opts.tier
|
|
973
|
+
});
|
|
974
|
+
if (!opts.json && result.ok && "text" in result && typeof result.text === "string") {
|
|
975
|
+
logger.log(result.text);
|
|
976
|
+
}
|
|
977
|
+
return result;
|
|
978
|
+
},
|
|
979
|
+
logger,
|
|
980
|
+
Boolean(opts.json)
|
|
981
|
+
);
|
|
982
|
+
});
|
|
741
983
|
program.command("publish").description("[maintainers] Not implemented \u2014 use Changesets (see RELEASING.md)").action(() => {
|
|
742
984
|
baseLogger.log(
|
|
743
985
|
"lessonkit publish is not implemented. Monorepo releases use Changesets: npm run changeset && npm run version-packages && npm run release. See RELEASING.md."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessonkit/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "LessonKit CLI — init, dev, build, and package learning experiences.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -45,8 +45,9 @@
|
|
|
45
45
|
"lint": "eslint --max-warnings 0 \"src/**/*.{ts,tsx}\" \"test/**/*.{ts,tsx}\""
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@lessonkit/core": "1.
|
|
49
|
-
"@lessonkit/lxpack": "1.
|
|
48
|
+
"@lessonkit/core": "1.6.0",
|
|
49
|
+
"@lessonkit/lxpack": "1.6.0",
|
|
50
|
+
"@lessonkit/react": "1.6.0",
|
|
50
51
|
"commander": "^15.0.0"
|
|
51
52
|
},
|
|
52
53
|
"engines": {
|
|
@@ -16,16 +16,16 @@
|
|
|
16
16
|
"test:coverage": "vitest run --coverage --passWithNoTests=false"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@lessonkit/core": "^1.
|
|
20
|
-
"@lessonkit/react": "^1.
|
|
21
|
-
"@lessonkit/themes": "^1.
|
|
22
|
-
"@lessonkit/xapi": "^1.
|
|
19
|
+
"@lessonkit/core": "^1.6.0",
|
|
20
|
+
"@lessonkit/react": "^1.6.0",
|
|
21
|
+
"@lessonkit/themes": "^1.6.0",
|
|
22
|
+
"@lessonkit/xapi": "^1.6.0",
|
|
23
23
|
"react": "^19.2.7",
|
|
24
24
|
"react-dom": "^19.2.7"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@lessonkit/cli": "^1.
|
|
28
|
-
"@lessonkit/lxpack": "^1.
|
|
27
|
+
"@lessonkit/cli": "^1.6.0",
|
|
28
|
+
"@lessonkit/lxpack": "^1.6.0",
|
|
29
29
|
"@testing-library/react": "^16.3.0",
|
|
30
30
|
"@testing-library/dom": "^10.4.1",
|
|
31
31
|
"@types/react": "^19.2.17",
|