@learnpack/learnpack 5.0.51 → 5.0.52
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 +12 -12
- package/lib/commands/audit.js +17 -0
- package/lib/commands/clean.js +6 -5
- package/lib/commands/init.js +30 -14
- package/lib/commands/publish.js +3 -1
- package/lib/managers/config/index.js +16 -1
- package/lib/utils/creatorUtilities.js +1 -1
- package/lib/utils/misc.d.ts +1 -0
- package/lib/utils/misc.js +23 -0
- package/lib/utils/rigoActions.d.ts +5 -0
- package/lib/utils/rigoActions.js +13 -7
- package/lib/utils/sidebarGenerator.d.ts +2 -0
- package/lib/utils/sidebarGenerator.js +49 -1
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/audit.ts +17 -0
- package/src/commands/clean.ts +30 -29
- package/src/commands/init.ts +43 -22
- package/src/commands/publish.ts +2 -0
- package/src/managers/config/index.ts +26 -7
- package/src/models/config-manager.ts +1 -0
- package/src/utils/creatorUtilities.ts +1 -1
- package/src/utils/misc.ts +52 -23
- package/src/utils/rigoActions.ts +26 -6
- package/src/utils/sidebarGenerator.ts +75 -0
package/README.md
CHANGED
@@ -21,7 +21,7 @@ $ npm install -g @learnpack/learnpack
|
|
21
21
|
$ learnpack COMMAND
|
22
22
|
running command...
|
23
23
|
$ learnpack (-v|--version|version)
|
24
|
-
@learnpack/learnpack/5.0.
|
24
|
+
@learnpack/learnpack/5.0.52 win32-x64 node-v22.14.0
|
25
25
|
$ learnpack --help [COMMAND]
|
26
26
|
USAGE
|
27
27
|
$ learnpack COMMAND
|
@@ -79,7 +79,7 @@ DESCRIPTION
|
|
79
79
|
12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
|
80
80
|
```
|
81
81
|
|
82
|
-
_See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
82
|
+
_See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.52/src\commands\audit.ts)_
|
83
83
|
|
84
84
|
## `learnpack breakToken`
|
85
85
|
|
@@ -94,7 +94,7 @@ OPTIONS
|
|
94
94
|
-y, --yes Skip all prompts and initialize an empty project
|
95
95
|
```
|
96
96
|
|
97
|
-
_See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
97
|
+
_See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.52/src\commands\breakToken.ts)_
|
98
98
|
|
99
99
|
## `learnpack clean`
|
100
100
|
|
@@ -109,7 +109,7 @@ DESCRIPTION
|
|
109
109
|
Extra documentation goes here
|
110
110
|
```
|
111
111
|
|
112
|
-
_See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
112
|
+
_See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.52/src\commands\clean.ts)_
|
113
113
|
|
114
114
|
## `learnpack download [PACKAGE]`
|
115
115
|
|
@@ -127,7 +127,7 @@ DESCRIPTION
|
|
127
127
|
Extra documentation goes here
|
128
128
|
```
|
129
129
|
|
130
|
-
_See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
130
|
+
_See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.52/src\commands\download.ts)_
|
131
131
|
|
132
132
|
## `learnpack help [COMMAND]`
|
133
133
|
|
@@ -159,7 +159,7 @@ OPTIONS
|
|
159
159
|
-y, --yes Skip all prompts and initialize an empty project
|
160
160
|
```
|
161
161
|
|
162
|
-
_See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
162
|
+
_See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.52/src\commands\init.ts)_
|
163
163
|
|
164
164
|
## `learnpack login [PACKAGE]`
|
165
165
|
|
@@ -177,7 +177,7 @@ DESCRIPTION
|
|
177
177
|
Extra documentation goes here
|
178
178
|
```
|
179
179
|
|
180
|
-
_See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
180
|
+
_See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.52/src\commands\login.ts)_
|
181
181
|
|
182
182
|
## `learnpack logout [PACKAGE]`
|
183
183
|
|
@@ -195,7 +195,7 @@ DESCRIPTION
|
|
195
195
|
Extra documentation goes here
|
196
196
|
```
|
197
197
|
|
198
|
-
_See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
198
|
+
_See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.52/src\commands\logout.ts)_
|
199
199
|
|
200
200
|
## `learnpack plugins`
|
201
201
|
|
@@ -327,7 +327,7 @@ OPTIONS
|
|
327
327
|
-s, --strict strict mode
|
328
328
|
```
|
329
329
|
|
330
|
-
_See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
330
|
+
_See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.52/src\commands\publish.ts)_
|
331
331
|
|
332
332
|
## `learnpack start`
|
333
333
|
|
@@ -349,7 +349,7 @@ OPTIONS
|
|
349
349
|
-y, --yes Skip all prompts and initialize an empty project
|
350
350
|
```
|
351
351
|
|
352
|
-
_See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
352
|
+
_See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.52/src\commands\start.ts)_
|
353
353
|
|
354
354
|
## `learnpack test [EXERCISESLUG]`
|
355
355
|
|
@@ -366,7 +366,7 @@ OPTIONS
|
|
366
366
|
-y, --yes Skip all prompts and initialize an empty project
|
367
367
|
```
|
368
368
|
|
369
|
-
_See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
369
|
+
_See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.52/src\commands\test.ts)_
|
370
370
|
|
371
371
|
## `learnpack translate`
|
372
372
|
|
@@ -380,7 +380,7 @@ OPTIONS
|
|
380
380
|
-y, --yes Skip all prompts and initialize an empty project
|
381
381
|
```
|
382
382
|
|
383
|
-
_See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
383
|
+
_See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.52/src\commands\translate.ts)_
|
384
384
|
<!-- commandsstop -->
|
385
385
|
|
386
386
|
> > > > > > > 0cb3e56d84c197f9d008836bb573eade212b7e57
|
package/lib/commands/audit.js
CHANGED
@@ -7,6 +7,7 @@ const audit_1 = require("../utils/audit");
|
|
7
7
|
const SessionCommand_1 = require("../utils/SessionCommand");
|
8
8
|
const path = require("path");
|
9
9
|
const command_1 = require("@oclif/command");
|
10
|
+
const sidebarGenerator_1 = require("../utils/sidebarGenerator");
|
10
11
|
// eslint-disable-next-line
|
11
12
|
const fetch = require("node-fetch");
|
12
13
|
class AuditCommand extends SessionCommand_1.default {
|
@@ -83,6 +84,22 @@ class AuditCommand extends SessionCommand_1.default {
|
|
83
84
|
msg: "The slug property is not a valid slug. It must start and end with a letter and be less than 50 characters.",
|
84
85
|
});
|
85
86
|
}
|
87
|
+
console_1.default.info("Checking if the sidebar.json file is fine...");
|
88
|
+
const isSidebarFine = await (0, sidebarGenerator_1.checkAndFixSidebar)(config, false);
|
89
|
+
if (!isSidebarFine) {
|
90
|
+
if (strict) {
|
91
|
+
errors.push({
|
92
|
+
exercise: undefined,
|
93
|
+
msg: "The sidebar.json file has errors, please fill all missing translations",
|
94
|
+
});
|
95
|
+
}
|
96
|
+
else {
|
97
|
+
warnings.push({
|
98
|
+
exercise: undefined,
|
99
|
+
msg: "The sidebar.json file has errors, please fill all missing translations",
|
100
|
+
});
|
101
|
+
}
|
102
|
+
}
|
86
103
|
// check if the duration property is in the configuration object
|
87
104
|
if (!((_h = config.config) === null || _h === void 0 ? void 0 : _h.duration))
|
88
105
|
warnings.push({
|
package/lib/commands/clean.js
CHANGED
@@ -9,15 +9,16 @@ class CleanCommand extends SessionCommand_1.default {
|
|
9
9
|
await this.initSession(flags);
|
10
10
|
}
|
11
11
|
async run() {
|
12
|
-
var _a;
|
12
|
+
var _a, _b;
|
13
13
|
const { flags } = this.parse(CleanCommand);
|
14
|
-
(_a = this.configManager) === null || _a === void 0 ? void 0 : _a.
|
14
|
+
(_a = this.configManager) === null || _a === void 0 ? void 0 : _a.buildIndex();
|
15
|
+
await ((_b = this.configManager) === null || _b === void 0 ? void 0 : _b.clean());
|
15
16
|
console_1.default.success("Package cleaned successfully, ready to publish");
|
16
17
|
}
|
17
18
|
}
|
18
|
-
CleanCommand.description = `Clean the configuration object
|
19
|
-
...
|
20
|
-
Extra documentation goes here
|
19
|
+
CleanCommand.description = `Clean the configuration object
|
20
|
+
...
|
21
|
+
Extra documentation goes here
|
21
22
|
`;
|
22
23
|
CleanCommand.flags = {
|
23
24
|
// name: flags.string({char: 'n', description: 'name to print'}),
|
package/lib/commands/init.js
CHANGED
@@ -17,9 +17,9 @@ const api_2 = require("../utils/api");
|
|
17
17
|
const creatorUtilities_1 = require("../utils/creatorUtilities");
|
18
18
|
const session_1 = require("../managers/session");
|
19
19
|
const durationByKind = {
|
20
|
-
code:
|
21
|
-
quiz:
|
22
|
-
read:
|
20
|
+
code: 5,
|
21
|
+
quiz: 3,
|
22
|
+
read: 2,
|
23
23
|
};
|
24
24
|
function estimateActivities(estimatedDuration) {
|
25
25
|
const result = {};
|
@@ -78,6 +78,7 @@ async function processExercise(rigoToken, steps, packageContext, exercise, exerc
|
|
78
78
|
fkgl_results: JSON.stringify(readability.fkglResult),
|
79
79
|
expected_grade_level: PARAMS.expected_grade_level,
|
80
80
|
});
|
81
|
+
// console.log("REDUCED README START", reducedReadme, "REDUCED README END")
|
81
82
|
if (!reducedReadme)
|
82
83
|
break;
|
83
84
|
readability = (0, creatorUtilities_1.checkReadability)(reducedReadme.parsed.content, PARAMS.max_words, duration || 1);
|
@@ -192,9 +193,11 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
|
|
192
193
|
const { targetAudience, estimatedDuration } = await whichTargetAudienceAndEstimatedDuration();
|
193
194
|
const contentIndex = await (0, creatorUtilities_1.appendContentIndex)();
|
194
195
|
const airules = await (0, creatorUtilities_1.appendAIRules)();
|
195
|
-
// If the airules is not empty, create the file in target directory
|
196
196
|
if (airules) {
|
197
|
-
|
197
|
+
const rulesDir = path.join(tutorialDir, ".learn", "rules");
|
198
|
+
// Crear el directorio si no existe
|
199
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
200
|
+
fs.writeFileSync(path.join(rulesDir, "airules.txt"), airules);
|
198
201
|
}
|
199
202
|
let packageContext = `
|
200
203
|
\n
|
@@ -218,6 +221,10 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
|
|
218
221
|
|
219
222
|
|
220
223
|
Within the estimated duration, is possible to have the following activities:
|
224
|
+
Format=
|
225
|
+
Activity: Maximum number of steps for duration
|
226
|
+
|
227
|
+
Estimated activities:
|
221
228
|
${JSON.stringify(estimateActivities(estimatedDuration))}
|
222
229
|
|
223
230
|
You should create a tutorial that is engaging and fun to follow.
|
@@ -276,6 +283,13 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
|
|
276
283
|
console_1.default.info("Images generated successfully! 🎉 Your tutorial will be ready soon!");
|
277
284
|
console_1.default.info("Creating preview readme...");
|
278
285
|
await (0, rigoActions_1.createPreviewReadme)(tutorialDir, packageInfo, rigoToken, readmeContents);
|
286
|
+
const imagePath = path.join(tutorialDir, "preview.png");
|
287
|
+
const res = await (0, rigoActions_1.generateImage)(rigoToken, {
|
288
|
+
prompt: "Generate a preview image for the tutorial. This is all the tutorial information: " +
|
289
|
+
packageContext +
|
290
|
+
"\n Generate only a basic preview image, add the tutorial Title as a text add the top middle, avoid adding any other text elements. Try to generate an image that related with the tutorial content.",
|
291
|
+
});
|
292
|
+
await (0, rigoActions_1.downloadImage)(res.image_url, imagePath);
|
279
293
|
return true;
|
280
294
|
};
|
281
295
|
const getChoices = async (empty) => {
|
@@ -364,15 +378,17 @@ class InitComand extends BaseCommand_1.default {
|
|
364
378
|
for (const language of languages) {
|
365
379
|
const readmeFilename = `README${language !== "en" ? `.${language}` : ""}`;
|
366
380
|
const readmeTemplatePath = path.resolve(templatesDir, `${readmeFilename}.ejs`);
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
381
|
+
if (choices.useAI !== "yes") {
|
382
|
+
const readmeObject = {
|
383
|
+
title: packageInfo.title.us,
|
384
|
+
description: packageInfo.description.us,
|
385
|
+
grading: packageInfo.grading,
|
386
|
+
difficulty: packageInfo.difficulty,
|
387
|
+
duration: packageInfo.duration,
|
388
|
+
};
|
389
|
+
const readmeContent = eta.render(fs.readFileSync(readmeTemplatePath, "utf-8"), readmeObject);
|
390
|
+
fs.writeFileSync(path.join(tutorialDir, `${readmeFilename}.md`), readmeContent);
|
391
|
+
}
|
376
392
|
if (fs.existsSync(path.join(tutorialDir, `${readmeFilename}.ejs`)))
|
377
393
|
fs.removeSync(path.join(tutorialDir, `${readmeFilename}.ejs`));
|
378
394
|
}
|
package/lib/commands/publish.js
CHANGED
@@ -16,6 +16,7 @@ const file_1 = require("../managers/file");
|
|
16
16
|
const api_1 = require("../utils/api");
|
17
17
|
const prompts = require("prompts");
|
18
18
|
const rigoActions_1 = require("../utils/rigoActions");
|
19
|
+
const misc_1 = require("../utils/misc");
|
19
20
|
const uploadZipEndpont = api_1.RIGOBOT_HOST + "/v1/learnpack/upload";
|
20
21
|
const handleAssetCreation = async (sessionPayload, academy, learnJson, learnpackDeployUrl) => {
|
21
22
|
try {
|
@@ -199,7 +200,8 @@ class BuildCommand extends SessionCommand_1.default {
|
|
199
200
|
.replace(/<title>.*<\/title>/, `<title>${title}</title>`)
|
200
201
|
.replace(/{{title}}/g, title)
|
201
202
|
.replace(/{{preview}}/g, previewUrl)
|
202
|
-
.replace(/{{slug}}/g, learnJson.slug)
|
203
|
+
.replace(/{{slug}}/g, learnJson.slug)
|
204
|
+
.replace(/{{duration}}/g, (0, misc_1.minutesToISO8601Duration)(learnJson.duration));
|
203
205
|
// Write the modified content to the build directory
|
204
206
|
fs.writeFileSync(buildIndexHtmlPath, indexHtmlContent);
|
205
207
|
}
|
@@ -8,6 +8,7 @@ const util_1 = require("util");
|
|
8
8
|
const console_1 = require("../../utils/console");
|
9
9
|
const watcher_1 = require("../../utils/watcher");
|
10
10
|
const errors_1 = require("../../utils/errors");
|
11
|
+
const sidebarGenerator_1 = require("../../utils/sidebarGenerator");
|
11
12
|
const defaults_1 = require("./defaults");
|
12
13
|
const exercise_1 = require("./exercise");
|
13
14
|
const file_1 = require("../file");
|
@@ -59,6 +60,18 @@ const getCodespacesNamespace = () => {
|
|
59
60
|
}
|
60
61
|
return codespace_name;
|
61
62
|
};
|
63
|
+
const checkDuration = (configObj) => {
|
64
|
+
if (!configObj.exercises || !configObj.config || !configObj.config.dirPath)
|
65
|
+
return;
|
66
|
+
const learnJSONPath = path.join(configObj.config.dirPath, "../learn.json");
|
67
|
+
const learnJSON = fs.readFileSync(learnJSONPath, "utf8");
|
68
|
+
const learnJSONObj = JSON.parse(learnJSON);
|
69
|
+
const duration = learnJSONObj.duration;
|
70
|
+
if (!duration) {
|
71
|
+
learnJSONObj.duration = configObj.exercises.reduce((acc) => acc + 2, 0);
|
72
|
+
fs.writeFileSync(learnJSONPath, JSON.stringify(learnJSONObj, null, 2));
|
73
|
+
}
|
74
|
+
};
|
62
75
|
exports.default = async ({ grading, mode, disableGrading, version, }) => {
|
63
76
|
var _a, _b, _c;
|
64
77
|
const confPath = getConfigPath();
|
@@ -264,7 +277,7 @@ exports.default = async ({ grading, mode, disableGrading, version, }) => {
|
|
264
277
|
(0, file_1.rmSync)(configObj.config.dirPath + "/.session");
|
265
278
|
}
|
266
279
|
},
|
267
|
-
clean: () => {
|
280
|
+
clean: async () => {
|
268
281
|
if (configObj.config) {
|
269
282
|
if (configObj.config.outputPath) {
|
270
283
|
(0, file_1.rmSync)(configObj.config.outputPath);
|
@@ -292,6 +305,8 @@ exports.default = async ({ grading, mode, disableGrading, version, }) => {
|
|
292
305
|
fs.unlinkSync(configObj.config.dirPath + "/telemetry.json");
|
293
306
|
if (fs.existsSync(configObj.config.dirPath + "/vscode_queue.json"))
|
294
307
|
fs.unlinkSync(configObj.config.dirPath + "/vscode_queue.json");
|
308
|
+
await (0, sidebarGenerator_1.checkAndFixSidebar)(configObj, true);
|
309
|
+
checkDuration(configObj);
|
295
310
|
}
|
296
311
|
},
|
297
312
|
getExercise: slug => {
|
@@ -199,7 +199,7 @@ function getDesktopFile(fileName) {
|
|
199
199
|
const filePath = (0, path_1.join)(desktopPath, fileName);
|
200
200
|
const content = fs.readFileSync(filePath, "utf8");
|
201
201
|
// Delete the file after reading it
|
202
|
-
fs.unlinkSync(filePath)
|
202
|
+
// fs.unlinkSync(filePath)
|
203
203
|
return content;
|
204
204
|
}
|
205
205
|
// export function fleschKincaidReadingEase(text: string): number {
|
package/lib/utils/misc.d.ts
CHANGED
package/lib/utils/misc.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.prioritizeHTMLFile = void 0;
|
4
|
+
exports.minutesToISO8601Duration = minutesToISO8601Duration;
|
4
5
|
const prioritizeHTMLFile = (entryFiles) => {
|
5
6
|
let files = [];
|
6
7
|
// Find the html file and put it as latest in the files array
|
@@ -22,3 +23,25 @@ const prioritizeHTMLFile = (entryFiles) => {
|
|
22
23
|
return files;
|
23
24
|
};
|
24
25
|
exports.prioritizeHTMLFile = prioritizeHTMLFile;
|
26
|
+
function minutesToISO8601Duration(minutes) {
|
27
|
+
if (minutes <= 0)
|
28
|
+
return "PT0M";
|
29
|
+
const weeks = Math.floor(minutes / (60 * 24 * 7));
|
30
|
+
minutes %= 60 * 24 * 7;
|
31
|
+
const days = Math.floor(minutes / (60 * 24));
|
32
|
+
minutes %= 60 * 24;
|
33
|
+
const hours = Math.floor(minutes / 60);
|
34
|
+
const mins = minutes % 60;
|
35
|
+
let duration = "P";
|
36
|
+
if (weeks)
|
37
|
+
duration += `${weeks}W`;
|
38
|
+
if (days)
|
39
|
+
duration += `${days}D`;
|
40
|
+
if (hours || mins)
|
41
|
+
duration += "T";
|
42
|
+
if (hours)
|
43
|
+
duration += `${hours}H`;
|
44
|
+
if (mins)
|
45
|
+
duration += `${mins}M`;
|
46
|
+
return duration;
|
47
|
+
}
|
@@ -66,4 +66,9 @@ type TGenerateCourseShortNameInputs = {
|
|
66
66
|
learnJSON: string;
|
67
67
|
};
|
68
68
|
export declare const generateCourseShortName: (token: string, inputs: TGenerateCourseShortNameInputs) => Promise<any>;
|
69
|
+
type TFillSidebarJSONInputs = {
|
70
|
+
needed_translations: string;
|
71
|
+
sidebar_json: string;
|
72
|
+
};
|
73
|
+
export declare const fillSidebarJSON: (token: string, inputs: TFillSidebarJSONInputs) => Promise<any>;
|
69
74
|
export {};
|
package/lib/utils/rigoActions.js
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.generateCourseShortName = exports.isValidRigoToken = exports.readmeCreator = exports.createCodingReadme = exports.createCodeFile = exports.interactiveCreation = exports.generateCourseIntroduction = exports.translateExercise = exports.generateImage = exports.hasCreatorPermission = exports.createReadme = void 0;
|
3
|
+
exports.fillSidebarJSON = exports.generateCourseShortName = 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
6
|
exports.makeReadmeReadable = makeReadmeReadable;
|
7
7
|
const axios_1 = require("axios");
|
8
|
-
const
|
8
|
+
const promises_1 = require("fs/promises");
|
9
9
|
const console_1 = require("../utils/console");
|
10
10
|
const fs = require("fs");
|
11
11
|
const path = require("path");
|
@@ -72,11 +72,7 @@ exports.generateImage = generateImage;
|
|
72
72
|
async function downloadImage(imageUrl, savePath) {
|
73
73
|
const response = await axios_1.default.get(imageUrl, { responseType: "arraybuffer" });
|
74
74
|
const buffer = Buffer.from(response.data, "binary");
|
75
|
-
(0,
|
76
|
-
if (err) {
|
77
|
-
console_1.default.error(`Error saving the image: ${err}`);
|
78
|
-
}
|
79
|
-
});
|
75
|
+
await (0, promises_1.writeFile)(savePath, buffer);
|
80
76
|
}
|
81
77
|
const translateExercise = async (token, inputs) => {
|
82
78
|
const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion/159/`, {
|
@@ -212,3 +208,13 @@ const generateCourseShortName = async (token, inputs) => {
|
|
212
208
|
return response.data;
|
213
209
|
};
|
214
210
|
exports.generateCourseShortName = generateCourseShortName;
|
211
|
+
const fillSidebarJSON = async (token, inputs) => {
|
212
|
+
const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion/951/`, { inputs, include_purpose_objective: false, execute_async: false }, {
|
213
|
+
headers: {
|
214
|
+
"Content-Type": "application/json",
|
215
|
+
Authorization: "Token " + token,
|
216
|
+
},
|
217
|
+
});
|
218
|
+
return response.data;
|
219
|
+
};
|
220
|
+
exports.fillSidebarJSON = fillSidebarJSON;
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { IExercise } from "../models/exercise-obj";
|
2
|
+
import { IConfigObj } from "../models/config";
|
2
3
|
type TTitleTranslations = {
|
3
4
|
[key: string]: string;
|
4
5
|
};
|
@@ -7,4 +8,5 @@ export type TSidebar = {
|
|
7
8
|
};
|
8
9
|
export declare const generateSidebar: (exercises: IExercise[], learnPath: string) => TSidebar;
|
9
10
|
export declare const addExerciseToSidebar: (exerciseSlug: string, targetLanguage: string, translatedSlug: string, learnPath: string) => TSidebar;
|
11
|
+
export declare const checkAndFixSidebar: (configObj: IConfigObj, autoFix?: boolean) => Promise<boolean>;
|
10
12
|
export {};
|
@@ -1,8 +1,11 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.addExerciseToSidebar = exports.generateSidebar = void 0;
|
3
|
+
exports.checkAndFixSidebar = exports.addExerciseToSidebar = exports.generateSidebar = void 0;
|
4
4
|
const path = require("path");
|
5
|
+
const console_1 = require("./console");
|
5
6
|
const fs = require("fs");
|
7
|
+
const session_1 = require("../managers/session");
|
8
|
+
const rigoActions_1 = require("./rigoActions");
|
6
9
|
const generateSidebar = (exercises, learnPath) => {
|
7
10
|
const sidebarPath = path.join(learnPath, "sidebar.json");
|
8
11
|
let sidebar = {};
|
@@ -27,3 +30,48 @@ const addExerciseToSidebar = (exerciseSlug, targetLanguage, translatedSlug, lear
|
|
27
30
|
return sidebar;
|
28
31
|
};
|
29
32
|
exports.addExerciseToSidebar = addExerciseToSidebar;
|
33
|
+
const checkAndFixSidebar = async (configObj, autoFix = false) => {
|
34
|
+
var _a;
|
35
|
+
if (configObj.config &&
|
36
|
+
fs.existsSync(configObj.config.dirPath + "/sidebar.json")) {
|
37
|
+
let hasErrors = false;
|
38
|
+
const sidebar = fs.readFileSync(configObj.config.dirPath + "/sidebar.json", "utf8");
|
39
|
+
// parse the sidebar.json file
|
40
|
+
const sidebarJson = JSON.parse(sidebar);
|
41
|
+
const exerciseTranslations = new Set();
|
42
|
+
(_a = configObj.exercises) === null || _a === void 0 ? void 0 : _a.map(e =>
|
43
|
+
// eslint-disable-next-line
|
44
|
+
Object.keys((e.translations || {})).forEach((t) => exerciseTranslations.add(t)));
|
45
|
+
// Validation
|
46
|
+
for (const [key, value] of Object.entries(sidebarJson)) {
|
47
|
+
for (const lang of exerciseTranslations) {
|
48
|
+
if (!Object.prototype.hasOwnProperty.call(value, lang)) {
|
49
|
+
hasErrors = true;
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
if (hasErrors && autoFix) {
|
54
|
+
console_1.default.warning("Filling sidebar.json file with missing translations");
|
55
|
+
const sessionPayload = await session_1.default.getPayload();
|
56
|
+
const rigoToken = sessionPayload.rigobot.key;
|
57
|
+
if (!rigoToken) {
|
58
|
+
console_1.default.error("No Rigobot token found, please login first!");
|
59
|
+
return false;
|
60
|
+
}
|
61
|
+
const response = await (0, rigoActions_1.fillSidebarJSON)(rigoToken, {
|
62
|
+
needed_translations: JSON.stringify(exerciseTranslations),
|
63
|
+
sidebar_json: JSON.stringify(sidebarJson),
|
64
|
+
});
|
65
|
+
const newSidebarJson = JSON.parse(response.parsed.new_sidebar_file);
|
66
|
+
fs.writeFileSync(configObj.config.dirPath + "/sidebar.json", JSON.stringify(newSidebarJson, null, 4));
|
67
|
+
console_1.default.info("Sidebar.json was filled with missing translations");
|
68
|
+
return true;
|
69
|
+
}
|
70
|
+
if (hasErrors && !autoFix) {
|
71
|
+
return false;
|
72
|
+
}
|
73
|
+
return true;
|
74
|
+
}
|
75
|
+
return false;
|
76
|
+
};
|
77
|
+
exports.checkAndFixSidebar = checkAndFixSidebar;
|
package/oclif.manifest.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":"5.0.
|
1
|
+
{"version":"5.0.52","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false}},"args":[]},"breakToken":{"id":"breakToken","description":"Break the token","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false},"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[]}}}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@learnpack/learnpack",
|
3
3
|
"description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
|
4
|
-
"version": "5.0.
|
4
|
+
"version": "5.0.52",
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
6
6
|
"contributors": [
|
7
7
|
{
|
package/src/commands/audit.ts
CHANGED
@@ -11,6 +11,7 @@ import { IAuditErrors } from "../models/audit"
|
|
11
11
|
import { ICounter } from "../models/counter"
|
12
12
|
import { IFindings } from "../models/findings"
|
13
13
|
import { flags } from "@oclif/command"
|
14
|
+
import { checkAndFixSidebar } from "../utils/sidebarGenerator"
|
14
15
|
|
15
16
|
// eslint-disable-next-line
|
16
17
|
const fetch = require("node-fetch")
|
@@ -113,6 +114,22 @@ class AuditCommand extends SessionCommand {
|
|
113
114
|
})
|
114
115
|
}
|
115
116
|
|
117
|
+
Console.info("Checking if the sidebar.json file is fine...")
|
118
|
+
const isSidebarFine = await checkAndFixSidebar(config!, false)
|
119
|
+
if (!isSidebarFine) {
|
120
|
+
if (strict) {
|
121
|
+
errors.push({
|
122
|
+
exercise: undefined,
|
123
|
+
msg: "The sidebar.json file has errors, please fill all missing translations",
|
124
|
+
})
|
125
|
+
} else {
|
126
|
+
warnings.push({
|
127
|
+
exercise: undefined,
|
128
|
+
msg: "The sidebar.json file has errors, please fill all missing translations",
|
129
|
+
})
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
116
133
|
// check if the duration property is in the configuration object
|
117
134
|
if (!config!.config?.duration)
|
118
135
|
warnings.push({
|
package/src/commands/clean.ts
CHANGED
@@ -1,29 +1,30 @@
|
|
1
|
-
// import {flags} from '@oclif/command'
|
2
|
-
import Console from "../utils/console"
|
3
|
-
import SessionCommand from "../utils/SessionCommand"
|
4
|
-
|
5
|
-
class CleanCommand extends SessionCommand {
|
6
|
-
static description = `Clean the configuration object
|
7
|
-
...
|
8
|
-
Extra documentation goes here
|
9
|
-
`
|
10
|
-
|
11
|
-
static flags: any = {
|
12
|
-
// name: flags.string({char: 'n', description: 'name to print'}),
|
13
|
-
}
|
14
|
-
|
15
|
-
async init() {
|
16
|
-
const { flags } = this.parse(CleanCommand)
|
17
|
-
await this.initSession(flags)
|
18
|
-
}
|
19
|
-
|
20
|
-
async run() {
|
21
|
-
const { flags } = this.parse(CleanCommand)
|
22
|
-
|
23
|
-
this.configManager?.
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
}
|
28
|
-
|
29
|
-
|
1
|
+
// import {flags} from '@oclif/command'
|
2
|
+
import Console from "../utils/console"
|
3
|
+
import SessionCommand from "../utils/SessionCommand"
|
4
|
+
|
5
|
+
class CleanCommand extends SessionCommand {
|
6
|
+
static description = `Clean the configuration object
|
7
|
+
...
|
8
|
+
Extra documentation goes here
|
9
|
+
`
|
10
|
+
|
11
|
+
static flags: any = {
|
12
|
+
// name: flags.string({char: 'n', description: 'name to print'}),
|
13
|
+
}
|
14
|
+
|
15
|
+
async init() {
|
16
|
+
const { flags } = this.parse(CleanCommand)
|
17
|
+
await this.initSession(flags)
|
18
|
+
}
|
19
|
+
|
20
|
+
async run() {
|
21
|
+
const { flags } = this.parse(CleanCommand)
|
22
|
+
|
23
|
+
this.configManager?.buildIndex()
|
24
|
+
await this.configManager?.clean()
|
25
|
+
|
26
|
+
Console.success("Package cleaned successfully, ready to publish")
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
export default CleanCommand
|
package/src/commands/init.ts
CHANGED
@@ -39,9 +39,9 @@ import {
|
|
39
39
|
import SessionManager from "../managers/session"
|
40
40
|
|
41
41
|
const durationByKind: Record<string, number> = {
|
42
|
-
code:
|
43
|
-
quiz:
|
44
|
-
read:
|
42
|
+
code: 5,
|
43
|
+
quiz: 3,
|
44
|
+
read: 2,
|
45
45
|
}
|
46
46
|
|
47
47
|
function estimateActivities(estimatedDuration: number) {
|
@@ -125,6 +125,8 @@ async function processExercise(
|
|
125
125
|
expected_grade_level: PARAMS.expected_grade_level,
|
126
126
|
})
|
127
127
|
|
128
|
+
// console.log("REDUCED README START", reducedReadme, "REDUCED README END")
|
129
|
+
|
128
130
|
if (!reducedReadme)
|
129
131
|
break
|
130
132
|
|
@@ -303,12 +305,13 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
|
303
305
|
const contentIndex = await appendContentIndex()
|
304
306
|
const airules = await appendAIRules()
|
305
307
|
|
306
|
-
// If the airules is not empty, create the file in target directory
|
307
308
|
if (airules) {
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
)
|
309
|
+
const rulesDir = path.join(tutorialDir, ".learn", "rules")
|
310
|
+
|
311
|
+
// Crear el directorio si no existe
|
312
|
+
fs.mkdirSync(rulesDir, { recursive: true })
|
313
|
+
|
314
|
+
fs.writeFileSync(path.join(rulesDir, "airules.txt"), airules)
|
312
315
|
}
|
313
316
|
|
314
317
|
let packageContext = `
|
@@ -335,6 +338,10 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
|
335
338
|
|
336
339
|
|
337
340
|
Within the estimated duration, is possible to have the following activities:
|
341
|
+
Format=
|
342
|
+
Activity: Maximum number of steps for duration
|
343
|
+
|
344
|
+
Estimated activities:
|
338
345
|
${JSON.stringify(estimateActivities(estimatedDuration))}
|
339
346
|
|
340
347
|
You should create a tutorial that is engaging and fun to follow.
|
@@ -392,6 +399,7 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
|
392
399
|
const imagePromises = imagesArray.map(async (image: any) => {
|
393
400
|
try {
|
394
401
|
const filename = getFilenameFromUrl(image.url)
|
402
|
+
|
395
403
|
const imagePath = path.join(tutorialDir, ".learn", "assets", filename)
|
396
404
|
|
397
405
|
const res = await generateImage(rigoToken, { prompt: image.alt })
|
@@ -411,6 +419,16 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
|
411
419
|
Console.info("Creating preview readme...")
|
412
420
|
|
413
421
|
await createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeContents)
|
422
|
+
const imagePath = path.join(tutorialDir, "preview.png")
|
423
|
+
|
424
|
+
const res = await generateImage(rigoToken, {
|
425
|
+
prompt:
|
426
|
+
"Generate a preview image for the tutorial. This is all the tutorial information: " +
|
427
|
+
packageContext +
|
428
|
+
"\n Generate only a basic preview image, add the tutorial Title as a text add the top middle, avoid adding any other text elements. Try to generate an image that related with the tutorial content.",
|
429
|
+
})
|
430
|
+
|
431
|
+
await downloadImage(res.image_url, imagePath)
|
414
432
|
|
415
433
|
return true
|
416
434
|
}
|
@@ -538,21 +556,24 @@ class InitComand extends BaseCommand {
|
|
538
556
|
`${readmeFilename}.ejs`
|
539
557
|
)
|
540
558
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
559
|
+
if (choices.useAI !== "yes") {
|
560
|
+
const readmeObject = {
|
561
|
+
title: packageInfo.title.us,
|
562
|
+
description: packageInfo.description.us,
|
563
|
+
grading: packageInfo.grading,
|
564
|
+
difficulty: packageInfo.difficulty,
|
565
|
+
duration: packageInfo.duration,
|
566
|
+
}
|
567
|
+
const readmeContent = eta.render(
|
568
|
+
fs.readFileSync(readmeTemplatePath, "utf-8"),
|
569
|
+
readmeObject
|
570
|
+
)
|
571
|
+
fs.writeFileSync(
|
572
|
+
path.join(tutorialDir, `${readmeFilename}.md`),
|
573
|
+
readmeContent
|
574
|
+
)
|
547
575
|
}
|
548
|
-
|
549
|
-
fs.readFileSync(readmeTemplatePath, "utf-8"),
|
550
|
-
readmeObject
|
551
|
-
)
|
552
|
-
fs.writeFileSync(
|
553
|
-
path.join(tutorialDir, `${readmeFilename}.md`),
|
554
|
-
readmeContent
|
555
|
-
)
|
576
|
+
|
556
577
|
if (fs.existsSync(path.join(tutorialDir, `${readmeFilename}.ejs`)))
|
557
578
|
fs.removeSync(path.join(tutorialDir, `${readmeFilename}.ejs`))
|
558
579
|
}
|
package/src/commands/publish.ts
CHANGED
@@ -18,6 +18,7 @@ import {
|
|
18
18
|
import api, { getConsumable, RIGOBOT_HOST, TAcademy } from "../utils/api"
|
19
19
|
import * as prompts from "prompts"
|
20
20
|
import { generateCourseShortName, isValidRigoToken } from "../utils/rigoActions"
|
21
|
+
import { minutesToISO8601Duration } from "../utils/misc"
|
21
22
|
|
22
23
|
const uploadZipEndpont = RIGOBOT_HOST + "/v1/learnpack/upload"
|
23
24
|
|
@@ -275,6 +276,7 @@ class BuildCommand extends SessionCommand {
|
|
275
276
|
.replace(/{{title}}/g, title)
|
276
277
|
.replace(/{{preview}}/g, previewUrl)
|
277
278
|
.replace(/{{slug}}/g, learnJson.slug)
|
279
|
+
.replace(/{{duration}}/g, minutesToISO8601Duration(learnJson.duration))
|
278
280
|
|
279
281
|
// Write the modified content to the build directory
|
280
282
|
fs.writeFileSync(buildIndexHtmlPath, indexHtmlContent)
|
@@ -11,23 +11,20 @@ import {
|
|
11
11
|
NotFoundError,
|
12
12
|
InternalError,
|
13
13
|
} from "../../utils/errors"
|
14
|
+
import { checkAndFixSidebar } from "../../utils/sidebarGenerator"
|
14
15
|
|
15
16
|
import defaults from "./defaults"
|
16
17
|
import { exercise } from "./exercise"
|
17
18
|
|
18
19
|
import { rmSync } from "../file"
|
19
|
-
import {
|
20
|
-
IConfigObj,
|
21
|
-
TConfigObjAttributes,
|
22
|
-
TMode,
|
23
|
-
TAgent,
|
24
|
-
} from "../../models/config"
|
20
|
+
import { IConfigObj, TMode, TAgent } from "../../models/config"
|
25
21
|
import {
|
26
22
|
IConfigManagerAttributes,
|
27
23
|
IConfigManager,
|
28
24
|
} from "../../models/config-manager"
|
29
25
|
import { IFile } from "../../models/file"
|
30
26
|
import { TSuggestions } from "../../models/config"
|
27
|
+
import { IExercise } from "../../models/exercise-obj"
|
31
28
|
/* exercise folder name standard */
|
32
29
|
const execAsync = promisify(exec)
|
33
30
|
|
@@ -91,6 +88,24 @@ const getCodespacesNamespace = () => {
|
|
91
88
|
return codespace_name
|
92
89
|
}
|
93
90
|
|
91
|
+
const checkDuration = (configObj: IConfigObj) => {
|
92
|
+
if (!configObj.exercises || !configObj.config || !configObj.config.dirPath)
|
93
|
+
return
|
94
|
+
|
95
|
+
const learnJSONPath = path.join(configObj.config.dirPath, "../learn.json")
|
96
|
+
|
97
|
+
const learnJSON = fs.readFileSync(learnJSONPath, "utf8")
|
98
|
+
const learnJSONObj = JSON.parse(learnJSON)
|
99
|
+
const duration = learnJSONObj.duration
|
100
|
+
if (!duration) {
|
101
|
+
learnJSONObj.duration = configObj.exercises.reduce(
|
102
|
+
(acc: number) => acc + 2,
|
103
|
+
0
|
104
|
+
)
|
105
|
+
fs.writeFileSync(learnJSONPath, JSON.stringify(learnJSONObj, null, 2))
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
94
109
|
export default async ({
|
95
110
|
grading,
|
96
111
|
mode,
|
@@ -369,7 +384,7 @@ return true
|
|
369
384
|
rmSync(configObj.config.dirPath + "/.session")
|
370
385
|
}
|
371
386
|
},
|
372
|
-
clean: () => {
|
387
|
+
clean: async () => {
|
373
388
|
if (configObj.config) {
|
374
389
|
if (configObj.config.outputPath) {
|
375
390
|
rmSync(configObj.config.outputPath)
|
@@ -404,6 +419,10 @@ return true
|
|
404
419
|
|
405
420
|
if (fs.existsSync(configObj.config.dirPath + "/vscode_queue.json"))
|
406
421
|
fs.unlinkSync(configObj.config.dirPath + "/vscode_queue.json")
|
422
|
+
|
423
|
+
await checkAndFixSidebar(configObj, true)
|
424
|
+
|
425
|
+
checkDuration(configObj)
|
407
426
|
}
|
408
427
|
},
|
409
428
|
getExercise: slug => {
|
package/src/utils/misc.ts
CHANGED
@@ -1,23 +1,52 @@
|
|
1
|
-
export const prioritizeHTMLFile = (entryFiles: string[]) => {
|
2
|
-
let files = []
|
3
|
-
|
4
|
-
// Find the html file and put it as latest in the files array
|
5
|
-
// in order to keep the html file opened in vscode plugin
|
6
|
-
const index = entryFiles.findIndex(file => {
|
7
|
-
return /.*\.html$/.test(file)
|
8
|
-
})
|
9
|
-
|
10
|
-
if (index !== -1) {
|
11
|
-
for (const [i, entryFile] of entryFiles.entries()) {
|
12
|
-
if (i !== index) {
|
13
|
-
files.push(entryFile)
|
14
|
-
}
|
15
|
-
}
|
16
|
-
|
17
|
-
files.push(entryFiles[index])
|
18
|
-
} else {
|
19
|
-
files = entryFiles
|
20
|
-
}
|
21
|
-
|
22
|
-
return files
|
23
|
-
}
|
1
|
+
export const prioritizeHTMLFile = (entryFiles: string[]) => {
|
2
|
+
let files = []
|
3
|
+
|
4
|
+
// Find the html file and put it as latest in the files array
|
5
|
+
// in order to keep the html file opened in vscode plugin
|
6
|
+
const index = entryFiles.findIndex(file => {
|
7
|
+
return /.*\.html$/.test(file)
|
8
|
+
})
|
9
|
+
|
10
|
+
if (index !== -1) {
|
11
|
+
for (const [i, entryFile] of entryFiles.entries()) {
|
12
|
+
if (i !== index) {
|
13
|
+
files.push(entryFile)
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
files.push(entryFiles[index])
|
18
|
+
} else {
|
19
|
+
files = entryFiles
|
20
|
+
}
|
21
|
+
|
22
|
+
return files
|
23
|
+
}
|
24
|
+
|
25
|
+
export function minutesToISO8601Duration(minutes: number): string {
|
26
|
+
if (minutes <= 0)
|
27
|
+
return "PT0M"
|
28
|
+
|
29
|
+
const weeks = Math.floor(minutes / (60 * 24 * 7))
|
30
|
+
minutes %= 60 * 24 * 7
|
31
|
+
|
32
|
+
const days = Math.floor(minutes / (60 * 24))
|
33
|
+
minutes %= 60 * 24
|
34
|
+
|
35
|
+
const hours = Math.floor(minutes / 60)
|
36
|
+
const mins = minutes % 60
|
37
|
+
|
38
|
+
let duration = "P"
|
39
|
+
if (weeks)
|
40
|
+
duration += `${weeks}W`
|
41
|
+
if (days)
|
42
|
+
duration += `${days}D`
|
43
|
+
|
44
|
+
if (hours || mins)
|
45
|
+
duration += "T"
|
46
|
+
if (hours)
|
47
|
+
duration += `${hours}H`
|
48
|
+
if (mins)
|
49
|
+
duration += `${mins}M`
|
50
|
+
|
51
|
+
return duration
|
52
|
+
}
|
package/src/utils/rigoActions.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import axios from "axios"
|
2
|
-
import { writeFile } from "fs"
|
2
|
+
import { writeFile } from "fs/promises"
|
3
3
|
import Console from "../utils/console"
|
4
4
|
import { PackageInfo } from "./creatorUtilities"
|
5
5
|
import * as fs from "fs"
|
@@ -100,13 +100,10 @@ export async function downloadImage(
|
|
100
100
|
savePath: string
|
101
101
|
): Promise<void> {
|
102
102
|
const response = await axios.get(imageUrl, { responseType: "arraybuffer" })
|
103
|
+
|
103
104
|
const buffer = Buffer.from(response.data, "binary")
|
104
105
|
|
105
|
-
writeFile(savePath, buffer
|
106
|
-
if (err) {
|
107
|
-
Console.error(`Error saving the image: ${err}`)
|
108
|
-
}
|
109
|
-
})
|
106
|
+
await writeFile(savePath, buffer)
|
110
107
|
}
|
111
108
|
|
112
109
|
type TTranslateInputs = {
|
@@ -359,3 +356,26 @@ export const generateCourseShortName = async (
|
|
359
356
|
|
360
357
|
return response.data
|
361
358
|
}
|
359
|
+
|
360
|
+
type TFillSidebarJSONInputs = {
|
361
|
+
needed_translations: string
|
362
|
+
sidebar_json: string
|
363
|
+
}
|
364
|
+
|
365
|
+
export const fillSidebarJSON = async (
|
366
|
+
token: string,
|
367
|
+
inputs: TFillSidebarJSONInputs
|
368
|
+
) => {
|
369
|
+
const response = await axios.post(
|
370
|
+
`${RIGOBOT_HOST}/v1/prompting/completion/951/`,
|
371
|
+
{ inputs, include_purpose_objective: false, execute_async: false },
|
372
|
+
{
|
373
|
+
headers: {
|
374
|
+
"Content-Type": "application/json",
|
375
|
+
Authorization: "Token " + token,
|
376
|
+
},
|
377
|
+
}
|
378
|
+
)
|
379
|
+
|
380
|
+
return response.data
|
381
|
+
}
|
@@ -1,6 +1,11 @@
|
|
1
1
|
import path = require("path")
|
2
|
+
|
3
|
+
import Console from "./console"
|
2
4
|
import fs = require("fs")
|
3
5
|
import { IExercise, IExerciseData } from "../models/exercise-obj"
|
6
|
+
import { IConfigObj } from "../models/config"
|
7
|
+
import SessionManager from "../managers/session"
|
8
|
+
import { fillSidebarJSON } from "./rigoActions"
|
4
9
|
|
5
10
|
type TTitleTranslations = {
|
6
11
|
[key: string]: string
|
@@ -50,3 +55,73 @@ export const addExerciseToSidebar = (
|
|
50
55
|
|
51
56
|
return sidebar
|
52
57
|
}
|
58
|
+
|
59
|
+
export const checkAndFixSidebar = async (
|
60
|
+
configObj: IConfigObj,
|
61
|
+
autoFix = false
|
62
|
+
): Promise<boolean> => {
|
63
|
+
if (
|
64
|
+
configObj.config &&
|
65
|
+
fs.existsSync(configObj.config.dirPath + "/sidebar.json")
|
66
|
+
) {
|
67
|
+
let hasErrors = false
|
68
|
+
const sidebar = fs.readFileSync(
|
69
|
+
configObj.config.dirPath + "/sidebar.json",
|
70
|
+
"utf8"
|
71
|
+
)
|
72
|
+
// parse the sidebar.json file
|
73
|
+
const sidebarJson = JSON.parse(sidebar)
|
74
|
+
|
75
|
+
const exerciseTranslations: Set<string> = new Set()
|
76
|
+
configObj.exercises?.map(e =>
|
77
|
+
// eslint-disable-next-line
|
78
|
+
Object.keys((e.translations || {}) as any).forEach((t) =>
|
79
|
+
exerciseTranslations.add(t)
|
80
|
+
)
|
81
|
+
)
|
82
|
+
// Validation
|
83
|
+
for (const [key, value] of Object.entries(sidebarJson) as [
|
84
|
+
string,
|
85
|
+
TTitleTranslations
|
86
|
+
][]) {
|
87
|
+
for (const lang of exerciseTranslations) {
|
88
|
+
if (!Object.prototype.hasOwnProperty.call(value, lang)) {
|
89
|
+
hasErrors = true
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
if (hasErrors && autoFix) {
|
95
|
+
Console.warning("Filling sidebar.json file with missing translations")
|
96
|
+
const sessionPayload = await SessionManager.getPayload()
|
97
|
+
|
98
|
+
const rigoToken = sessionPayload.rigobot.key
|
99
|
+
|
100
|
+
if (!rigoToken) {
|
101
|
+
Console.error("No Rigobot token found, please login first!")
|
102
|
+
return false
|
103
|
+
}
|
104
|
+
|
105
|
+
const response = await fillSidebarJSON(rigoToken, {
|
106
|
+
needed_translations: JSON.stringify(exerciseTranslations),
|
107
|
+
sidebar_json: JSON.stringify(sidebarJson),
|
108
|
+
})
|
109
|
+
|
110
|
+
const newSidebarJson = JSON.parse(response.parsed.new_sidebar_file)
|
111
|
+
fs.writeFileSync(
|
112
|
+
configObj.config.dirPath + "/sidebar.json",
|
113
|
+
JSON.stringify(newSidebarJson, null, 4)
|
114
|
+
)
|
115
|
+
Console.info("Sidebar.json was filled with missing translations")
|
116
|
+
return true
|
117
|
+
}
|
118
|
+
|
119
|
+
if (hasErrors && !autoFix) {
|
120
|
+
return false
|
121
|
+
}
|
122
|
+
|
123
|
+
return true
|
124
|
+
}
|
125
|
+
|
126
|
+
return false
|
127
|
+
}
|