@lessonkit/cli 0.9.1 → 0.9.3
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 +74 -13
- package/dist/index.js +74 -13
- package/package.json +3 -2
- package/template/vite-react/dist/assets/index-Dg5lKLi0.js +8 -0
- package/template/vite-react/dist/index.html +1 -1
- package/template/vite-react/package.json +6 -6
- package/template/vite-react/dist/assets/index-Dt_e2Ur-.js +0 -8
package/dist/bin.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createRequire } from "module";
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/init.ts
|
|
8
|
+
import { slugifyId } from "@lessonkit/core";
|
|
8
9
|
import { cp, mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
9
10
|
import { existsSync } from "fs";
|
|
10
11
|
import { basename, dirname, join, resolve } from "path";
|
|
@@ -92,10 +93,14 @@ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".lxpack", ".gi
|
|
|
92
93
|
var SKIP_FILES = /* @__PURE__ */ new Set([".DS_Store"]);
|
|
93
94
|
function getTemplateDir() {
|
|
94
95
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
const candidates = [
|
|
97
|
+
resolve(thisDir, "../template/vite-react"),
|
|
98
|
+
resolve(thisDir, "../../template/vite-react")
|
|
99
|
+
];
|
|
100
|
+
for (const candidate of candidates) {
|
|
101
|
+
if (existsSync(candidate)) return candidate;
|
|
102
|
+
}
|
|
103
|
+
return candidates[0];
|
|
99
104
|
}
|
|
100
105
|
async function isDirEmpty(dir) {
|
|
101
106
|
if (!existsSync(dir)) return true;
|
|
@@ -108,7 +113,7 @@ async function isDirEmptyOrDotfilesOnly(dir) {
|
|
|
108
113
|
return entries.every((name) => name.startsWith("."));
|
|
109
114
|
}
|
|
110
115
|
function escapeJsxString(value) {
|
|
111
|
-
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
116
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\{/g, "\\{").replace(/\}/g, "\\}").replace(/</g, "\\u003c").replace(/\r\n|\n|\r/g, "\\n");
|
|
112
117
|
}
|
|
113
118
|
async function copyTemplate(src, dest) {
|
|
114
119
|
await mkdir(dest, { recursive: true });
|
|
@@ -146,14 +151,14 @@ async function applyTemplateSubstitutions(projectDir, projectName, slug) {
|
|
|
146
151
|
}
|
|
147
152
|
async function runInit(opts, logger) {
|
|
148
153
|
const cwd = process.cwd();
|
|
149
|
-
const rawName = opts.name ?? (opts.here ?
|
|
154
|
+
const rawName = opts.name ?? (opts.here ? slugifyId(basename(process.cwd()) || "my-course") : void 0);
|
|
150
155
|
if (!rawName && !opts.here) {
|
|
151
156
|
throw new CliError("Project name is required. Usage: lessonkit init <name> or lessonkit init --here", {
|
|
152
157
|
code: "INVALID_PROJECT",
|
|
153
158
|
exitCode: EXIT_INVALID_PROJECT
|
|
154
159
|
});
|
|
155
160
|
}
|
|
156
|
-
const slug =
|
|
161
|
+
const slug = slugifyId(rawName ?? "my-course");
|
|
157
162
|
const projectName = rawName ?? slug;
|
|
158
163
|
const projectDir = opts.here ? cwd : resolve(cwd, slug);
|
|
159
164
|
if (!opts.here && existsSync(projectDir)) {
|
|
@@ -276,6 +281,19 @@ async function loadLessonkitJson(projectRoot) {
|
|
|
276
281
|
exitCode: EXIT_INVALID_PROJECT
|
|
277
282
|
});
|
|
278
283
|
}
|
|
284
|
+
const courseObj = courseRaw;
|
|
285
|
+
if (courseObj.lessons !== void 0 && !Array.isArray(courseObj.lessons)) {
|
|
286
|
+
throw new CliError(`${configPath}: "course.lessons" must be an array.`, {
|
|
287
|
+
code: "INVALID_PROJECT",
|
|
288
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
if (courseObj.assessments !== void 0 && !Array.isArray(courseObj.assessments)) {
|
|
292
|
+
throw new CliError(`${configPath}: "course.assessments" must be an array.`, {
|
|
293
|
+
code: "INVALID_PROJECT",
|
|
294
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
295
|
+
});
|
|
296
|
+
}
|
|
279
297
|
const validation = validateDescriptor(courseRaw);
|
|
280
298
|
if (!validation.ok) {
|
|
281
299
|
throw new CliError(`${configPath}: invalid course descriptor.`, {
|
|
@@ -295,11 +313,48 @@ async function loadLessonkitJson(projectRoot) {
|
|
|
295
313
|
}
|
|
296
314
|
const pathsRaw = config.paths;
|
|
297
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.`, {
|
|
318
|
+
code: "INVALID_PROJECT",
|
|
319
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
320
|
+
});
|
|
321
|
+
}
|
|
298
322
|
if (pathsRaw && typeof pathsRaw === "object") {
|
|
299
323
|
const p = pathsRaw;
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
324
|
+
if (p.spaDistDir !== void 0) {
|
|
325
|
+
if (typeof p.spaDistDir !== "string" || !p.spaDistDir.trim()) {
|
|
326
|
+
throw new CliError(`${configPath}: "paths.spaDistDir" must be a non-empty string.`, {
|
|
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
|
+
);
|
|
303
358
|
}
|
|
304
359
|
const pathIssues = validateProjectPaths(projectRoot, paths);
|
|
305
360
|
if (pathIssues.length) {
|
|
@@ -380,8 +435,13 @@ function resolveLxpackOutDir(project) {
|
|
|
380
435
|
function resolvePackageOutput(project, target, override) {
|
|
381
436
|
const outputBaseDir = project.paths.outputBaseDir;
|
|
382
437
|
if (override) {
|
|
383
|
-
|
|
384
|
-
|
|
438
|
+
try {
|
|
439
|
+
const resolved = resolveSafePackageOutputOverride(project.root, override);
|
|
440
|
+
return { output: resolved, dir: target === "standalone", outputBaseDir };
|
|
441
|
+
} catch (err) {
|
|
442
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
443
|
+
throw new CliError(message, { code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT });
|
|
444
|
+
}
|
|
385
445
|
}
|
|
386
446
|
if (target === "standalone") {
|
|
387
447
|
return { output: `${outputBaseDir}/standalone`, dir: true, outputBaseDir };
|
|
@@ -458,7 +518,7 @@ async function runPackage(opts) {
|
|
|
458
518
|
}
|
|
459
519
|
if (!existsSync3(distDir)) {
|
|
460
520
|
throw new CliError(`Build completed but dist directory not found at ${distDir}.`, {
|
|
461
|
-
code: "
|
|
521
|
+
code: "INVALID_PROJECT",
|
|
462
522
|
exitCode: EXIT_INVALID_PROJECT
|
|
463
523
|
});
|
|
464
524
|
}
|
|
@@ -480,6 +540,7 @@ async function runPackage(opts) {
|
|
|
480
540
|
descriptor: project.course,
|
|
481
541
|
outDir,
|
|
482
542
|
spaDistDir: distDir,
|
|
543
|
+
projectRoot: project.root,
|
|
483
544
|
target,
|
|
484
545
|
output,
|
|
485
546
|
dir,
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createRequire } from "module";
|
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
|
|
5
5
|
// src/commands/init.ts
|
|
6
|
+
import { slugifyId } from "@lessonkit/core";
|
|
6
7
|
import { cp, mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
7
8
|
import { existsSync } from "fs";
|
|
8
9
|
import { basename, dirname, join, resolve } from "path";
|
|
@@ -90,10 +91,14 @@ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".lxpack", ".gi
|
|
|
90
91
|
var SKIP_FILES = /* @__PURE__ */ new Set([".DS_Store"]);
|
|
91
92
|
function getTemplateDir() {
|
|
92
93
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
const candidates = [
|
|
95
|
+
resolve(thisDir, "../template/vite-react"),
|
|
96
|
+
resolve(thisDir, "../../template/vite-react")
|
|
97
|
+
];
|
|
98
|
+
for (const candidate of candidates) {
|
|
99
|
+
if (existsSync(candidate)) return candidate;
|
|
100
|
+
}
|
|
101
|
+
return candidates[0];
|
|
97
102
|
}
|
|
98
103
|
async function isDirEmpty(dir) {
|
|
99
104
|
if (!existsSync(dir)) return true;
|
|
@@ -106,7 +111,7 @@ async function isDirEmptyOrDotfilesOnly(dir) {
|
|
|
106
111
|
return entries.every((name) => name.startsWith("."));
|
|
107
112
|
}
|
|
108
113
|
function escapeJsxString(value) {
|
|
109
|
-
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
114
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\{/g, "\\{").replace(/\}/g, "\\}").replace(/</g, "\\u003c").replace(/\r\n|\n|\r/g, "\\n");
|
|
110
115
|
}
|
|
111
116
|
async function copyTemplate(src, dest) {
|
|
112
117
|
await mkdir(dest, { recursive: true });
|
|
@@ -144,14 +149,14 @@ async function applyTemplateSubstitutions(projectDir, projectName, slug) {
|
|
|
144
149
|
}
|
|
145
150
|
async function runInit(opts, logger) {
|
|
146
151
|
const cwd = process.cwd();
|
|
147
|
-
const rawName = opts.name ?? (opts.here ?
|
|
152
|
+
const rawName = opts.name ?? (opts.here ? slugifyId(basename(process.cwd()) || "my-course") : void 0);
|
|
148
153
|
if (!rawName && !opts.here) {
|
|
149
154
|
throw new CliError("Project name is required. Usage: lessonkit init <name> or lessonkit init --here", {
|
|
150
155
|
code: "INVALID_PROJECT",
|
|
151
156
|
exitCode: EXIT_INVALID_PROJECT
|
|
152
157
|
});
|
|
153
158
|
}
|
|
154
|
-
const slug =
|
|
159
|
+
const slug = slugifyId(rawName ?? "my-course");
|
|
155
160
|
const projectName = rawName ?? slug;
|
|
156
161
|
const projectDir = opts.here ? cwd : resolve(cwd, slug);
|
|
157
162
|
if (!opts.here && existsSync(projectDir)) {
|
|
@@ -274,6 +279,19 @@ async function loadLessonkitJson(projectRoot) {
|
|
|
274
279
|
exitCode: EXIT_INVALID_PROJECT
|
|
275
280
|
});
|
|
276
281
|
}
|
|
282
|
+
const courseObj = courseRaw;
|
|
283
|
+
if (courseObj.lessons !== void 0 && !Array.isArray(courseObj.lessons)) {
|
|
284
|
+
throw new CliError(`${configPath}: "course.lessons" must be an array.`, {
|
|
285
|
+
code: "INVALID_PROJECT",
|
|
286
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (courseObj.assessments !== void 0 && !Array.isArray(courseObj.assessments)) {
|
|
290
|
+
throw new CliError(`${configPath}: "course.assessments" must be an array.`, {
|
|
291
|
+
code: "INVALID_PROJECT",
|
|
292
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
293
|
+
});
|
|
294
|
+
}
|
|
277
295
|
const validation = validateDescriptor(courseRaw);
|
|
278
296
|
if (!validation.ok) {
|
|
279
297
|
throw new CliError(`${configPath}: invalid course descriptor.`, {
|
|
@@ -293,11 +311,48 @@ async function loadLessonkitJson(projectRoot) {
|
|
|
293
311
|
}
|
|
294
312
|
const pathsRaw = config.paths;
|
|
295
313
|
const paths = { ...DEFAULT_PATHS };
|
|
314
|
+
if (pathsRaw !== void 0 && (typeof pathsRaw !== "object" || pathsRaw === null)) {
|
|
315
|
+
throw new CliError(`${configPath}: "paths" must be an object.`, {
|
|
316
|
+
code: "INVALID_PROJECT",
|
|
317
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
318
|
+
});
|
|
319
|
+
}
|
|
296
320
|
if (pathsRaw && typeof pathsRaw === "object") {
|
|
297
321
|
const p = pathsRaw;
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
322
|
+
if (p.spaDistDir !== void 0) {
|
|
323
|
+
if (typeof p.spaDistDir !== "string" || !p.spaDistDir.trim()) {
|
|
324
|
+
throw new CliError(`${configPath}: "paths.spaDistDir" must be a non-empty string.`, {
|
|
325
|
+
code: "INVALID_PROJECT",
|
|
326
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
paths.spaDistDir = p.spaDistDir;
|
|
330
|
+
}
|
|
331
|
+
if (p.lxpackOutDir !== void 0) {
|
|
332
|
+
if (typeof p.lxpackOutDir !== "string" || !p.lxpackOutDir.trim()) {
|
|
333
|
+
throw new CliError(`${configPath}: "paths.lxpackOutDir" must be a non-empty string.`, {
|
|
334
|
+
code: "INVALID_PROJECT",
|
|
335
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
paths.lxpackOutDir = p.lxpackOutDir;
|
|
339
|
+
}
|
|
340
|
+
if (p.outputBaseDir !== void 0) {
|
|
341
|
+
if (typeof p.outputBaseDir !== "string" || !p.outputBaseDir.trim()) {
|
|
342
|
+
throw new CliError(`${configPath}: "paths.outputBaseDir" must be a non-empty string.`, {
|
|
343
|
+
code: "INVALID_PROJECT",
|
|
344
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
paths.outputBaseDir = p.outputBaseDir;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const courseSpaDistDir = validation.descriptor.spaDistDir?.trim();
|
|
351
|
+
if (courseSpaDistDir && courseSpaDistDir !== paths.spaDistDir) {
|
|
352
|
+
throw new CliError(
|
|
353
|
+
`${configPath}: "course.spaDistDir" (${courseSpaDistDir}) differs from "paths.spaDistDir" (${paths.spaDistDir}). Use paths.spaDistDir for CLI build and package.`,
|
|
354
|
+
{ code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT }
|
|
355
|
+
);
|
|
301
356
|
}
|
|
302
357
|
const pathIssues = validateProjectPaths(projectRoot, paths);
|
|
303
358
|
if (pathIssues.length) {
|
|
@@ -378,8 +433,13 @@ function resolveLxpackOutDir(project) {
|
|
|
378
433
|
function resolvePackageOutput(project, target, override) {
|
|
379
434
|
const outputBaseDir = project.paths.outputBaseDir;
|
|
380
435
|
if (override) {
|
|
381
|
-
|
|
382
|
-
|
|
436
|
+
try {
|
|
437
|
+
const resolved = resolveSafePackageOutputOverride(project.root, override);
|
|
438
|
+
return { output: resolved, dir: target === "standalone", outputBaseDir };
|
|
439
|
+
} catch (err) {
|
|
440
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
441
|
+
throw new CliError(message, { code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT });
|
|
442
|
+
}
|
|
383
443
|
}
|
|
384
444
|
if (target === "standalone") {
|
|
385
445
|
return { output: `${outputBaseDir}/standalone`, dir: true, outputBaseDir };
|
|
@@ -456,7 +516,7 @@ async function runPackage(opts) {
|
|
|
456
516
|
}
|
|
457
517
|
if (!existsSync3(distDir)) {
|
|
458
518
|
throw new CliError(`Build completed but dist directory not found at ${distDir}.`, {
|
|
459
|
-
code: "
|
|
519
|
+
code: "INVALID_PROJECT",
|
|
460
520
|
exitCode: EXIT_INVALID_PROJECT
|
|
461
521
|
});
|
|
462
522
|
}
|
|
@@ -478,6 +538,7 @@ async function runPackage(opts) {
|
|
|
478
538
|
descriptor: project.course,
|
|
479
539
|
outDir,
|
|
480
540
|
spaDistDir: distDir,
|
|
541
|
+
projectRoot: project.root,
|
|
481
542
|
target,
|
|
482
543
|
output,
|
|
483
544
|
dir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessonkit/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "LessonKit CLI — init, dev, build, and package learning experiences.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"lint": "echo \"(no lint configured yet)\""
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@lessonkit/
|
|
45
|
+
"@lessonkit/core": "0.9.3",
|
|
46
|
+
"@lessonkit/lxpack": "0.9.3",
|
|
46
47
|
"commander": "^14.0.1"
|
|
47
48
|
},
|
|
48
49
|
"engines": {
|