@lessonkit/cli 1.3.0 → 1.4.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 +13 -1
- package/dist/index.js +13 -1
- package/package.json +9 -6
- package/template/vite-react/.env.example +5 -0
- package/template/vite-react/README.md +6 -0
- package/template/vite-react/package.json +17 -13
- package/template/vite-react/src/App.test.tsx +3 -3
- package/template/vite-react/src/App.tsx +2 -14
- package/template/vite-react/src/courseConfig.test.ts +91 -0
- package/template/vite-react/src/courseConfig.ts +117 -0
- package/template/vite-react/vitest.config.ts +1 -1
package/dist/bin.js
CHANGED
|
@@ -177,8 +177,17 @@ async function applyTemplateSubstitutions(projectDir, projectName, slug) {
|
|
|
177
177
|
const course = lessonkit.course;
|
|
178
178
|
course.courseId = slug;
|
|
179
179
|
course.title = projectName;
|
|
180
|
+
const tracking = lessonkit.tracking ?? {};
|
|
181
|
+
const xapi = tracking.xapi ?? {};
|
|
182
|
+
xapi.activityIri = `https://example.com/courses/${slug}`;
|
|
183
|
+
tracking.xapi = xapi;
|
|
184
|
+
lessonkit.tracking = tracking;
|
|
180
185
|
await writeFile(lessonkitPath, `${JSON.stringify(lessonkit, null, 2)}
|
|
181
186
|
`, "utf8");
|
|
187
|
+
const courseConfigPath = join(projectDir, "src", "courseConfig.ts");
|
|
188
|
+
let courseConfigSource = await readFile(courseConfigPath, "utf8");
|
|
189
|
+
courseConfigSource = courseConfigSource.replace(/courseId: "my-course"/g, `courseId: "${slug}"`);
|
|
190
|
+
await writeFile(courseConfigPath, courseConfigSource, "utf8");
|
|
182
191
|
const appPath = join(projectDir, "src", "App.tsx");
|
|
183
192
|
let appSource = await readFile(appPath, "utf8");
|
|
184
193
|
appSource = appSource.replace(/courseId="my-course"/g, `courseId="${slug}"`);
|
|
@@ -466,7 +475,10 @@ async function runDev(opts) {
|
|
|
466
475
|
const pkg = await readPackageJson(project.root);
|
|
467
476
|
assertViteProject(pkg, project.root);
|
|
468
477
|
const viteJs = resolveViteJs(project.root);
|
|
469
|
-
await runCommand(process.execPath, [viteJs, ...opts.viteArgs ?? []], {
|
|
478
|
+
await runCommand(process.execPath, [viteJs, ...opts.viteArgs ?? []], {
|
|
479
|
+
cwd: project.root,
|
|
480
|
+
timeoutMs: 0
|
|
481
|
+
});
|
|
470
482
|
return { ok: true, command: "dev", projectRoot: project.root };
|
|
471
483
|
}
|
|
472
484
|
async function runBuild(opts) {
|
package/dist/index.js
CHANGED
|
@@ -175,8 +175,17 @@ async function applyTemplateSubstitutions(projectDir, projectName, slug) {
|
|
|
175
175
|
const course = lessonkit.course;
|
|
176
176
|
course.courseId = slug;
|
|
177
177
|
course.title = projectName;
|
|
178
|
+
const tracking = lessonkit.tracking ?? {};
|
|
179
|
+
const xapi = tracking.xapi ?? {};
|
|
180
|
+
xapi.activityIri = `https://example.com/courses/${slug}`;
|
|
181
|
+
tracking.xapi = xapi;
|
|
182
|
+
lessonkit.tracking = tracking;
|
|
178
183
|
await writeFile(lessonkitPath, `${JSON.stringify(lessonkit, null, 2)}
|
|
179
184
|
`, "utf8");
|
|
185
|
+
const courseConfigPath = join(projectDir, "src", "courseConfig.ts");
|
|
186
|
+
let courseConfigSource = await readFile(courseConfigPath, "utf8");
|
|
187
|
+
courseConfigSource = courseConfigSource.replace(/courseId: "my-course"/g, `courseId: "${slug}"`);
|
|
188
|
+
await writeFile(courseConfigPath, courseConfigSource, "utf8");
|
|
180
189
|
const appPath = join(projectDir, "src", "App.tsx");
|
|
181
190
|
let appSource = await readFile(appPath, "utf8");
|
|
182
191
|
appSource = appSource.replace(/courseId="my-course"/g, `courseId="${slug}"`);
|
|
@@ -464,7 +473,10 @@ async function runDev(opts) {
|
|
|
464
473
|
const pkg = await readPackageJson(project.root);
|
|
465
474
|
assertViteProject(pkg, project.root);
|
|
466
475
|
const viteJs = resolveViteJs(project.root);
|
|
467
|
-
await runCommand(process.execPath, [viteJs, ...opts.viteArgs ?? []], {
|
|
476
|
+
await runCommand(process.execPath, [viteJs, ...opts.viteArgs ?? []], {
|
|
477
|
+
cwd: project.root,
|
|
478
|
+
timeoutMs: 0
|
|
479
|
+
});
|
|
468
480
|
return { ok: true, command: "dev", projectRoot: project.root };
|
|
469
481
|
}
|
|
470
482
|
async function runBuild(opts) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessonkit/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "LessonKit CLI — init, dev, build, and package learning experiences.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -25,7 +25,10 @@
|
|
|
25
25
|
"lessonkit": "./dist/bin.js"
|
|
26
26
|
},
|
|
27
27
|
"exports": {
|
|
28
|
-
".":
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
29
32
|
},
|
|
30
33
|
"files": [
|
|
31
34
|
"dist",
|
|
@@ -42,15 +45,15 @@
|
|
|
42
45
|
"lint": "echo \"(no lint configured yet)\""
|
|
43
46
|
},
|
|
44
47
|
"dependencies": {
|
|
45
|
-
"@lessonkit/core": "1.
|
|
46
|
-
"@lessonkit/lxpack": "1.
|
|
47
|
-
"commander": "^
|
|
48
|
+
"@lessonkit/core": "1.4.0",
|
|
49
|
+
"@lessonkit/lxpack": "1.4.0",
|
|
50
|
+
"commander": "^15.0.0"
|
|
48
51
|
},
|
|
49
52
|
"engines": {
|
|
50
53
|
"node": ">=18"
|
|
51
54
|
},
|
|
52
55
|
"devDependencies": {
|
|
53
|
-
"@types/node": "^
|
|
56
|
+
"@types/node": "^25.9.2",
|
|
54
57
|
"tsup": "^8.5.0",
|
|
55
58
|
"typescript": "^5.8.3",
|
|
56
59
|
"vitest": "^4.1.8"
|
|
@@ -14,8 +14,14 @@ npm run package:scorm12
|
|
|
14
14
|
## Files
|
|
15
15
|
|
|
16
16
|
- `src/App.tsx` — course UI (IDs match `lessonkit.json`)
|
|
17
|
+
- `src/courseConfig.ts` — production transports, observability hooks, and LMS bridge config
|
|
18
|
+
- `.env.example` — `VITE_XAPI_PROXY_URL` and `VITE_ANALYTICS_URL` for production builds
|
|
17
19
|
- `lessonkit.json` — manifest for CLI and LXPack packaging
|
|
18
20
|
|
|
21
|
+
## Production
|
|
22
|
+
|
|
23
|
+
Copy `.env.example` to `.env.production` and set your LRS/analytics proxy URLs before `npm run build`. See the [production checklist](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/production-checklist.html).
|
|
24
|
+
|
|
19
25
|
## Docs
|
|
20
26
|
|
|
21
27
|
[CLI reference](https://lessonkit.readthedocs.io/en/latest/reference/cli.html) · [React quickstart](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/quickstart.html) · [Packaging guide](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/packaging-and-cli.html)
|
|
@@ -7,29 +7,33 @@
|
|
|
7
7
|
"build": "lessonkit build",
|
|
8
8
|
"preview": "vite preview",
|
|
9
9
|
"package:scorm12": "lessonkit package --target scorm12",
|
|
10
|
+
"package:scorm2004": "lessonkit package --target scorm2004",
|
|
10
11
|
"package:standalone": "lessonkit package --target standalone",
|
|
12
|
+
"package:xapi": "lessonkit package --target xapi",
|
|
13
|
+
"package:cmi5": "lessonkit package --target cmi5",
|
|
11
14
|
"typecheck": "tsc -p tsconfig.json",
|
|
12
15
|
"test": "vitest run --passWithNoTests",
|
|
13
16
|
"test:coverage": "vitest run --coverage --passWithNoTests=false"
|
|
14
17
|
},
|
|
15
18
|
"dependencies": {
|
|
16
|
-
"@lessonkit/core": "^1.
|
|
17
|
-
"@lessonkit/react": "^1.
|
|
18
|
-
"@lessonkit/themes": "^1.
|
|
19
|
-
"@lessonkit/xapi": "^1.
|
|
20
|
-
"react": "^
|
|
21
|
-
"react-dom": "^
|
|
19
|
+
"@lessonkit/core": "^1.4.0",
|
|
20
|
+
"@lessonkit/react": "^1.4.0",
|
|
21
|
+
"@lessonkit/themes": "^1.4.0",
|
|
22
|
+
"@lessonkit/xapi": "^1.4.0",
|
|
23
|
+
"react": "^19.2.7",
|
|
24
|
+
"react-dom": "^19.2.7"
|
|
22
25
|
},
|
|
23
26
|
"devDependencies": {
|
|
24
|
-
"@lessonkit/cli": "^1.
|
|
25
|
-
"@lessonkit/lxpack": "^1.
|
|
27
|
+
"@lessonkit/cli": "^1.4.0",
|
|
28
|
+
"@lessonkit/lxpack": "^1.4.0",
|
|
26
29
|
"@testing-library/react": "^16.3.0",
|
|
27
|
-
"@
|
|
28
|
-
"@types/react
|
|
29
|
-
"@
|
|
30
|
-
"
|
|
30
|
+
"@testing-library/dom": "^10.4.1",
|
|
31
|
+
"@types/react": "^19.2.17",
|
|
32
|
+
"@types/react-dom": "^19.2.3",
|
|
33
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
34
|
+
"jsdom": "^29.1.1",
|
|
31
35
|
"typescript": "^5.8.3",
|
|
32
|
-
"vite": "^
|
|
36
|
+
"vite": "^8.0.11",
|
|
33
37
|
"vitest": "^4.1.8"
|
|
34
38
|
}
|
|
35
39
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
|
-
import { render } from "@testing-library/react";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
4
|
import App from "./App";
|
|
5
5
|
|
|
6
6
|
describe("template App", () => {
|
|
7
|
-
it("renders
|
|
7
|
+
it("renders the starter quiz question", () => {
|
|
8
8
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
9
9
|
render(<App />);
|
|
10
|
+
expect(screen.getByText("Ready to build?")).toBeDefined();
|
|
10
11
|
spy.mockRestore();
|
|
11
|
-
expect(true).toBe(true);
|
|
12
12
|
});
|
|
13
13
|
});
|
|
14
14
|
|
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Course, Lesson, Quiz, Scenario, ThemeProvider } from "@lessonkit/react";
|
|
3
|
-
import
|
|
4
|
-
import type { XAPIStatement } from "@lessonkit/xapi";
|
|
3
|
+
import { createCourseConfig } from "./courseConfig";
|
|
5
4
|
|
|
6
|
-
const courseConfig =
|
|
7
|
-
tracking: {
|
|
8
|
-
sink: (event: TelemetryEvent) => {
|
|
9
|
-
console.log("[telemetry]", event);
|
|
10
|
-
},
|
|
11
|
-
},
|
|
12
|
-
xapi: {
|
|
13
|
-
transport: (statement: XAPIStatement) => {
|
|
14
|
-
console.log("[xapi]", statement);
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
} as const;
|
|
5
|
+
const courseConfig = createCourseConfig();
|
|
18
6
|
|
|
19
7
|
export default function App() {
|
|
20
8
|
return (
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { TelemetryEvent } from "@lessonkit/core";
|
|
3
|
+
import { createCourseConfig } from "./courseConfig";
|
|
4
|
+
|
|
5
|
+
describe("createCourseConfig", () => {
|
|
6
|
+
const originalFetch = globalThis.fetch;
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
vi.unstubAllEnvs();
|
|
10
|
+
globalThis.fetch = originalFetch;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns dev console sinks when proxy URLs are unset", () => {
|
|
14
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
15
|
+
const config = createCourseConfig();
|
|
16
|
+
|
|
17
|
+
expect(config.courseId).toBe("my-course");
|
|
18
|
+
expect(config.lxpack?.bridge).toBe("off");
|
|
19
|
+
expect(config.observability?.onTelemetrySinkError).toBeTypeOf("function");
|
|
20
|
+
expect(config.observability?.onLxpackBridgeMiss).toBeTypeOf("function");
|
|
21
|
+
|
|
22
|
+
config.tracking?.sink?.({
|
|
23
|
+
name: "interaction",
|
|
24
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
25
|
+
courseId: "my-course",
|
|
26
|
+
} as TelemetryEvent);
|
|
27
|
+
expect(log).toHaveBeenCalledWith("[telemetry]", expect.any(Object));
|
|
28
|
+
|
|
29
|
+
log.mockRestore();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("invokes observability hooks without throwing", () => {
|
|
33
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
34
|
+
const config = createCourseConfig();
|
|
35
|
+
|
|
36
|
+
config.observability?.onTelemetrySinkError?.(new Error("sink"), { sinkId: "tracking" });
|
|
37
|
+
config.observability?.onTelemetryBufferDrop?.();
|
|
38
|
+
config.observability?.onXapiQueueDepth?.(10);
|
|
39
|
+
config.observability?.onXapiQueueDepth?.(60);
|
|
40
|
+
config.observability?.onXapiQueueCap?.();
|
|
41
|
+
config.observability?.onLxpackBridgeMiss?.({
|
|
42
|
+
name: "course_completed",
|
|
43
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
44
|
+
courseId: "my-course",
|
|
45
|
+
} as TelemetryEvent);
|
|
46
|
+
config.observability?.onXapiTransportError?.(new Error("transport"));
|
|
47
|
+
|
|
48
|
+
expect(warn).toHaveBeenCalled();
|
|
49
|
+
warn.mockRestore();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("uses fetch transports when proxy URLs are set", async () => {
|
|
53
|
+
vi.stubEnv("VITE_XAPI_PROXY_URL", "https://lrs.example/statements");
|
|
54
|
+
vi.stubEnv("VITE_ANALYTICS_URL", "https://analytics.example/events");
|
|
55
|
+
const fetchMock = vi.fn(() => Promise.resolve(new Response(null, { status: 204 })));
|
|
56
|
+
globalThis.fetch = fetchMock as typeof fetch;
|
|
57
|
+
|
|
58
|
+
const config = createCourseConfig();
|
|
59
|
+
|
|
60
|
+
expect(config.tracking?.batchSink).toBeTypeOf("function");
|
|
61
|
+
expect(config.xapi?.transport).toBeTypeOf("function");
|
|
62
|
+
|
|
63
|
+
await config.tracking?.batchSink?.([
|
|
64
|
+
{ name: "course_started", timestamp: "t", courseId: "my-course" } as TelemetryEvent,
|
|
65
|
+
]);
|
|
66
|
+
await config.xapi?.transport?.({
|
|
67
|
+
id: "s1",
|
|
68
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
69
|
+
verb: "http://adlnet.gov/expapi/verbs/completed",
|
|
70
|
+
object: { id: "https://example.com/a" },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(fetchMock).toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("skips production guard when MODE is test even with proxy URLs set", () => {
|
|
77
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
78
|
+
vi.stubEnv("MODE", "test");
|
|
79
|
+
vi.stubEnv("VITE_XAPI_PROXY_URL", "https://lrs.example/statements");
|
|
80
|
+
vi.stubEnv("VITE_ANALYTICS_URL", "https://analytics.example/events");
|
|
81
|
+
|
|
82
|
+
expect(() => createCourseConfig()).not.toThrow();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("throws when production guard is enforced with console sinks", () => {
|
|
86
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
87
|
+
vi.stubEnv("MODE", "production");
|
|
88
|
+
|
|
89
|
+
expect(() => createCourseConfig()).toThrow(/console telemetry sinks|observability hooks/);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { TelemetryEvent } from "@lessonkit/core";
|
|
2
|
+
import type { LessonkitConfig } from "@lessonkit/react";
|
|
3
|
+
import { assertProductionCourseConfig, shouldEnforceProductionGuard } from "@lessonkit/react";
|
|
4
|
+
import { createFetchBatchSink, createFetchTransport } from "@lessonkit/xapi";
|
|
5
|
+
import type { XAPIStatement } from "@lessonkit/xapi";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Replace with your backend token proxy. Never ship static LRS passwords in the bundle.
|
|
9
|
+
* Example: fetch("/api/lrs-token") and return { Authorization: `Bearer ${token}` }.
|
|
10
|
+
*/
|
|
11
|
+
function lrsAuthHeaders(): Record<string, string> {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function createObservability(): NonNullable<LessonkitConfig["observability"]> {
|
|
16
|
+
const report = (channel: string, detail: unknown) => {
|
|
17
|
+
if (import.meta.env.DEV) {
|
|
18
|
+
console.warn(`[lessonkit:${channel}]`, detail);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Wire to your monitoring stack (Sentry, Datadog, etc.).
|
|
22
|
+
/* v8 ignore next -- production-only; swap for your APM in go-live builds */
|
|
23
|
+
console.error(`[lessonkit:${channel}]`, detail);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
onTelemetrySinkError: (err, ctx) => report("telemetry-sink", { err, ...ctx }),
|
|
28
|
+
onTelemetryBufferDrop: () => report("telemetry-buffer-cap", {}),
|
|
29
|
+
onXapiQueueDepth: (depth) => {
|
|
30
|
+
if (depth > 50) report("xapi-queue-depth", { depth });
|
|
31
|
+
},
|
|
32
|
+
onXapiQueueCap: () => report("xapi-queue-cap", {}),
|
|
33
|
+
onLxpackBridgeMiss: (event) => report("lxpack-bridge-miss", { event: event.name }),
|
|
34
|
+
onXapiTransportError: (err) => report("xapi-transport", { err }),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readProxyUrls(): { xapiProxyUrl?: string; analyticsUrl?: string } {
|
|
39
|
+
return {
|
|
40
|
+
xapiProxyUrl: import.meta.env.VITE_XAPI_PROXY_URL as string | undefined,
|
|
41
|
+
analyticsUrl: import.meta.env.VITE_ANALYTICS_URL as string | undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function devConsoleTracking(): LessonkitConfig["tracking"] {
|
|
46
|
+
return {
|
|
47
|
+
sink: (event: TelemetryEvent) => {
|
|
48
|
+
console.log("[telemetry]", event);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function devConsoleXapi(): LessonkitConfig["xapi"] {
|
|
54
|
+
return {
|
|
55
|
+
enabled: true,
|
|
56
|
+
transport: (statement: XAPIStatement) => {
|
|
57
|
+
console.log("[xapi]", statement);
|
|
58
|
+
return Promise.resolve();
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function productionTracking(analyticsUrl: string | undefined): LessonkitConfig["tracking"] {
|
|
64
|
+
if (!analyticsUrl) {
|
|
65
|
+
/* v8 ignore next */
|
|
66
|
+
throw new Error(
|
|
67
|
+
"VITE_ANALYTICS_URL is required in production. Point it at your analytics ingest proxy.",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const { batchSink, exitBatchSink } = createFetchBatchSink({
|
|
71
|
+
url: analyticsUrl,
|
|
72
|
+
headers: lrsAuthHeaders,
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
enabled: true,
|
|
76
|
+
batchSink,
|
|
77
|
+
exitBatchSink,
|
|
78
|
+
batch: { enabled: true },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function productionXapi(xapiProxyUrl: string | undefined): LessonkitConfig["xapi"] {
|
|
83
|
+
if (!xapiProxyUrl) {
|
|
84
|
+
/* v8 ignore next */
|
|
85
|
+
throw new Error(
|
|
86
|
+
"VITE_XAPI_PROXY_URL is required in production. Point it at your LRS proxy (never the raw LRS with embedded secrets).",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const { transport, exitTransport, abortInFlight } = createFetchTransport({
|
|
90
|
+
url: xapiProxyUrl,
|
|
91
|
+
headers: lrsAuthHeaders,
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
enabled: true,
|
|
95
|
+
transport,
|
|
96
|
+
exitTransport,
|
|
97
|
+
abortInFlight,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function createCourseConfig(): LessonkitConfig {
|
|
102
|
+
const { xapiProxyUrl, analyticsUrl } = readProxyUrls();
|
|
103
|
+
const useProductionTransports = import.meta.env.PROD || (xapiProxyUrl && analyticsUrl);
|
|
104
|
+
|
|
105
|
+
const config: LessonkitConfig = {
|
|
106
|
+
courseId: "my-course",
|
|
107
|
+
lxpack: { bridge: "off" },
|
|
108
|
+
observability: createObservability(),
|
|
109
|
+
tracking: useProductionTransports ? productionTracking(analyticsUrl) : devConsoleTracking(),
|
|
110
|
+
xapi: useProductionTransports ? productionXapi(xapiProxyUrl) : devConsoleXapi(),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
if (shouldEnforceProductionGuard()) {
|
|
114
|
+
assertProductionCourseConfig(config);
|
|
115
|
+
}
|
|
116
|
+
return config;
|
|
117
|
+
}
|
|
@@ -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: 85, branches:
|
|
11
|
+
thresholds: { statements: 85, branches: 80, functions: 85, lines: 85 },
|
|
12
12
|
},
|
|
13
13
|
},
|
|
14
14
|
});
|