@lessonkit/cli 0.7.0 → 0.8.1
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 +9 -6
- package/dist/bin.js +40 -10
- package/dist/index.js +40 -10
- package/package.json +2 -2
- package/template/vite-react/README.md +6 -6
- package/template/vite-react/dist/assets/index-DPOpbRl5.js +8 -0
- package/template/vite-react/dist/index.html +1 -1
- package/template/vite-react/package.json +6 -6
- package/template/vite-react/src/App.tsx +1 -1
- package/template/vite-react/dist/assets/index-B7c44fhF.js +0 -8
package/README.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# `@lessonkit/cli`
|
|
2
2
|
|
|
3
3
|
[](https://github.com/eddiethedean/lessonkit/actions/workflows/ci.yml)
|
|
4
|
+
[](https://lessonkit.readthedocs.io/en/latest/)
|
|
4
5
|
[](https://www.npmjs.com/package/@lessonkit/cli)
|
|
5
|
-
[](
|
|
6
|
+
[](https://github.com/eddiethedean/lessonkit/blob/main/LICENSE)
|
|
6
7
|
|
|
7
8
|
LessonKit CLI — scaffold, dev, build, and package learning experiences.
|
|
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)
|
|
11
|
+
|
|
9
12
|
## Install
|
|
10
13
|
|
|
11
14
|
```bash
|
|
@@ -34,7 +37,7 @@ lessonkit package --target scorm12
|
|
|
34
37
|
| `lessonkit dev` | Run Vite dev server |
|
|
35
38
|
| `lessonkit build` | Production Vite build |
|
|
36
39
|
| `lessonkit package --target <target>` | Build or package for web / LMS |
|
|
37
|
-
| `lessonkit publish` | Stub — see [`RELEASING.md`](
|
|
40
|
+
| `lessonkit publish` | Stub — see [`RELEASING.md`](https://github.com/eddiethedean/lessonkit/blob/main/RELEASING.md) |
|
|
38
41
|
|
|
39
42
|
### Package targets
|
|
40
43
|
|
|
@@ -43,10 +46,10 @@ lessonkit package --target scorm12
|
|
|
43
46
|
|
|
44
47
|
## Project manifest
|
|
45
48
|
|
|
46
|
-
Projects include a `lessonkit.json` at the root. See [
|
|
49
|
+
Projects include a `lessonkit.json` at the root. See the [CLI reference](https://lessonkit.readthedocs.io/en/latest/reference/cli.html) for the schema, flags, exit codes, and JSON output mode.
|
|
47
50
|
|
|
48
51
|
## Related
|
|
49
52
|
|
|
50
|
-
- [
|
|
51
|
-
- [
|
|
52
|
-
- [`templates/vite-react`](
|
|
53
|
+
- [Packaging reference](https://lessonkit.readthedocs.io/en/latest/reference/packaging.html) — LXPack output layout
|
|
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
|
package/dist/bin.js
CHANGED
|
@@ -102,6 +102,14 @@ async function isDirEmpty(dir) {
|
|
|
102
102
|
const entries = await readdir(dir);
|
|
103
103
|
return entries.length === 0;
|
|
104
104
|
}
|
|
105
|
+
async function isDirEmptyOrDotfilesOnly(dir) {
|
|
106
|
+
if (!existsSync(dir)) return true;
|
|
107
|
+
const entries = await readdir(dir);
|
|
108
|
+
return entries.every((name) => name.startsWith("."));
|
|
109
|
+
}
|
|
110
|
+
function escapeJsxString(value) {
|
|
111
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
112
|
+
}
|
|
105
113
|
async function copyTemplate(src, dest) {
|
|
106
114
|
await mkdir(dest, { recursive: true });
|
|
107
115
|
const entries = await readdir(src, { withFileTypes: true });
|
|
@@ -133,9 +141,7 @@ async function applyTemplateSubstitutions(projectDir, projectName, slug) {
|
|
|
133
141
|
const appPath = join(projectDir, "src", "App.tsx");
|
|
134
142
|
let appSource = await readFile(appPath, "utf8");
|
|
135
143
|
appSource = appSource.replace(/courseId="my-course"/g, `courseId="${slug}"`);
|
|
136
|
-
appSource = appSource.replace(/
|
|
137
|
-
appSource = appSource.replace(/preset="dark"/g, 'preset="default"');
|
|
138
|
-
appSource = appSource.replace(/mode="dark"/g, 'mode="light"');
|
|
144
|
+
appSource = appSource.replace(/\{\{courseTitle\}\}/g, escapeJsxString(projectName));
|
|
139
145
|
await writeFile(appPath, appSource, "utf8");
|
|
140
146
|
}
|
|
141
147
|
async function runInit(opts, logger) {
|
|
@@ -151,10 +157,13 @@ async function runInit(opts, logger) {
|
|
|
151
157
|
const projectName = rawName ?? slug;
|
|
152
158
|
const projectDir = opts.here ? cwd : resolve(cwd, slug);
|
|
153
159
|
if (!opts.here && existsSync(projectDir)) {
|
|
154
|
-
throw new CliError(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
throw new CliError(
|
|
161
|
+
`Directory already exists: ${projectDir}. Choose a different name or remove the directory.`,
|
|
162
|
+
{
|
|
163
|
+
code: "INVALID_PROJECT",
|
|
164
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
165
|
+
}
|
|
166
|
+
);
|
|
158
167
|
}
|
|
159
168
|
if (opts.here && !await isDirEmpty(projectDir) && !opts.force) {
|
|
160
169
|
throw new CliError(`Directory is not empty: ${projectDir}. Use --force to initialize anyway.`, {
|
|
@@ -162,6 +171,15 @@ async function runInit(opts, logger) {
|
|
|
162
171
|
exitCode: EXIT_INVALID_PROJECT
|
|
163
172
|
});
|
|
164
173
|
}
|
|
174
|
+
if (opts.here && opts.force && !await isDirEmptyOrDotfilesOnly(projectDir)) {
|
|
175
|
+
throw new CliError(
|
|
176
|
+
`Directory is not empty: ${projectDir}. --force only initializes when the directory is empty or contains dotfiles only (e.g. .git).`,
|
|
177
|
+
{
|
|
178
|
+
code: "INVALID_PROJECT",
|
|
179
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
}
|
|
165
183
|
const templateDir = getTemplateDir();
|
|
166
184
|
if (!existsSync(templateDir)) {
|
|
167
185
|
throw new CliError(`Bundled template not found at ${templateDir}. Reinstall @lessonkit/cli.`, {
|
|
@@ -186,7 +204,7 @@ async function runInit(opts, logger) {
|
|
|
186
204
|
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
187
205
|
import { readFile as readFile2 } from "fs/promises";
|
|
188
206
|
import { dirname as dirname2, join as join2, parse, resolve as resolve2 } from "path";
|
|
189
|
-
import { validateDescriptor } from "@lessonkit/lxpack";
|
|
207
|
+
import { validateDescriptor, validateProjectPaths } from "@lessonkit/lxpack";
|
|
190
208
|
var LESSONKIT_JSON = "lessonkit.json";
|
|
191
209
|
var PACKAGE_JSON = "package.json";
|
|
192
210
|
var DEFAULT_PATHS = {
|
|
@@ -283,6 +301,14 @@ async function loadLessonkitJson(projectRoot) {
|
|
|
283
301
|
if (typeof p.lxpackOutDir === "string" && p.lxpackOutDir.trim()) paths.lxpackOutDir = p.lxpackOutDir;
|
|
284
302
|
if (typeof p.outputBaseDir === "string" && p.outputBaseDir.trim()) paths.outputBaseDir = p.outputBaseDir;
|
|
285
303
|
}
|
|
304
|
+
const pathIssues = validateProjectPaths(projectRoot, paths);
|
|
305
|
+
if (pathIssues.length) {
|
|
306
|
+
throw new CliError(`${configPath}: invalid paths.`, {
|
|
307
|
+
code: "INVALID_PROJECT",
|
|
308
|
+
exitCode: EXIT_INVALID_PROJECT,
|
|
309
|
+
issues: pathIssues
|
|
310
|
+
});
|
|
311
|
+
}
|
|
286
312
|
return {
|
|
287
313
|
root: projectRoot,
|
|
288
314
|
schemaVersion: 1,
|
|
@@ -344,6 +370,7 @@ function assertNode20ForLxpack() {
|
|
|
344
370
|
|
|
345
371
|
// src/lib/paths.ts
|
|
346
372
|
import { resolve as resolve3 } from "path";
|
|
373
|
+
import { resolveSafePackageOutputOverride } from "@lessonkit/lxpack";
|
|
347
374
|
function resolveDistDir(project) {
|
|
348
375
|
return resolve3(project.root, project.paths.spaDistDir);
|
|
349
376
|
}
|
|
@@ -353,7 +380,8 @@ function resolveLxpackOutDir(project) {
|
|
|
353
380
|
function resolvePackageOutput(project, target, override) {
|
|
354
381
|
const outputBaseDir = project.paths.outputBaseDir;
|
|
355
382
|
if (override) {
|
|
356
|
-
|
|
383
|
+
const resolved = resolveSafePackageOutputOverride(project.root, override);
|
|
384
|
+
return { output: resolved, dir: target === "standalone", outputBaseDir };
|
|
357
385
|
}
|
|
358
386
|
if (target === "standalone") {
|
|
359
387
|
return { output: `${outputBaseDir}/standalone`, dir: true, outputBaseDir };
|
|
@@ -425,7 +453,9 @@ async function runPackage(opts) {
|
|
|
425
453
|
const project = await loadProject(opts.cwd ?? process.cwd());
|
|
426
454
|
const distDir = resolveDistDir(project);
|
|
427
455
|
if (target === "react-vite") {
|
|
428
|
-
|
|
456
|
+
if (!opts.noBuild || !existsSync3(distDir)) {
|
|
457
|
+
await runBuild({ cwd: project.root, json: opts.json });
|
|
458
|
+
}
|
|
429
459
|
if (!existsSync3(distDir)) {
|
|
430
460
|
throw new CliError(`Build completed but dist directory not found at ${distDir}.`, {
|
|
431
461
|
code: "RUNTIME",
|
package/dist/index.js
CHANGED
|
@@ -100,6 +100,14 @@ async function isDirEmpty(dir) {
|
|
|
100
100
|
const entries = await readdir(dir);
|
|
101
101
|
return entries.length === 0;
|
|
102
102
|
}
|
|
103
|
+
async function isDirEmptyOrDotfilesOnly(dir) {
|
|
104
|
+
if (!existsSync(dir)) return true;
|
|
105
|
+
const entries = await readdir(dir);
|
|
106
|
+
return entries.every((name) => name.startsWith("."));
|
|
107
|
+
}
|
|
108
|
+
function escapeJsxString(value) {
|
|
109
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
110
|
+
}
|
|
103
111
|
async function copyTemplate(src, dest) {
|
|
104
112
|
await mkdir(dest, { recursive: true });
|
|
105
113
|
const entries = await readdir(src, { withFileTypes: true });
|
|
@@ -131,9 +139,7 @@ async function applyTemplateSubstitutions(projectDir, projectName, slug) {
|
|
|
131
139
|
const appPath = join(projectDir, "src", "App.tsx");
|
|
132
140
|
let appSource = await readFile(appPath, "utf8");
|
|
133
141
|
appSource = appSource.replace(/courseId="my-course"/g, `courseId="${slug}"`);
|
|
134
|
-
appSource = appSource.replace(/
|
|
135
|
-
appSource = appSource.replace(/preset="dark"/g, 'preset="default"');
|
|
136
|
-
appSource = appSource.replace(/mode="dark"/g, 'mode="light"');
|
|
142
|
+
appSource = appSource.replace(/\{\{courseTitle\}\}/g, escapeJsxString(projectName));
|
|
137
143
|
await writeFile(appPath, appSource, "utf8");
|
|
138
144
|
}
|
|
139
145
|
async function runInit(opts, logger) {
|
|
@@ -149,10 +155,13 @@ async function runInit(opts, logger) {
|
|
|
149
155
|
const projectName = rawName ?? slug;
|
|
150
156
|
const projectDir = opts.here ? cwd : resolve(cwd, slug);
|
|
151
157
|
if (!opts.here && existsSync(projectDir)) {
|
|
152
|
-
throw new CliError(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
throw new CliError(
|
|
159
|
+
`Directory already exists: ${projectDir}. Choose a different name or remove the directory.`,
|
|
160
|
+
{
|
|
161
|
+
code: "INVALID_PROJECT",
|
|
162
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
163
|
+
}
|
|
164
|
+
);
|
|
156
165
|
}
|
|
157
166
|
if (opts.here && !await isDirEmpty(projectDir) && !opts.force) {
|
|
158
167
|
throw new CliError(`Directory is not empty: ${projectDir}. Use --force to initialize anyway.`, {
|
|
@@ -160,6 +169,15 @@ async function runInit(opts, logger) {
|
|
|
160
169
|
exitCode: EXIT_INVALID_PROJECT
|
|
161
170
|
});
|
|
162
171
|
}
|
|
172
|
+
if (opts.here && opts.force && !await isDirEmptyOrDotfilesOnly(projectDir)) {
|
|
173
|
+
throw new CliError(
|
|
174
|
+
`Directory is not empty: ${projectDir}. --force only initializes when the directory is empty or contains dotfiles only (e.g. .git).`,
|
|
175
|
+
{
|
|
176
|
+
code: "INVALID_PROJECT",
|
|
177
|
+
exitCode: EXIT_INVALID_PROJECT
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
}
|
|
163
181
|
const templateDir = getTemplateDir();
|
|
164
182
|
if (!existsSync(templateDir)) {
|
|
165
183
|
throw new CliError(`Bundled template not found at ${templateDir}. Reinstall @lessonkit/cli.`, {
|
|
@@ -184,7 +202,7 @@ async function runInit(opts, logger) {
|
|
|
184
202
|
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
185
203
|
import { readFile as readFile2 } from "fs/promises";
|
|
186
204
|
import { dirname as dirname2, join as join2, parse, resolve as resolve2 } from "path";
|
|
187
|
-
import { validateDescriptor } from "@lessonkit/lxpack";
|
|
205
|
+
import { validateDescriptor, validateProjectPaths } from "@lessonkit/lxpack";
|
|
188
206
|
var LESSONKIT_JSON = "lessonkit.json";
|
|
189
207
|
var PACKAGE_JSON = "package.json";
|
|
190
208
|
var DEFAULT_PATHS = {
|
|
@@ -281,6 +299,14 @@ async function loadLessonkitJson(projectRoot) {
|
|
|
281
299
|
if (typeof p.lxpackOutDir === "string" && p.lxpackOutDir.trim()) paths.lxpackOutDir = p.lxpackOutDir;
|
|
282
300
|
if (typeof p.outputBaseDir === "string" && p.outputBaseDir.trim()) paths.outputBaseDir = p.outputBaseDir;
|
|
283
301
|
}
|
|
302
|
+
const pathIssues = validateProjectPaths(projectRoot, paths);
|
|
303
|
+
if (pathIssues.length) {
|
|
304
|
+
throw new CliError(`${configPath}: invalid paths.`, {
|
|
305
|
+
code: "INVALID_PROJECT",
|
|
306
|
+
exitCode: EXIT_INVALID_PROJECT,
|
|
307
|
+
issues: pathIssues
|
|
308
|
+
});
|
|
309
|
+
}
|
|
284
310
|
return {
|
|
285
311
|
root: projectRoot,
|
|
286
312
|
schemaVersion: 1,
|
|
@@ -342,6 +368,7 @@ function assertNode20ForLxpack() {
|
|
|
342
368
|
|
|
343
369
|
// src/lib/paths.ts
|
|
344
370
|
import { resolve as resolve3 } from "path";
|
|
371
|
+
import { resolveSafePackageOutputOverride } from "@lessonkit/lxpack";
|
|
345
372
|
function resolveDistDir(project) {
|
|
346
373
|
return resolve3(project.root, project.paths.spaDistDir);
|
|
347
374
|
}
|
|
@@ -351,7 +378,8 @@ function resolveLxpackOutDir(project) {
|
|
|
351
378
|
function resolvePackageOutput(project, target, override) {
|
|
352
379
|
const outputBaseDir = project.paths.outputBaseDir;
|
|
353
380
|
if (override) {
|
|
354
|
-
|
|
381
|
+
const resolved = resolveSafePackageOutputOverride(project.root, override);
|
|
382
|
+
return { output: resolved, dir: target === "standalone", outputBaseDir };
|
|
355
383
|
}
|
|
356
384
|
if (target === "standalone") {
|
|
357
385
|
return { output: `${outputBaseDir}/standalone`, dir: true, outputBaseDir };
|
|
@@ -423,7 +451,9 @@ async function runPackage(opts) {
|
|
|
423
451
|
const project = await loadProject(opts.cwd ?? process.cwd());
|
|
424
452
|
const distDir = resolveDistDir(project);
|
|
425
453
|
if (target === "react-vite") {
|
|
426
|
-
|
|
454
|
+
if (!opts.noBuild || !existsSync3(distDir)) {
|
|
455
|
+
await runBuild({ cwd: project.root, json: opts.json });
|
|
456
|
+
}
|
|
427
457
|
if (!existsSync3(distDir)) {
|
|
428
458
|
throw new CliError(`Build completed but dist directory not found at ${distDir}.`, {
|
|
429
459
|
code: "RUNTIME",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessonkit/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
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,7 @@
|
|
|
42
42
|
"lint": "echo \"(no lint configured yet)\""
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@lessonkit/lxpack": "0.
|
|
45
|
+
"@lessonkit/lxpack": "0.8.1",
|
|
46
46
|
"commander": "^14.0.1"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# LessonKit Vite + React template
|
|
2
2
|
|
|
3
|
-
[](
|
|
3
|
+
[](https://lessonkit.readthedocs.io/en/latest/)
|
|
4
|
+
[](https://github.com/eddiethedean/lessonkit/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Starter template copied by `lessonkit init`. See the [CLI reference](https://lessonkit.readthedocs.io/en/latest/reference/cli.html) and [React quickstart](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/quickstart.html).
|
|
7
7
|
|
|
8
8
|
## Run
|
|
9
9
|
|
|
@@ -14,6 +14,6 @@ npm run dev
|
|
|
14
14
|
|
|
15
15
|
## Notes
|
|
16
16
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
|
|
17
|
+
- Depends on [`@lessonkit/react`](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/components-and-hooks.html).
|
|
18
|
+
- Copied by [`@lessonkit/cli`](https://lessonkit.readthedocs.io/en/latest/reference/cli.html) when you run `lessonkit init`.
|
|
19
|
+
- Package for an LMS with the [packaging guide](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/packaging-and-cli.html).
|