@learnpack/learnpack 5.0.33 → 5.0.35

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
@@ -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.33 win32-x64 node-v22.14.0
24
+ @learnpack/learnpack/5.0.35 win32-x64 node-v22.14.0
25
25
  $ learnpack --help [COMMAND]
26
26
  USAGE
27
27
  $ learnpack COMMAND
@@ -76,7 +76,7 @@ DESCRIPTION
76
76
  12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
77
77
  ```
78
78
 
79
- _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.33/src\commands\audit.ts)_
79
+ _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\audit.ts)_
80
80
 
81
81
  ## `learnpack breakToken`
82
82
 
@@ -91,7 +91,7 @@ OPTIONS
91
91
  -y, --yes Skip all prompts and initialize an empty project
92
92
  ```
93
93
 
94
- _See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.33/src\commands\breakToken.ts)_
94
+ _See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\breakToken.ts)_
95
95
 
96
96
  ## `learnpack clean`
97
97
 
@@ -106,7 +106,7 @@ DESCRIPTION
106
106
  Extra documentation goes here
107
107
  ```
108
108
 
109
- _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.33/src\commands\clean.ts)_
109
+ _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\clean.ts)_
110
110
 
111
111
  ## `learnpack download [PACKAGE]`
112
112
 
@@ -124,7 +124,7 @@ DESCRIPTION
124
124
  Extra documentation goes here
125
125
  ```
126
126
 
127
- _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.33/src\commands\download.ts)_
127
+ _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\download.ts)_
128
128
 
129
129
  ## `learnpack help [COMMAND]`
130
130
 
@@ -156,7 +156,7 @@ OPTIONS
156
156
  -y, --yes Skip all prompts and initialize an empty project
157
157
  ```
158
158
 
159
- _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.33/src\commands\init.ts)_
159
+ _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\init.ts)_
160
160
 
161
161
  ## `learnpack login [PACKAGE]`
162
162
 
@@ -174,7 +174,7 @@ DESCRIPTION
174
174
  Extra documentation goes here
175
175
  ```
176
176
 
177
- _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.33/src\commands\login.ts)_
177
+ _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\login.ts)_
178
178
 
179
179
  ## `learnpack logout [PACKAGE]`
180
180
 
@@ -192,7 +192,7 @@ DESCRIPTION
192
192
  Extra documentation goes here
193
193
  ```
194
194
 
195
- _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.33/src\commands\logout.ts)_
195
+ _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\logout.ts)_
196
196
 
197
197
  ## `learnpack plugins`
198
198
 
@@ -323,7 +323,7 @@ OPTIONS
323
323
  -h, --help show CLI help
324
324
  ```
325
325
 
326
- _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.33/src\commands\publish.ts)_
326
+ _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\publish.ts)_
327
327
 
328
328
  ## `learnpack start`
329
329
 
@@ -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\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.33/src\commands\start.ts)_
348
+ _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\start.ts)_
349
349
 
350
350
  ## `learnpack test [EXERCISESLUG]`
351
351
 
@@ -362,7 +362,7 @@ OPTIONS
362
362
  -y, --yes Skip all prompts and initialize an empty project
363
363
  ```
364
364
 
365
- _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.33/src\commands\test.ts)_
365
+ _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\test.ts)_
366
366
 
367
367
  ## `learnpack translate`
368
368
 
@@ -376,7 +376,7 @@ OPTIONS
376
376
  -y, --yes Skip all prompts and initialize an empty project
377
377
  ```
378
378
 
379
- _See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.33/src\commands\translate.ts)_
379
+ _See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.35/src\commands\translate.ts)_
380
380
  <!-- commandsstop -->
381
381
 
382
382
  > > > > > > > 0cb3e56d84c197f9d008836bb573eade212b7e57
@@ -4,18 +4,46 @@ const command_1 = require("@oclif/command");
4
4
  const BaseCommand_1 = require("../utils/BaseCommand");
5
5
  const console_1 = require("../utils/console");
6
6
  const creatorUtilities_1 = require("../utils/creatorUtilities");
