@learnpack/learnpack 5.0.196 → 5.0.202

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.
Files changed (40) hide show
  1. package/README.md +13 -13
  2. package/lib/commands/serve.d.ts +5 -28
  3. package/lib/commands/serve.js +45 -20
  4. package/lib/creatorDist/assets/index-C_HbkVCg.js +38491 -0
  5. package/lib/creatorDist/index.html +1 -1
  6. package/lib/models/creator.d.ts +30 -0
  7. package/lib/models/creator.js +2 -0
  8. package/lib/utils/creatorUtilities.js +3 -2
  9. package/oclif.manifest.json +1 -1
  10. package/package.json +1 -1
  11. package/src/commands/serve.ts +59 -59
  12. package/src/creator/package-lock.json +97 -1
  13. package/src/creator/package.json +3 -0
  14. package/src/creator/src/App.tsx +91 -32
  15. package/src/creator/src/components/FileCard.tsx +2 -2
  16. package/src/creator/src/components/FileUploader.tsx +6 -5
  17. package/src/creator/src/components/LinkUploader.tsx +3 -1
  18. package/src/creator/src/components/Login.tsx +33 -27
  19. package/src/creator/src/components/PurposeSelector.tsx +32 -25
  20. package/src/creator/src/components/StepWizard.tsx +8 -4
  21. package/src/creator/src/components/Uploader.tsx +8 -5
  22. package/src/creator/src/components/syllabus/ContentIndex.tsx +17 -11
  23. package/src/creator/src/components/syllabus/Sidebar.tsx +7 -7
  24. package/src/creator/src/components/syllabus/SyllabusEditor.tsx +79 -76
  25. package/src/creator/src/i18n.ts +28 -0
  26. package/src/creator/src/locales/en.json +110 -0
  27. package/src/creator/src/locales/es.json +110 -0
  28. package/src/creator/src/main.tsx +1 -0
  29. package/src/creator/src/utils/creatorUtils.ts +7 -3
  30. package/src/creator/src/utils/lib.ts +17 -1
  31. package/src/creator/src/utils/store.ts +37 -10
  32. package/src/creatorDist/assets/index-C_HbkVCg.js +38491 -0
  33. package/src/creatorDist/index.html +1 -1
  34. package/src/models/creator.ts +32 -0
  35. package/src/ui/_app/app.css +1 -1
  36. package/src/ui/_app/app.js +55 -55
  37. package/src/ui/app.tar.gz +0 -0
  38. package/src/utils/creatorUtilities.ts +4 -4
  39. package/lib/creatorDist/assets/index-CXaPa6wN.js +0 -35382
  40. package/src/creatorDist/assets/index-CXaPa6wN.js +0 -35382
@@ -10,7 +10,7 @@
10
10
  />
11
11
 
12
12
  <title>Learnpack Creator: Craft tutorials in seconds!</title>
13
- <script type="module" crossorigin src="/creator/assets/index-CXaPa6wN.js"></script>
13
+ <script type="module" crossorigin src="/creator/assets/index-C_HbkVCg.js"></script>
14
14
  <link rel="stylesheet" crossorigin href="/creator/assets/index-Bnq3eZ3T.css">
15
15
  </head>
16
16
  <body>
