@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.
- package/README.md +409 -409
- package/lib/commands/audit.js +15 -15
- package/lib/commands/breakToken.js +19 -19
- package/lib/commands/clean.js +3 -3
- package/lib/commands/init.js +41 -41
- package/lib/commands/logout.js +3 -3
- package/lib/commands/publish.js +5 -10
- package/lib/commands/serve.js +55 -2
- package/lib/creatorDist/assets/index-BfLyIQVh.js +10343 -10224
- package/lib/managers/config/index.js +77 -77
- package/lib/utils/api.d.ts +1 -1
- package/lib/utils/api.js +12 -9
- package/lib/utils/creatorUtilities.js +14 -14
- package/lib/utils/export/epub.d.ts +2 -0
- package/lib/utils/export/epub.js +298 -0
- package/lib/utils/export/index.d.ts +3 -0
- package/lib/utils/export/index.js +7 -0
- package/lib/utils/export/scorm.d.ts +2 -0
- package/lib/utils/export/scorm.js +84 -0
- package/lib/utils/export/shared.d.ts +4 -0
- package/lib/utils/export/shared.js +61 -0
- package/lib/utils/export/types.d.ts +15 -0
- package/lib/utils/export/types.js +2 -0
- package/package.json +2 -1
- package/src/commands/audit.ts +487 -487
- package/src/commands/breakToken.ts +67 -67
- package/src/commands/clean.ts +30 -30
- package/src/commands/init.ts +650 -650
- package/src/commands/logout.ts +38 -38
- package/src/commands/publish.ts +20 -25
- package/src/commands/serve.ts +69 -4
- package/src/commands/start.ts +333 -333
- package/src/commands/translate.ts +123 -123
- package/src/creator/README.md +54 -54
- package/src/creator/eslint.config.js +7 -7
- package/src/creator/src/components/syllabus/ContentIndex.tsx +312 -312
- package/src/creator/src/i18n.ts +28 -28
- package/src/creator/src/index.css +217 -217
- package/src/creator/src/locales/en.json +126 -126
- package/src/creator/src/locales/es.json +126 -126
- package/src/creator/src/utils/configTypes.ts +122 -122
- package/src/creator/src/utils/constants.ts +13 -13
- package/src/creator/src/utils/creatorUtils.ts +46 -46
- package/src/creator/src/utils/eventBus.ts +2 -2
- package/src/creator/src/utils/lib.ts +468 -468
- package/src/creator/src/utils/socket.ts +61 -61
- package/src/creator/src/utils/store.ts +222 -222
- package/src/creator/src/vite-env.d.ts +1 -1
- package/src/creator/vite.config.ts +13 -13
- package/src/creatorDist/assets/index-BfLyIQVh.js +10343 -10224
- package/src/managers/config/defaults.ts +49 -49
- package/src/managers/config/exercise.ts +364 -364
- package/src/managers/config/index.ts +775 -775
- package/src/managers/file.ts +236 -236
- package/src/managers/server/routes.ts +554 -554
- package/src/managers/session.ts +182 -182
- package/src/managers/telemetry.ts +188 -188
- package/src/models/action.ts +13 -13
- package/src/models/config-manager.ts +28 -28
- package/src/models/config.ts +106 -106
- package/src/models/creator.ts +47 -47
- package/src/models/exercise-obj.ts +30 -30
- package/src/models/session.ts +39 -39
- package/src/models/socket.ts +61 -61
- package/src/models/status.ts +16 -16
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +400 -397
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/BaseCommand.ts +56 -56
- package/src/utils/api.ts +53 -39
- package/src/utils/audit.ts +392 -392
- package/src/utils/checkNotInstalled.ts +267 -267
- package/src/utils/configBuilder.ts +82 -82
- package/src/utils/convertCreds.js +34 -34
- package/src/utils/creatorUtilities.ts +504 -504
- package/src/utils/export/README.md +178 -0
- package/src/utils/export/epub.ts +400 -0
- package/src/utils/export/index.ts +3 -0
- package/src/utils/export/scorm.ts +121 -0
- package/src/utils/export/shared.ts +61 -0
- package/src/utils/export/types.ts +17 -0
- package/src/utils/incrementVersion.js +74 -74
- package/src/utils/misc.ts +58 -58
- package/src/utils/rigoActions.ts +500 -500
- package/src/utils/sidebarGenerator.ts +195 -195
- package/src/utils/templates/epub/epub.css +133 -0
- package/src/utils/templates/isolated/exercises/01-hello-world/README.es.md +26 -26
- package/src/utils/templates/isolated/exercises/01-hello-world/README.md +26 -26
- package/src/utils/templates/scorm/adlcp_rootv1p2.xsd +110 -0
- package/src/utils/templates/scorm/config/api.js +175 -0
- package/src/utils/templates/scorm/config/index.html +210 -0
- package/src/utils/templates/scorm/ims_xml.xsd +1 -0
- package/src/utils/templates/scorm/imscp_rootv1p1p2.xsd +345 -0
- package/src/utils/templates/scorm/imsmanifest.xml +38 -0
- 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.
|
package/lib/utils/api.d.ts
CHANGED
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 }, {
|
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
|
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 }, {
|
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,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,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,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
|
+
}
|