@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.
@@ -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;
@@ -1 +1 @@
1
- {"version":"5.0.50","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":[]}}}
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.50",
4
+ "version": "5.0.52",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -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({
@@ -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?.clean()
24
-
25
- Console.success("Package cleaned successfully, ready to publish")
26
- }
27
- }
28
-
29
- export default CleanCommand
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
@@ -39,9 +39,9 @@ import {
39
39
  import SessionManager from "../managers/session"
40
40
 
41
41
  const durationByKind: Record<string, number> = {
42
- code: 3,
43
- quiz: 2,
44
- read: 1,
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
- fs.writeFileSync(
309
- path.join(tutorialDir, ".learn", "rules", "airules.txt"),
310
- airules
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
- const readmeObject = {
542
- title: packageInfo.title.us,
543
- description: packageInfo.description.us,
544
- grading: packageInfo.grading,
545
- difficulty: packageInfo.difficulty,
546
- duration: packageInfo.duration,
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
- const readmeContent = eta.render(
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
  }
@@ -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
- // const academies = await api.listUserAcademies(sessionPayload.token)
194
+ const academies = await api.listUserAcademies(sessionPayload.token)
152
195
  // // console.log(academies, "academies")
153
- // const academy = await selectAcademy(academies)
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
- // Remove the zip file after uploading
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 => {
@@ -22,6 +22,7 @@ export interface IConfigManager {
22
22
  watchIndex: (onChange: (...args: Array<any>) => void) => void
23
23
  save: () => void
24
24
  getSidebar: () => TSidebar
25
+ // checkAndFixSidebar: () => void
25
26
  noCurrentExercise: () => void
26
27
  getAllExercises: () => IExercise[]
27
28
  }
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
- const academy = role.academy
369
- if (!academiesMap.has(academy.id)) {
370
- academiesMap.set(academy.id, academy)
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
  }
@@ -248,7 +248,7 @@ export function getDesktopFile(fileName: string) {
248
248
 
249
249
  const content = fs.readFileSync(filePath, "utf8")
250
250
  // Delete the file after reading it
251
- fs.unlinkSync(filePath)
251
+ // fs.unlinkSync(filePath)
252
252
  return content
253
253
  }
254
254