@learnpack/learnpack 5.0.275 → 5.0.277

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.
Files changed (95) hide show
  1. package/README.md +409 -409
  2. package/lib/commands/audit.js +15 -15
  3. package/lib/commands/breakToken.js +19 -19
  4. package/lib/commands/clean.js +3 -3
  5. package/lib/commands/init.js +41 -41
  6. package/lib/commands/logout.js +3 -3
  7. package/lib/commands/publish.js +5 -10
  8. package/lib/commands/serve.js +55 -2
  9. package/lib/creatorDist/assets/index-BfLyIQVh.js +10343 -10224
  10. package/lib/managers/config/index.js +77 -77
  11. package/lib/utils/api.d.ts +1 -1
  12. package/lib/utils/api.js +12 -9
  13. package/lib/utils/creatorUtilities.js +14 -14
  14. package/lib/utils/export/epub.d.ts +2 -0
  15. package/lib/utils/export/epub.js +298 -0
  16. package/lib/utils/export/index.d.ts +3 -0
  17. package/lib/utils/export/index.js +7 -0
  18. package/lib/utils/export/scorm.d.ts +2 -0
  19. package/lib/utils/export/scorm.js +84 -0
  20. package/lib/utils/export/shared.d.ts +4 -0
  21. package/lib/utils/export/shared.js +61 -0
  22. package/lib/utils/export/types.d.ts +15 -0
  23. package/lib/utils/export/types.js +2 -0
  24. package/package.json +2 -1
  25. package/src/commands/audit.ts +487 -487
  26. package/src/commands/breakToken.ts +67 -67
  27. package/src/commands/clean.ts +30 -30
  28. package/src/commands/init.ts +650 -650
  29. package/src/commands/logout.ts +38 -38
  30. package/src/commands/publish.ts +20 -25
  31. package/src/commands/serve.ts +69 -4
  32. package/src/commands/start.ts +333 -333
  33. package/src/commands/translate.ts +123 -123
  34. package/src/creator/README.md +54 -54
  35. package/src/creator/eslint.config.js +7 -7
  36. package/src/creator/src/components/syllabus/ContentIndex.tsx +312 -312
  37. package/src/creator/src/i18n.ts +28 -28
  38. package/src/creator/src/index.css +217 -217
  39. package/src/creator/src/locales/en.json +126 -126
  40. package/src/creator/src/locales/es.json +126 -126
  41. package/src/creator/src/utils/configTypes.ts +122 -122
  42. package/src/creator/src/utils/constants.ts +13 -13
  43. package/src/creator/src/utils/creatorUtils.ts +46 -46
  44. package/src/creator/src/utils/eventBus.ts +2 -2
  45. package/src/creator/src/utils/lib.ts +468 -468
  46. package/src/creator/src/utils/socket.ts +61 -61
  47. package/src/creator/src/utils/store.ts +222 -222
  48. package/src/creator/src/vite-env.d.ts +1 -1
  49. package/src/creator/vite.config.ts +13 -13
  50. package/src/creatorDist/assets/index-BfLyIQVh.js +10343 -10224
  51. package/src/managers/config/defaults.ts +49 -49
  52. package/src/managers/config/exercise.ts +364 -364
  53. package/src/managers/config/index.ts +775 -775
  54. package/src/managers/file.ts +236 -236
  55. package/src/managers/server/routes.ts +554 -554
  56. package/src/managers/session.ts +182 -182
  57. package/src/managers/telemetry.ts +188 -188
  58. package/src/models/action.ts +13 -13
  59. package/src/models/config-manager.ts +28 -28
  60. package/src/models/config.ts +106 -106
  61. package/src/models/creator.ts +47 -47
  62. package/src/models/exercise-obj.ts +30 -30
  63. package/src/models/session.ts +39 -39
  64. package/src/models/socket.ts +61 -61
  65. package/src/models/status.ts +16 -16
  66. package/src/ui/_app/app.css +1 -1
  67. package/src/ui/_app/app.js +400 -397
  68. package/src/ui/app.tar.gz +0 -0
  69. package/src/utils/BaseCommand.ts +56 -56
  70. package/src/utils/api.ts +53 -39
  71. package/src/utils/audit.ts +392 -392
  72. package/src/utils/checkNotInstalled.ts +267 -267
  73. package/src/utils/configBuilder.ts +82 -82
  74. package/src/utils/convertCreds.js +34 -34
  75. package/src/utils/creatorUtilities.ts +504 -504
  76. package/src/utils/export/README.md +178 -0
  77. package/src/utils/export/epub.ts +400 -0
  78. package/src/utils/export/index.ts +3 -0
  79. package/src/utils/export/scorm.ts +121 -0
  80. package/src/utils/export/shared.ts +61 -0
  81. package/src/utils/export/types.ts +17 -0
  82. package/src/utils/incrementVersion.js +74 -74
  83. package/src/utils/misc.ts +58 -58
  84. package/src/utils/rigoActions.ts +500 -500
  85. package/src/utils/sidebarGenerator.ts +195 -195
  86. package/src/utils/templates/epub/epub.css +133 -0
  87. package/src/utils/templates/isolated/exercises/01-hello-world/README.es.md +26 -26
  88. package/src/utils/templates/isolated/exercises/01-hello-world/README.md +26 -26
  89. package/src/utils/templates/scorm/adlcp_rootv1p2.xsd +110 -0
  90. package/src/utils/templates/scorm/config/api.js +175 -0
  91. package/src/utils/templates/scorm/config/index.html +210 -0
  92. package/src/utils/templates/scorm/ims_xml.xsd +1 -0
  93. package/src/utils/templates/scorm/imscp_rootv1p1p2.xsd +345 -0
  94. package/src/utils/templates/scorm/imsmanifest.xml +38 -0
  95. package/src/utils/templates/scorm/imsmd_rootv1p2p1.xsd +573 -0
