@learnpack/learnpack 5.0.5 → 5.0.7
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +22 -10
- package/lib/commands/init.js +43 -7
- package/lib/commands/translate.d.ts +6 -0
- package/lib/commands/translate.js +98 -0
- package/lib/managers/session.js +1 -0
- package/lib/utils/rigoActions.d.ts +10 -0
- package/lib/utils/rigoActions.js +29 -1
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/init.ts +67 -15
- package/src/commands/publish.ts +249 -249
- package/src/commands/translate.ts +140 -0
- package/src/managers/session.ts +1 -0
- package/src/utils/rigoActions.ts +53 -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.7 win32-x64 node-v20.16.0
|
25
25
|
$ learnpack --help [COMMAND]
|
26
26
|
USAGE
|
27
27
|
$ learnpack COMMAND
|
@@ -47,6 +47,7 @@ USAGE
|
|
47
47
|
* [`learnpack publish`](#learnpack-publish)
|
48
48
|
* [`learnpack start`](#learnpack-start)
|
49
49
|
* [`learnpack test [EXERCISESLUG]`](#learnpack-test-exerciseslug)
|
50
|
+
* [`learnpack translate`](#learnpack-translate)
|
50
51
|
|
51
52
|
## `learnpack audit`
|
52
53
|
|
@@ -74,7 +75,7 @@ DESCRIPTION
|
|
74
75
|
12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
|
75
76
|
```
|
76
77
|
|
77
|
-
_See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
78
|
+
_See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.7/src\commands\audit.ts)_
|
78
79
|
|
79
80
|
## `learnpack clean`
|
80
81
|
|
@@ -89,7 +90,7 @@ DESCRIPTION
|
|
89
90
|
Extra documentation goes here
|
90
91
|
```
|
91
92
|
|
92
|
-
_See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
93
|
+
_See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.7/src\commands\clean.ts)_
|
93
94
|
|
94
95
|
## `learnpack download [PACKAGE]`
|
95
96
|
|
@@ -107,7 +108,7 @@ DESCRIPTION
|
|
107
108
|
Extra documentation goes here
|
108
109
|
```
|
109
110
|
|
110
|
-
_See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
111
|
+
_See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.7/src\commands\download.ts)_
|
111
112
|
|
112
113
|
## `learnpack help [COMMAND]`
|
113
114
|
|
@@ -138,7 +139,7 @@ OPTIONS
|
|
138
139
|
-h, --grading show CLI help
|
139
140
|
```
|
140
141
|
|
141
|
-
_See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
142
|
+
_See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.7/src\commands\init.ts)_
|
142
143
|
|
143
144
|
## `learnpack login [PACKAGE]`
|
144
145
|
|
@@ -156,7 +157,7 @@ DESCRIPTION
|
|
156
157
|
Extra documentation goes here
|
157
158
|
```
|
158
159
|
|
159
|
-
_See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
160
|
+
_See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.7/src\commands\login.ts)_
|
160
161
|
|
161
162
|
## `learnpack logout [PACKAGE]`
|
162
163
|
|
@@ -174,7 +175,7 @@ DESCRIPTION
|
|
174
175
|
Extra documentation goes here
|
175
176
|
```
|
176
177
|
|
177
|
-
_See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
178
|
+
_See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.7/src\commands\logout.ts)_
|
178
179
|
|
179
180
|
## `learnpack plugins`
|
180
181
|
|
@@ -305,7 +306,7 @@ OPTIONS
|
|
305
306
|
-h, --help show CLI help
|
306
307
|
```
|
307
308
|
|
308
|
-
_See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
309
|
+
_See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.7/src\commands\publish.ts)_
|
309
310
|
|
310
311
|
## `learnpack start`
|
311
312
|
|
@@ -326,7 +327,7 @@ OPTIONS
|
|
326
327
|
-w, --watch Watch for file changes
|
327
328
|
```
|
328
329
|
|
329
|
-
_See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
330
|
+
_See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.7/src\commands\start.ts)_
|
330
331
|
|
331
332
|
## `learnpack test [EXERCISESLUG]`
|
332
333
|
|
@@ -340,7 +341,18 @@ ARGUMENTS
|
|
340
341
|
EXERCISESLUG The name of the exercise to test
|
341
342
|
```
|
342
343
|
|
343
|
-
_See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.
|
344
|
+
_See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.7/src\commands\test.ts)_
|
345
|
+
|
346
|
+
## `learnpack translate`
|
347
|
+
|
348
|
+
List all the lessons, the user is able of select many of them to translate to the given languages
|
349
|
+
|
350
|
+
```
|
351
|
+
USAGE
|
352
|
+
$ learnpack translate
|
353
|
+
```
|
354
|
+
|
355
|
+
_See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.7/src\commands\translate.ts)_
|
344
356
|
<!-- commandsstop -->
|
345
357
|
|
346
358
|
> > > > > > > 0cb3e56d84c197f9d008836bb573eade212b7e57
|
package/lib/commands/init.js
CHANGED
@@ -6,7 +6,6 @@ const BaseCommand_1 = require("../utils/BaseCommand");
|
|
6
6
|
const fs = require("fs-extra");
|
7
7
|
const prompts = require("prompts");
|
8
8
|
const cli_ux_1 = require("cli-ux");
|
9
|
-
const eta = require("eta");
|
10
9
|
const api_1 = require("../utils/api");
|
11
10
|
const console_1 = require("../utils/console");
|
12
11
|
const errors_1 = require("../utils/errors");
|
@@ -39,7 +38,15 @@ function extractImagesFromMarkdown(markdown) {
|
|
39
38
|
function getFilenameFromUrl(url) {
|
40
39
|
return path.basename(url);
|
41
40
|
}
|
42
|
-
|
41
|
+
async function createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeContents) {
|
42
|
+
const readmeFilename = `README.md`;
|
43
|
+
const readmeContent = await (0, rigoActions_1.generateCourseIntroduction)(rigoToken, {
|
44
|
+
course_title: packageInfo.title.us,
|
45
|
+
lessons_context: readmeContents.join("\n"),
|
46
|
+
});
|
47
|
+
fs.writeFileSync(path.join(tutorialDir, readmeFilename), readmeContent.answer);
|
48
|
+
}
|
49
|
+
const handleAILogic = async (tutorialDir, packageInfo) => {
|
43
50
|
console_1.default.info("Almost there! First you need to login to use the AI creator");
|
44
51
|
fs.removeSync(path.join(tutorialDir, "exercises", "01-hello-world"));
|
45
52
|
const loginPrompts = await prompts([
|
@@ -93,8 +100,19 @@ const handleAILogic = async (tutorialDir) => {
|
|
93
100
|
},
|
94
101
|
},
|
95
102
|
]);
|
103
|
+
const packageContext = `
|
104
|
+
\n
|
105
|
+
The following information comes from user inputs
|
106
|
+
Title: ${packageInfo.title.us}
|
107
|
+
Description: ${packageInfo.description.us}
|
108
|
+
Grading: ${packageInfo.grading}
|
109
|
+
Difficulty: ${packageInfo.difficulty}
|
110
|
+
Duration: ${packageInfo.duration}
|
111
|
+
|
112
|
+
Use it to generate more relevant exercises
|
113
|
+
`;
|
96
114
|
const inputs = {
|
97
|
-
tutorial_about: aiChoices.tutorialAbout,
|
115
|
+
tutorial_about: aiChoices.tutorialAbout + packageContext,
|
98
116
|
number_of_exercises: aiChoices.exercisesNumber,
|
99
117
|
};
|
100
118
|
console_1.default.info("Creating lessons...");
|
@@ -139,6 +157,8 @@ const handleAILogic = async (tutorialDir) => {
|
|
139
157
|
});
|
140
158
|
await Promise.all(imagePromises);
|
141
159
|
console_1.default.info("Images generated successfully! 🎉 Your tutorial will be ready soon!");
|
160
|
+
console_1.default.info("Creating preview readme...");
|
161
|
+
await createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeContents);
|
142
162
|
return true;
|
143
163
|
};
|
144
164
|
class InitComand extends BaseCommand_1.default {
|
@@ -233,15 +253,31 @@ class InitComand extends BaseCommand_1.default {
|
|
233
253
|
throw (0, errors_1.ValidationError)(`Template ${templatesDir} does not exists`);
|
234
254
|
await fs.copySync(templatesDir, tutorialDir);
|
235
255
|
if (choices.useAI === "yes") {
|
236
|
-
await handleAILogic(tutorialDir);
|
256
|
+
await handleAILogic(tutorialDir, packageInfo);
|
237
257
|
}
|
238
258
|
const languages = ["en", "es"];
|
239
259
|
// Creating README files
|
240
260
|
for (const language of languages) {
|
241
261
|
const readmeFilename = `README${language !== "en" ? `.${language}` : ""}`;
|
242
|
-
const readmeTemplatePath = path.resolve(
|
243
|
-
|
244
|
-
|
262
|
+
// const readmeTemplatePath = path.resolve(
|
263
|
+
// templatesDir,
|
264
|
+
// `${readmeFilename}.ejs`
|
265
|
+
// )
|
266
|
+
// const readmeObject = {
|
267
|
+
// title: packageInfo.title.us,
|
268
|
+
// description: packageInfo.description.us,
|
269
|
+
// grading: packageInfo.grading,
|
270
|
+
// difficulty: packageInfo.difficulty,
|
271
|
+
// duration: packageInfo.duration,
|
272
|
+
// }
|
273
|
+
// const readmeContent = eta.render(
|
274
|
+
// fs.readFileSync(readmeTemplatePath, "utf-8"),
|
275
|
+
// readmeObject
|
276
|
+
// )
|
277
|
+
// fs.writeFileSync(
|
278
|
+
// path.join(tutorialDir, `${readmeFilename}.md`),
|
279
|
+
// readmeContent
|
280
|
+
// )
|
245
281
|
if (fs.existsSync(path.join(tutorialDir, `${readmeFilename}.ejs`)))
|
246
282
|
fs.removeSync(path.join(tutorialDir, `${readmeFilename}.ejs`));
|
247
283
|
}
|
@@ -0,0 +1,98 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const SessionCommand_1 = require("../utils/SessionCommand");
|
4
|
+
const fs = require("fs");
|
5
|
+
const path = require("path");
|
6
|
+
const prompts = require("prompts");
|
7
|
+
const rigoActions_1 = require("../utils/rigoActions");
|
8
|
+
const session_1 = require("../managers/session");
|
9
|
+
const console_1 = require("../utils/console");
|
10
|
+
// This function list the names of the exercise directories inside the ./exercises folder, if the exercises folder doesn't exist, it will look for the ./.learn/exercises folder
|
11
|
+
const listExercises = async () => {
|
12
|
+
const exercisesDir = path.join(process.cwd(), "exercises");
|
13
|
+
const learnExercisesDir = path.join(process.cwd(), ".learn", "exercises");
|
14
|
+
const exercises = fs.readdirSync(exercisesDir).filter(file => {
|
15
|
+
return fs.statSync(path.join(exercisesDir, file)).isDirectory();
|
16
|
+
});
|
17
|
+
if (exercises.length > 0) {
|
18
|
+
return exercises;
|
19
|
+
}
|
20
|
+
return fs.readdirSync(learnExercisesDir).filter(file => {
|
21
|
+
return fs.statSync(path.join(learnExercisesDir, file)).isDirectory();
|
22
|
+
});
|
23
|
+
};
|
24
|
+
const cleanReadme = (readme) => {
|
25
|
+
// Replace <text> and </text> with nothing
|
26
|
+
return readme.replace(/<text>/g, "").replace(/<\/text>/g, "");
|
27
|
+
};
|
28
|
+
const getReadmeForExercise = async (exercise) => {
|
29
|
+
const readmePath = path.join(process.cwd(), "exercises", exercise, "README.md");
|
30
|
+
return fs.readFileSync(readmePath, "utf8");
|
31
|
+
};
|
32
|
+
const saveTranslatedReadme = async (exercise, languageCode, readme) => {
|
33
|
+
const readmePath = path.join(process.cwd(), "exercises", exercise, `README.${languageCode}.md`);
|
34
|
+
fs.writeFileSync(readmePath, cleanReadme(readme));
|
35
|
+
};
|
36
|
+
class BuildCommand extends SessionCommand_1.default {
|
37
|
+
async init() {
|
38
|
+
const { flags } = this.parse(BuildCommand);
|
39
|
+
await this.initSession(flags);
|
40
|
+
}
|
41
|
+
async run() {
|
42
|
+
var _a, _b;
|
43
|
+
const { flags } = this.parse(BuildCommand);
|
44
|
+
const exercises = await listExercises();
|
45
|
+
const exercisesToTranslate = await prompts([
|
46
|
+
{
|
47
|
+
type: "multiselect",
|
48
|
+
name: "exercises",
|
49
|
+
message: "Select the exercises to translate",
|
50
|
+
choices: exercises.map(exercise => ({
|
51
|
+
title: exercise,
|
52
|
+
value: exercise,
|
53
|
+
})),
|
54
|
+
},
|
55
|
+
{
|
56
|
+
type: "text",
|
57
|
+
name: "output_language",
|
58
|
+
message: "Write the languages to translate to comma separated. Example: English, Spanish, French",
|
59
|
+
initial: "Spanish",
|
60
|
+
},
|
61
|
+
]);
|
62
|
+
const configObject = (_a = this.configManager) === null || _a === void 0 ? void 0 : _a.get();
|
63
|
+
if (configObject) {
|
64
|
+
// build exerises
|
65
|
+
console_1.default.debug("Building exercises");
|
66
|
+
(_b = this.configManager) === null || _b === void 0 ? void 0 : _b.buildIndex();
|
67
|
+
}
|
68
|
+
let sessionPayload = await session_1.default.getPayload();
|
69
|
+
if (!sessionPayload || !sessionPayload.rigobot) {
|
70
|
+
console_1.default.error("You must be logged in to upload a LearnPack package");
|
71
|
+
try {
|
72
|
+
sessionPayload = await session_1.default.login();
|
73
|
+
}
|
74
|
+
catch (error) {
|
75
|
+
console_1.default.error("Error trying to authenticate");
|
76
|
+
console_1.default.error(error.message || error);
|
77
|
+
}
|
78
|
+
}
|
79
|
+
const rigoToken = sessionPayload.rigobot.key;
|
80
|
+
await Promise.all(exercisesToTranslate.exercises.map(async (exercise) => {
|
81
|
+
await Promise.all(exercisesToTranslate.output_language
|
82
|
+
.split(",")
|
83
|
+
.map(async (language) => {
|
84
|
+
const readme = await getReadmeForExercise(exercise);
|
85
|
+
const response = await (0, rigoActions_1.translateExercise)(rigoToken, {
|
86
|
+
text_to_translate: readme,
|
87
|
+
output_language: language,
|
88
|
+
});
|
89
|
+
console.log(response, "RESPONSE");
|
90
|
+
await saveTranslatedReadme(exercise, response.parsed.output_language_code, response.parsed.translation);
|
91
|
+
console_1.default.success(`Translated ${exercise} to ${language} successfully`);
|
92
|
+
}));
|
93
|
+
}));
|
94
|
+
this.exit(0);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
BuildCommand.description = "List all the lessons, the user is able of select many of them to translate to the given languages";
|
98
|
+
exports.default = BuildCommand;
|
package/lib/managers/session.js
CHANGED
@@ -12,4 +12,14 @@ type TGenerateImageParams = {
|
|
12
12
|
};
|
13
13
|
export declare const generateImage: (token: string, { prompt }: TGenerateImageParams) => Promise<any>;
|
14
14
|
export declare function downloadImage(imageUrl: string, savePath: string): Promise<void>;
|
15
|
+
type TTranslateInputs = {
|
16
|
+
text_to_translate: string;
|
17
|
+
output_language: string;
|
18
|
+
};
|
19
|
+
export declare const translateExercise: (token: string, inputs: TTranslateInputs) => Promise<any>;
|
20
|
+
type TGenerateCourseIntroductionInputs = {
|
21
|
+
course_title: string;
|
22
|
+
lessons_context: string;
|
23
|
+
};
|
24
|
+
export declare const generateCourseIntroduction: (token: string, inputs: TGenerateCourseIntroductionInputs) => Promise<any>;
|
15
25
|
export {};
|
package/lib/utils/rigoActions.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.generateImage = exports.hasCreatorPermission = exports.createReadme = exports.getExercisesNames = void 0;
|
3
|
+
exports.generateCourseIntroduction = exports.translateExercise = exports.generateImage = exports.hasCreatorPermission = exports.createReadme = exports.getExercisesNames = void 0;
|
4
4
|
exports.downloadImage = downloadImage;
|
5
5
|
const axios_1 = require("axios");
|
6
6
|
const fs_1 = require("fs");
|
@@ -82,3 +82,31 @@ async function downloadImage(imageUrl, savePath) {
|
|
82
82
|
}
|
83
83
|
});
|
84
84
|
}
|
85
|
+
const translateExercise = async (token, inputs) => {
|
86
|
+
const response = await axios_1.default.post(`${RIGOBOT_HOST}/v1/prompting/completion/159/`, {
|
87
|
+
inputs: inputs,
|
88
|
+
include_purpose_objective: false,
|
89
|
+
execute_async: false,
|
90
|
+
}, {
|
91
|
+
headers: {
|
92
|
+
"Content-Type": "application/json",
|
93
|
+
Authorization: "Token " + token,
|
94
|
+
},
|
95
|
+
});
|
96
|
+
return response.data;
|
97
|
+
};
|
98
|
+
exports.translateExercise = translateExercise;
|
99
|
+
const generateCourseIntroduction = async (token, inputs) => {
|
100
|
+
const response = await axios_1.default.post(`${RIGOBOT_HOST}/v1/prompting/completion/192/`, {
|
101
|
+
inputs: inputs,
|
102
|
+
include_purpose_objective: false,
|
103
|
+
execute_async: false,
|
104
|
+
}, {
|
105
|
+
headers: {
|
106
|
+
"Content-Type": "application/json",
|
107
|
+
Authorization: "Token " + token,
|
108
|
+
},
|
109
|
+
});
|
110
|
+
return response.data;
|
111
|
+
};
|
112
|
+
exports.generateCourseIntroduction = generateCourseIntroduction;
|
package/oclif.manifest.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":"5.0.
|
1
|
+
{"version":"5.0.7","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"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":{"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"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":{},"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":{},"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.7",
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
6
6
|
"contributors": [
|
7
7
|
{
|
package/src/commands/init.ts
CHANGED
@@ -17,6 +17,7 @@ import {
|
|
17
17
|
getExercisesNames,
|
18
18
|
generateImage,
|
19
19
|
downloadImage,
|
20
|
+
generateCourseIntroduction,
|
20
21
|
} from "../utils/rigoActions"
|
21
22
|
|
22
23
|
const slugify = (text: string) => {
|
@@ -52,7 +53,34 @@ function getFilenameFromUrl(url: string) {
|
|
52
53
|
return path.basename(url)
|
53
54
|
}
|
54
55
|
|
55
|
-
|
56
|
+
async function createPreviewReadme(
|
57
|
+
tutorialDir: string,
|
58
|
+
packageInfo: PackageInfo,
|
59
|
+
rigoToken: string,
|
60
|
+
readmeContents: string[]
|
61
|
+
) {
|
62
|
+
const readmeFilename = `README.md`
|
63
|
+
|
64
|
+
const readmeContent = await generateCourseIntroduction(rigoToken, {
|
65
|
+
course_title: packageInfo.title.us,
|
66
|
+
lessons_context: readmeContents.join("\n"),
|
67
|
+
})
|
68
|
+
fs.writeFileSync(path.join(tutorialDir, readmeFilename), readmeContent.answer)
|
69
|
+
}
|
70
|
+
|
71
|
+
type PackageInfo = {
|
72
|
+
grading: string
|
73
|
+
difficulty: string
|
74
|
+
duration: number
|
75
|
+
description: {
|
76
|
+
us: string
|
77
|
+
}
|
78
|
+
title: {
|
79
|
+
us: string
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
56
84
|
Console.info("Almost there! First you need to login to use the AI creator")
|
57
85
|
|
58
86
|
fs.removeSync(path.join(tutorialDir, "exercises", "01-hello-world"))
|
@@ -117,8 +145,20 @@ const handleAILogic = async (tutorialDir: string) => {
|
|
117
145
|
},
|
118
146
|
])
|
119
147
|
|
148
|
+
const packageContext = `
|
149
|
+
\n
|
150
|
+
The following information comes from user inputs
|
151
|
+
Title: ${packageInfo.title.us}
|
152
|
+
Description: ${packageInfo.description.us}
|
153
|
+
Grading: ${packageInfo.grading}
|
154
|
+
Difficulty: ${packageInfo.difficulty}
|
155
|
+
Duration: ${packageInfo.duration}
|
156
|
+
|
157
|
+
Use it to generate more relevant exercises
|
158
|
+
`
|
159
|
+
|
120
160
|
const inputs = {
|
121
|
-
tutorial_about: aiChoices.tutorialAbout,
|
161
|
+
tutorial_about: aiChoices.tutorialAbout + packageContext,
|
122
162
|
number_of_exercises: aiChoices.exercisesNumber,
|
123
163
|
}
|
124
164
|
Console.info("Creating lessons...")
|
@@ -189,6 +229,10 @@ const handleAILogic = async (tutorialDir: string) => {
|
|
189
229
|
"Images generated successfully! 🎉 Your tutorial will be ready soon!"
|
190
230
|
)
|
191
231
|
|
232
|
+
Console.info("Creating preview readme...")
|
233
|
+
|
234
|
+
await createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeContents)
|
235
|
+
|
192
236
|
return true
|
193
237
|
}
|
194
238
|
|
@@ -302,7 +346,7 @@ class InitComand extends BaseCommand {
|
|
302
346
|
await fs.copySync(templatesDir, tutorialDir)
|
303
347
|
|
304
348
|
if (choices.useAI === "yes") {
|
305
|
-
await handleAILogic(tutorialDir)
|
349
|
+
await handleAILogic(tutorialDir, packageInfo)
|
306
350
|
}
|
307
351
|
|
308
352
|
const languages = ["en", "es"]
|
@@ -310,18 +354,26 @@ class InitComand extends BaseCommand {
|
|
310
354
|
// Creating README files
|
311
355
|
for (const language of languages) {
|
312
356
|
const readmeFilename = `README${language !== "en" ? `.${language}` : ""}`
|
313
|
-
const readmeTemplatePath = path.resolve(
|
314
|
-
|
315
|
-
|
316
|
-
)
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
357
|
+
// const readmeTemplatePath = path.resolve(
|
358
|
+
// templatesDir,
|
359
|
+
// `${readmeFilename}.ejs`
|
360
|
+
// )
|
361
|
+
|
362
|
+
// const readmeObject = {
|
363
|
+
// title: packageInfo.title.us,
|
364
|
+
// description: packageInfo.description.us,
|
365
|
+
// grading: packageInfo.grading,
|
366
|
+
// difficulty: packageInfo.difficulty,
|
367
|
+
// duration: packageInfo.duration,
|
368
|
+
// }
|
369
|
+
// const readmeContent = eta.render(
|
370
|
+
// fs.readFileSync(readmeTemplatePath, "utf-8"),
|
371
|
+
// readmeObject
|
372
|
+
// )
|
373
|
+
// fs.writeFileSync(
|
374
|
+
// path.join(tutorialDir, `${readmeFilename}.md`),
|
375
|
+
// readmeContent
|
376
|
+
// )
|
325
377
|
if (fs.existsSync(path.join(tutorialDir, `${readmeFilename}.ejs`)))
|
326
378
|
fs.removeSync(path.join(tutorialDir, `${readmeFilename}.ejs`))
|
327
379
|
}
|
package/src/commands/publish.ts
CHANGED
@@ -1,249 +1,249 @@
|
|
1
|
-
/* eslint-disable arrow-parens */
|
2
|
-
/* eslint-disable unicorn/no-array-for-each */
|
3
|
-
import { flags } from "@oclif/command"
|
4
|
-
import SessionCommand from "../utils/SessionCommand"
|
5
|
-
import SessionManager from "../managers/session"
|
6
|
-
import * as fs from "fs"
|
7
|
-
import * as path from "path"
|
8
|
-
import * as archiver from "archiver"
|
9
|
-
import axios from "axios"
|
10
|
-
import FormData = require("form-data")
|
11
|
-
import Console from "../utils/console"
|
12
|
-
import {
|
13
|
-
decompress,
|
14
|
-
downloadEditor,
|
15
|
-
checkIfDirectoryExists,
|
16
|
-
} from "../managers/file"
|
17
|
-
|
18
|
-
const RIGOBOT_HOST = "https://rigobot.herokuapp.com"
|
19
|
-
// const RIGOBOT_HOST =
|
20
|
-
// "https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us116.gitpod.io"
|
21
|
-
const uploadZipEndpont = RIGOBOT_HOST + "/v1/learnpack/upload"
|
22
|
-
|
23
|
-
export default class BuildCommand extends SessionCommand {
|
24
|
-
static description =
|
25
|
-
"Builds the project by copying necessary files and directories into a zip file"
|
26
|
-
|
27
|
-
static flags = {
|
28
|
-
help: flags.help({ char: "h" }),
|
29
|
-
}
|
30
|
-
|
31
|
-
async init() {
|
32
|
-
const { flags } = this.parse(BuildCommand)
|
33
|
-
await this.initSession(flags)
|
34
|
-
}
|
35
|
-
|
36
|
-
async run() {
|
37
|
-
const buildDir = path.join(process.cwd(), "build")
|
38
|
-
// this.configManager?.clean()
|
39
|
-
|
40
|
-
const configObject = this.configManager?.get()
|
41
|
-
// Console.success("Package cleaned successfully, ready to publish")
|
42
|
-
|
43
|
-
if (configObject) {
|
44
|
-
// build exerises
|
45
|
-
Console.debug("Building exercises")
|
46
|
-
this.configManager?.buildIndex()
|
47
|
-
}
|
48
|
-
|
49
|
-
let sessionPayload = await SessionManager.getPayload()
|
50
|
-
if (!sessionPayload || !sessionPayload.rigobot) {
|
51
|
-
Console.error("You must be logged in to upload a LearnPack package")
|
52
|
-
try {
|
53
|
-
sessionPayload = await SessionManager.login()
|
54
|
-
} catch (error) {
|
55
|
-
Console.error("Error trying to authenticate")
|
56
|
-
Console.error((error as TypeError).message || (error as string))
|
57
|
-
}
|
58
|
-
}
|
59
|
-
|
60
|
-
const rigoToken = sessionPayload.rigobot.key
|
61
|
-
// const rigoToken = "417d612d226a1606ad3a4e94b1881a9f0124b667"
|
62
|
-
|
63
|
-
// Read learn.json to get the slug
|
64
|
-
const learnJsonPath = path.join(process.cwd(), "learn.json")
|
65
|
-
if (!fs.existsSync(learnJsonPath)) {
|
66
|
-
this.error("learn.json not found")
|
67
|
-
}
|
68
|
-
|
69
|
-
const learnJson = JSON.parse(fs.readFileSync(learnJsonPath, "utf-8"))
|
70
|
-
|
71
|
-
const zipFilePath = path.join(process.cwd(), `${learnJson.slug}.zip`)
|
72
|
-
|
73
|
-
// Ensure build directory exists
|
74
|
-
if (!fs.existsSync(buildDir)) {
|
75
|
-
fs.mkdirSync(buildDir)
|
76
|
-
}
|
77
|
-
|
78
|
-
if (configObject) {
|
79
|
-
const { config } = configObject
|
80
|
-
const appAlreadyExists = checkIfDirectoryExists(`${config?.dirPath}/_app`)
|
81
|
-
|
82
|
-
if (!appAlreadyExists) {
|
83
|
-
// download app and decompress
|
84
|
-
await downloadEditor(
|
85
|
-
config?.editor.version,
|
86
|
-
`${config?.dirPath}/app.tar.gz`
|
87
|
-
)
|
88
|
-
|
89
|
-
Console.info("Decompressing LearnPack UI, this may take a minute...")
|
90
|
-
await decompress(
|
91
|
-
`${config?.dirPath}/app.tar.gz`,
|
92
|
-
`${config?.dirPath}/_app/`
|
93
|
-
)
|
94
|
-
}
|
95
|
-
}
|
96
|
-
|
97
|
-
// Copy config.json
|
98
|
-
const configPath = path.join(process.cwd(), ".learn", "config.json")
|
99
|
-
if (fs.existsSync(configPath)) {
|
100
|
-
fs.copyFileSync(configPath, path.join(buildDir, "config.json"))
|
101
|
-
} else {
|
102
|
-
this.error("config.json not found")
|
103
|
-
}
|
104
|
-
|
105
|
-
// Copy .learn/assets directory, if it exists else create it
|
106
|
-
const assetsDir = path.join(process.cwd(), ".learn", "assets")
|
107
|
-
if (fs.existsSync(assetsDir)) {
|
108
|
-
this.copyDirectory(assetsDir, path.join(buildDir, ".learn", "assets"))
|
109
|
-
} else {
|
110
|
-
fs.mkdirSync(path.join(buildDir, ".learn", "assets"), { recursive: true })
|
111
|
-
}
|
112
|
-
|
113
|
-
// Copy .learn/_app directory files to the same level as config.json
|
114
|
-
const appDir = path.join(process.cwd(), ".learn", "_app")
|
115
|
-
if (fs.existsSync(appDir)) {
|
116
|
-
this.copyDirectory(appDir, buildDir)
|
117
|
-
} else {
|
118
|
-
this.error(".learn/_app directory not found")
|
119
|
-
}
|
120
|
-
|
121
|
-
// After copying the _app directory
|
122
|
-
const indexHtmlPath = path.join(appDir, "index.html")
|
123
|
-
const buildIndexHtmlPath = path.join(buildDir, "index.html")
|
124
|
-
|
125
|
-
if (fs.existsSync(indexHtmlPath)) {
|
126
|
-
let indexHtmlContent = fs.readFileSync(indexHtmlPath, "utf-8")
|
127
|
-
|
128
|
-
const description = learnJson.description.us || "LearnPack is awesome!"
|
129
|
-
const title =
|
130
|
-
learnJson.title.us || "LearnPack: Interactive Learning as a Service"
|
131
|
-
|
132
|
-
const previewUrl =
|
133
|
-
learnJson.preview ||
|
134
|
-
"https://raw.githubusercontent.com/learnpack/ide/refs/heads/master/public/learnpack.svg"
|
135
|
-
// Replace placeholders and the <title>Old title </title> tag for a new tag with the title
|
136
|
-
indexHtmlContent = indexHtmlContent
|
137
|
-
.replace(/{{description}}/g, description)
|
138
|
-
.replace(/<title>.*<\/title>/, `<title>${title}</title>`)
|
139
|
-
.replace(/{{title}}/g, title)
|
140
|
-
.replace(/{{preview}}/g, previewUrl)
|
141
|
-
|
142
|
-
// Write the modified content to the build directory
|
143
|
-
fs.writeFileSync(buildIndexHtmlPath, indexHtmlContent)
|
144
|
-
} else {
|
145
|
-
this.error("index.html not found in _app directory")
|
146
|
-
}
|
147
|
-
|
148
|
-
// Copy exercises directory
|
149
|
-
const exercisesDir = path.join(process.cwd(), "exercises")
|
150
|
-
const learnExercisesDir = path.join(process.cwd(), ".learn", "exercises")
|
151
|
-
|
152
|
-
if (fs.existsSync(exercisesDir)) {
|
153
|
-
this.copyDirectory(exercisesDir, path.join(buildDir, "exercises"))
|
154
|
-
} else if (fs.existsSync(learnExercisesDir)) {
|
155
|
-
this.copyDirectory(learnExercisesDir, path.join(buildDir, "exercises"))
|
156
|
-
} else {
|
157
|
-
this.error("exercises directory not found in either location")
|
158
|
-
}
|
159
|
-
|
160
|
-
// Copy learn.json
|
161
|
-
fs.copyFileSync(learnJsonPath, path.join(buildDir, "learn.json"))
|
162
|
-
|
163
|
-
// Create zip file
|
164
|
-
const output = fs.createWriteStream(zipFilePath)
|
165
|
-
const archive = archiver("zip", {
|
166
|
-
zlib: { level: 9 },
|
167
|
-
})
|
168
|
-
|
169
|
-
output.on("close", async () => {
|
170
|
-
this.log(
|
171
|
-
`Build completed: ${zipFilePath} (${archive.pointer()} total bytes)`
|
172
|
-
)
|
173
|
-
// Remove build directory after zip is created
|
174
|
-
// this.removeDirectory(buildDir)
|
175
|
-
Console.debug("Zip file saved in project root")
|
176
|
-
|
177
|
-
const formData = new FormData()
|
178
|
-
formData.append("file", fs.createReadStream(zipFilePath))
|
179
|
-
formData.append("config", JSON.stringify(learnJson))
|
180
|
-
|
181
|
-
try {
|
182
|
-
const res = await axios.post(uploadZipEndpont, formData, {
|
183
|
-
headers: {
|
184
|
-
...formData.getHeaders(),
|
185
|
-
Authorization: `Token ${rigoToken}`,
|
186
|
-
},
|
187
|
-
})
|
188
|
-
console.log(res.data)
|
189
|
-
// Remove the zip file after uploading
|
190
|
-
fs.unlinkSync(zipFilePath)
|
191
|
-
} catch (error) {
|
192
|
-
if (axios.isAxiosError(error)) {
|
193
|
-
if (error.response && error.response.status === 403) {
|
194
|
-
console.error("Error 403:", error.response.data.error)
|
195
|
-
} else if (error.response && error.response.status === 400) {
|
196
|
-
console.error(error.response.data.error)
|
197
|
-
} else {
|
198
|
-
console.error("Error uploading file:", error)
|
199
|
-
}
|
200
|
-
} else {
|
201
|
-
console.error("Error uploading file:", error)
|
202
|
-
}
|
203
|
-
|
204
|
-
fs.unlinkSync(zipFilePath)
|
205
|
-
}
|
206
|
-
})
|
207
|
-
|
208
|
-
archive.on("error", (err: any) => {
|
209
|
-
throw err
|
210
|
-
})
|
211
|
-
|
212
|
-
archive.pipe(output)
|
213
|
-
archive.directory(buildDir, false)
|
214
|
-
await archive.finalize()
|
215
|
-
}
|
216
|
-
|
217
|
-
copyDirectory(src: string, dest: string) {
|
218
|
-
if (!fs.existsSync(dest)) {
|
219
|
-
fs.mkdirSync(dest, { recursive: true })
|
220
|
-
}
|
221
|
-
|
222
|
-
const entries = fs.readdirSync(src, { withFileTypes: true })
|
223
|
-
|
224
|
-
for (const entry of entries) {
|
225
|
-
const srcPath = path.join(src, entry.name)
|
226
|
-
const destPath = path.join(dest, entry.name)
|
227
|
-
|
228
|
-
if (entry.isDirectory()) {
|
229
|
-
this.copyDirectory(srcPath, destPath)
|
230
|
-
} else {
|
231
|
-
fs.copyFileSync(srcPath, destPath)
|
232
|
-
}
|
233
|
-
}
|
234
|
-
}
|
235
|
-
|
236
|
-
removeDirectory(dir: string) {
|
237
|
-
if (fs.existsSync(dir)) {
|
238
|
-
fs.readdirSync(dir).forEach((file) => {
|
239
|
-
const currentPath = path.join(dir, file)
|
240
|
-
if (fs.lstatSync(currentPath).isDirectory()) {
|
241
|
-
this.removeDirectory(currentPath)
|
242
|
-
} else {
|
243
|
-
fs.unlinkSync(currentPath)
|
244
|
-
}
|
245
|
-
})
|
246
|
-
fs.rmdirSync(dir)
|
247
|
-
}
|
248
|
-
}
|
249
|
-
}
|
1
|
+
/* eslint-disable arrow-parens */
|
2
|
+
/* eslint-disable unicorn/no-array-for-each */
|
3
|
+
import { flags } from "@oclif/command"
|
4
|
+
import SessionCommand from "../utils/SessionCommand"
|
5
|
+
import SessionManager from "../managers/session"
|
6
|
+
import * as fs from "fs"
|
7
|
+
import * as path from "path"
|
8
|
+
import * as archiver from "archiver"
|
9
|
+
import axios from "axios"
|
10
|
+
import FormData = require("form-data")
|
11
|
+
import Console from "../utils/console"
|
12
|
+
import {
|
13
|
+
decompress,
|
14
|
+
downloadEditor,
|
15
|
+
checkIfDirectoryExists,
|
16
|
+
} from "../managers/file"
|
17
|
+
|
18
|
+
const RIGOBOT_HOST = "https://rigobot.herokuapp.com"
|
19
|
+
// const RIGOBOT_HOST =
|
20
|
+
// "https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us116.gitpod.io"
|
21
|
+
const uploadZipEndpont = RIGOBOT_HOST + "/v1/learnpack/upload"
|
22
|
+
|
23
|
+
export default class BuildCommand extends SessionCommand {
|
24
|
+
static description =
|
25
|
+
"Builds the project by copying necessary files and directories into a zip file"
|
26
|
+
|
27
|
+
static flags = {
|
28
|
+
help: flags.help({ char: "h" }),
|
29
|
+
}
|
30
|
+
|
31
|
+
async init() {
|
32
|
+
const { flags } = this.parse(BuildCommand)
|
33
|
+
await this.initSession(flags)
|
34
|
+
}
|
35
|
+
|
36
|
+
async run() {
|
37
|
+
const buildDir = path.join(process.cwd(), "build")
|
38
|
+
// this.configManager?.clean()
|
39
|
+
|
40
|
+
const configObject = this.configManager?.get()
|
41
|
+
// Console.success("Package cleaned successfully, ready to publish")
|
42
|
+
|
43
|
+
if (configObject) {
|
44
|
+
// build exerises
|
45
|
+
Console.debug("Building exercises")
|
46
|
+
this.configManager?.buildIndex()
|
47
|
+
}
|
48
|
+
|
49
|
+
let sessionPayload = await SessionManager.getPayload()
|
50
|
+
if (!sessionPayload || !sessionPayload.rigobot) {
|
51
|
+
Console.error("You must be logged in to upload a LearnPack package")
|
52
|
+
try {
|
53
|
+
sessionPayload = await SessionManager.login()
|
54
|
+
} catch (error) {
|
55
|
+
Console.error("Error trying to authenticate")
|
56
|
+
Console.error((error as TypeError).message || (error as string))
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
const rigoToken = sessionPayload.rigobot.key
|
61
|
+
// const rigoToken = "417d612d226a1606ad3a4e94b1881a9f0124b667"
|
62
|
+
|
63
|
+
// Read learn.json to get the slug
|
64
|
+
const learnJsonPath = path.join(process.cwd(), "learn.json")
|
65
|
+
if (!fs.existsSync(learnJsonPath)) {
|
66
|
+
this.error("learn.json not found")
|
67
|
+
}
|
68
|
+
|
69
|
+
const learnJson = JSON.parse(fs.readFileSync(learnJsonPath, "utf-8"))
|
70
|
+
|
71
|
+
const zipFilePath = path.join(process.cwd(), `${learnJson.slug}.zip`)
|
72
|
+
|
73
|
+
// Ensure build directory exists
|
74
|
+
if (!fs.existsSync(buildDir)) {
|
75
|
+
fs.mkdirSync(buildDir)
|
76
|
+
}
|
77
|
+
|
78
|
+
if (configObject) {
|
79
|
+
const { config } = configObject
|
80
|
+
const appAlreadyExists = checkIfDirectoryExists(`${config?.dirPath}/_app`)
|
81
|
+
|
82
|
+
if (!appAlreadyExists) {
|
83
|
+
// download app and decompress
|
84
|
+
await downloadEditor(
|
85
|
+
config?.editor.version,
|
86
|
+
`${config?.dirPath}/app.tar.gz`
|
87
|
+
)
|
88
|
+
|
89
|
+
Console.info("Decompressing LearnPack UI, this may take a minute...")
|
90
|
+
await decompress(
|
91
|
+
`${config?.dirPath}/app.tar.gz`,
|
92
|
+
`${config?.dirPath}/_app/`
|
93
|
+
)
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
// Copy config.json
|
98
|
+
const configPath = path.join(process.cwd(), ".learn", "config.json")
|
99
|
+
if (fs.existsSync(configPath)) {
|
100
|
+
fs.copyFileSync(configPath, path.join(buildDir, "config.json"))
|
101
|
+
} else {
|
102
|
+
this.error("config.json not found")
|
103
|
+
}
|
104
|
+
|
105
|
+
// Copy .learn/assets directory, if it exists else create it
|
106
|
+
const assetsDir = path.join(process.cwd(), ".learn", "assets")
|
107
|
+
if (fs.existsSync(assetsDir)) {
|
108
|
+
this.copyDirectory(assetsDir, path.join(buildDir, ".learn", "assets"))
|
109
|
+
} else {
|
110
|
+
fs.mkdirSync(path.join(buildDir, ".learn", "assets"), { recursive: true })
|
111
|
+
}
|
112
|
+
|
113
|
+
// Copy .learn/_app directory files to the same level as config.json
|
114
|
+
const appDir = path.join(process.cwd(), ".learn", "_app")
|
115
|
+
if (fs.existsSync(appDir)) {
|
116
|
+
this.copyDirectory(appDir, buildDir)
|
117
|
+
} else {
|
118
|
+
this.error(".learn/_app directory not found")
|
119
|
+
}
|
120
|
+
|
121
|
+
// After copying the _app directory
|
122
|
+
const indexHtmlPath = path.join(appDir, "index.html")
|
123
|
+
const buildIndexHtmlPath = path.join(buildDir, "index.html")
|
124
|
+
|
125
|
+
if (fs.existsSync(indexHtmlPath)) {
|
126
|
+
let indexHtmlContent = fs.readFileSync(indexHtmlPath, "utf-8")
|
127
|
+
|
128
|
+
const description = learnJson.description.us || "LearnPack is awesome!"
|
129
|
+
const title =
|
130
|
+
learnJson.title.us || "LearnPack: Interactive Learning as a Service"
|
131
|
+
|
132
|
+
const previewUrl =
|
133
|
+
learnJson.preview ||
|
134
|
+
"https://raw.githubusercontent.com/learnpack/ide/refs/heads/master/public/learnpack.svg"
|
135
|
+
// Replace placeholders and the <title>Old title </title> tag for a new tag with the title
|
136
|
+
indexHtmlContent = indexHtmlContent
|
137
|
+
.replace(/{{description}}/g, description)
|
138
|
+
.replace(/<title>.*<\/title>/, `<title>${title}</title>`)
|
139
|
+
.replace(/{{title}}/g, title)
|
140
|
+
.replace(/{{preview}}/g, previewUrl)
|
141
|
+
|
142
|
+
// Write the modified content to the build directory
|
143
|
+
fs.writeFileSync(buildIndexHtmlPath, indexHtmlContent)
|
144
|
+
} else {
|
145
|
+
this.error("index.html not found in _app directory")
|
146
|
+
}
|
147
|
+
|
148
|
+
// Copy exercises directory
|
149
|
+
const exercisesDir = path.join(process.cwd(), "exercises")
|
150
|
+
const learnExercisesDir = path.join(process.cwd(), ".learn", "exercises")
|
151
|
+
|
152
|
+
if (fs.existsSync(exercisesDir)) {
|
153
|
+
this.copyDirectory(exercisesDir, path.join(buildDir, "exercises"))
|
154
|
+
} else if (fs.existsSync(learnExercisesDir)) {
|
155
|
+
this.copyDirectory(learnExercisesDir, path.join(buildDir, "exercises"))
|
156
|
+
} else {
|
157
|
+
this.error("exercises directory not found in either location")
|
158
|
+
}
|
159
|
+
|
160
|
+
// Copy learn.json
|
161
|
+
fs.copyFileSync(learnJsonPath, path.join(buildDir, "learn.json"))
|
162
|
+
|
163
|
+
// Create zip file
|
164
|
+
const output = fs.createWriteStream(zipFilePath)
|
165
|
+
const archive = archiver("zip", {
|
166
|
+
zlib: { level: 9 },
|
167
|
+
})
|
168
|
+
|
169
|
+
output.on("close", async () => {
|
170
|
+
this.log(
|
171
|
+
`Build completed: ${zipFilePath} (${archive.pointer()} total bytes)`
|
172
|
+
)
|
173
|
+
// Remove build directory after zip is created
|
174
|
+
// this.removeDirectory(buildDir)
|
175
|
+
Console.debug("Zip file saved in project root")
|
176
|
+
|
177
|
+
const formData = new FormData()
|
178
|
+
formData.append("file", fs.createReadStream(zipFilePath))
|
179
|
+
formData.append("config", JSON.stringify(learnJson))
|
180
|
+
|
181
|
+
try {
|
182
|
+
const res = await axios.post(uploadZipEndpont, formData, {
|
183
|
+
headers: {
|
184
|
+
...formData.getHeaders(),
|
185
|
+
Authorization: `Token ${rigoToken}`,
|
186
|
+
},
|
187
|
+
})
|
188
|
+
console.log(res.data)
|
189
|
+
// Remove the zip file after uploading
|
190
|
+
fs.unlinkSync(zipFilePath)
|
191
|
+
} catch (error) {
|
192
|
+
if (axios.isAxiosError(error)) {
|
193
|
+
if (error.response && error.response.status === 403) {
|
194
|
+
console.error("Error 403:", error.response.data.error)
|
195
|
+
} else if (error.response && error.response.status === 400) {
|
196
|
+
console.error(error.response.data.error)
|
197
|
+
} else {
|
198
|
+
console.error("Error uploading file:", error)
|
199
|
+
}
|
200
|
+
} else {
|
201
|
+
console.error("Error uploading file:", error)
|
202
|
+
}
|
203
|
+
|
204
|
+
fs.unlinkSync(zipFilePath)
|
205
|
+
}
|
206
|
+
})
|
207
|
+
|
208
|
+
archive.on("error", (err: any) => {
|
209
|
+
throw err
|
210
|
+
})
|
211
|
+
|
212
|
+
archive.pipe(output)
|
213
|
+
archive.directory(buildDir, false)
|
214
|
+
await archive.finalize()
|
215
|
+
}
|
216
|
+
|
217
|
+
copyDirectory(src: string, dest: string) {
|
218
|
+
if (!fs.existsSync(dest)) {
|
219
|
+
fs.mkdirSync(dest, { recursive: true })
|
220
|
+
}
|
221
|
+
|
222
|
+
const entries = fs.readdirSync(src, { withFileTypes: true })
|
223
|
+
|
224
|
+
for (const entry of entries) {
|
225
|
+
const srcPath = path.join(src, entry.name)
|
226
|
+
const destPath = path.join(dest, entry.name)
|
227
|
+
|
228
|
+
if (entry.isDirectory()) {
|
229
|
+
this.copyDirectory(srcPath, destPath)
|
230
|
+
} else {
|
231
|
+
fs.copyFileSync(srcPath, destPath)
|
232
|
+
}
|
233
|
+
}
|
234
|
+
}
|
235
|
+
|
236
|
+
removeDirectory(dir: string) {
|
237
|
+
if (fs.existsSync(dir)) {
|
238
|
+
fs.readdirSync(dir).forEach((file) => {
|
239
|
+
const currentPath = path.join(dir, file)
|
240
|
+
if (fs.lstatSync(currentPath).isDirectory()) {
|
241
|
+
this.removeDirectory(currentPath)
|
242
|
+
} else {
|
243
|
+
fs.unlinkSync(currentPath)
|
244
|
+
}
|
245
|
+
})
|
246
|
+
fs.rmdirSync(dir)
|
247
|
+
}
|
248
|
+
}
|
249
|
+
}
|
@@ -0,0 +1,140 @@
|
|
1
|
+
import { getExercisesNames } from "../utils/rigoActions"
|
2
|
+
import SessionCommand from "../utils/SessionCommand"
|
3
|
+
import * as fs from "fs"
|
4
|
+
import * as path from "path"
|
5
|
+
import * as prompts from "prompts"
|
6
|
+
import { translateExercise } from "../utils/rigoActions"
|
7
|
+
import SessionManager from "../managers/session"
|
8
|
+
import Console from "../utils/console"
|
9
|
+
import { log } from "node-persist"
|
10
|
+
|
11
|
+
// This function list the names of the exercise directories inside the ./exercises folder, if the exercises folder doesn't exist, it will look for the ./.learn/exercises folder
|
12
|
+
const listExercises = async () => {
|
13
|
+
const exercisesDir = path.join(process.cwd(), "exercises")
|
14
|
+
const learnExercisesDir = path.join(process.cwd(), ".learn", "exercises")
|
15
|
+
|
16
|
+
const exercises = fs.readdirSync(exercisesDir).filter(file => {
|
17
|
+
return fs.statSync(path.join(exercisesDir, file)).isDirectory()
|
18
|
+
})
|
19
|
+
|
20
|
+
if (exercises.length > 0) {
|
21
|
+
return exercises
|
22
|
+
}
|
23
|
+
|
24
|
+
return fs.readdirSync(learnExercisesDir).filter(file => {
|
25
|
+
return fs.statSync(path.join(learnExercisesDir, file)).isDirectory()
|
26
|
+
})
|
27
|
+
}
|
28
|
+
|
29
|
+
const cleanReadme = (readme: string) => {
|
30
|
+
// Replace <text> and </text> with nothing
|
31
|
+
return readme.replace(/<text>/g, "").replace(/<\/text>/g, "")
|
32
|
+
}
|
33
|
+
|
34
|
+
const getReadmeForExercise = async (exercise: string) => {
|
35
|
+
const readmePath = path.join(
|
36
|
+
process.cwd(),
|
37
|
+
"exercises",
|
38
|
+
exercise,
|
39
|
+
"README.md"
|
40
|
+
)
|
41
|
+
return fs.readFileSync(readmePath, "utf8")
|
42
|
+
}
|
43
|
+
|
44
|
+
const saveTranslatedReadme = async (
|
45
|
+
exercise: string,
|
46
|
+
languageCode: string,
|
47
|
+
readme: string
|
48
|
+
) => {
|
49
|
+
const readmePath = path.join(
|
50
|
+
process.cwd(),
|
51
|
+
"exercises",
|
52
|
+
exercise,
|
53
|
+
`README.${languageCode}.md`
|
54
|
+
)
|
55
|
+
fs.writeFileSync(readmePath, cleanReadme(readme))
|
56
|
+
}
|
57
|
+
|
58
|
+
export default class BuildCommand extends SessionCommand {
|
59
|
+
static description =
|
60
|
+
"List all the lessons, the user is able of select many of them to translate to the given languages"
|
61
|
+
|
62
|
+
async init() {
|
63
|
+
const { flags } = this.parse(BuildCommand)
|
64
|
+
await this.initSession(flags)
|
65
|
+
}
|
66
|
+
|
67
|
+
async run() {
|
68
|
+
const { flags } = this.parse(BuildCommand)
|
69
|
+
|
70
|
+
const exercises = await listExercises()
|
71
|
+
|
72
|
+
const exercisesToTranslate = await prompts([
|
73
|
+
{
|
74
|
+
type: "multiselect",
|
75
|
+
name: "exercises",
|
76
|
+
message: "Select the exercises to translate",
|
77
|
+
choices: exercises.map(exercise => ({
|
78
|
+
title: exercise,
|
79
|
+
value: exercise,
|
80
|
+
})),
|
81
|
+
},
|
82
|
+
{
|
83
|
+
type: "text",
|
84
|
+
name: "output_language",
|
85
|
+
message:
|
86
|
+
"Write the languages to translate to comma separated. Example: English, Spanish, French",
|
87
|
+
initial: "Spanish",
|
88
|
+
},
|
89
|
+
])
|
90
|
+
|
91
|
+
const configObject = this.configManager?.get()
|
92
|
+
|
93
|
+
if (configObject) {
|
94
|
+
// build exerises
|
95
|
+
Console.debug("Building exercises")
|
96
|
+
this.configManager?.buildIndex()
|
97
|
+
}
|
98
|
+
|
99
|
+
let sessionPayload = await SessionManager.getPayload()
|
100
|
+
if (!sessionPayload || !sessionPayload.rigobot) {
|
101
|
+
Console.error("You must be logged in to upload a LearnPack package")
|
102
|
+
try {
|
103
|
+
sessionPayload = await SessionManager.login()
|
104
|
+
} catch (error) {
|
105
|
+
Console.error("Error trying to authenticate")
|
106
|
+
Console.error((error as TypeError).message || (error as string))
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
const rigoToken = sessionPayload.rigobot.key
|
111
|
+
|
112
|
+
await Promise.all(
|
113
|
+
exercisesToTranslate.exercises.map(async (exercise: string) => {
|
114
|
+
await Promise.all(
|
115
|
+
exercisesToTranslate.output_language
|
116
|
+
.split(",")
|
117
|
+
.map(async (language: string) => {
|
118
|
+
const readme = await getReadmeForExercise(exercise)
|
119
|
+
const response = await translateExercise(rigoToken, {
|
120
|
+
text_to_translate: readme,
|
121
|
+
output_language: language,
|
122
|
+
})
|
123
|
+
console.log(response, "RESPONSE")
|
124
|
+
|
125
|
+
await saveTranslatedReadme(
|
126
|
+
exercise,
|
127
|
+
response.parsed.output_language_code,
|
128
|
+
response.parsed.translation
|
129
|
+
)
|
130
|
+
Console.success(
|
131
|
+
`Translated ${exercise} to ${language} successfully`
|
132
|
+
)
|
133
|
+
})
|
134
|
+
)
|
135
|
+
})
|
136
|
+
)
|
137
|
+
|
138
|
+
this.exit(0)
|
139
|
+
}
|
140
|
+
}
|
package/src/managers/session.ts
CHANGED
package/src/utils/rigoActions.ts
CHANGED
@@ -119,3 +119,56 @@ export async function downloadImage(
|
|
119
119
|
}
|
120
120
|
})
|
121
121
|
}
|
122
|
+
|
123
|
+
type TTranslateInputs = {
|
124
|
+
text_to_translate: string
|
125
|
+
output_language: string
|
126
|
+
}
|
127
|
+
export const translateExercise = async (
|
128
|
+
token: string,
|
129
|
+
inputs: TTranslateInputs
|
130
|
+
) => {
|
131
|
+
const response = await axios.post(
|
132
|
+
`${RIGOBOT_HOST}/v1/prompting/completion/159/`,
|
133
|
+
{
|
134
|
+
inputs: inputs,
|
135
|
+
include_purpose_objective: false,
|
136
|
+
execute_async: false,
|
137
|
+
},
|
138
|
+
{
|
139
|
+
headers: {
|
140
|
+
"Content-Type": "application/json",
|
141
|
+
Authorization: "Token " + token,
|
142
|
+
},
|
143
|
+
}
|
144
|
+
)
|
145
|
+
|
146
|
+
return response.data
|
147
|
+
}
|
148
|
+
|
149
|
+
type TGenerateCourseIntroductionInputs = {
|
150
|
+
course_title: string
|
151
|
+
lessons_context: string
|
152
|
+
}
|
153
|
+
|
154
|
+
export const generateCourseIntroduction = async (
|
155
|
+
token: string,
|
156
|
+
inputs: TGenerateCourseIntroductionInputs
|
157
|
+
) => {
|
158
|
+
const response = await axios.post(
|
159
|
+
`${RIGOBOT_HOST}/v1/prompting/completion/192/`,
|
160
|
+
{
|
161
|
+
inputs: inputs,
|
162
|
+
include_purpose_objective: false,
|
163
|
+
execute_async: false,
|
164
|
+
},
|
165
|
+
{
|
166
|
+
headers: {
|
167
|
+
"Content-Type": "application/json",
|
168
|
+
Authorization: "Token " + token,
|
169
|
+
},
|
170
|
+
}
|
171
|
+
)
|
172
|
+
|
173
|
+
return response.data
|
174
|
+
}
|