@lessonkit/cli 1.1.0 → 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
  }
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessonkit/cli",
3
- "version": "1.1.0",
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.1.0",
46
- "@lessonkit/lxpack": "1.1.0",
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.1.0",
17
- "@lessonkit/react": "^1.1.0",
18
- "@lessonkit/themes": "^1.1.0",
19
- "@lessonkit/xapi": "^1.1.0",
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.1.0",
25
- "@lessonkit/lxpack": "^1.1.0",
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: { statements: 95, branches: 95, functions: 95, lines: 95 },
11
+ thresholds: { statements: 85, branches: 85, functions: 85, lines: 85 },
12
12
  },
13
13
  },
14
14
  });