@@ -0,0 +1,30 @@
1
+ export interface Lesson {
2
+ id: string;
3
+ uid: string;
4
+ title: string;
5
+ type: "READ" | "CODE" | "QUIZ";
6
+ description: string;
7
+ duration?: number;
8
+ }
9
+ export interface ParsedLink {
10
+ name: string;
11
+ text: string;
12
+ }
13
+ export type FormState = {
14
+ description: string;
15
+ duration: number;
16
+ hasContentIndex: boolean;
17
+ contentIndex: string;
18
+ language?: string;
19
+ technologies?: string[];
20
+ isCompleted: boolean;
21
+ variables: string[];
22
+ currentStep: string;
23
+ title: string;
24
+ purpose: string;
25
+ };
26
+ export type Syllabus = {
27
+ lessons: Lesson[];
28
+ courseInfo: FormState;
29
+ sources: ParsedLink[];
30
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -87,11 +87,12 @@ const slugify = (text) => {
87
87
  return text
88
88
  .toString()
89
89
  .normalize("NFD")
90
- .replace(/[\u0300-\u036F]/g, "")
90
+ .replace(/[\u0300-\u036F]/g, "") // Elimina acentos
91
91
  .toLowerCase()
92
92
  .trim()
93
93
  .replace(/\s+/g, "-")
94
- .replace(/[^\w.-]+/g, "");
94
+ .replace(/["*/:<>?\\|]/g, "")
95
+ .replace(/-+/g, "-");
95
96
  };
96
97
  exports.slugify = slugify;
97
98
  const getExInfo = (title) => {
@@ -1 +1 @@
1
- {"version":"5.0.196","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":[]},"serve":{"id":"serve","description":"Runs a small server to build tutorials","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"},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","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.202","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":[]},"serve":{"id":"serve","description":"Runs a small server to build tutorials","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"},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","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.196",
4
+ "version": "5.0.202",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -45,10 +45,31 @@ import { buildConfig, ConfigResponse } from "../utils/configBuilder"
45
45
  import { checkReadability, slugify } from "../utils/creatorUtilities"
46
46
  import { checkAndFixSidebarPure } from "../utils/sidebarGenerator"
47
47
  import { handleAssetCreation } from "./publish"
48
+ import { FormState, Lesson, Syllabus } from "../models/creator"
48
49
  const frontMatter = require("front-matter")
49
50
 
50
51
  dotenv.config()
51
52
 
53
+ const validateLanguage = (language: string) => {
54
+ if (!language || language.length !== 2) {
55
+ return "en"
56
+ }
57
+
58
+ return language
59
+ }
60
+
61
+ function fixSlugLength(slug: string): string {
62
+ let clean = slug.toLowerCase()
63
+ clean = clean.replace(/[^\da-z-]+/g, "-")
64
+ clean = clean.replace(/-+/g, "-")
65
+ clean = clean.slice(0, 49)
66
+ clean = clean.replace(/^-+/, "").replace(/-+$/, "")
67
+ clean = clean.replace(/^[^\da-z]+/, "").replace(/[^\da-z]+$/, "")
68
+ if (!clean) throw new Error("Invalid slug after cleaning")
69
+
70
+ return clean
71
+ }
72
+
52
73
  export const createLearnJson = (courseInfo: FormState) => {
53
74
  console.log("courseInfo to create learn json", courseInfo)
54
75
 
@@ -57,11 +78,19 @@ export const createLearnJson = (courseInfo: FormState) => {
57
78
  )}.learn-pack.com/preview.png`
58
79
  console.log("Preview url in generated learn.json", expectedPreviewUrl)
59
80
 
81
+ const language = courseInfo.language || "en"
82
+
60
83
  const learnJson = {
61
84
  slug: slugify(courseInfo.title as string),
62
- title: {
63
- us: courseInfo.title,
64
- },
85
+ title:
86
+ language === "en" ?
87
+ {
88
+ us: courseInfo.title,
89
+ } :
90
+ {
91
+ [language]: courseInfo.title,
92
+ // us: courseInfo.title,
93
+ },
65
94
  technologies: courseInfo.technologies || [],
66
95
  difficulty: "beginner",
67
96
  description: {
@@ -153,7 +182,7 @@ export async function processExercise(
153
182
  bucket: Bucket,
154
183
  rigoToken: string,
155
184
  steps: Lesson[],
156
- packageContext: string,
185
+ packageContext: FormState,
157
186
  exercise: Lesson,
158
187
  exercisesDir: string,
159
188
  courseSlug: string,
@@ -163,7 +192,11 @@ export async function processExercise(
163
192
  const exSlug = slugify(exercise.id + "-" + exercise.title)
164
193
  console.log("exSlug", exSlug)
165
194
 
166
- const readmeFilename = "README.md"
195
+ const readmeFilename = `README.${
196
+ packageContext.language && packageContext.language !== "en" ?
197
+ `${packageContext.language}.` :
198
+ ""
199
+ }md`
167
200
  const targetDir = `${exercisesDir}/${exSlug}`
168
201
 
169
202
  console.log("✍🏻 Generating lesson", exercise.id, exercise.title)
@@ -184,9 +217,9 @@ export async function processExercise(
184
217
  rigoToken,
185
218
  {
186
219
  title: `${exercise.id} - ${exercise.title}`,
187
- output_lang: "en",
220
+ output_lang: packageContext.language || "en",
188
221
  list_of_exercises: JSON.stringify(steps),
189
- tutorial_description: packageContext,
222
+ tutorial_description: JSON.stringify(packageContext),
190
223
  lesson_description: exercise.description,
191
224
  kind: exercise.type.toLowerCase(),
192
225
  },
@@ -195,7 +228,7 @@ export async function processExercise(
195
228
 
196
229
  const duration = exercise.duration
197
230
  let attempts = 0
198
- let readability = checkReadability(readme.parsed.content, 200, duration || 1)
231
+ let readability = checkReadability(readme.parsed.content, 250, duration || 3)
199
232
 
200
233
  while (
201
234
  readability.fkglResult.fkgl > PARAMS.max_fkgl &&
@@ -220,8 +253,7 @@ export async function processExercise(
220
253
  purposeSlug
221
254
  )
222
255
 
223
- if (!reducedReadme)
224
- break
256
+ if (!reducedReadme) break
225
257
 
226
258
  readability = checkReadability(
227
259
  reducedReadme.parsed.content,
@@ -248,7 +280,7 @@ break
248
280
 
249
281
  const codeFile = await createCodeFile(rigoToken, {
250
282
  readme: readability.newMarkdown,
251
- tutorial_info: packageContext,
283
+ tutorial_info: JSON.stringify(packageContext),
252
284
  })
253
285
  await uploadFileToBucket(
254
286
  bucket,
@@ -265,40 +297,6 @@ break
265
297
  return readability.newMarkdown
266
298
  }
267
299
 
268
- interface Lesson {
269
- id: string
270
- uid: string
271
- title: string
272
- type: "READ" | "CODE" | "QUIZ"
273
- description: string
274
- duration?: number
275
- }
276
-
277
- interface ParsedLink {
278
- name: string
279
- text: string
280
- }
281
- type FormState = {
282
- description: string
283
- duration: number
284
- targetAudience: string
285
- hasContentIndex: boolean
286
- contentIndex: string
287
- sources: ParsedLink[]
288
- isCompleted: boolean
289
- variables: string[]
290
- currentStep: string
291
- title: string
292
- technologies?: string[]
293
- purpose: string
294
- }
295
-
296
- type Syllabus = {
297
- lessons: Lesson[]
298
- courseInfo: FormState
299
- sources: ParsedLink[]
300
- }
301
-
302
300
  const fixPreviewUrl = (slug: string, previewUrl: string) => {
303
301
  if (!previewUrl) {
304
302
  return null
@@ -345,14 +343,6 @@ export default class ServeCommand extends SessionCommand {
345
343
  process.exit(1)
346
344
  }
347
345
 
348
- let deploymentURL = process.env.DEPLOYMENT_URL
349
- if (!deploymentURL) {
350
- console.log("DEPLOYMENT_URL is not set, using default value")
351
- deploymentURL = "https://app.learnpack.co"
352
- }
353
-
354
- console.log("DEPLOYMENT_URL", deploymentURL)
355
-
356
346
  const credentials = JSON.parse(crendsEnv)
357
347
  const bucketStorage = new Storage({
358
348
  credentials,
@@ -969,7 +959,7 @@ export default class ServeCommand extends SessionCommand {
969
959
  bucket,
970
960
  rigoToken,
971
961
  syllabus.lessons,
972
- JSON.stringify(syllabus.courseInfo),
962
+ syllabus.courseInfo,
973
963
  lesson,
974
964
  tutorialDir + "/exercises",
975
965
  slugify(syllabus.courseInfo.title),
@@ -1071,7 +1061,7 @@ export default class ServeCommand extends SessionCommand {
1071
1061
  if (!rigoToken || !bcToken) {
1072
1062
  return res
1073
1063
  .status(400)
1074
- .json({ error: "Faltan tokens o academy/category" })
1064
+ .json({ error: "Authentication failed, missing tokens" })
1075
1065
  }
1076
1066
 
1077
1067
  const { config, exercises }: ConfigResponse = await buildConfig(
@@ -1079,7 +1069,12 @@ export default class ServeCommand extends SessionCommand {
1079
1069
  slug
1080
1070
  )
1081
1071
 
1082
- // 3) Preparar dirs temporales
1072
+ // console.log(slug, "SLUG")
1073
+
1074
+ const fixedSlug = fixSlugLength(slug)
1075
+
1076
+ // console.log(fixedSlug, "FIXED SLUG")
1077
+
1083
1078
  const prefix = `courses/${slug}/`
1084
1079
  const tmpRoot = path.join(os.tmpdir(), `learnpack-${slug}`)
1085
1080
  const buildRoot = path.join(tmpRoot, "build")
@@ -1111,7 +1106,7 @@ export default class ServeCommand extends SessionCommand {
1111
1106
  .replace(/{{description}}/g, config.description.us)
1112
1107
  .replace(
1113
1108
  /{{preview}}/g,
1114
- fixPreviewUrl(slug, config.preview) ||
1109
+ fixPreviewUrl(fixedSlug, "") ||
1115
1110
  "https://raw.githubusercontent.com/learnpack/ide/master/public/learnpack.svg"
1116
1111
  )
1117
1112
  .replace(/{{slug}}/g, slug)
@@ -1141,6 +1136,11 @@ export default class ServeCommand extends SessionCommand {
1141
1136
 
1142
1137
  // 8) Crear el config.json en buildRoot con lo que retorna buildConfig
1143
1138
  const fullConfig = { config, exercises }
1139
+ fullConfig.config.slug = fixedSlug
1140
+ fullConfig.config.preview =
1141
+ fixPreviewUrl(fixedSlug, "") ||
1142
+ "https://raw.githubusercontent.com/learnpack/ide/master/public/learnpack.svg"
1143
+
1144
1144
  fs.writeFileSync(
1145
1145
  path.join(buildRoot, "config.json"),
1146
1146
  JSON.stringify(fullConfig, null, 2),
@@ -1148,7 +1148,7 @@ export default class ServeCommand extends SessionCommand {
1148
1148
  )
1149
1149
 
1150
1150
  // 9) Empaquetar en ZIP (solo contenido de buildRoot)
1151
- const zipName = `${slug}.zip`
1151
+ const zipName = `${fixedSlug}.zip`
1152
1152
  const zipPath = path.join(tmpRoot, zipName)
1153
1153
  const output = fs.createWriteStream(zipPath)
1154
1154
  const archive = archiver("zip", { zlib: { level: 9 } })
@@ -1172,7 +1172,7 @@ export default class ServeCommand extends SessionCommand {
1172
1172
 
1173
1173
  await handleAssetCreation(
1174
1174
  { token: bcToken, rigobotToken: rigoToken },
1175
- config,
1175
+ fullConfig.config,
1176
1176
  rigoRes.data.url
1177
1177
  )
1178
1178
 
@@ -14,6 +14,8 @@
14
14
  "framer-motion": "^12.9.2",
15
15
  "front-matter": "^4.0.2",
16
16
  "html2canvas": "^1.4.1",
17
+ "i18next": "^25.2.1",
18
+ "i18next-browser-languagedetector": "^8.2.0",
17
19
  "js-yaml": "^4.1.0",
18
20
  "mammoth": "^1.9.0",
19
21
  "mitt": "^3.0.1",
@@ -21,6 +23,7 @@
21
23
  "react": "^19.0.0",
22
24
  "react-dom": "^19.0.0",
23
25
  "react-hot-toast": "^2.5.2",
26
+ "react-i18next": "^15.5.2",
24
27
  "react-markdown": "^10.1.0",
25
28
  "react-router": "^7.5.0",
26
29
  "socket.io-client": "^4.8.1",
@@ -45,6 +48,15 @@
45
48
  "vite": "^6.2.0"
46
49
  }
47
50
  },
51
+ "node_modules/@babel/runtime": {
52
+ "version": "7.27.6",
53
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
54
+ "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
55
+ "license": "MIT",
56
+ "engines": {
57
+ "node": ">=6.9.0"
58
+ }
59
+ },
48
60
  "node_modules/@esbuild/aix-ppc64": {
49
61
  "version": "0.25.2",
50
62
  "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
@@ -3613,6 +3625,15 @@
3613
3625
  ],
3614
3626
  "license": "MIT"
3615
3627
  },
3628
+ "node_modules/html-parse-stringify": {
3629
+ "version": "3.0.1",
3630
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
3631
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
3632
+ "license": "MIT",
3633
+ "dependencies": {
3634
+ "void-elements": "3.1.0"
3635
+ }
3636
+ },
3616
3637
  "node_modules/html-url-attributes": {
3617
3638
  "version": "3.0.1",
3618
3639
  "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
@@ -3675,6 +3696,46 @@
3675
3696
  "node": ">= 14"
3676
3697
  }
3677
3698
  },
3699
+ "node_modules/i18next": {
3700
+ "version": "25.2.1",
3701
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.2.1.tgz",
3702
+ "integrity": "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==",
3703
+ "funding": [
3704
+ {
3705
+ "type": "individual",
3706
+ "url": "https://locize.com"
3707
+ },
3708
+ {
3709
+ "type": "individual",
3710
+ "url": "https://locize.com/i18next.html"
3711
+ },
3712
+ {
3713
+ "type": "individual",
3714
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
3715
+ }
3716
+ ],
3717
+ "license": "MIT",
3718
+ "dependencies": {
3719
+ "@babel/runtime": "^7.27.1"
3720
+ },
3721
+ "peerDependencies": {
3722
+ "typescript": "^5"
3723
+ },
3724
+ "peerDependenciesMeta": {
3725
+ "typescript": {
3726
+ "optional": true
3727
+ }
3728
+ }
3729
+ },
3730
+ "node_modules/i18next-browser-languagedetector": {
3731
+ "version": "8.2.0",
3732
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz",
3733
+ "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==",
3734
+ "license": "MIT",
3735
+ "dependencies": {
3736
+ "@babel/runtime": "^7.23.2"
3737
+ }
3738
+ },
3678
3739
  "node_modules/ignore": {
3679
3740
  "version": "5.3.2",
3680
3741
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -5378,6 +5439,32 @@
5378
5439
  "react-dom": ">=16"
5379
5440
  }
5380
5441
  },
5442
+ "node_modules/react-i18next": {
5443
+ "version": "15.5.2",
5444
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz",
5445
+ "integrity": "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==",
5446
+ "license": "MIT",
5447
+ "dependencies": {
5448
+ "@babel/runtime": "^7.25.0",
5449
+ "html-parse-stringify": "^3.0.1"
5450
+ },
5451
+ "peerDependencies": {
5452
+ "i18next": ">= 23.2.3",
5453
+ "react": ">= 16.8.0",
5454
+ "typescript": "^5"
5455
+ },
5456
+ "peerDependenciesMeta": {
5457
+ "react-dom": {
5458
+ "optional": true
5459
+ },
5460
+ "react-native": {
5461
+ "optional": true
5462
+ },
5463
+ "typescript": {
5464
+ "optional": true
5465
+ }
5466
+ }
5467
+ },
5381
5468
  "node_modules/react-markdown": {
5382
5469
  "version": "10.1.0",
5383
5470
  "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
@@ -6021,7 +6108,7 @@
6021
6108
  "version": "5.7.3",
6022
6109
  "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
6023
6110
  "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
6024
- "dev": true,
6111
+ "devOptional": true,
6025
6112
  "license": "Apache-2.0",
6026
6113
  "bin": {
6027
6114
  "tsc": "bin/tsc",
@@ -6317,6 +6404,15 @@
6317
6404
  }
6318
6405
  }
6319
6406
  },
6407
+ "node_modules/void-elements": {
6408
+ "version": "3.1.0",
6409
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
6410
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
6411
+ "license": "MIT",
6412
+ "engines": {
6413
+ "node": ">=0.10.0"
6414
+ }
6415
+ },
6320
6416
  "node_modules/webidl-conversions": {
6321
6417
  "version": "3.0.1",
6322
6418
  "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -17,6 +17,8 @@
17
17
  "framer-motion": "^12.9.2",
18
18
  "front-matter": "^4.0.2",
19
19
  "html2canvas": "^1.4.1",
20
+ "i18next": "^25.2.1",
21
+ "i18next-browser-languagedetector": "^8.2.0",
20
22
  "js-yaml": "^4.1.0",
21
23
  "mammoth": "^1.9.0",
22
24
  "mitt": "^3.0.1",
@@ -24,6 +26,7 @@
24
26
  "react": "^19.0.0",
25
27
  "react-dom": "^19.0.0",
26
28
  "react-hot-toast": "^2.5.2",
29
+ "react-i18next": "^15.5.2",
27
30
  "react-markdown": "^10.1.0",
28
31
  "react-router": "^7.5.0",
29
32
  "socket.io-client": "^4.8.1",