@learnpack/learnpack 5.0.204 → 5.0.209
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/init.js +1 -0
- package/lib/commands/serve.d.ts +1 -1
- package/lib/commands/serve.js +244 -85
- package/lib/creatorDist/assets/{index-hTGEtFj4.js → index-DzQGLCWt.js} +13530 -13529
- package/lib/creatorDist/index.html +1 -1
- package/lib/models/creator.d.ts +1 -1
- package/lib/utils/creatorUtilities.js +6 -5
- package/lib/utils/rigoActions.d.ts +9 -6
- package/lib/utils/rigoActions.js +14 -10
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/init.ts +2 -2
- package/src/commands/serve.ts +460 -138
- package/src/creator/src/App.tsx +2 -3
- package/src/creator/src/components/syllabus/ContentIndex.tsx +2 -1
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +43 -22
- package/src/creator/src/locales/en.json +4 -1
- package/src/creator/src/locales/es.json +4 -1
- package/src/creator/src/utils/creatorUtils.ts +7 -5
- package/src/creatorDist/assets/{index-hTGEtFj4.js → index-DzQGLCWt.js} +13530 -13529
- package/src/creatorDist/index.html +1 -1
- package/src/models/creator.ts +2 -1
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +315 -315
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/creatorUtilities.ts +6 -5
- package/src/utils/rigoActions.ts +25 -15
package/README.md
CHANGED
@@ -21,7 +21,7 @@ $ npm install -g @learnpack/learnpack
|
|
21
21
|
$ learnpack COMMAND
|
22
22
|
running command...
|
23
23
|
$ learnpack (-v|--version|version)
|
24
|
-
@learnpack/learnpack/5.0.
|
24
|
+
@learnpack/learnpack/5.0.209 win32-x64 node-v22.15.0
|
25
25
|
$ learnpack --help [COMMAND]
|
26
26
|
USAGE
|
27
27
|
$ learnpack COMMAND
|
@@ -80,7 +80,7 @@ DESCRIPTION
|
|
80
80
|
12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
|
81
81
|
```
|
82
82
|
|
83
|
-
_See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
83
|
+
_See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\audit.ts)_
|
84
84
|
|
85
85
|
## `learnpack breakToken`
|
86
86
|
|
@@ -95,7 +95,7 @@ OPTIONS
|
|
95
95
|
-y, --yes Skip all prompts and initialize an empty project
|
96
96
|
```
|
97
97
|
|
98
|
-
_See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
98
|
+
_See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\breakToken.ts)_
|
99
99
|
|
100
100
|
## `learnpack clean`
|
101
101
|
|
@@ -110,7 +110,7 @@ DESCRIPTION
|
|
110
110
|
Extra documentation goes here
|
111
111
|
```
|
112
112
|
|
113
|
-
_See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
113
|
+
_See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\clean.ts)_
|
114
114
|
|
115
115
|
## `learnpack download [PACKAGE]`
|
116
116
|
|
@@ -128,7 +128,7 @@ DESCRIPTION
|
|
128
128
|
Extra documentation goes here
|
129
129
|
```
|
130
130
|
|
131
|
-
_See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
131
|
+
_See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\download.ts)_
|
132
132
|
|
133
133
|
## `learnpack help [COMMAND]`
|
134
134
|
|
@@ -160,7 +160,7 @@ OPTIONS
|
|
160
160
|
-y, --yes Skip all prompts and initialize an empty project
|
161
161
|
```
|
162
162
|
|
163
|
-
_See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
163
|
+
_See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\init.ts)_
|
164
164
|
|
165
165
|
## `learnpack login [PACKAGE]`
|
166
166
|
|
@@ -178,7 +178,7 @@ DESCRIPTION
|
|
178
178
|
Extra documentation goes here
|
179
179
|
```
|
180
180
|
|
181
|
-
_See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
181
|
+
_See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\login.ts)_
|
182
182
|
|
183
183
|
## `learnpack logout [PACKAGE]`
|
184
184
|
|
@@ -196,7 +196,7 @@ DESCRIPTION
|
|
196
196
|
Extra documentation goes here
|
197
197
|
```
|
198
198
|
|
199
|
-
_See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
199
|
+
_See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\logout.ts)_
|
200
200
|
|
201
201
|
## `learnpack plugins`
|
202
202
|
|
@@ -328,7 +328,7 @@ OPTIONS
|
|
328
328
|
-s, --strict strict mode
|
329
329
|
```
|
330
330
|
|
331
|
-
_See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
331
|
+
_See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\publish.ts)_
|
332
332
|
|
333
333
|
## `learnpack serve`
|
334
334
|
|
@@ -345,7 +345,7 @@ OPTIONS
|
|
345
345
|
-y, --yes Skip all prompts and initialize an empty project
|
346
346
|
```
|
347
347
|
|
348
|
-
_See code: [src\commands\serve.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
348
|
+
_See code: [src\commands\serve.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\serve.ts)_
|
349
349
|
|
350
350
|
## `learnpack start`
|
351
351
|
|
@@ -367,7 +367,7 @@ OPTIONS
|
|
367
367
|
-y, --yes Skip all prompts and initialize an empty project
|
368
368
|
```
|
369
369
|
|
370
|
-
_See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
370
|
+
_See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\start.ts)_
|
371
371
|
|
372
372
|
## `learnpack test [EXERCISESLUG]`
|
373
373
|
|
@@ -384,7 +384,7 @@ OPTIONS
|
|
384
384
|
-y, --yes Skip all prompts and initialize an empty project
|
385
385
|
```
|
386
386
|
|
387
|
-
_See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
387
|
+
_See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\test.ts)_
|
388
388
|
|
389
389
|
## `learnpack translate`
|
390
390
|
|
@@ -398,7 +398,7 @@ OPTIONS
|
|
398
398
|
-y, --yes Skip all prompts and initialize an empty project
|
399
399
|
```
|
400
400
|
|
401
|
-
_See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
401
|
+
_See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\translate.ts)_
|
402
402
|
<!-- commandsstop -->
|
403
403
|
|
404
404
|
> > > > > > > 0cb3e56d84c197f9d008836bb573eade212b7e57
|
package/lib/commands/init.js
CHANGED
@@ -63,6 +63,7 @@ async function processExercise(rigoToken, steps, packageContext, exercise, exerc
|
|
63
63
|
tutorial_description: packageContext,
|
64
64
|
lesson_description: description,
|
65
65
|
kind: kind.toLowerCase(),
|
66
|
+
last_lesson: "",
|
66
67
|
}, "learnpack-lesson-writer");
|
67
68
|
const duration = durationByKind[kind.toLowerCase()];
|
68
69
|
let attempts = 0;
|
package/lib/commands/serve.d.ts
CHANGED
@@ -21,7 +21,7 @@ export declare const createLearnJson: (courseInfo: FormState) => {
|
|
21
21
|
preview: string;
|
22
22
|
};
|
23
23
|
export declare const processImage: (bucket: Bucket, tutorialDir: string, url: string, description: string, rigoToken: string) => Promise<boolean>;
|
24
|
-
export declare function processExercise(bucket: Bucket, rigoToken: string, steps: Lesson[], packageContext: FormState, exercise: Lesson,
|
24
|
+
export declare function processExercise(bucket: Bucket, rigoToken: string, steps: Lesson[], packageContext: FormState, exercise: Lesson, tutorialDir: string, courseSlug: string, purposeSlug: string, lastLesson?: string): Promise<string>;
|
25
25
|
export default class ServeCommand extends SessionCommand {
|
26
26
|
static description: string;
|
27
27
|
static flags: any;
|
package/lib/commands/serve.js
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.processImage = exports.createLearnJson = void 0;
|
4
4
|
exports.processExercise = processExercise;
|
5
|
+
const tslib_1 = require("tslib");
|
5
6
|
const command_1 = require("@oclif/command");
|
6
7
|
// import { readDocument } from "../utils/readDocuments"
|
7
8
|
const youtube_transcript_1 = require("youtube-transcript");
|
@@ -33,25 +34,15 @@ const sidebarGenerator_1 = require("../utils/sidebarGenerator");
|
|
33
34
|
const publish_1 = require("./publish");
|
34
35
|
const frontMatter = require("front-matter");
|
35
36
|
dotenv.config();
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
function findLast(array, predicate) {
|
38
|
+
for (let i = array.length - 1; i >= 0; i--) {
|
39
|
+
if (predicate(array[i]))
|
40
|
+
return array[i];
|
39
41
|
}
|
40
|
-
return
|
41
|
-
};
|
42
|
-
function fixSlugLength(slug) {
|
43
|
-
let clean = slug.toLowerCase();
|
44
|
-
clean = clean.replace(/[^\da-z-]+/g, "-");
|
45
|
-
clean = clean.replace(/-+/g, "-");
|
46
|
-
clean = clean.slice(0, 49);
|
47
|
-
clean = clean.replace(/^-+/, "").replace(/-+$/, "");
|
48
|
-
clean = clean.replace(/^[^\da-z]+/, "").replace(/[^\da-z]+$/, "");
|
49
|
-
if (!clean)
|
50
|
-
throw new Error("Invalid slug after cleaning");
|
51
|
-
return clean;
|
42
|
+
return undefined;
|
52
43
|
}
|
53
44
|
const createLearnJson = (courseInfo) => {
|
54
|
-
console.log("courseInfo to create learn json", courseInfo)
|
45
|
+
// console.log("courseInfo to create learn json", courseInfo)
|
55
46
|
const expectedPreviewUrl = `https://${(0, creatorUtilities_2.slugify)(courseInfo.title)}.learn-pack.com/preview.png`;
|
56
47
|
console.log("Preview url in generated learn.json", expectedPreviewUrl);
|
57
48
|
const language = courseInfo.language || "en";
|
@@ -118,16 +109,53 @@ const processImage = async (bucket, tutorialDir, url, description, rigoToken) =>
|
|
118
109
|
}
|
119
110
|
};
|
120
111
|
exports.processImage = processImage;
|
121
|
-
const createInitialSidebar = async (slugs) => {
|
112
|
+
const createInitialSidebar = async (slugs, initialLanguage = "en") => {
|
113
|
+
const language = initialLanguage === "en" ? "us" : initialLanguage;
|
122
114
|
const sidebar = {};
|
123
115
|
for (const slug of slugs) {
|
124
116
|
sidebar[slug] = {
|
125
|
-
|
117
|
+
[language]: slug,
|
126
118
|
};
|
127
119
|
}
|
128
120
|
return sidebar;
|
129
121
|
};
|
130
|
-
async
|
122
|
+
const uploadInitialReadme = async (bucket, exSlug, targetDir, packageContext) => {
|
123
|
+
const isGeneratingText = `
|
124
|
+
\`\`\`loader slug="${exSlug}"
|
125
|
+
:rigo
|
126
|
+
\`\`\`
|
127
|
+
`;
|
128
|
+
const readmeFilename = `README.${packageContext.language && packageContext.language !== "en" ?
|
129
|
+
`${packageContext.language}.` :
|
130
|
+
""}md`;
|
131
|
+
await uploadFileToBucket(bucket, isGeneratingText, `${targetDir}/${readmeFilename}`);
|
132
|
+
};
|
133
|
+
const cleanFormState = (formState) => {
|
134
|
+
// keysToDelete: description, technologies, purpose
|
135
|
+
const { description, technologies, purpose, hasContentIndex, duration, isCompleted, variables, currentStep, language } = formState, rest = tslib_1.__rest(formState, ["description", "technologies", "purpose", "hasContentIndex", "duration", "isCompleted", "variables", "currentStep", "language"]);
|
136
|
+
return rest;
|
137
|
+
};
|
138
|
+
async function startExerciseGeneration(bucket, rigoToken, steps, packageContext, exercise, tutorialDir, courseSlug, purposeSlug, lastLesson = "") {
|
139
|
+
const exercisesDir = `${tutorialDir}/exercises`;
|
140
|
+
const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
|
141
|
+
console.log("exSlug", exSlug);
|
142
|
+
const readmeFilename = `README.${packageContext.language && packageContext.language !== "en" ?
|
143
|
+
`${packageContext.language}.` :
|
144
|
+
""}md`;
|
145
|
+
const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/exercise-processor/${exercise.id}/${rigoToken}`;
|
146
|
+
const res = await (0, rigoActions_1.readmeCreator)(rigoToken, {
|
147
|
+
title: `${exercise.id} - ${exercise.title}`,
|
148
|
+
output_lang: packageContext.language || "en",
|
149
|
+
list_of_exercises: JSON.stringify(steps.map(step => step.id + "-" + step.title)),
|
150
|
+
tutorial_description: JSON.stringify(cleanFormState(packageContext)),
|
151
|
+
lesson_description: exercise.description,
|
152
|
+
kind: exercise.type.toLowerCase(),
|
153
|
+
last_lesson: lastLesson,
|
154
|
+
}, purposeSlug, webhookUrl);
|
155
|
+
// console.log("res processing in background", res)
|
156
|
+
}
|
157
|
+
async function processExercise(bucket, rigoToken, steps, packageContext, exercise, tutorialDir, courseSlug, purposeSlug, lastLesson = "") {
|
158
|
+
const exercisesDir = `${tutorialDir}/exercises`;
|
131
159
|
// const tid = toast.loading("Generating lesson...")
|
132
160
|
const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
|
133
161
|
console.log("exSlug", exSlug);
|
@@ -136,62 +164,70 @@ async function processExercise(bucket, rigoToken, steps, packageContext, exercis
|
|
136
164
|
""}md`;
|
137
165
|
const targetDir = `${exercisesDir}/${exSlug}`;
|
138
166
|
console.log("✍🏻 Generating lesson", exercise.id, exercise.title);
|
139
|
-
const isGeneratingText = `
|
140
|
-
\`\`\`loader slug="${exSlug}"
|
141
|
-
:rigo
|
142
|
-
\`\`\`
|
143
|
-
`;
|
144
|
-
await uploadFileToBucket(bucket, isGeneratingText, `${targetDir}/${readmeFilename}`);
|
145
167
|
const readme = await (0, rigoActions_1.readmeCreator)(rigoToken, {
|
146
168
|
title: `${exercise.id} - ${exercise.title}`,
|
147
169
|
output_lang: packageContext.language || "en",
|
148
|
-
list_of_exercises: JSON.stringify(steps),
|
149
|
-
tutorial_description: JSON.stringify(packageContext),
|
170
|
+
list_of_exercises: JSON.stringify(steps.map(step => step.id + "-" + step.title)),
|
171
|
+
tutorial_description: JSON.stringify(cleanFormState(packageContext)),
|
150
172
|
lesson_description: exercise.description,
|
151
173
|
kind: exercise.type.toLowerCase(),
|
174
|
+
last_lesson: lastLesson,
|
152
175
|
}, purposeSlug);
|
153
176
|
const duration = exercise.duration;
|
154
|
-
let attempts = 0
|
155
|
-
|
156
|
-
while (readability.fkglResult.fkgl > PARAMS.max_fkgl &&
|
157
|
-
attempts < PARAMS.max_rewrite_attempts) {
|
158
|
-
(0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
|
159
|
-
lesson: exSlug,
|
160
|
-
status: "generating",
|
161
|
-
log: `🔄 The lesson ${exercise.id} - ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
|
162
|
-
});
|
163
|
-
// eslint-disable-next-line
|
164
|
-
const reducedReadme = await (0, rigoActions_1.makeReadmeReadable)(rigoToken, {
|
165
|
-
lesson: readability.body,
|
166
|
-
number_of_words: readability.minutes.toString(),
|
167
|
-
expected_number_words: PARAMS.max_words.toString(),
|
168
|
-
fkgl_results: JSON.stringify(readability.fkglResult),
|
169
|
-
expected_grade_level: PARAMS.expected_grade_level,
|
170
|
-
}, purposeSlug);
|
171
|
-
if (!reducedReadme)
|
172
|
-
break;
|
173
|
-
readability = (0, creatorUtilities_2.checkReadability)(reducedReadme.parsed.content, PARAMS.max_words, duration || 2);
|
174
|
-
attempts++;
|
175
|
-
}
|
176
|
-
await uploadFileToBucket(bucket, readability.newMarkdown, `${targetDir}/${readmeFilename}`);
|
177
|
+
// let attempts = 0
|
178
|
+
const readability = (0, creatorUtilities_2.checkReadability)(readme.parsed.content, PARAMS.max_words, duration || 3);
|
177
179
|
(0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
|
178
180
|
lesson: exSlug,
|
179
|
-
status: "
|
180
|
-
log:
|
181
|
+
status: "generating",
|
182
|
+
log: `🔄 The lesson ${exercise.id} - ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
|
181
183
|
});
|
182
|
-
|
184
|
+
// while (
|
185
|
+
// readability.fkglResult.fkgl > PARAMS.max_fkgl &&
|
186
|
+
// attempts < PARAMS.max_rewrite_attempts
|
187
|
+
// ) {
|
188
|
+
// // eslint-disable-next-line
|
189
|
+
// const reducedReadme = await makeReadmeReadable(
|
190
|
+
// rigoToken,
|
191
|
+
// {
|
192
|
+
// lesson: readability.body,
|
193
|
+
// number_of_words: readability.minutes.toString(),
|
194
|
+
// expected_number_words: PARAMS.max_words.toString(),
|
195
|
+
// fkgl_results: JSON.stringify(readability.fkglResult),
|
196
|
+
// expected_grade_level: PARAMS.expected_grade_level,
|
197
|
+
// },
|
198
|
+
// purposeSlug
|
199
|
+
// )
|
200
|
+
// if (!reducedReadme) break
|
201
|
+
// readability = checkReadability(
|
202
|
+
// reducedReadme.parsed.content,
|
203
|
+
// PARAMS.max_words,
|
204
|
+
// duration || 3
|
205
|
+
// )
|
206
|
+
// attempts++
|
207
|
+
// }
|
208
|
+
await uploadFileToBucket(bucket, readability.newMarkdown, `${targetDir}/${readmeFilename}`);
|
209
|
+
if (exercise.type.toLowerCase() === "code" &&
|
210
|
+
readme.parsed.codefile_content) {
|
183
211
|
console.log("🔍 Creating code file for", exercise.title);
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
await uploadFileToBucket(bucket, codeFile.parsed.content, `${targetDir}/index.${codeFile.parsed.extension.replace(".", "")}`);
|
212
|
+
await uploadFileToBucket(bucket, readme.parsed.codefile_content, `${targetDir}/${readme.parsed.codefile_name.toLowerCase().trim()}`);
|
213
|
+
}
|
214
|
+
const imagesArray = (0, creatorUtilities_1.extractImagesFromMarkdown)(readability.newMarkdown);
|
215
|
+
if (imagesArray.length > 0) {
|
189
216
|
(0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
|
190
217
|
lesson: exSlug,
|
191
218
|
status: "done",
|
192
|
-
log:
|
219
|
+
log: `🔄 Generating images for ${exercise.title}`,
|
193
220
|
});
|
221
|
+
for (const image of imagesArray) {
|
222
|
+
// eslint-disable-next-line no-await-in-loop
|
223
|
+
await (0, exports.processImage)(bucket, tutorialDir, image.url, image.alt, rigoToken);
|
224
|
+
}
|
194
225
|
}
|
226
|
+
(0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
|
227
|
+
lesson: exSlug,
|
228
|
+
status: "done",
|
229
|
+
log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
|
230
|
+
});
|
195
231
|
return readability.newMarkdown;
|
196
232
|
}
|
197
233
|
const fixPreviewUrl = (slug, previewUrl) => {
|
@@ -226,6 +262,14 @@ class ServeCommand extends SessionCommand_1.default {
|
|
226
262
|
credentials,
|
227
263
|
});
|
228
264
|
const bucket = bucketStorage.bucket(process.env.GCP_BUCKET_NAME || "learnpack-packages");
|
265
|
+
const host = process.env.HOST;
|
266
|
+
if (!host) {
|
267
|
+
console.log("HOST is not set");
|
268
|
+
process.exit(1);
|
269
|
+
}
|
270
|
+
else {
|
271
|
+
console.log("HOST is set to", host);
|
272
|
+
}
|
229
273
|
// async function listFilesWithPrefix(prefix: string) {
|
230
274
|
// const [files] = await bucket.getFiles({ prefix })
|
231
275
|
// return files
|
@@ -380,6 +424,76 @@ class ServeCommand extends SessionCommand_1.default {
|
|
380
424
|
(0, creatorSocket_1.emitToNotification)(id, body);
|
381
425
|
res.json({ id, status: "SUCCESS" });
|
382
426
|
});
|
427
|
+
app.post("/webhooks/:courseSlug/exercise-processor/:lessonID/:rigoToken", async (req, res) => {
|
428
|
+
// console.log("Receiving a webhook to exercise processor")
|
429
|
+
const { courseSlug, lessonID, rigoToken } = req.params;
|
430
|
+
const readme = req.body;
|
431
|
+
const syllabus = await bucket.file(`courses/${courseSlug}/.learn/initialSyllabus.json`);
|
432
|
+
const [content] = await syllabus.download();
|
433
|
+
const syllabusJson = JSON.parse(content.toString());
|
434
|
+
const exerciseIndex = syllabusJson.lessons.findIndex(lesson => lesson.id === lessonID);
|
435
|
+
if (exerciseIndex === -1) {
|
436
|
+
console.log("Exercise not found receiving webhook, this should not happen", lessonID);
|
437
|
+
return res.json({ status: "ERROR", error: "Exercise not found" });
|
438
|
+
}
|
439
|
+
const exercise = syllabusJson.lessons[exerciseIndex];
|
440
|
+
const nextExercise = syllabusJson.lessons[exerciseIndex + 1] || null;
|
441
|
+
if (!exercise) {
|
442
|
+
console.log("Exercise not found receiving webhook, this should not happen", lessonID);
|
443
|
+
return res.json({ status: "SUCCESS" });
|
444
|
+
}
|
445
|
+
const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
|
446
|
+
const readability = (0, creatorUtilities_2.checkReadability)(readme.parsed.content, PARAMS.max_words, 3);
|
447
|
+
(0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
|
448
|
+
lesson: exSlug,
|
449
|
+
status: "generating",
|
450
|
+
log: `🔄 The lesson ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
|
451
|
+
});
|
452
|
+
const exercisesDir = `courses/${courseSlug}/exercises`;
|
453
|
+
const targetDir = `${exercisesDir}/${exSlug}`;
|
454
|
+
const readmeFilename = `README.${readme.parsed.language_code && readme.parsed.language_code !== "en" ?
|
455
|
+
`${readme.parsed.language_code}.` :
|
456
|
+
""}md`;
|
457
|
+
await uploadFileToBucket(bucket, readability.newMarkdown, `${targetDir}/${readmeFilename}`);
|
458
|
+
if (exercise.type.toLowerCase() === "code" &&
|
459
|
+
readme.parsed.codefile_content) {
|
460
|
+
(0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
|
461
|
+
lesson: exSlug,
|
462
|
+
status: "generating",
|
463
|
+
log: `🔄 Creating code file for ${exercise.title}`,
|
464
|
+
});
|
465
|
+
await uploadFileToBucket(bucket, readme.parsed.codefile_content, `${targetDir}/${readme.parsed.codefile_name.toLowerCase().trim()}`);
|
466
|
+
}
|
467
|
+
if (nextExercise) {
|
468
|
+
startExerciseGeneration(bucket, rigoToken, syllabusJson.lessons, syllabusJson.courseInfo, nextExercise, `courses/${courseSlug}`, courseSlug, syllabusJson.courseInfo.purpose, readme.parsed.content);
|
469
|
+
}
|
470
|
+
const imagesArray = (0, creatorUtilities_1.extractImagesFromMarkdown)(readability.newMarkdown);
|
471
|
+
if (imagesArray.length > 0) {
|
472
|
+
console.log("This course requires images and I don't have the token :)");
|
473
|
+
(0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
|
474
|
+
lesson: exSlug,
|
475
|
+
status: "pending",
|
476
|
+
log: `🔄 Generating images for ${exercise.title}`,
|
477
|
+
});
|
478
|
+
for (const image of imagesArray) {
|
479
|
+
// eslint-disable-next-line no-await-in-loop
|
480
|
+
await (0, exports.processImage)(bucket, `courses/${courseSlug}`, image.url, image.alt, rigoToken);
|
481
|
+
}
|
482
|
+
}
|
483
|
+
(0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
|
484
|
+
lesson: exSlug,
|
485
|
+
status: "done",
|
486
|
+
log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
|
487
|
+
});
|
488
|
+
const newSyllabus = Object.assign(Object.assign({}, syllabusJson), { lessons: syllabusJson.lessons.map(lesson => {
|
489
|
+
if (lesson.id === exercise.id) {
|
490
|
+
return Object.assign(Object.assign({}, lesson), { generated: true });
|
491
|
+
}
|
492
|
+
return lesson;
|
493
|
+
}) });
|
494
|
+
await uploadFileToBucket(bucket, JSON.stringify(newSyllabus), `courses/${courseSlug}/.learn/initialSyllabus.json`);
|
495
|
+
res.json({ status: "SUCCESS" });
|
496
|
+
});
|
383
497
|
app.get("/check-preview-image/:slug", async (req, res) => {
|
384
498
|
const { slug } = req.params;
|
385
499
|
const file = bucket.file(`courses/${slug}/preview.png`);
|
@@ -662,38 +776,85 @@ class ServeCommand extends SessionCommand_1.default {
|
|
662
776
|
if (!rigoToken || !bcToken) {
|
663
777
|
return res.status(400).json({ error: "Missing tokens" });
|
664
778
|
}
|
665
|
-
const
|
666
|
-
const tutorialDir = `courses/${
|
779
|
+
const courseSlug = (0, creatorUtilities_2.slugify)(syllabus.courseInfo.title);
|
780
|
+
const tutorialDir = `courses/${courseSlug}`;
|
667
781
|
const learnJson = (0, exports.createLearnJson)(syllabus.courseInfo);
|
668
782
|
try {
|
669
|
-
await api_1.default.createRigoPackage(rigoToken,
|
783
|
+
await api_1.default.createRigoPackage(rigoToken, courseSlug, learnJson);
|
670
784
|
}
|
671
785
|
catch (error) {
|
672
786
|
console.error("Failed to create Rigo package:", error);
|
673
787
|
return res.status(400).json({ error: "Failed to create Rigo package" });
|
674
788
|
}
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
789
|
+
try {
|
790
|
+
await uploadFileToBucket(bucket, JSON.stringify(learnJson), `${tutorialDir}/learn.json`);
|
791
|
+
}
|
792
|
+
catch (error) {
|
793
|
+
console.error("Failed to upload learn.json:", error);
|
794
|
+
return res.status(400).json({ error: "Failed to upload learn.json" });
|
795
|
+
}
|
796
|
+
console.log("🔄 Learn.json and initialSyllabus.json uploaded to bucket to", tutorialDir);
|
797
|
+
for (let i = 0; i < syllabus.lessons.length; i++) {
|
798
|
+
const lesson = syllabus.lessons[i];
|
799
|
+
const exSlug = (0, creatorUtilities_2.slugify)(lesson.id + "-" + lesson.title);
|
800
|
+
const targetDir = `${tutorialDir}/exercises/${exSlug}`;
|
801
|
+
// eslint-disable-next-line no-await-in-loop
|
802
|
+
await uploadInitialReadme(bucket, exSlug, targetDir, syllabus.courseInfo);
|
803
|
+
}
|
804
|
+
const sidebar = await createInitialSidebar(syllabus.lessons.map(lesson => (0, creatorUtilities_2.slugify)(lesson.id + "-" + lesson.title)), syllabus.courseInfo.language);
|
805
|
+
const initialSyllabus = Object.assign(Object.assign({}, syllabus), { lessons: syllabus.lessons.map((lesson, index) => {
|
806
|
+
if (index < 1) {
|
807
|
+
return Object.assign(Object.assign({}, lesson), { generated: true });
|
808
|
+
}
|
809
|
+
return Object.assign(Object.assign({}, lesson), { generated: false });
|
810
|
+
}) });
|
811
|
+
await uploadFileToBucket(bucket, JSON.stringify(initialSyllabus), `${tutorialDir}/.learn/initialSyllabus.json`);
|
690
812
|
await uploadFileToBucket(bucket, JSON.stringify(sidebar), `${tutorialDir}/.learn/sidebar.json`);
|
691
|
-
|
813
|
+
const firstLesson = syllabus.lessons[0];
|
814
|
+
const lastResult = "Nothing";
|
815
|
+
startExerciseGeneration(bucket, rigoToken, syllabus.lessons, syllabus.courseInfo, firstLesson, tutorialDir, courseSlug, syllabus.courseInfo.purpose, lastResult);
|
692
816
|
return res.json({
|
693
817
|
message: "Course created",
|
694
818
|
slug: (0, creatorUtilities_2.slugify)(syllabus.courseInfo.title),
|
695
819
|
});
|
696
820
|
});
|
821
|
+
app.get("/courses/:courseSlug/syllabus", async (req, res) => {
|
822
|
+
try {
|
823
|
+
console.log("GET /courses/:courseSlug/syllabus");
|
824
|
+
const { courseSlug } = req.params;
|
825
|
+
const syllabus = await bucket.file(`courses/${courseSlug}/.learn/initialSyllabus.json`);
|
826
|
+
const [content] = await syllabus.download();
|
827
|
+
const syllabusJson = JSON.parse(content.toString());
|
828
|
+
res.json(syllabusJson);
|
829
|
+
}
|
830
|
+
catch (error) {
|
831
|
+
console.error("Error getting syllabus:", error);
|
832
|
+
return res.status(500).json({ error: "Error getting syllabus" });
|
833
|
+
}
|
834
|
+
});
|
835
|
+
app.post("/actions/continue-course/:courseSlug", async (req, res) => {
|
836
|
+
console.log("POST /actions/continue-course/:courseSlug");
|
837
|
+
const { courseSlug } = req.params;
|
838
|
+
const { feedback } = req.body;
|
839
|
+
const rigoToken = req.header("x-rigo-token");
|
840
|
+
const bcToken = req.header("x-breathecode-token");
|
841
|
+
if (!rigoToken || !bcToken) {
|
842
|
+
return res.status(400).json({ error: "Missing tokens" });
|
843
|
+
}
|
844
|
+
const syllabus = await bucket.file(`courses/${courseSlug}/.learn/initialSyllabus.json`);
|
845
|
+
const [content] = await syllabus.download();
|
846
|
+
const syllabusJson = JSON.parse(content.toString());
|
847
|
+
const notGeneratedLessons = syllabusJson.lessons.filter(lesson => !lesson.generated);
|
848
|
+
const lastGeneratedLesson = findLast(syllabusJson.lessons, lesson => { var _a; return (_a = lesson.generated) !== null && _a !== void 0 ? _a : false; });
|
849
|
+
console.log("ABout to generate", notGeneratedLessons.length, "lessons");
|
850
|
+
const firstLessonToGenerate = notGeneratedLessons[0];
|
851
|
+
await startExerciseGeneration(bucket, rigoToken, syllabusJson.lessons, syllabusJson.courseInfo, firstLessonToGenerate, `courses/${courseSlug}`, courseSlug, syllabusJson.courseInfo.purpose, JSON.stringify(lastGeneratedLesson) +
|
852
|
+
`\n\nThe user provided this feedback in relation to the course: ${feedback}`);
|
853
|
+
return res.json({
|
854
|
+
message: "Course continued",
|
855
|
+
slug: courseSlug,
|
856
|
+
});
|
857
|
+
});
|
697
858
|
app.get("/courses/:courseSlug/exercises/:exerciseSlug/", async (req, res) => {
|
698
859
|
var _a;
|
699
860
|
console.log("GET /courses/:courseSlug/exercises/:exerciseSlug/");
|
@@ -737,9 +898,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
737
898
|
.json({ error: "Authentication failed, missing tokens" });
|
738
899
|
}
|
739
900
|
const { config, exercises } = await (0, configBuilder_1.buildConfig)(bucket, slug);
|
740
|
-
//
|
741
|
-
const fixedSlug = fixSlugLength(slug);
|
742
|
-
// console.log(fixedSlug, "FIXED SLUG")
|
901
|
+
// const fixedSlug = fixSlugLength(slug)
|
743
902
|
const prefix = `courses/${slug}/`;
|
744
903
|
const tmpRoot = path.join(os.tmpdir(), `learnpack-${slug}`);
|
745
904
|
const buildRoot = path.join(tmpRoot, "build");
|
@@ -767,7 +926,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
767
926
|
.replace(/{{title}}/g, config.title.us)
|
768
927
|
.replace(/<title>.*<\/title>/, `<title>${config.title.us}</title>`)
|
769
928
|
.replace(/{{description}}/g, config.description.us)
|
770
|
-
.replace(/{{preview}}/g, fixPreviewUrl(
|
929
|
+
.replace(/{{preview}}/g, fixPreviewUrl(slug, "") ||
|
771
930
|
"https://raw.githubusercontent.com/learnpack/ide/master/public/learnpack.svg")
|
772
931
|
.replace(/{{slug}}/g, slug)
|
773
932
|
.replace(/{{duration}}/g, (0, misc_1.minutesToISO8601Duration)(config.duration));
|
@@ -788,13 +947,13 @@ class ServeCommand extends SessionCommand_1.default {
|
|
788
947
|
}));
|
789
948
|
// 8) Crear el config.json en buildRoot con lo que retorna buildConfig
|
790
949
|
const fullConfig = { config, exercises };
|
791
|
-
fullConfig.config.slug =
|
950
|
+
fullConfig.config.slug = slug;
|
792
951
|
fullConfig.config.preview =
|
793
|
-
fixPreviewUrl(
|
952
|
+
fixPreviewUrl(slug, "") ||
|
794
953
|
"https://raw.githubusercontent.com/learnpack/ide/master/public/learnpack.svg";
|
795
954
|
fs.writeFileSync(path.join(buildRoot, "config.json"), JSON.stringify(fullConfig, null, 2), "utf-8");
|
796
955
|
// 9) Empaquetar en ZIP (solo contenido de buildRoot)
|
797
|
-
const zipName = `${
|
956
|
+
const zipName = `${slug}.zip`;
|
798
957
|
const zipPath = path.join(tmpRoot, zipName);
|
799
958
|
const output = fs.createWriteStream(zipPath);
|
800
959
|
const archive = archiver("zip", { zlib: { level: 9 } });
|