@lessonkit/cli 1.0.2 → 1.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 CHANGED
@@ -32,6 +32,8 @@ lessonkit package --target scorm12 # LMS artifact
32
32
 
33
33
  Every project includes a root `lessonkit.json` manifest (`schemaVersion: 1`).
34
34
 
35
+ Subprocess timeout defaults to **30 minutes** (`LESSONKIT_CMD_TIMEOUT_MS`; set `0` to disable).
36
+
35
37
  ## Docs
36
38
 
37
39
  [CLI reference](https://lessonkit.readthedocs.io/en/latest/reference/cli.html) · [Packaging guide](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/packaging-and-cli.html) · [Template source](https://github.com/eddiethedean/lessonkit/tree/main/templates/vite-react)
package/dist/bin.js CHANGED
@@ -54,7 +54,19 @@ ${details}`;
54
54
 
55
55
  // src/lib/exec.ts
56
56
  import { spawn } from "child_process";
57
+ var DEFAULT_CMD_TIMEOUT_MS = 30 * 60 * 1e3;
58
+ function resolveCommandTimeoutMs(explicit) {
59
+ if (explicit !== void 0) {
60
+ return explicit > 0 ? explicit : void 0;
61
+ }
62
+ const raw = process.env.LESSONKIT_CMD_TIMEOUT_MS;
63
+ if (raw === void 0 || raw === "") return DEFAULT_CMD_TIMEOUT_MS;
64
+ const parsed = Number(raw);
65
+ if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
66
+ return parsed;
67
+ }
57
68
  async function runCommand(command, args, opts) {
69
+ const timeoutMs = resolveCommandTimeoutMs(opts.timeoutMs);
58
70
  await new Promise((resolvePromise, rejectPromise) => {
59
71
  const child = spawn(command, args, {
60
72
  cwd: opts.cwd,
@@ -62,25 +74,48 @@ async function runCommand(command, args, opts) {
62
74
  stdio: "inherit",
63
75
  shell: false
64
76
  });
77
+ let timedOut = false;
78
+ const timer = timeoutMs !== void 0 ? setTimeout(() => {
79
+ timedOut = true;
80
+ child.kill("SIGTERM");
81
+ setTimeout(() => child.kill("SIGKILL"), 5e3).unref?.();
82
+ }, timeoutMs) : void 0;
83
+ const settle = (fn) => {
84
+ if (timer !== void 0) clearTimeout(timer);
85
+ fn();
86
+ };
65
87
  child.on("error", (err) => {
66
- rejectPromise(
67
- new CliError(`Failed to run ${command}: ${err.message}`, {
68
- code: "RUNTIME",
69
- exitCode: EXIT_RUNTIME
70
- })
71
- );
88
+ settle(() => {
89
+ rejectPromise(
90
+ new CliError(`Failed to run ${command}: ${err.message}`, {
91
+ code: "RUNTIME",
92
+ exitCode: EXIT_RUNTIME
93
+ })
94
+ );
95
+ });
72
96
  });
73
97
  child.on("close", (code) => {
74
- if (code === 0) {
75
- resolvePromise();
76
- return;
77
- }
78
- rejectPromise(
79
- new CliError(`${command} exited with code ${code ?? "unknown"}.`, {
80
- code: "RUNTIME",
81
- exitCode: EXIT_RUNTIME
82
- })
83
- );
98
+ settle(() => {
99
+ if (timedOut) {
100
+ rejectPromise(
101
+ new CliError(`${command} timed out after ${timeoutMs}ms.`, {
102
+ code: "RUNTIME",
103
+ exitCode: EXIT_RUNTIME
104
+ })
105
+ );
106
+ return;
107
+ }
108
+ if (code === 0) {
109
+ resolvePromise();
110
+ return;
111
+ }
112
+ rejectPromise(
113
+ new CliError(`${command} exited with code ${code ?? "unknown"}.`, {
114
+ code: "RUNTIME",
115
+ exitCode: EXIT_RUNTIME
116
+ })
117
+ );
118
+ });
84
119
  });
85
120
  });
86
121
  }
@@ -126,6 +161,7 @@ async function copyTemplate(src, dest) {
126
161
  await copyTemplate(srcPath, destPath);
127
162
  } else if (entry.isFile()) {
128
163
  await cp(srcPath, destPath);
164
+ } else {
129
165
  }
130
166
  }
131
167
  }
@@ -164,8 +200,8 @@ async function runInit(opts, logger) {
164
200
  exitCode: EXIT_INVALID_PROJECT
165
201
  });
166
202
  }
167
- const slug = slugifyId(rawName ?? "my-course");
168
- const projectName = rawName ?? slug;
203
+ const slug = slugifyId(rawName);
204
+ const projectName = rawName;
169
205
  const projectDir = opts.here ? cwd : resolve(cwd, slug);
170
206
  if (!opts.here && existsSync(projectDir)) {
171
207
  throw new CliError(
@@ -598,10 +634,10 @@ function createProgram(baseLogger = console) {
598
634
  out: opts.out,
599
635
  json: opts.json
600
636
  });
601
- if (!opts.json && result.ok) {
602
- if (result.command === "package" && result.target === "react-vite") {
637
+ if (!opts.json && result.ok && result.command === "package") {
638
+ if (result.target === "react-vite") {
603
639
  logger.log(`Built react-vite \u2192 ${result.distDir}`);
604
- } else if (result.command === "package") {
640
+ } else {
605
641
  const dest = result.outputPath ?? result.outputDir;
606
642
  logger.log(
607
643
  `Packaged ${result.target}${dest ? ` \u2192 ${dest}` : ""} (${result.fileCount} files)`
package/dist/index.js CHANGED
@@ -52,7 +52,19 @@ ${details}`;
52
52
 
53
53
  // src/lib/exec.ts
54
54
  import { spawn } from "child_process";
55
+ var DEFAULT_CMD_TIMEOUT_MS = 30 * 60 * 1e3;
56
+ function resolveCommandTimeoutMs(explicit) {
57
+ if (explicit !== void 0) {
58
+ return explicit > 0 ? explicit : void 0;
59
+ }
60
+ const raw = process.env.LESSONKIT_CMD_TIMEOUT_MS;
61
+ if (raw === void 0 || raw === "") return DEFAULT_CMD_TIMEOUT_MS;
62
+ const parsed = Number(raw);
63
+ if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
64
+ return parsed;
65
+ }
55
66
  async function runCommand(command, args, opts) {
67
+ const timeoutMs = resolveCommandTimeoutMs(opts.timeoutMs);
56
68
  await new Promise((resolvePromise, rejectPromise) => {
57
69
  const child = spawn(command, args, {
58
70
  cwd: opts.cwd,
@@ -60,25 +72,48 @@ async function runCommand(command, args, opts) {
60
72
  stdio: "inherit",
61
73
  shell: false
62
74
  });
75
+ let timedOut = false;
76
+ const timer = timeoutMs !== void 0 ? setTimeout(() => {
77
+ timedOut = true;
78
+ child.kill("SIGTERM");
79
+ setTimeout(() => child.kill("SIGKILL"), 5e3).unref?.();
80
+ }, timeoutMs) : void 0;
81
+ const settle = (fn) => {
82
+ if (timer !== void 0) clearTimeout(timer);
83
+ fn();
84
+ };
63
85
  child.on("error", (err) => {
64
- rejectPromise(
65
- new CliError(`Failed to run ${command}: ${err.message}`, {
66
- code: "RUNTIME",
67
- exitCode: EXIT_RUNTIME
68
- })
69
- );
86
+ settle(() => {
87
+ rejectPromise(
88
+ new CliError(`Failed to run ${command}: ${err.message}`, {
89
+ code: "RUNTIME",
90
+ exitCode: EXIT_RUNTIME
91
+ })
92
+ );
93
+ });
70
94
  });
71
95
  child.on("close", (code) => {
72
- if (code === 0) {
73
- resolvePromise();
74
- return;
75
- }
76
- rejectPromise(
77
- new CliError(`${command} exited with code ${code ?? "unknown"}.`, {
78
- code: "RUNTIME",
79
- exitCode: EXIT_RUNTIME
80
- })
81
- );
96
+ settle(() => {
97
+ if (timedOut) {
98
+ rejectPromise(
99
+ new CliError(`${command} timed out after ${timeoutMs}ms.`, {
100
+ code: "RUNTIME",
101
+ exitCode: EXIT_RUNTIME
102
+ })
103
+ );
104
+ return;
105
+ }
106
+ if (code === 0) {
107
+ resolvePromise();
108
+ return;
109
+ }
110
+ rejectPromise(
111
+ new CliError(`${command} exited with code ${code ?? "unknown"}.`, {
112
+ code: "RUNTIME",
113
+ exitCode: EXIT_RUNTIME
114
+ })
115
+ );
116
+ });
82
117
  });
83
118
  });
84
119
  }
@@ -124,6 +159,7 @@ async function copyTemplate(src, dest) {
124
159
  await copyTemplate(srcPath, destPath);
125
160
  } else if (entry.isFile()) {
126
161
  await cp(srcPath, destPath);
162
+ } else {
127
163
  }
128
164
  }
129
165
  }