7
+ const exampleMd = `# How to Install Node.js
8
+
9
+ Node.js lets you run JavaScript outside a web browser.
10
+
11
+ ## Step 1: Download Node.js
12
+
13
+ Get the Node.js installer from the [official site](https://nodejs.org/en/download/).
14
+
15
+ ## Step 2: Install Node.js
16
+
17
+ Open the installer and follow the steps to finish.
18
+
19
+ ## Step 3: Verify the Installation
20
+
21
+ Open a terminal and type:
22
+
23
+ \`\`\`bash
24
+ node -v
25
+ \`\`\`
26
+ `;
7
27
  class BreakTokenCommand extends BaseCommand_1.default {
8
28
  async run() {
9
29
  const { flags } = this.parse(BreakTokenCommand);
10
30
  // await SessionManager.breakToken()
11
- await (0, creatorUtilities_1.createFileOnDesktop)();
12
- console_1.default.info("File created on desktop, please make the necessary changes and continue when ready");
13
- // Wait for user to press enter
14
- process.stdin.once("data", () => {
15
- console_1.default.info("File content:");
16
- console_1.default.info((0, creatorUtilities_1.getContentIndex)());
17
- process.exit(0);
18
- });
31
+ const paragraphs = (0, creatorUtilities_1.extractParagraphs)(exampleMd);
32
+ for (const paragraph of paragraphs) {
33
+ const syllables = (0, creatorUtilities_1.splitIntoSyllables)(paragraph);
34
+ const words = (0, creatorUtilities_1.extractWords)(paragraph);
35
+ const sentences = (0, creatorUtilities_1.countSentences)(paragraph);
36
+ const fkgl = (0, creatorUtilities_1.fleschKincaidGrade)(paragraph);
37
+ console_1.default.info(paragraph);
38
+ console_1.default.info(`Number of syllables: ${syllables.length}`);
39
+ console_1.default.info(syllables);
40
+ console_1.default.info(`Number of words: ${words.length}`);
41
+ console_1.default.info(words);
42
+ console_1.default.info(`Number of sentences: ${sentences}`);
43
+ console_1.default.info(`FKGL: ${fkgl}`);
44
+ console_1.default.info("---");
45
+ }
46
+ process.exit(0);
19
47
  }
20
48
  }
21
49
  BreakTokenCommand.description = "Break the token";
@@ -20,6 +20,12 @@ const durationByKind = {
20
20
  quiz: 2,
21
21
  read: 1,
22
22
  };
