@learnpack/learnpack 5.0.132 → 5.0.136
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 +13 -13
- package/lib/commands/serve.d.ts +1 -1
- package/lib/commands/serve.js +57 -11
- package/lib/creatorDist/assets/{index-wpTTgviz.js → index-nI-XBrtB.js} +19737 -21971
- package/lib/creatorDist/index.html +1 -1
- package/lib/utils/creatorSocket.d.ts +4 -0
- package/lib/utils/creatorSocket.js +56 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/serve.ts +64 -26
- package/src/creator/package.json +2 -1
- package/src/creator/src/components/LessonItem.tsx +14 -11
- package/src/creator/src/components/PreviewGenerator.tsx +4 -4
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +1 -1
- package/src/creator/src/utils/lib.ts +21 -0
- package/src/creatorDist/assets/{index-wpTTgviz.js → index-nI-XBrtB.js} +19737 -21971
- package/src/creatorDist/index.html +1 -1
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +388 -389
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/creatorSocket.ts +63 -0
@@ -10,7 +10,7 @@
|
|
10
10
|
/>
|
11
11
|
|
12
12
|
<title>Learnpack Creator: Craft tutorials in seconds!</title>
|
13
|
-
<script type="module" crossorigin src="/creator/assets/index-
|
13
|
+
<script type="module" crossorigin src="/creator/assets/index-nI-XBrtB.js"></script>
|
14
14
|
<link rel="stylesheet" crossorigin href="/creator/assets/index-1sKQbOKY.css">
|
15
15
|
</head>
|
16
16
|
<body>
|
@@ -0,0 +1,4 @@
|
|
1
|
+
import { Server as SocketIOServer } from "socket.io";
|
2
|
+
export declare function initSocketIO(server: any): SocketIOServer<import("socket.io/dist/typed-events").DefaultEventsMap, import("socket.io/dist/typed-events").DefaultEventsMap, import("socket.io/dist/typed-events").DefaultEventsMap, any>;
|
3
|
+
export declare function emitToCourse(courseSlug: string, event: string, payload: any): void;
|
4
|
+
export declare function getSocketIO(): SocketIOServer<import("socket.io/dist/typed-events").DefaultEventsMap, import("socket.io/dist/typed-events").DefaultEventsMap, import("socket.io/dist/typed-events").DefaultEventsMap, any>;
|
@@ -0,0 +1,56 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.initSocketIO = initSocketIO;
|
4
|
+
exports.emitToCourse = emitToCourse;
|
5
|
+
exports.getSocketIO = getSocketIO;
|
6
|
+
const socket_io_1 = require("socket.io");
|
7
|
+
const courseSocketMap = new Map(); // slug -> Set<socket.id>
|
8
|
+
const socketStore = new Map(); // socket.id -> socket
|
9
|
+
let io = null;
|
10
|
+
function initSocketIO(server) {
|
11
|
+
io = new socket_io_1.Server(server, {
|
12
|
+
cors: {
|
13
|
+
origin: "*",
|
14
|
+
methods: ["GET", "POST"],
|
15
|
+
},
|
16
|
+
path: "/sockete",
|
17
|
+
});
|
18
|
+
io.on("connection", socket => {
|
19
|
+
console.log("🧠 Socket connected:", socket.id);
|
20
|
+
socketStore.set(socket.id, socket);
|
21
|
+
socket.on("register", (data) => {
|
22
|
+
var _a;
|
23
|
+
const { courseSlug } = data;
|
24
|
+
if (!courseSlug)
|
25
|
+
return;
|
26
|
+
if (!courseSocketMap.has(courseSlug)) {
|
27
|
+
courseSocketMap.set(courseSlug, new Set());
|
28
|
+
}
|
29
|
+
(_a = courseSocketMap.get(courseSlug)) === null || _a === void 0 ? void 0 : _a.add(socket.id);
|
30
|
+
console.log(`📦 Socket ${socket.id} registered to course: ${courseSlug}`);
|
31
|
+
});
|
32
|
+
socket.on("disconnect", () => {
|
33
|
+
console.log("🔥 Socket disconnected:", socket.id);
|
34
|
+
socketStore.delete(socket.id);
|
35
|
+
for (const set of courseSocketMap.values()) {
|
36
|
+
set.delete(socket.id);
|
37
|
+
}
|
38
|
+
});
|
39
|
+
});
|
40
|
+
return io;
|
41
|
+
}
|
42
|
+
function emitToCourse(courseSlug, event, payload) {
|
43
|
+
const socketIds = courseSocketMap.get(courseSlug);
|
44
|
+
if (!socketIds || socketIds.size === 0)
|
45
|
+
return;
|
46
|
+
for (const id of socketIds) {
|
47
|
+
const socket = socketStore.get(id);
|
48
|
+
if (socket)
|
49
|
+
socket.emit(event, payload);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
function getSocketIO() {
|
53
|
+
if (!io)
|
54
|
+
throw new Error("Socket.IO not initialized");
|
55
|
+
return io;
|
56
|
+
}
|
package/oclif.manifest.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":"5.0.
|
1
|
+
{"version":"5.0.136","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false}},"args":[]},"breakToken":{"id":"breakToken","description":"Break the token","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false},"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"serve":{"id":"serve","description":"Runs a small server to build tutorials","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[]}}}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@learnpack/learnpack",
|
3
3
|
"description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
|
4
|
-
"version": "5.0.
|
4
|
+
"version": "5.0.136",
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
6
6
|
"contributors": [
|
7
7
|
{
|
package/src/commands/serve.ts
CHANGED
@@ -4,6 +4,10 @@ import { YoutubeTranscript } from "youtube-transcript"
|
|
4
4
|
import * as express from "express"
|
5
5
|
import * as cors from "cors"
|
6
6
|
import * as path from "path"
|
7
|
+
import * as http from "http"
|
8
|
+
|
9
|
+
import { initSocketIO, emitToCourse } from "../utils/creatorSocket"
|
10
|
+
|
7
11
|
import * as os from "os"
|
8
12
|
import * as archiver from "archiver"
|
9
13
|
import * as mkdirp from "mkdirp"
|
@@ -127,7 +131,8 @@ export async function processExercise(
|
|
127
131
|
steps: Lesson[],
|
128
132
|
packageContext: string,
|
129
133
|
exercise: Lesson,
|
130
|
-
exercisesDir: string
|
134
|
+
exercisesDir: string,
|
135
|
+
courseSlug: string
|
131
136
|
): Promise<string> {
|
132
137
|
// const tid = toast.loading("Generating lesson...")
|
133
138
|
const exSlug = slugify(exercise.id + "-" + exercise.title)
|
@@ -135,12 +140,12 @@ export async function processExercise(
|
|
135
140
|
const targetDir = `${exercisesDir}/${exSlug}`
|
136
141
|
|
137
142
|
console.log("✍🏻 Generating lesson", exercise.id, exercise.title)
|
138
|
-
|
139
|
-
|
143
|
+
|
144
|
+
const isGeneratingText = `
|
140
145
|
\`\`\`loader slug="${exSlug}"
|
141
|
-
|
146
|
+
:rigo
|
142
147
|
\`\`\`
|
143
|
-
|
148
|
+
`
|
144
149
|
|
145
150
|
await uploadFileToBucket(
|
146
151
|
bucket,
|
@@ -165,13 +170,11 @@ export async function processExercise(
|
|
165
170
|
readability.fkglResult.fkgl > PARAMS.max_fkgl &&
|
166
171
|
attempts < PARAMS.max_rewrite_attempts
|
167
172
|
) {
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
exercise.title
|
172
|
-
|
173
|
-
readability.fkglResult.fkgl
|
174
|
-
)
|
173
|
+
emitToCourse(courseSlug, "course-creation", {
|
174
|
+
lesson: exSlug,
|
175
|
+
status: "generating",
|
176
|
+
log: `🔄 The lesson ${exercise.id} - ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
|
177
|
+
})
|
175
178
|
|
176
179
|
// eslint-disable-next-line
|
177
180
|
const reducedReadme = await makeReadmeReadable(rigoToken, {
|
@@ -194,21 +197,16 @@ break
|
|
194
197
|
attempts++
|
195
198
|
}
|
196
199
|
|
197
|
-
console.log(
|
198
|
-
`✅ After ${attempts} attempts, the lesson ${
|
199
|
-
exercise.title
|
200
|
-
} has a readability score of ${
|
201
|
-
readability.fkglResult.fkgl
|
202
|
-
} using FKGL. And it has ${readability.minutes.toFixed(
|
203
|
-
2
|
204
|
-
)} minutes of reading time.`
|
205
|
-
)
|
206
|
-
|
207
200
|
await uploadFileToBucket(
|
208
201
|
bucket,
|
209
202
|
readability.newMarkdown,
|
210
203
|
`${targetDir}/${readmeFilename}`
|
211
204
|
)
|
205
|
+
emitToCourse(courseSlug, "course-creation", {
|
206
|
+
lesson: exSlug,
|
207
|
+
status: "done",
|
208
|
+
log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
|
209
|
+
})
|
212
210
|
|
213
211
|
if (exercise.type.toLowerCase() === "code") {
|
214
212
|
console.log("🔍 Creating code file for", exercise.title)
|
@@ -222,6 +220,11 @@ break
|
|
222
220
|
codeFile.parsed.content,
|
223
221
|
`${targetDir}/index.${codeFile.parsed.extension.replace(".", "")}`
|
224
222
|
)
|
223
|
+
emitToCourse(courseSlug, "course-creation", {
|
224
|
+
lesson: exSlug,
|
225
|
+
status: "done",
|
226
|
+
log: `✅ The code file for ${exercise.title} has been generated successfully!`,
|
227
|
+
})
|
225
228
|
}
|
226
229
|
|
227
230
|
return readability.newMarkdown
|
@@ -326,6 +329,9 @@ export default class ServeCommand extends SessionCommand {
|
|
326
329
|
// }
|
327
330
|
|
328
331
|
const app = express()
|
332
|
+
const server = http.createServer(app)
|
333
|
+
initSocketIO(server)
|
334
|
+
|
329
335
|
const PORT = process.env.PORT || 3000
|
330
336
|
|
331
337
|
const distPath = path.resolve(__dirname, "../creatorDist")
|
@@ -445,7 +451,7 @@ export default class ServeCommand extends SessionCommand {
|
|
445
451
|
})
|
446
452
|
|
447
453
|
app.get("/preview/:slug", async (req, res) => {
|
448
|
-
console.log("GET COURSE /")
|
454
|
+
console.log("GET COURSE PREVIEW /")
|
449
455
|
|
450
456
|
const file = path.resolve(__dirname, "../ui/_app/index.html")
|
451
457
|
res.sendFile(file)
|
@@ -458,6 +464,7 @@ export default class ServeCommand extends SessionCommand {
|
|
458
464
|
}
|
459
465
|
|
460
466
|
try {
|
467
|
+
console.log("GET CONFIG, COURSE SLUG", courseSlug)
|
461
468
|
const { config, exercises } = await buildConfig(bucket, courseSlug)
|
462
469
|
res.set("X-Creator-Web", "true")
|
463
470
|
res.set("Access-Control-Expose-Headers", "X-Creator-Web")
|
@@ -645,6 +652,35 @@ export default class ServeCommand extends SessionCommand {
|
|
645
652
|
res.send({ message: "Files deleted" })
|
646
653
|
})
|
647
654
|
|
655
|
+
app.get("/test/:slug", (req, res) => {
|
656
|
+
emitToCourse(req.params.slug, "course-creation", {
|
657
|
+
lesson: "000-welcome-to-bird-domestication",
|
658
|
+
status: "generating",
|
659
|
+
log: "Hello",
|
660
|
+
})
|
661
|
+
emitToCourse(req.params.slug, "course-creation", {
|
662
|
+
lesson: "000-welcome-to-bird-domestication",
|
663
|
+
status: "generating",
|
664
|
+
log: "Hello",
|
665
|
+
})
|
666
|
+
emitToCourse(req.params.slug, "course-creation", {
|
667
|
+
lesson: "000-welcome-to-bird-domestication",
|
668
|
+
status: "generating",
|
669
|
+
log: "Hello broder",
|
670
|
+
})
|
671
|
+
emitToCourse(req.params.slug, "course-creation", {
|
672
|
+
lesson: "000-welcome-to-bird-domestication",
|
673
|
+
status: "done",
|
674
|
+
log: "Hello broder",
|
675
|
+
})
|
676
|
+
emitToCourse(req.params.slug, "course-creation", {
|
677
|
+
lesson: "other",
|
678
|
+
status: "generating",
|
679
|
+
log: "Hello",
|
680
|
+
})
|
681
|
+
res.send({ message: "Course creation started" })
|
682
|
+
})
|
683
|
+
|
648
684
|
app.get("/assets/:file", (req, res) => {
|
649
685
|
const file = path.join(localAppPath, req.params.file)
|
650
686
|
res.sendFile(file)
|
@@ -683,8 +719,9 @@ export default class ServeCommand extends SessionCommand {
|
|
683
719
|
await uploadFileToBucket(
|
684
720
|
bucket,
|
685
721
|
JSON.stringify(learnJson),
|
686
|
-
|
722
|
+
`${tutorialDir}/learn.json`
|
687
723
|
)
|
724
|
+
console.log("🔄 Learn.json uploaded to bucket to", tutorialDir)
|
688
725
|
|
689
726
|
const lessonsPromises = syllabus.lessons.map(lesson =>
|
690
727
|
processExercise(
|
@@ -693,7 +730,8 @@ export default class ServeCommand extends SessionCommand {
|
|
693
730
|
syllabus.lessons,
|
694
731
|
JSON.stringify(syllabus.courseInfo),
|
695
732
|
lesson,
|
696
|
-
tutorialDir + "/exercises"
|
733
|
+
tutorialDir + "/exercises",
|
734
|
+
slugify(syllabus.courseInfo.title)
|
697
735
|
)
|
698
736
|
)
|
699
737
|
const readmeContents = await Promise.all(lessonsPromises)
|
@@ -973,7 +1011,7 @@ export default class ServeCommand extends SessionCommand {
|
|
973
1011
|
}
|
974
1012
|
})
|
975
1013
|
|
976
|
-
|
1014
|
+
server.listen(PORT, () => {
|
977
1015
|
console.log(
|
978
1016
|
`🚀 Creator UI server running at http://localhost:${PORT}/creator`
|
979
1017
|
)
|
package/src/creator/package.json
CHANGED
@@ -22,14 +22,14 @@ interface LessonItemProps {
|
|
22
22
|
onRemove: () => void
|
23
23
|
}
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
function cleanFloatString(input: string): string {
|
26
|
+
const num = parseFloat(input)
|
27
|
+
const isInteger = Number.isInteger(num)
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
return isInteger
|
30
|
+
? Math.floor(num).toString().padStart(2, "0")
|
31
|
+
: num.toString()
|
32
|
+
}
|
33
33
|
|
34
34
|
export const LessonItem: React.FC<LessonItemProps> = ({
|
35
35
|
lesson,
|
@@ -48,11 +48,14 @@ export const LessonItem: React.FC<LessonItemProps> = ({
|
|
48
48
|
}`}
|
49
49
|
>
|
50
50
|
{isNew && <span className="red-ball"></span>}
|
51
|
-
|
51
|
+
|
52
52
|
{mode === "teacher" && (
|
53
|
-
|
54
|
-
{lesson.
|
55
|
-
|
53
|
+
<>
|
54
|
+
<span className="index-circle">{cleanFloatString(lesson.id)}</span>
|
55
|
+
<span className="text-gray-500 text-sm">
|
56
|
+
{lesson.type[0] + lesson.type.slice(1).toLowerCase()} ●
|
57
|
+
</span>
|
58
|
+
</>
|
56
59
|
)}
|
57
60
|
|
58
61
|
{isEditing ? (
|
@@ -34,10 +34,10 @@ const PreviewGenerator: React.FC = () => {
|
|
34
34
|
html2canvas(previewElement, {
|
35
35
|
useCORS: true,
|
36
36
|
}).then(async (canvas) => {
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
// const anchor = document.createElement("a")
|
38
|
+
// anchor.href = canvas.toDataURL("image/png")
|
39
|
+
// anchor.download = "preview.png"
|
40
|
+
// anchor.click()
|
41
41
|
|
42
42
|
const imageUrl = canvas.toDataURL("image/png")
|
43
43
|
|
@@ -2,6 +2,7 @@ import axios from "axios"
|
|
2
2
|
import { BREATHECODE_HOST, RIGOBOT_HOST } from "./constants"
|
3
3
|
import { Lesson } from "../components/LessonItem"
|
4
4
|
import { randomUUID } from "./creatorUtils"
|
5
|
+
import { Syllabus } from "./store"
|
5
6
|
|
6
7
|
export function parseLesson(input: string, previous: Lesson[]): Lesson | null {
|
7
8
|
const pattern = /^([\d.]+)\s*-\s*(.*?)\s*\[(\w+):\s*(.+)\]$/
|
@@ -289,3 +290,23 @@ export const generateImage = async (
|
|
289
290
|
return null
|
290
291
|
}
|
291
292
|
}
|
293
|
+
|
294
|
+
export const createCourse = async (
|
295
|
+
syllabus: Syllabus,
|
296
|
+
token: string,
|
297
|
+
breathecodeToken: string
|
298
|
+
) => {
|
299
|
+
const response = await axios.post(
|
300
|
+
`/actions/create-course`,
|
301
|
+
{
|
302
|
+
syllabus,
|
303
|
+
},
|
304
|
+
{
|
305
|
+
headers: {
|
306
|
+
"x-breathecode-token": breathecodeToken,
|
307
|
+
"x-rigo-token": token,
|
308
|
+
},
|
309
|
+
}
|
310
|
+
)
|
311
|
+
return response.data
|
312
|
+
}
|