@@ -162,8 +198,8 @@ async function runInit(opts, logger) {
162
198
  exitCode: EXIT_INVALID_PROJECT
163
199
  });
164
200
  }
165
- const slug = slugifyId(rawName ?? "my-course");
166
- const projectName = rawName ?? slug;
201
+ const slug = slugifyId(rawName);
202
+ const projectName = rawName;
167
203
  const projectDir = opts.here ? cwd : resolve(cwd, slug);
168
204
  if (!opts.here && existsSync(projectDir)) {
169
205
  throw new CliError(
@@ -596,10 +632,10 @@ function createProgram(baseLogger = console) {
596
632
  out: opts.out,
597
633
  json: opts.json
598
634
  });
599
- if (!opts.json && result.ok) {
600
- if (result.command === "package" && result.target === "react-vite") {
635
+ if (!opts.json && result.ok && result.command === "package") {
636
+ if (result.target === "react-vite") {
601
637
  logger.log(`Built react-vite \u2192 ${result.distDir}`);
602
- } else if (result.command === "package") {
638
+ } else {
603
639
  const dest = result.outputPath ?? result.outputDir;
604
640
  logger.log(
605
641
  `Packaged ${result.target}${dest ? ` \u2192 ${dest}` : ""} (${result.fileCount} files)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessonkit/cli",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "private": false,
5
5
  "description": "LessonKit CLI — init, dev, build, and package learning experiences.",
6
6
  "license": "Apache-2.0",
@@ -9,7 +9,7 @@
9
9
  "url": "git+https://github.com/eddiethedean/lessonkit.git",
10
10
  "directory": "packages/cli"
11
11
  },
12
- "homepage": "https://github.com/eddiethedean/lessonkit",
12
+ "homepage": "https://lessonkit.readthedocs.io/en/latest/reference/cli.html",
13
13
  "bugs": {
14
14
  "url": "https://github.com/eddiethedean/lessonkit/issues"
15
15
  },
@@ -42,8 +42,8 @@
42
42
  "lint": "echo \"(no lint configured yet)\""
43
43
  },
44
44
  "dependencies": {
45
- "@lessonkit/core": "1.0.2",
46
- "@lessonkit/lxpack": "1.0.2",
45
+ "@lessonkit/core": "1.2.0",
46
+ "@lessonkit/lxpack": "1.2.0",
47
47
  "commander": "^14.0.1"
48
48
  },
49
49
  "engines": {
@@ -13,16 +13,16 @@
13
13
  "test:coverage": "vitest run --coverage --passWithNoTests=false"
14
14
  },
15
15
  "dependencies": {
16
- "@lessonkit/core": "^1.0.2",
17
- "@lessonkit/react": "^1.0.2",
18
- "@lessonkit/themes": "^1.0.2",
19
- "@lessonkit/xapi": "^1.0.2",
16
+ "@lessonkit/core": "^1.2.0",
17
+ "@lessonkit/react": "^1.2.0",
18
+ "@lessonkit/themes": "^1.2.0",
19
+ "@lessonkit/xapi": "^1.2.0",
20
20
  "react": "^18.3.1",
21
21
  "react-dom": "^18.3.1"
22
22
  },
23
23
  "devDependencies": {
24
- "@lessonkit/cli": "^1.0.2",
25
- "@lessonkit/lxpack": "^1.0.2",
24
+ "@lessonkit/cli": "^1.2.0",
25
+ "@lessonkit/lxpack": "^1.2.0",
26
26
  "@testing-library/react": "^16.3.0",
27
27
  "@types/react": "^18.3.23",
28
28
  "@types/react-dom": "^18.3.7",
@@ -8,7 +8,7 @@ export default defineConfig({
8
8
  provider: "v8",
9
9
  include: ["src/**/*.{ts,tsx}"],
10
10
  exclude: ["dist/**", "node_modules/**", "**/*.d.ts", "**/*.d.cts"],
11
- thresholds: { lines: 100 },
11
+ thresholds: { statements: 85, branches: 85, functions: 85, lines: 85 },
12
12
  },
13
13
  },
14
14
  });