23
+ const PARAMS = {
24
+ expected_grade_level: "6",
25
+ max_fkgl: 8,
26
+ max_words: 200,
27
+ max_rewrite_attempts: 3,
28
+ };
23
29
  const whichTargetAudience = async () => {
24
30
  const res = await prompts([
25
31
  {
@@ -30,6 +36,48 @@ const whichTargetAudience = async () => {
30
36
  ]);
31
37
  return res.targetAudience;
32
38
  };
39
+ async function processExercise(rigoToken, steps, packageContext, exercise, exercisesDir) {
40
+ const { exNumber, exTitle, kind, description } = (0, creatorUtilities_1.getExInfo)(exercise);
41
+ const exerciseDir = path.join(exercisesDir, `${exNumber}-${exTitle}`);
42
+ const readme = await (0, rigoActions_1.readmeCreator)(rigoToken, {
43
+ title: `${exNumber} - ${exTitle}`,
44
+ output_lang: "en",
45
+ list_of_exercises: steps.join(","),
46
+ tutorial_description: packageContext,
47
+ lesson_description: description,
48
+ kind: kind.toLowerCase(),
49
+ });
50
+ const duration = durationByKind[kind.toLowerCase()];
51
+ let attempts = 0;
52
+ let readability = (0, creatorUtilities_1.checkReadability)(readme.parsed.content, 200, duration || 1);
53
+ while (readability.fkglResult.fkgl > PARAMS.max_fkgl &&
54
+ attempts < PARAMS.max_rewrite_attempts) {
55
+ console_1.default.warning(`The lesson ${exTitle} has as readability score of ${readability.fkglResult.fkgl} . It exceeds the maximum of words per minute. Rewriting it... (Attempt ${attempts + 1})`);
56
+ // eslint-disable-next-line
57
+ const reducedReadme = await (0, rigoActions_1.makeReadmeReadable)(rigoToken, {
58
+ lesson: readability.body,
59
+ number_of_words: readability.minutes.toString(),
60
+ expected_number_words: PARAMS.max_words.toString(),
61
+ fkgl_results: JSON.stringify(readability.fkglResult),
62
+ expected_grade_level: PARAMS.expected_grade_level,
63
+ });
64
+ if (!reducedReadme)
65
+ break;
66
+ readability = (0, creatorUtilities_1.checkReadability)(reducedReadme.parsed.content, PARAMS.max_words, duration || 1);
67
+ attempts++;
68
+ }
69
+ console_1.default.success(`After ${attempts} attempts, the lesson ${exTitle} has a readability score of ${readability.fkglResult.fkgl} using FKGL. And it has ${readability.minutes.toFixed(2)} minutes of reading time.`);
70
+ const readmeFilename = "README.md";
71
+ fs.writeFileSync(path.join(exerciseDir, readmeFilename), readability.newMarkdown);
72
+ if (kind.toLowerCase() === "code") {
73
+ const codeFile = await (0, rigoActions_1.createCodeFile)(rigoToken, {
74
+ readme: readability.newMarkdown,
75
+ tutorial_info: packageContext,
76
+ });
77
+ fs.writeFileSync(path.join(exerciseDir, `app.${codeFile.parsed.extension.replace(".", "")}`), codeFile.parsed.content);
78
+ }
79
+ return readability.newMarkdown;
80
+ }
33
81
  const initializeInteractiveCreation = async (rigoToken, courseInfo) => {
34
82
  let prevInteractions = "";
35
83
  let isReady = false;
@@ -180,43 +228,7 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
180
228
  const exerciseDir = path.join(exercisesDir, `${exNumber}-${exTitle}`);
181
229
  fs.ensureDirSync(exerciseDir);
182
230
  }
183
- const exercisePromises = steps.map(async (exercise, index) => {
184
- const { exNumber, exTitle, kind, description } = (0, creatorUtilities_1.getExInfo)(exercise);
185
- const exerciseDir = path.join(exercisesDir, `${exNumber}-${exTitle}`);
186
- const readme = await (0, rigoActions_1.readmeCreator)(rigoToken, {
187
- title: `${exNumber} - ${exTitle}`,
188
- output_lang: "en",
189
- list_of_exercises: steps.join(","),
190
- tutorial_description: packageContext,
191
- lesson_description: description,
192
- kind: kind.toLowerCase(),
193
- });
194
- const duration = durationByKind[kind.toLowerCase()];
195
- let readingTime = (0, creatorUtilities_1.checkReadingTime)(readme.parsed.content, 200, duration || 1);
196
- if (readingTime.exceedsThreshold) {
197
- // Console.info(
198
- // `The reading time for the lesson ${exTitle} exceeds the threshold, reducing it...`
199
- // )
200
- const reducedReadme = await (0, rigoActions_1.reduceReadme)(rigoToken, {
201
- lesson: readingTime.body,
202
- number_of_words: readingTime.minutes.toString(),
203
- expected_number_words: "200",
204
- });
205
- if (reducedReadme) {
206
- readingTime = (0, creatorUtilities_1.checkReadingTime)(reducedReadme.parsed.content, 200, duration || 1);
207
- }
208
- }
209
- const readmeFilename = "README.md";
210
- fs.writeFileSync(path.join(exerciseDir, readmeFilename), readingTime.newMarkdown);
211
- if (kind.toLowerCase() === "code") {
212
- const codeFile = await (0, rigoActions_1.createCodeFile)(rigoToken, {
213
- readme: readme.parsed.content,
214
- tutorial_info: packageContext,
215
- });
216
- fs.writeFileSync(path.join(exerciseDir, `app.${codeFile.parsed.extension.replace(".", "")}`), codeFile.parsed.content);
217
- }
218
- return readingTime.newMarkdown;
219
- });
231
+ const exercisePromises = steps.map(exercise => processExercise(rigoToken, steps, packageContext, exercise, exercisesDir));
220
232
  const readmeContents = await Promise.all(exercisePromises);
221
233
  console_1.default.success("Lessons created! 🎉");
222
234
  console_1.default.info("Generating images for the lessons...");
@@ -94,6 +94,22 @@ const exercise = (path, position, configObject) => {
94
94
  }
95
95
  return content;
96
96
  },
97
+ renameFolder: function (newName) {
98
+ if (!(config === null || config === void 0 ? void 0 : config.dirPath)) {
99
+ throw new Error("No config directory found");
100
+ }
101
+ try {
102
+ const newPath = p.join(config === null || config === void 0 ? void 0 : config.exercisesPath, newName);
103
+ fs.renameSync(this.path, newPath);
104
+ this.path = newPath;
105
+ this.slug = newName;
106
+ this.title = newName;
107
+ }
108
+ catch (error) {
109
+ console.log(error);
110
+ throw new Error("Failed to rename exercise: " + error);
111
+ }
112
+ },
97
113
  saveFile: function (name, content) {
98
114
  const file = this.files.find((f) => f.name === name);
99
115
  if (file) {
@@ -312,6 +312,27 @@ async function default_1(app, configObject, configManager) {
312
312
  return res.status(400).json({ error: error.message });
313
313
  }
314
314
  }));