@@ -474,66 +474,66 @@ function deepMerge(...sources) {
474
474
  return acc;
475
475
  }
476
476
  const buildAgentWarning = (current, suggested) => {
477
- const message = `# Agent mismatch!\n
478
-
479
- In LearnPack, the agent is in charge of running the LearnPack interface.
480
-
481
- You're currently using LearnPack through \`${current}\` but the suggested agent is \`${suggested}\`.
482
-
483
- We recommend strongly recommend changing your agent.
484
-
485
- ${stepsToChangeAgent(suggested)}
477
+ const message = `# Agent mismatch!\n
478
+
479
+ In LearnPack, the agent is in charge of running the LearnPack interface.
480
+
481
+ You're currently using LearnPack through \`${current}\` but the suggested agent is \`${suggested}\`.
482
+
483
+ We recommend strongly recommend changing your agent.
484
+
485
+ ${stepsToChangeAgent(suggested)}
486
486
  `;
487
487
  return message;
488
488
  };
489
489
  const stepsToChangeAgent = (agent) => {
490
490
  if (agent === "vscode") {
491
- return `
492
- # Steps to Change Agent to VSCode
493
-
494
- 1. **Install VSCode**:
495
- - Visit the [VSCode website](https://code.visualstudio.com/Download) and download the latest version.
496
- - Follow the installation instructions for your operating system.
497
-
498
- 2. **Install LearnPack VSCode Extension**:
499
- - Open VSCode.
500
- - Go to the Extensions view by clicking on the Extensions icon in the Activity Bar on the side of the window or by pressing \`Ctrl+Shift+X\`.
501
- - Search for "LearnPack" and click "Install".
502
- - If the extension is already installed but disabled, enable it.
503
-
504
- 3. **Run LearnPack in VSCode**:
505
- - Open your terminal in VSCode.
506
- - Navigate to your LearnPack project directory.
507
- - Run the following command:
508
- \`\`\`sh
509
- learnpack start
510
- \`\`\`
511
-
512
- We strongly recommend using VSCode for a better learning experience.
491
+ return `
492
+ # Steps to Change Agent to VSCode
493
+
494
+ 1. **Install VSCode**:
495
+ - Visit the [VSCode website](https://code.visualstudio.com/Download) and download the latest version.
496
+ - Follow the installation instructions for your operating system.
497
+
498
+ 2. **Install LearnPack VSCode Extension**:
499
+ - Open VSCode.
500
+ - Go to the Extensions view by clicking on the Extensions icon in the Activity Bar on the side of the window or by pressing \`Ctrl+Shift+X\`.
501
+ - Search for "LearnPack" and click "Install".
502
+ - If the extension is already installed but disabled, enable it.
503
+
504
+ 3. **Run LearnPack in VSCode**:
505
+ - Open your terminal in VSCode.
506
+ - Navigate to your LearnPack project directory.
507
+ - Run the following command:
508
+ \`\`\`sh
509
+ learnpack start
510
+ \`\`\`
511
+
512
+ We strongly recommend using VSCode for a better learning experience.
513
513
  `;
514
514
  }
515
515
  if (agent === "os") {
516
- return `
517
- # Steps to Change Agent to OS
518
-
519
- This learning package was designed to run outside of VSCode. We strongly recommend closing VSCode and running the package independently.
520
-
521
- 1. **Close VSCode**:
522
- - Save your work and close the VSCode application.
523
-
524
- 2. **Open a New Terminal**:
525
- - Open a terminal or command prompt on your operating system.
526
-
527
- 3. **Navigate to Your LearnPack Project Directory**:
528
- - Use the \`cd\` command to navigate to the directory where your LearnPack project is located.
529
-
530
- 4. **Run LearnPack**:
531
- - Run the following command to start LearnPack:
532
- \`\`\`sh
533
- learnpack start
534
- \`\`\`
535
-
536
- We strongly recommend running the package independently for a better learning experience.
516
+ return `
517
+ # Steps to Change Agent to OS
518
+
519
+ This learning package was designed to run outside of VSCode. We strongly recommend closing VSCode and running the package independently.
520
+
521
+ 1. **Close VSCode**:
522
+ - Save your work and close the VSCode application.
523
+
524
+ 2. **Open a New Terminal**:
525
+ - Open a terminal or command prompt on your operating system.
526
+
527
+ 3. **Navigate to Your LearnPack Project Directory**:
528
+ - Use the \`cd\` command to navigate to the directory where your LearnPack project is located.
529
+
530
+ 4. **Run LearnPack**:
531
+ - Run the following command to start LearnPack:
532
+ \`\`\`sh
533
+ learnpack start
534
+ \`\`\`
535
+
536
+ We strongly recommend running the package independently for a better learning experience.
537
537
  `;
538
538
  }
539
539
  return "";
@@ -559,31 +559,31 @@ const isExtensionInstalled = (extensionName) => {
559
559
  const result = shell.exec("code --list-extensions", { silent: true });
560
560
  return result.stdout.split("\n").includes(extensionName);
561
561
  };
562
- const EXTENSION_INSTALLATION_STEPS = `
563
- # Steps to Install LearnPack VSCode Extension
564
-
565
- 1. **Open VSCode**:
566
- - Launch the Visual Studio Code application on your computer.
567
-
568
- 2. **Go to Extensions View**:
569
- - Click on the Extensions icon in the Activity Bar on the side of the window.
570
- - Alternatively, you can open the Extensions view by pressing \`Ctrl+Shift+X\`.
571
-
572
- 3. **Search for LearnPack**:
573
- - In the Extensions view, type "LearnPack" into the search bar.
574
-
575
- 4. **Install the Extension**:
576
- - Find the "LearnPack" extension in the search results and click the "Install" button.
577
- - If the extension is already installed but disabled, click the "Enable" button.
578
-
579
- 5. **Verify Installation**:
580
- - Once installed, you can verify the installation by running the following command in the terminal:
581
- \`\`\`sh
582
- code --list-extensions | grep learn-pack.learnpack-vscode
583
- \`\`\`
584
- - If the extension is listed, it means the installation was successful.
585
-
586
- We strongly recommend using the LearnPack extension in VSCode for a better learning experience.
562
+ const EXTENSION_INSTALLATION_STEPS = `
563
+ # Steps to Install LearnPack VSCode Extension
564
+
565
+ 1. **Open VSCode**:
566
+ - Launch the Visual Studio Code application on your computer.
567
+
568
+ 2. **Go to Extensions View**:
569
+ - Click on the Extensions icon in the Activity Bar on the side of the window.
570
+ - Alternatively, you can open the Extensions view by pressing \`Ctrl+Shift+X\`.
571
+
572
+ 3. **Search for LearnPack**:
573
+ - In the Extensions view, type "LearnPack" into the search bar.
574
+
575
+ 4. **Install the Extension**:
576
+ - Find the "LearnPack" extension in the search results and click the "Install" button.
577
+ - If the extension is already installed but disabled, click the "Enable" button.
578
+
579
+ 5. **Verify Installation**:
580
+ - Once installed, you can verify the installation by running the following command in the terminal:
581
+ \`\`\`sh
582
+ code --list-extensions | grep learn-pack.learnpack-vscode
583
+ \`\`\`
584
+ - If the extension is listed, it means the installation was successful.
585
+
586
+ We strongly recommend using the LearnPack extension in VSCode for a better learning experience.
587
587
  `;
588
588
  /**
589
589
  * Installs the LearnPack VSCode extension if the 'code' command is available.
@@ -19,7 +19,7 @@ type TAssetMissing = {
19
19
  description: string;
20
20
  learnpack_deploy_url: string;
21
21
  technologies: string[];
22
- category: number;
22
+ category: number | string;
23
23
  owner: number;
24
24
  author: number;
25
25
  preview: string;
package/lib/utils/api.js CHANGED
@@ -420,13 +420,14 @@ const getCategories = async (token) => {
420
420
  }
421
421
  };
422
422
  const updateRigoAssetID = async (token, slug, asset_id) => {
423
+ const cleanToken = token.replace(/[\n\r]/g, "");
423
424
  const url = `${exports.RIGOBOT_HOST}/v1/learnpack/package/${slug}/`;
424
- const headers = {
425
- Authorization: "Token " + token.trim(),
426
- };
427
- console.log("HEADERS", headers);
428
425
  try {
429
- const response = await axios_1.default.put(url, { asset_id }, { headers });
426
+ const response = await axios_1.default.put(url, { asset_id }, {
427
+ headers: {
428
+ Authorization: "Token " + cleanToken,
429
+ },
430
+ });
430
431
  return response.data;
431
432
  }
432
433
  catch (error) {
@@ -436,11 +437,13 @@ const updateRigoAssetID = async (token, slug, asset_id) => {
436
437
  };
437
438
  const createRigoPackage = async (token, slug, config) => {
438
439
  const url = `${exports.RIGOBOT_HOST}/v1/learnpack/package`;
439
- const headers = {
440
- Authorization: "Token " + token.trim(),
441
- };
440
+ const cleanToken = token.replace(/[\n\r]/g, "");
442
441
  try {
443
- const response = await axios_1.default.post(url, { slug, config }, { headers });
442
+ const response = await axios_1.default.post(url, { slug, config }, {
443
+ headers: {
444
+ Authorization: "Token " + cleanToken,
445
+ },
446
+ });
444
447
  return response.data;
445
448
  }
446
449
  catch (error) {
@@ -333,13 +333,13 @@ function countSentences(text) {
333
333
  function howManyDifficultParagraphs(paragraphs, maxFKGL) {
334
334
  return paragraphs.filter(paragraph => paragraph.fkgl > maxFKGL).length;
335
335
  }
336
- const example_content = `Write or paste your table of content below this line, each topic should be defined on a new line, here is an example:
337
-
338
- Introduction to AI: Explain what is AI and its applications
339
- Introduction to Machine Learning: Explain what is machine learning and its applications
340
- What is an AI Model: Explain what is an AI model and its applications
341
- How to use an AI Model: Different APIs, local models, etc.
342
- How to build an AI Model: Fine-tuning, data collection, cleaning and more.
336
+ const example_content = `Write or paste your table of content below this line, each topic should be defined on a new line, here is an example:
337
+
338
+ Introduction to AI: Explain what is AI and its applications
339
+ Introduction to Machine Learning: Explain what is machine learning and its applications
340
+ What is an AI Model: Explain what is an AI model and its applications
341
+ How to use an AI Model: Different APIs, local models, etc.
342
+ How to build an AI Model: Fine-tuning, data collection, cleaning and more.
343
343
  `;
344
344
  const appendContentIndex = async () => {
345
345
  const choices = await prompts([
@@ -369,13 +369,13 @@ const appendContentIndex = async () => {
369
369
  return null;
370
370
  };
371
371
  exports.appendContentIndex = appendContentIndex;
372
- const example_airules = `
373
- Write with an engaging tone, use simple words and avoid complex sentences.
374
- Write in first person, as if you are talking to the reader.
375
- Add mental maps to help the reader understand the content.
376
- Add diagrams to help the reader understand the content.
377
- No code exercises required
378
-
372
+ const example_airules = `
373
+ Write with an engaging tone, use simple words and avoid complex sentences.
374
+ Write in first person, as if you are talking to the reader.
375
+ Add mental maps to help the reader understand the content.
376
+ Add diagrams to help the reader understand the content.
377
+ No code exercises required
378
+
379
379
  `;
380
380
  const appendAIRules = async () => {
381
381
  const choices = await prompts([
@@ -0,0 +1,2 @@
1
+ import { ExportOptions } from "./types";
2
+ export declare function exportToEpub(options: ExportOptions): Promise<string>;
@@ -0,0 +1,298 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exportToEpub = exportToEpub;
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+ const mkdirp = require("mkdirp");
7
+ const rimraf = require("rimraf");
8
+ const uuid_1 = require("uuid");
9
+ const child_process_1 = require("child_process");
10
+ const shared_1 = require("./shared");
11
+ // Convert markdown to HTML using pandoc
12
+ async function convertMarkdownToHtml(markdownPath, outputPath) {
13
+ return new Promise((resolve, reject) => {
14
+ let stdout = "";
15
+ let stderr = "";
16
+ const pandocArgs = [
17
+ markdownPath,
18
+ "-o",
19
+ outputPath,
20
+ "--from",
21
+ "markdown",
22
+ "--to",
23
+ "html",
24
+ "--standalone",
25
+ "--css",
26
+ path.join(__dirname, "../templates/epub/epub.css"),
27
+ ];
28
+ console.log("Executing pandoc command:", "pandoc", pandocArgs.join(" "));
29
+ console.log("Input file exists:", fs.existsSync(markdownPath));
30
+ console.log("Output directory exists:", fs.existsSync(path.dirname(outputPath)));
31
+ const pandoc = (0, child_process_1.spawn)("pandoc", pandocArgs);
32
+ pandoc.stdout.on("data", (data) => {
33
+ stdout += data.toString();
34
+ });
35
+ pandoc.stderr.on("data", (data) => {
36
+ stderr += data.toString();
37
+ });
38
+ pandoc.on("close", (code) => {
39
+ if (code === 0) {
40
+ resolve();
41
+ }
42
+ else {
43
+ const errorMessage = `Pandoc process exited with code ${code}\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`;
44
+ console.error("Pandoc Error Details:", errorMessage);
45
+ reject(new Error(errorMessage));
46
+ }
47
+ });
48
+ pandoc.on("error", (err) => {
49
+ console.error("Pandoc spawn error:", err);
50
+ reject(err);
51
+ });
52
+ });
53
+ }
54
+ // Generate EPUB using pandoc
55
+ async function generateEpub(htmlFiles, outputPath, metadata) {
56
+ return new Promise((resolve, reject) => {
57
+ var _a;
58
+ let stdout = "";
59
+ let stderr = "";
60
+ // Include all HTML files in the EPUB
61
+ const pandocArgs = [
62
+ ...htmlFiles, // All HTML files
63
+ "-o",
64
+ outputPath,
65
+ "--from",
66
+ "html",
67
+ "--to",
68
+ "epub",
69
+ "--metadata",
70
+ `title=${metadata.title}`,
71
+ "--metadata",
72
+ `language=${metadata.language || "en"}`,
73
+ "--metadata",
74
+ `creator=FourGeeksAcademy`,
75
+ "--metadata",
76
+ `description=${metadata.description || ""}`,
77
+ "--metadata",
78
+ `subject=${((_a = metadata.technologies) === null || _a === void 0 ? void 0 : _a.join(", ")) || "Programming"}`,
79
+ "--css",
80
+ path.join(__dirname, "../templates/epub/epub.css"),
81
+ ];
82
+ console.log("Executing pandoc EPUB command:", "pandoc", pandocArgs.join(" "));
83
+ console.log("HTML files to include:", htmlFiles);
84
+ console.log("All input files exist:", htmlFiles.every((file) => fs.existsSync(file)));
85
+ console.log("Output directory exists:", fs.existsSync(path.dirname(outputPath)));
86
+ const pandoc = (0, child_process_1.spawn)("pandoc", pandocArgs);
87
+ pandoc.stdout.on("data", (data) => {
88
+ stdout += data.toString();
89
+ });
90
+ pandoc.stderr.on("data", (data) => {
91
+ stderr += data.toString();
92
+ });
93
+ pandoc.on("close", (code) => {
94
+ if (code === 0) {
95
+ resolve();
96
+ }
97
+ else {
98
+ const errorMessage = `Pandoc process exited with code ${code}\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`;
99
+ console.error("Pandoc Error Details:", errorMessage);
100
+ reject(new Error(errorMessage));
101
+ }
102
+ });
103
+ pandoc.on("error", (err) => {
104
+ console.error("Pandoc spawn error:", err);
105
+ reject(err);
106
+ });
107
+ });
108
+ }
109
+ // Process exercises and convert to HTML
110
+ async function processExercises(exercisesDir, outputDir, language = "en") {
111
+ const htmlFiles = [];
112
+ console.log("Processing exercises directory:", exercisesDir);
113
+ console.log("Output directory:", outputDir);
114
+ console.log("Language:", language);
115
+ if (!fs.existsSync(exercisesDir)) {
116
+ console.log("Exercises directory does not exist:", exercisesDir);
117
+ return htmlFiles;
118
+ }
119
+ console.log("Exercises directory exists, scanning for files...");
120
+ const processDirectory = async (dir, relativePath = "") => {
121
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
122
+ // Sort entries to prioritize README files first
123
+ entries.sort((a, b) => {
124
+ const aName = a.name.toLowerCase();
125
+ const bName = b.name.toLowerCase();
126
+ // Prioritize README files
127
+ if (aName === "readme.md" && bName !== "readme.md")
128
+ return -1;
129
+ if (bName === "readme.md" && aName !== "readme.md")
130
+ return 1;
131
+ return aName.localeCompare(bName);
132
+ });
133
+ for (const entry of entries) {
134
+ const fullPath = path.join(dir, entry.name);
135
+ const relativeFilePath = path.join(relativePath, entry.name);
136
+ if (entry.isDirectory()) {
137
+ console.log("Processing subdirectory:", relativeFilePath);
138
+ // esling-disable-next-line
139
+ await processDirectory(fullPath, relativeFilePath);
140
+ }
141
+ else if (entry.name.endsWith(".md") ||
142
+ entry.name.endsWith(".markdown")) {
143
+ // Apply language filtering for all markdown files
144
+ const baseName = entry.name.replace(/\.(md|markdown)$/, "");
145
+ if (language === "en") {
146
+ // For English: exclude files with language suffixes
147
+ if (baseName.includes(".") &&
148
+ /\.(es|fr|de|it|pt|ja|ko|zh|ar|ru)$/i.test(baseName)) {
149
+ console.log("Skipping language-specific file for English:", relativeFilePath);
150
+ continue;
151
+ }
152
+ }
153
+ else {
154
+ // For other languages: only include files with the correct language suffix
155
+ if (!baseName.endsWith(`.${language}`)) {
156
+ console.log("Skipping file with different language:", relativeFilePath);
157
+ continue;
158
+ }
159
+ }
160
+ // Convert markdown to HTML
161
+ console.log("Converting markdown file:", relativeFilePath);
162
+ const htmlFileName = entry.name.replace(/\.(md|markdown)$/, ".html");
163
+ const htmlPath = path.join(outputDir, relativeFilePath.replace(/\.(md|markdown)$/, ".html"));
164
+ console.log("HTML output path:", htmlPath);
165
+ mkdirp.sync(path.dirname(htmlPath));
166
+ // esling-disable-next-line
167
+ await convertMarkdownToHtml(fullPath, htmlPath);
168
+ htmlFiles.push(htmlPath);
169
+ console.log("Successfully converted:", relativeFilePath, "->", htmlPath);
170
+ }
171
+ else if (entry.name.endsWith(".html")) {
172
+ // Copy HTML files as-is
173
+ console.log("Copying HTML file:", relativeFilePath);
174
+ const destPath = path.join(outputDir, relativeFilePath);
175
+ mkdirp.sync(path.dirname(destPath));
176
+ // esling-disable-next-line
177
+ fs.copyFileSync(fullPath, destPath);
178
+ htmlFiles.push(destPath);
179
+ console.log("Successfully copied:", relativeFilePath, "->", destPath);
180
+ }
181
+ else {
182
+ console.log("Skipping file:", relativeFilePath, "(not markdown or HTML)");
183
+ }
184
+ }
185
+ };
186
+ await processDirectory(exercisesDir);
187
+ return htmlFiles;
188
+ }
189
+ // Create EPUB table of contents
190
+ function createTocHtml(htmlFiles, metadata) {
191
+ let tocContent = `
192
+ <!DOCTYPE html>
193
+ <html>
194
+ <head>
195
+ <meta charset="UTF-8">
196
+ <title>Table of Contents - ${metadata.title}</title>
197
+ <link rel="stylesheet" href="epub.css">
198
+ </head>
199
+ <body>
200
+ <h1>${metadata.title}</h1>
201
+ <nav id="toc">
202
+ <h2>Table of Contents</h2>
203
+ <ol>
204
+ <li><a href="main.html">Course Overview</a></li>
205
+ `;
206
+ htmlFiles.forEach((file) => {
207
+ const fileName = path.basename(file, ".html");
208
+ const displayName = fileName
209
+ .replace(/[-_]/g, " ")
210
+ .replace(/\b\w/g, (l) => l.toUpperCase());
211
+ // Use the full relative path for the href to ensure proper navigation
212
+ const relativePath = file.replace(/.*\/html\//, "").replace(/\\/g, "/");
213
+ tocContent += ` <li><a href="${relativePath}">${displayName}</a></li>\n`;
214
+ });
215
+ tocContent += `
216
+ </ol>
217
+ </nav>
218
+ </body>
219
+ </html>`;
220
+ return tocContent;
221
+ }
222
+ async function exportToEpub(options) {
223
+ var _a, _b, _c;
224
+ const { courseSlug, bucket, outDir, language = "en" } = options;
225
+ console.log("Starting EPUB export for course:", courseSlug);
226
+ console.log("Output directory:", outDir);
227
+ console.log("Language:", language);
228
+ // 1. Create temporary folder
229
+ const tmpName = (0, uuid_1.v4)();
230
+ const epubOutDir = path.join(outDir, tmpName);
231
+ rimraf.sync(epubOutDir);
232
+ mkdirp.sync(epubOutDir);
233
+ console.log("Created temporary directory:", epubOutDir);
234
+ // 2. Create EPUB template directory
235
+ const epubTemplateDir = path.join(epubOutDir, "template");
236
+ mkdirp.sync(epubTemplateDir);
237
+ // 3. Download exercises and .learn from bucket
238
+ console.log("Downloading exercises...");
239
+ await (0, shared_1.downloadS3Folder)(bucket, `courses/${courseSlug}/exercises/`, path.join(epubOutDir, "exercises"));
240
+ console.log("Downloading .learn files...");
241
+ await (0, shared_1.downloadS3Folder)(bucket, `courses/${courseSlug}/.learn/`, path.join(epubOutDir, ".learn"));
242
+ // 4. Read learn.json for course info
243
+ console.log("Reading course metadata...");
244
+ const learnJson = await (0, shared_1.getCourseMetadata)(bucket, courseSlug);
245
+ const metadata = {
246
+ title: ((_a = learnJson.title) === null || _a === void 0 ? void 0 : _a.en) || learnJson.title || courseSlug,
247
+ description: ((_b = learnJson.description) === null || _b === void 0 ? void 0 : _b.en) || learnJson.description,
248
+ language: learnJson.language || "en",
249
+ technologies: learnJson.technologies || [],
250
+ difficulty: learnJson.difficulty || "beginner",
251
+ };
252
+ console.log("Course metadata:", metadata);
253
+ // 5. Process exercises and convert to HTML
254
+ console.log("Processing exercises and converting to HTML...");
255
+ const htmlFiles = await processExercises(path.join(epubOutDir, "exercises"), path.join(epubOutDir, "html"), language);
256
+ console.log("Generated HTML files:", htmlFiles.length);
257
+ console.log("HTML files:", htmlFiles);
258
+ // 6. Create table of contents
259
+ const tocHtml = createTocHtml(htmlFiles, metadata);
260
+ const tocPath = path.join(epubOutDir, "html", "toc.html");
261
+ mkdirp.sync(path.dirname(tocPath));
262
+ fs.writeFileSync(tocPath, tocHtml, "utf-8");
263
+ // 7. Create main content file
264
+ const mainContent = `
265
+ <!DOCTYPE html>
266
+ <html>
267
+ <head>
268
+ <meta charset="UTF-8">
269
+ <title>${metadata.title}</title>
270
+ <link rel="stylesheet" href="epub.css">
271
+ </head>
272
+ <body>
273
+ <h1>${metadata.title}</h1>
274
+ <p>${metadata.description || ""}</p>
275
+ <p><strong>Technologies:</strong> ${((_c = metadata.technologies) === null || _c === void 0 ? void 0 : _c.join(", ")) || "Programming"}</p>
276
+ <p><strong>Difficulty:</strong> ${metadata.difficulty}</p>
277
+ <p><strong>Language:</strong> ${language}</p>
278
+ <p><a href="toc.html">Go to Table of Contents</a></p>
279
+ </body>
280
+ </html>`;
281
+ const mainContentPath = path.join(epubOutDir, "html", "main.html");
282
+ fs.writeFileSync(mainContentPath, mainContent, "utf-8");
283
+ // 8. Prepare all HTML files for EPUB generation
284
+ const allHtmlFiles = [mainContentPath, tocPath, ...htmlFiles];
285
+ console.log("All HTML files for EPUB:", allHtmlFiles);
286
+ // 9. Generate EPUB using pandoc
287
+ console.log("Generating EPUB file...");
288
+ const epubPath = path.join(epubOutDir, `${courseSlug}.epub`);
289
+ await generateEpub(allHtmlFiles, epubPath, metadata);
290
+ console.log("EPUB generated successfully:", epubPath);
291
+ // Clean up temporary files
292
+ console.log("Cleaning up temporary files...");
293
+ rimraf.sync(path.join(epubOutDir, "html"));
294
+ rimraf.sync(path.join(epubOutDir, "exercises"));
295
+ rimraf.sync(path.join(epubOutDir, ".learn"));
296
+ console.log("EPUB export completed successfully");
297
+ return epubPath;
298
+ }
@@ -0,0 +1,3 @@
1
+ export { exportToScorm } from "./scorm";
2
+ export { exportToEpub } from "./epub";
3
+ export { ExportFormat, ExportOptions } from "./types";
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exportToEpub = exports.exportToScorm = void 0;
4
+ var scorm_1 = require("./scorm");
5
+ Object.defineProperty(exports, "exportToScorm", { enumerable: true, get: function () { return scorm_1.exportToScorm; } });
6
+ var epub_1 = require("./epub");
7
+ Object.defineProperty(exports, "exportToEpub", { enumerable: true, get: function () { return epub_1.exportToEpub; } });
@@ -0,0 +1,2 @@
1
+ import { ExportOptions } from "./types";
2
+ export declare function exportToScorm(options: ExportOptions): Promise<string>;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exportToScorm = exportToScorm;
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+ const archiver = require("archiver");
7
+ const mkdirp = require("mkdirp");
8
+ const rimraf = require("rimraf");
9
+ const uuid_1 = require("uuid");
10
+ const shared_1 = require("./shared");
11
+ async function exportToScorm(options) {
12
+ var _a, _b;
13
+ const { courseSlug, bucket, outDir } = options;
14
+ // 1. Create temporary folder
15
+ const tmpName = (0, uuid_1.v4)();
16
+ const scormOutDir = path.join(outDir, tmpName);
17
+ rimraf.sync(scormOutDir);
18
+ mkdirp.sync(scormOutDir);
19
+ // 2. Copy SCORM template
20
+ const scormTpl = path.join(__dirname, "../templates/scorm");
21
+ (0, shared_1.copyDir)(scormTpl, scormOutDir);
22
+ // Paths
23
+ const appDir = path.resolve(__dirname, "../../ui/_app");
24
+ const configDir = path.join(scormOutDir, "config");
25
+ // Check if _app exists and has content
26
+ if (fs.existsSync(appDir) && fs.readdirSync(appDir).length > 0) {
27
+ // Copy app.css and app.js to config/
28
+ for (const file of ["app.css", "app.js"]) {
29
+ const src = path.join(appDir, file);
30
+ const dest = path.join(configDir, file);
31
+ if (fs.existsSync(src)) {
32
+ fs.copyFileSync(src, dest);
33
+ }
34
+ }
35
+ // Copy logo-192.png and logo-512.png to SCORM build root
36
+ for (const file of ["logo-192.png", "logo-512.png"]) {
37
+ const src = path.join(appDir, file);
38
+ const dest = path.join(scormOutDir, file);
39
+ if (fs.existsSync(src)) {
40
+ fs.copyFileSync(src, dest);
41
+ }
42
+ }
43
+ }
44
+ // 3. Download exercises and .learn from bucket
45
+ await (0, shared_1.downloadS3Folder)(bucket, `courses/${courseSlug}/exercises/`, path.join(scormOutDir, "exercises"));
46
+ await (0, shared_1.downloadS3Folder)(bucket, `courses/${courseSlug}/.learn/`, path.join(scormOutDir, ".learn"));
47
+ // 4. Read learn.json for course info
48
+ const learnJson = await (0, shared_1.getCourseMetadata)(bucket, courseSlug);
49
+ // 5. Replace imsmanifest.xml
50
+ const manifestPath = path.join(scormOutDir, "imsmanifest.xml");
51
+ let manifest = fs.readFileSync(manifestPath, "utf-8");
52
+ manifest = manifest.replace(/{{organization_name}}/g, "FourGeeksAcademy");
53
+ manifest = manifest.replace(/{{course_title}}/g, ((_a = learnJson.title) === null || _a === void 0 ? void 0 : _a.en) || learnJson.title || courseSlug);
54
+ // Generate exercises list
55
+ const exercisesList = (0, shared_1.getFilesList)(path.join(scormOutDir, "exercises"), "exercises").join("\n ");
56
+ manifest = manifest.replace(/{{exercises_list}}/g, exercisesList);
57
+ // Generate images list (optional, if you have assets)
58
+ let imagesList = "";
59
+ const assetsDir = path.join(scormOutDir, ".learn", "assets");
60
+ if (fs.existsSync(assetsDir)) {
61
+ imagesList = (0, shared_1.getFilesList)(assetsDir, ".learn/assets").join("\n ");
62
+ }
63
+ manifest = manifest.replace(/{{images_list}}/g, imagesList);
64
+ fs.writeFileSync(manifestPath, manifest, "utf-8");
65
+ // 6. Replace title in config/index.html
66
+ const configIndexPath = path.join(scormOutDir, "config", "index.html");
67
+ let indexHtml = fs.readFileSync(configIndexPath, "utf-8");
68
+ indexHtml = indexHtml.replace(/{{title}}/g, ((_b = learnJson.title) === null || _b === void 0 ? void 0 : _b.en) || learnJson.title || courseSlug);
69
+ fs.writeFileSync(configIndexPath, indexHtml, "utf-8");
70
+ // 7. Compress as ZIP
71
+ const zipPath = `${scormOutDir}.zip`;
72
+ await new Promise((resolve, reject) => {
73
+ const output = fs.createWriteStream(zipPath);
74
+ const archive = archiver("zip", { zlib: { level: 9 } });
75
+ output.on("close", resolve);
76
+ archive.on("error", reject);
77
+ archive.pipe(output);
78
+ archive.directory(scormOutDir, false);
79
+ archive.finalize();
80
+ });
81
+ // Clean up temporary directory
82
+ rimraf.sync(scormOutDir);
83
+ return zipPath;
84
+ }