@learnpack/learnpack 5.0.50 → 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 +38 -7
- package/lib/managers/config/index.js +16 -1
- package/lib/utils/api.d.ts +20 -0
- package/lib/utils/api.js +95 -4
- 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 +56 -6
- package/src/managers/config/index.ts +26 -7
- package/src/models/config-manager.ts +1 -0
- package/src/utils/api.ts +120 -3
- 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
@@ -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,9 +18,52 @@ 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
|
|
25
|
+
const handleAssetCreation = async (
|
26
|
+
sessionPayload: any,
|
27
|
+
academy: TAcademy,
|
28
|
+
learnJson: any,
|
29
|
+
learnpackDeployUrl: string
|
30
|
+
) => {
|
31
|
+
try {
|
32
|
+
const { exists, academyId } = await api.doesAssetExists(
|
33
|
+
sessionPayload.token,
|
34
|
+
learnJson.slug
|
35
|
+
)
|
36
|
+
if (!exists) {
|
37
|
+
Console.info("Asset does not exist in this academy, creating it")
|
38
|
+
const asset = await api.createAsset(sessionPayload.token, academy.id, {
|
39
|
+
slug: learnJson.slug,
|
40
|
+
title: learnJson.title.us,
|
41
|
+
lang: "us",
|
42
|
+
description: learnJson.description.us,
|
43
|
+
learnpack_deploy_url: learnpackDeployUrl,
|
44
|
+
technologies: ["node", "bash"],
|
45
|
+
url: "https://4geeksacademy.com",
|
46
|
+
})
|
47
|
+
Console.info("Asset created with id", asset.id)
|
48
|
+
} else {
|
49
|
+
Console.info("Asset exists, updating it")
|
50
|
+
const asset = await api.updateAsset(
|
51
|
+
sessionPayload.token,
|
52
|
+
academyId as number,
|
53
|
+
learnJson.slug,
|
54
|
+
{
|
55
|
+
learnpack_deploy_url: learnpackDeployUrl,
|
56
|
+
title: learnJson.title.us,
|
57
|
+
description: learnJson.description.us,
|
58
|
+
}
|
59
|
+
)
|
60
|
+
Console.info("Asset updated with id", asset.id)
|
61
|
+
}
|
62
|
+
} catch (error) {
|
63
|
+
Console.error("Error updating or creating asset:", error)
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
24
67
|
const runAudit = (strict: boolean) => {
|
25
68
|
try {
|
26
69
|
Console.info("Running learnpack audit before publishing...")
|
@@ -148,10 +191,9 @@ class BuildCommand extends SessionCommand {
|
|
148
191
|
this.configManager?.buildIndex()
|
149
192
|
}
|
150
193
|
|
151
|
-
|
194
|
+
const academies = await api.listUserAcademies(sessionPayload.token)
|
152
195
|
// // console.log(academies, "academies")
|
153
|
-
|
154
|
-
// console.log(academy, "academy")
|
196
|
+
const academy = await selectAcademy(academies)
|
155
197
|
|
156
198
|
// Read learn.json to get the slug
|
157
199
|
const learnJsonPath = path.join(process.cwd(), "learn.json")
|
@@ -234,6 +276,7 @@ class BuildCommand extends SessionCommand {
|
|
234
276
|
.replace(/{{title}}/g, title)
|
235
277
|
.replace(/{{preview}}/g, previewUrl)
|
236
278
|
.replace(/{{slug}}/g, learnJson.slug)
|
279
|
+
.replace(/{{duration}}/g, minutesToISO8601Duration(learnJson.duration))
|
237
280
|
|
238
281
|
// Write the modified content to the build directory
|
239
282
|
fs.writeFileSync(buildIndexHtmlPath, indexHtmlContent)
|
@@ -306,9 +349,16 @@ class BuildCommand extends SessionCommand {
|
|
306
349
|
},
|
307
350
|
})
|
308
351
|
console.log(res.data)
|
309
|
-
|
352
|
+
|
310
353
|
fs.unlinkSync(zipFilePath)
|
311
354
|
this.removeDirectory(buildDir)
|
355
|
+
|
356
|
+
await handleAssetCreation(
|
357
|
+
sessionPayload,
|
358
|
+
academy,
|
359
|
+
learnJson,
|
360
|
+
res.data.url
|
361
|
+
)
|
312
362
|
} catch (error) {
|
313
363
|
if (axios.isAxiosError(error)) {
|
314
364
|
if (error.response && error.response.status === 403) {
|
@@ -322,8 +372,8 @@ class BuildCommand extends SessionCommand {
|
|
322
372
|
console.error("Error uploading file:", error)
|
323
373
|
}
|
324
374
|
|
325
|
-
fs.unlinkSync(zipFilePath)
|
326
|
-
this.removeDirectory(buildDir)
|
375
|
+
// fs.unlinkSync(zipFilePath)
|
376
|
+
// this.removeDirectory(buildDir)
|
327
377
|
}
|
328
378
|
})
|
329
379
|
|
@@ -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/api.ts
CHANGED
@@ -349,6 +349,15 @@ export interface TAcademy {
|
|
349
349
|
timezone: string
|
350
350
|
}
|
351
351
|
|
352
|
+
const with_crud_asset_roles = new Set([
|
353
|
+
"content_writer",
|
354
|
+
"growth_manager",
|
355
|
+
"syllabus_coordinator",
|
356
|
+
"country_manager",
|
357
|
+
"community_manager",
|
358
|
+
"carrer_support_head",
|
359
|
+
])
|
360
|
+
|
352
361
|
export const listUserAcademies = async (
|
353
362
|
breathecodeToken: string
|
354
363
|
): Promise<TAcademy[]> => {
|
@@ -364,10 +373,14 @@ export const listUserAcademies = async (
|
|
364
373
|
const data = response.data
|
365
374
|
|
366
375
|
const academiesMap = new Map<number, TAcademy>()
|
376
|
+
|
367
377
|
for (const role of data.roles) {
|
368
|
-
|
369
|
-
if (
|
370
|
-
|
378
|
+
// Only add academies where the user's role is in the whitelist
|
379
|
+
if (with_crud_asset_roles.has(role.role)) {
|
380
|
+
const academy = role.academy
|
381
|
+
if (!academiesMap.has(academy.id)) {
|
382
|
+
academiesMap.set(academy.id, academy)
|
383
|
+
}
|
371
384
|
}
|
372
385
|
}
|
373
386
|
|
@@ -392,6 +405,107 @@ export const validateToken = async (token: string) => {
|
|
392
405
|
}
|
393
406
|
}
|
394
407
|
|
408
|
+
type TAssetMissing = {
|
409
|
+
slug: string
|
410
|
+
title: string
|
411
|
+
lang: string
|
412
|
+
url: string
|
413
|
+
description: string
|
414
|
+
learnpack_deploy_url: string
|
415
|
+
technologies: string[]
|
416
|
+
}
|
417
|
+
|
418
|
+
export const createAsset = async (
|
419
|
+
token: string,
|
420
|
+
academyId: number,
|
421
|
+
asset: TAssetMissing
|
422
|
+
) => {
|
423
|
+
const body = {
|
424
|
+
slug: asset.slug,
|
425
|
+
title: asset.title,
|
426
|
+
lang: asset.lang,
|
427
|
+
asset_type: "EXERCISE",
|
428
|
+
visibility: "PUBLIC",
|
429
|
+
status: "PUBLISHED",
|
430
|
+
url: "https://4geeksacademy.com",
|
431
|
+
readme_url: null,
|
432
|
+
difficulty: null,
|
433
|
+
duration: null,
|
434
|
+
graded: false,
|
435
|
+
gitpod: true,
|
436
|
+
category: 7,
|
437
|
+
preview: null,
|
438
|
+
description: asset.description,
|
439
|
+
external: true,
|
440
|
+
interactive: true,
|
441
|
+
solution_video_url: null,
|
442
|
+
intro_video_url: null,
|
443
|
+
translations: ["us"],
|
444
|
+
learnpack_deploy_url: asset.learnpack_deploy_url,
|
445
|
+
technologies: asset.technologies,
|
446
|
+
}
|
447
|
+
const url = `https://breathecode.herokuapp.com/v1/registry/academy/asset`
|
448
|
+
const headers = {
|
449
|
+
Authorization: `Token ${token}`,
|
450
|
+
Academy: academyId,
|
451
|
+
}
|
452
|
+
|
453
|
+
try {
|
454
|
+
const response = await axios.post(url, body, { headers })
|
455
|
+
return response.data
|
456
|
+
} catch (error: any) {
|
457
|
+
// console.error("Failed to create asset:", error)
|
458
|
+
throw error.response.data
|
459
|
+
}
|
460
|
+
}
|
461
|
+
|
462
|
+
export const doesAssetExists = async (
|
463
|
+
token: string,
|
464
|
+
assetSlug: string
|
465
|
+
): Promise<{ exists: boolean; academyId?: number }> => {
|
466
|
+
const url = `https://breathecode.herokuapp.com/v1/registry/asset/${assetSlug}`
|
467
|
+
const headers = {
|
468
|
+
Authorization: `Token ${token}`,
|
469
|
+
}
|
470
|
+
|
471
|
+
try {
|
472
|
+
const response = await axios.get(url, { headers })
|
473
|
+
if (response.status === 200) {
|
474
|
+
const data = response.data
|
475
|
+
const academy = data.academy.id
|
476
|
+
return { exists: true, academyId: academy }
|
477
|
+
}
|
478
|
+
|
479
|
+
return { exists: false }
|
480
|
+
|
481
|
+
} catch (error) {
|
482
|
+
console.error("Failed to get asset:", error)
|
483
|
+
return { exists: false }
|
484
|
+
}
|
485
|
+
}
|
486
|
+
|
487
|
+
const updateAsset = async (
|
488
|
+
token: string,
|
489
|
+
academyId: number,
|
490
|
+
assetSlug: string,
|
491
|
+
asset: Partial<TAssetMissing>
|
492
|
+
) => {
|
493
|
+
const url = `https://breathecode.herokuapp.com/v1/registry/academy/asset/${assetSlug}`
|
494
|
+
const headers = {
|
495
|
+
Authorization: `Token ${token}`,
|
496
|
+
Academy: academyId,
|
497
|
+
}
|
498
|
+
try {
|
499
|
+
const response = await axios.put(url, asset, { headers })
|
500
|
+
return response.data
|
501
|
+
} catch (error: any) {
|
502
|
+
// console.error("Failed to update asset:", error)
|
503
|
+
// Try to print the data
|
504
|
+
// console.log(error.response.data)
|
505
|
+
throw error.response.data
|
506
|
+
}
|
507
|
+
}
|
508
|
+
|
395
509
|
export default {
|
396
510
|
login,
|
397
511
|
publish,
|
@@ -403,4 +517,7 @@ export default {
|
|
403
517
|
sendStreamTelemetry,
|
404
518
|
listUserAcademies,
|
405
519
|
validateToken,
|
520
|
+
createAsset,
|
521
|
+
doesAssetExists,
|
522
|
+
updateAsset,
|
406
523
|
}
|