315
+ app.put("/actions/rename", jsonBodyParser, withHandler(async (req, res) => {
316
+ const { slug, newSlug } = req.body;
317
+ const exercise = configManager.getExercise(slug);
318
+ if (!exercise) {
319
+ return res.status(400).json({ error: "Exercise not found" });
320
+ }
321
+ try {
322
+ if (exercise.renameFolder) {
323
+ exercise.renameFolder(newSlug);
324
+ res.json({ status: "ok" });
325
+ }
326
+ else {
327
+ res.status(500).json({
328
+ error: "Failed to rename exercise because it's not supported by the exercise",
329
+ });
330
+ }
331
+ }
332
+ catch (_a) {
333
+ res.status(500).json({ error: "Failed to rename exercise" });
334
+ }
335
+ }));
315
336
  app.post("/exercise/:slug/create", jsonBodyParser, withHandler(async (req, res) => {
316
337
  const { title, readme, language } = req.body;
317
338
  const { slug } = req.params;
@@ -17,6 +17,7 @@ export interface IExercise {
17
17
  getReadme?: (lang: string | null) => any;
18
18
  getFile?: (name: string) => string | Buffer;
19
19
  saveFile?: (name: string, content: string) => void;
20
+ renameFolder?: (newName: string) => void;
20
21
  getTestReport?: () => any;
21
22
  test?: (sessionConfig: any, config: IConfig, socket: ISocket) => void;
22
23
  }
@@ -14,11 +14,16 @@ export type PackageInfo = {
14
14
  us: string;
15
15
  };
16
16
  };
17
- export declare function checkReadingTime(markdown: string, wordsPerMinute?: number, maxMinutes?: number): {
17
+ type TFKGLResult = {
18
+ text: string;
19
+ fkgl: number;
20
+ };
21
+ export declare function checkReadability(markdown: string, wordsPerMinute?: number, maxMinutes?: number): {
18
22
  newMarkdown: string;
19
23
  exceedsThreshold: boolean;
20
24
  minutes: number;
21
25
  body: string;
26
+ fkglResult: TFKGLResult;
22
27
  };
23
28
  export declare const getExInfo: (title: string) => {
24
29
  exNumber: string;
@@ -48,4 +53,41 @@ export declare function createFileOnDesktop(): Promise<void>;
48
53
  export declare function getContentIndex(): string;
49
54
  export declare function extractTextFromMarkdown(mdContent: string): string;
50
55
  export declare const saveTranslatedReadme: (exercise: string, languageCode: string, readme: string) => Promise<void>;
56
+ /**
57
+ * Extracts paragraphs from a Markdown string.
58
+ * @param markdownText The input Markdown string.
59
+ * @returns An array of paragraph contents.
60
+ */
61
+ export declare function extractParagraphs(markdownText: string): string[];
62
+ /**
63
+ * Splits a paragraph into words and separates each word into syllables.
64
+ * @param paragraph The input paragraph.
65
+ * @returns An array of words, each split into syllables.
66
+ */
67
+ export declare function splitIntoSyllables(paragraph: string): string[];
68
+ /**
69
+ * Splits a word into its syllables using a basic estimation.
70
+ * @param word The word to split.
71
+ * @returns An array of syllables.
72
+ */
73
+ export declare function splitWordIntoSyllables(word: string): string[];
74
+ /**
75
+ * Extracts words from a given paragraph.
76
+ * @param paragraph The input text.
77
+ * @returns An array of words.
78
+ */
79
+ export declare function extractWords(paragraph: string): string[];
80
+ /**
81
+ * Calculates the Flesch-Kincaid Grade Level (FKGL) for a given text.
82
+ * @param text The input paragraph.
83
+ * @returns The FKGL score.
84
+ */
85
+ export declare function fleschKincaidGrade(text: string): TFKGLResult;
86
+ /**
87
+ * Counts the number of sentences in a given text.
88
+ * @param text The input paragraph.
89
+ * @returns The total number of sentences.
90
+ */
91
+ export declare function countSentences(text: string): number;
92
+ export declare function howManyDifficultParagraphs(paragraphs: TFKGLResult[], maxFKGL: number): number;
51
93
  export {};
@@ -1,14 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.saveTranslatedReadme = exports.makePackageInfo = exports.getExInfo = exports.estimateReadingTime = void 0;
4
- exports.checkReadingTime = checkReadingTime;
4
+ exports.checkReadability = checkReadability;
5
5
  exports.extractImagesFromMarkdown = extractImagesFromMarkdown;
6
6
  exports.getFilenameFromUrl = getFilenameFromUrl;
7
7
  exports.estimateDuration = estimateDuration;
8
8
  exports.createFileOnDesktop = createFileOnDesktop;
9
9
  exports.getContentIndex = getContentIndex;
10
10
  exports.extractTextFromMarkdown = extractTextFromMarkdown;
11
+ exports.extractParagraphs = extractParagraphs;
12
+ exports.splitIntoSyllables = splitIntoSyllables;
13
+ exports.splitWordIntoSyllables = splitWordIntoSyllables;
14
+ exports.extractWords = extractWords;
15
+ exports.fleschKincaidGrade = fleschKincaidGrade;
16
+ exports.countSentences = countSentences;
17
+ exports.howManyDifficultParagraphs = howManyDifficultParagraphs;
18
+ const console_1 = require("./console");
11
19
  const frontMatter = require("front-matter");
20
+ const MarkdownIt = require("markdown-it");
21
+ const syllable_1 = require("syllable");
12
22
  const path = require("path");
13
23
  const fs = require("fs");
14
24
  const os_1 = require("os");
@@ -38,36 +48,38 @@ const estimateReadingTime = (text, wordsPerMinute = 150) => {
38
48
  };
39
49
  };
40
50
  exports.estimateReadingTime = estimateReadingTime;
41
- function checkReadingTime(markdown, wordsPerMinute = 200, maxMinutes = 1) {
51
+ function checkReadability(markdown, wordsPerMinute = 200, maxMinutes = 1) {
42
52
  const parsed = frontMatter(markdown);
53
+ const fkglResult = fleschKincaidGrade(parsed.body);
43
54
  const readingTime = (0, exports.estimateReadingTime)(parsed.body, wordsPerMinute);
44
55
  // const readingEase = estimateReadingEase(parsed.body)
45
56
  let attributes = parsed.attributes ? parsed.attributes : {};
46
57
  if (typeof parsed.attributes !== "object") {
47
58
  attributes = {};
48
59
  }
49
- const updatedAttributes = Object.assign(Object.assign({}, attributes), { readingTime });
60
+ const updatedAttributes = Object.assign(Object.assign({}, attributes), { readingTime, fkglResult: fkglResult.fkgl });
50
61
  let yamlFrontMatter = "";
51
62
  try {
52
63
  yamlFrontMatter = yaml.dump(updatedAttributes).trim();
53
64
  }
54
65
  catch (_a) {
66
+ console_1.default.error("Error dumping YAML front matter");
55
67
  return {
56
68
  newMarkdown: "",
57
69
  exceedsThreshold: false,
58
70
  minutes: 0,
59
71
  body: "",
72
+ fkglResult,
60
73
  // readingEase: 0,
61
74
  };
62
75
  }
63
- // Reconstruct the markdown with the front matter
64
76
  const newMarkdown = `---\n${yamlFrontMatter}\n---\n\n${parsed.body}`;
65
77
  return {
66
78
  newMarkdown,
67
79
  exceedsThreshold: readingTime.minutes > maxMinutes,
68
80
  minutes: readingTime.minutes,
69
81
  body: parsed.body,
70
- // readingEase: 0,
82
+ fkglResult,
71
83
  };
72
84
  }
73
85
  const slugify = (text) => {
@@ -205,24 +217,16 @@ function getContentIndex() {
205
217
  // return Math.round(206.835 - 1.015 * ASL - 84.6 * ASW)
206
218
  // }
207
219
  function extractTextFromMarkdown(mdContent) {
208
- let content = mdContent.replace(/!\[.*?]\(.*?\)/g, "");
209
- content = content.replace(/\[.*?]\(.*?\)/g, "");
210
- content = content.replace(/`.*?`/g, "");
211
- content = content.replace(/```[\S\s]*?```/g, "");
212
- return content.trim();
220
+ return mdContent
221
+ .replace(/!\[.*?]\(.*?\)/g, "") // Remove images
222
+ .replace(/\[.*?]\(.*?\)/g, "") // Remove links
223
+ .replace(/(```[\S\s]*?```|`.*?`)/g, "") // Remove inline & block code
224
+ .replace(/^#.*$/gm, "") // Remove headings
225
+ .replace(/(\*{1,2}|_{1,2})/g, "") // Remove bold/italic markers
226
+ .replace(/>\s?/g, "") // Remove blockquotes
227
+ .replace(/[*-]\s+/g, "") // Remove bullets from lists
228
+ .trim();
213
229
  }
214
- // export const estimateReadingEase = async (text: string): Promise<number> => {
215
- // const cleanedText = extractTextFromMarkdown(text)
216
- // // @ts-ignore
217
- // const rs = (await import("text-readability")).default;
218
- // // return fleschKincaidReadingEase(cleanedText)
219
- // // const score = readability(cleanedText)
220
- // // console.log(score)
221
- // // return score.fleschKincaid ?? 0
222
- // const score = rs.fleschReadingEase(cleanedText)
223
- // console.log(score, "SCORE FLESCH READING EASE")
224
- // return score
225
- // }
226
230
  const cleanReadme = (readme) => {
227
231
  // Replace <text> and </text> with nothing
228
232
  return readme.replace(/<text>/g, "").replace(/<\/text>/g, "");
@@ -232,3 +236,100 @@ const saveTranslatedReadme = async (exercise, languageCode, readme) => {
232
236
  fs.writeFileSync(readmePath, cleanReadme(readme));
233
237
  };
234
238
  exports.saveTranslatedReadme = saveTranslatedReadme;
239
+ /**
240
+ * Extracts paragraphs from a Markdown string.
241
+ * @param markdownText The input Markdown string.
242
+ * @returns An array of paragraph contents.
243
+ */
244
+ function extractParagraphs(markdownText) {
245
+ var _a;
246
+ const md = new MarkdownIt();
247
+ const tokens = md.parse(markdownText, {});
248
+ const paragraphs = [];
249
+ for (let i = 0; i < tokens.length; i++) {
250
+ if (tokens[i].type === "paragraph_open" &&
251
+ ((_a = tokens[i + 1]) === null || _a === void 0 ? void 0 : _a.type) === "inline") {
252
+ paragraphs.push(tokens[i + 1].content);
253
+ }
254
+ }
255
+ return paragraphs;
256
+ }
257
+ /**
258
+ * Splits a paragraph into words and separates each word into syllables.
259
+ * @param paragraph The input paragraph.
260
+ * @returns An array of words, each split into syllables.
261
+ */
262
+ function splitIntoSyllables(paragraph) {
263
+ const words = paragraph.split(/\s+/);
264
+ const syllables = [];
265
+ for (const word of words) {
266
+ syllables.push(...splitWordIntoSyllables(word));
267
+ }
268
+ return syllables;
269
+ }
270
+ /**
271
+ * Splits a word into its syllables using a basic estimation.
272
+ * @param word The word to split.
273
+ * @returns An array of syllables.
274
+ */
275
+ function splitWordIntoSyllables(word) {
276
+ const syllableCount = (0, syllable_1.syllable)(word);
277
+ // Simple heuristic: Split word into equal parts (not perfect, better with a dictionary-based approach)
278
+ if (syllableCount <= 1)
279
+ return [word];
280
+ const approxLength = Math.ceil(word.length / syllableCount);
281
+ const syllables = [];
282
+ for (let i = 0; i < word.length; i += approxLength) {
283
+ // eslint-disable-next-line
284
+ syllables.push(word.substring(i, i + approxLength));
285
+ }
286
+ return syllables;
287
+ }
288
+ /**
289
+ * Extracts words from a given paragraph.
290
+ * @param paragraph The input text.
291
+ * @returns An array of words.
292
+ */
293
+ function extractWords(paragraph) {
294
+ const words = paragraph.match(/\b\w+\b/g); // Match words using regex
295
+ return words ? words : []; // Return words or an empty array if none found
296
+ }
297
+ /**
298
+ * Calculates the Flesch-Kincaid Grade Level (FKGL) for a given text.
299
+ * @param text The input paragraph.
300
+ * @returns The FKGL score.
301
+ */
302
+ function fleschKincaidGrade(text) {
303
+ const processableText = extractTextFromMarkdown(text);
304
+ const words = extractWords(processableText);
305
+ const numWords = words.length;
306
+ const numSentences = countSentences(processableText);
307
+ const numSyllables = words.reduce((total, word) => total + (0, syllable_1.syllable)(word), 0);
308
+ if (numWords === 0 || numSentences === 0) {
309
+ return {
310
+ text,
311
+ fkgl: 0,
312
+ };
313
+ }
314
+ const fkgl =
315
+ // eslint-disable-next-line
316
+ 0.39 * (numWords / numSentences) + 11.8 * (numSyllables / numWords) - 15.59;
317
+ return {
318
+ text,
319
+ fkgl: parseFloat(fkgl.toFixed(2)),
320
+ };
321
+ }
322
+ /**
323
+ * Counts the number of sentences in a given text.
324
+ * @param text The input paragraph.
325
+ * @returns The total number of sentences.
326
+ */
327
+ function countSentences(text) {
328
+ const sentences = text
329
+ .split(/[!.?]+/)
330
+ .filter(sentence => sentence.trim().length > 0);
331
+ return sentences.length;
332
+ }
333
+ function howManyDifficultParagraphs(paragraphs, maxFKGL) {
334
+ return paragraphs.filter(paragraph => paragraph.fkgl > maxFKGL).length;
335
+ }
@@ -56,7 +56,9 @@ type TReduceReadmeInputs = {
56
56
  lesson: string;
57
57
  number_of_words: string;
58
58
  expected_number_words: string;
59
+ fkgl_results: string;
60
+ expected_grade_level: string;
59
61
  };
60
- export declare function reduceReadme(rigoToken: string, inputs: TReduceReadmeInputs): Promise<any>;
62
+ export declare function makeReadmeReadable(rigoToken: string, inputs: TReduceReadmeInputs): Promise<any>;
61
63
  export declare const isValidRigoToken: (rigobotToken: string) => Promise<boolean>;
62
64
  export {};
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isValidRigoToken = exports.readmeCreator = exports.createCodingReadme = exports.createCodeFile = exports.interactiveCreation = exports.generateCourseIntroduction = exports.translateExercise = exports.generateImage = exports.hasCreatorPermission = exports.createReadme = void 0;
4
4
  exports.downloadImage = downloadImage;
5
5
  exports.createPreviewReadme = createPreviewReadme;
6
- exports.reduceReadme = reduceReadme;
6
+ exports.makeReadmeReadable = makeReadmeReadable;
7
7
  const axios_1 = require("axios");
8
8
  const fs_1 = require("fs");
9
9
  const console_1 = require("../utils/console");
@@ -176,7 +176,7 @@ async function createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeCo
176
176
  });
177
177
  fs.writeFileSync(path.join(tutorialDir, readmeFilename), readmeContent.answer);
178
178
  }
179
- async function reduceReadme(rigoToken, inputs) {
179
+ async function makeReadmeReadable(rigoToken, inputs) {
180
180
  try {
181
181
  const response = await axios_1.default.post(`${RIGOBOT_HOST}/v1/prompting/completion/588/`, { inputs, include_purpose_objective: false, execute_async: false }, {
182
182
  headers: {
@@ -1 +1 @@
1
- {"version":"5.0.33","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":{},"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":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","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":[]}}}
1
+ {"version":"5.0.35","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":{},"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":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","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":[]}}}