@learnpack/learnpack 5.0.26 → 5.0.28
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 +27 -11
- package/lib/commands/breakToken.d.ts +10 -0
- package/lib/commands/breakToken.js +15 -0
- package/lib/commands/init.js +64 -118
- package/lib/commands/publish.js +29 -2
- package/lib/managers/session.js +8 -0
- package/lib/models/session.d.ts +1 -0
- package/lib/utils/api.d.ts +11 -1
- package/lib/utils/api.js +49 -11
- package/lib/utils/creatorUtilities.d.ts +44 -0
- package/lib/utils/creatorUtilities.js +111 -0
- package/lib/utils/rigoActions.d.ts +3 -0
- package/lib/utils/rigoActions.js +11 -0
- package/oclif.manifest.json +1 -1
- package/package.json +3 -1
- package/src/commands/breakToken.ts +25 -0
- package/src/commands/init.ts +108 -179
- package/src/commands/publish.ts +34 -2
- package/src/managers/session.ts +11 -0
- package/src/models/session.ts +1 -0
- package/src/utils/api.ts +60 -12
- package/src/utils/creatorUtilities.ts +147 -0
- package/src/utils/rigoActions.ts +22 -4
package/src/managers/session.ts
CHANGED
@@ -167,6 +167,17 @@ const Session: ISession = {
|
|
167
167
|
this.token = null
|
168
168
|
Console.success("You have logged out")
|
169
169
|
},
|
170
|
+
breakToken: async function () {
|
171
|
+
const payload = await this.getPayload()
|
172
|
+
if (payload) {
|
173
|
+
this.token = "asdasdasdasd"
|
174
|
+
await storage.setItem("bc-payload", {
|
175
|
+
...payload,
|
176
|
+
token: "asdasdsad",
|
177
|
+
})
|
178
|
+
Console.success("Token broken successfully")
|
179
|
+
}
|
180
|
+
},
|
170
181
|
}
|
171
182
|
|
172
183
|
export default Session
|
package/src/models/session.ts
CHANGED
package/src/utils/api.ts
CHANGED
@@ -79,13 +79,12 @@ const login = async (identification: string, password: string) => {
|
|
79
79
|
await cli.wait(1000)
|
80
80
|
const url = `${HOST}/v1/auth/login/`
|
81
81
|
|
82
|
-
const
|
83
|
-
|
84
|
-
|
85
|
-
password: password,
|
86
|
-
}),
|
87
|
-
method: "post",
|
82
|
+
const res = await axios.post(url, {
|
83
|
+
email: identification,
|
84
|
+
password: password,
|
88
85
|
})
|
86
|
+
const data = res.data
|
87
|
+
|
89
88
|
cli.action.stop("ready")
|
90
89
|
let rigoPayload = null
|
91
90
|
try {
|
@@ -320,7 +319,7 @@ export const countConsumables = (
|
|
320
319
|
return consumable ? consumable.balance.unit : 0
|
321
320
|
}
|
322
321
|
|
323
|
-
export const
|
322
|
+
export const getConsumable = async (
|
324
323
|
token: string,
|
325
324
|
consumableSlug: TConsumableSlug = "ai-generation"
|
326
325
|
): Promise<any> => {
|
@@ -333,18 +332,65 @@ export const getConsumables = async (
|
|
333
332
|
try {
|
334
333
|
const response = await axios.get(url, { headers })
|
335
334
|
|
336
|
-
const
|
337
|
-
response.data,
|
338
|
-
consumableSlug
|
339
|
-
)
|
335
|
+
const count = countConsumables(response.data, consumableSlug)
|
340
336
|
|
341
|
-
return {
|
337
|
+
return { count }
|
342
338
|
} catch (error) {
|
343
339
|
console.error("Error fetching consumables:", error)
|
344
340
|
throw error
|
345
341
|
}
|
346
342
|
}
|
347
343
|
|
344
|
+
export interface TAcademy {
|
345
|
+
id: number
|
346
|
+
name: string
|
347
|
+
slug: string
|
348
|
+
timezone: string
|
349
|
+
}
|
350
|
+
|
351
|
+
export const listUserAcademies = async (
|
352
|
+
breathecodeToken: string
|
353
|
+
): Promise<TAcademy[]> => {
|
354
|
+
const url = "https://breathecode.herokuapp.com/v1/auth/user/me"
|
355
|
+
|
356
|
+
try {
|
357
|
+
const response = await axios.get(url, {
|
358
|
+
headers: {
|
359
|
+
Authorization: `Token ${breathecodeToken}`,
|
360
|
+
},
|
361
|
+
})
|
362
|
+
|
363
|
+
const data = response.data
|
364
|
+
|
365
|
+
const academiesMap = new Map<number, TAcademy>()
|
366
|
+
for (const role of data.roles) {
|
367
|
+
const academy = role.academy
|
368
|
+
if (!academiesMap.has(academy.id)) {
|
369
|
+
academiesMap.set(academy.id, academy)
|
370
|
+
}
|
371
|
+
}
|
372
|
+
|
373
|
+
return [...academiesMap.values()]
|
374
|
+
} catch (error) {
|
375
|
+
console.error("Failed to fetch user academies:", error)
|
376
|
+
return []
|
377
|
+
}
|
378
|
+
}
|
379
|
+
|
380
|
+
export const validateToken = async (token: string) => {
|
381
|
+
const url = "https://breathecode.herokuapp.com/v1/auth/user/me"
|
382
|
+
const headers = {
|
383
|
+
Authorization: `Token ${token}`,
|
384
|
+
}
|
385
|
+
|
386
|
+
try {
|
387
|
+
const response = await axios.get(url, { headers })
|
388
|
+
return response.data
|
389
|
+
} catch {
|
390
|
+
return false
|
391
|
+
}
|
392
|
+
}
|
393
|
+
|
348
394
|
export default {
|
349
395
|
login,
|
350
396
|
publish,
|
@@ -354,4 +400,6 @@ export default {
|
|
354
400
|
getAllPackages,
|
355
401
|
sendBatchTelemetry,
|
356
402
|
sendStreamTelemetry,
|
403
|
+
listUserAcademies,
|
404
|
+
validateToken,
|
357
405
|
}
|
@@ -0,0 +1,147 @@
|
|
1
|
+
// eslint-disable-next-line
|
2
|
+
const frontMatter = require("front-matter")
|
3
|
+
import * as path from "path"
|
4
|
+
|
5
|
+
import * as yaml from "js-yaml"
|
6
|
+
|
7
|
+
type TEstimateReadingTimeReturns = {
|
8
|
+
minutes: number
|
9
|
+
words: number
|
10
|
+
}
|
11
|
+
|
12
|
+
export const estimateReadingTime = (
|
13
|
+
text: string,
|
14
|
+
wordsPerMinute = 150
|
15
|
+
): TEstimateReadingTimeReturns => {
|
16
|
+
const words = text.trim().split(/\s+/).length
|
17
|
+
const minutes = words / wordsPerMinute
|
18
|
+
|
19
|
+
if (minutes < 1) {
|
20
|
+
if (words === 0)
|
21
|
+
return {
|
22
|
+
minutes: 1,
|
23
|
+
words,
|
24
|
+
}
|
25
|
+
} else {
|
26
|
+
return {
|
27
|
+
minutes,
|
28
|
+
words,
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
return {
|
33
|
+
minutes: 1,
|
34
|
+
words,
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
export type PackageInfo = {
|
39
|
+
grading: string
|
40
|
+
difficulty: string
|
41
|
+
duration: number
|
42
|
+
description: {
|
43
|
+
us: string
|
44
|
+
}
|
45
|
+
title: {
|
46
|
+
us: string
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
export function checkReadingTime(
|
51
|
+
markdown: string,
|
52
|
+
wordsPerMinute = 150
|
53
|
+
): { newMarkdown: string; exceedsThreshold: boolean } {
|
54
|
+
const parsed = frontMatter(markdown)
|
55
|
+
const readingTime = estimateReadingTime(parsed.body, wordsPerMinute)
|
56
|
+
let attributes = parsed.attributes ? parsed.attributes : {}
|
57
|
+
|
58
|
+
if (typeof parsed.attributes !== "object") {
|
59
|
+
attributes = {}
|
60
|
+
}
|
61
|
+
|
62
|
+
const updatedAttributes = {
|
63
|
+
...attributes,
|
64
|
+
readingTime,
|
65
|
+
}
|
66
|
+
|
67
|
+
// Convert the front matter back to a proper YAML string
|
68
|
+
const yamlFrontMatter = yaml.dump(updatedAttributes).trim()
|
69
|
+
|
70
|
+
// Reconstruct the markdown with the front matter
|
71
|
+
const newMarkdown = `---\n${yamlFrontMatter}\n---\n\n${parsed.body}`
|
72
|
+
|
73
|
+
return {
|
74
|
+
newMarkdown,
|
75
|
+
exceedsThreshold: readingTime.minutes > wordsPerMinute,
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
const slugify = (text: string) => {
|
80
|
+
return text
|
81
|
+
.toString()
|
82
|
+
.normalize("NFD")
|
83
|
+
.replace(/[\u0300-\u036F]/g, "")
|
84
|
+
.toLowerCase()
|
85
|
+
.trim()
|
86
|
+
.replace(/\s+/g, "-")
|
87
|
+
.replace(/[^\w-]+/g, "")
|
88
|
+
}
|
89
|
+
|
90
|
+
export const getExInfo = (title: string) => {
|
91
|
+
// Example title: '1.0 - Introduction to AI [READ: Small introduction to important concepts such as AI, machine learning, and their applications]'
|
92
|
+
let [exNumber, exTitle] = title.split(" - ")
|
93
|
+
|
94
|
+
// Extract kind and description
|
95
|
+
const kindMatch = exTitle.match(/\[(.*?):(.*?)]/)
|
96
|
+
const kind = kindMatch ? kindMatch[1].trim().toLowerCase() : "read"
|
97
|
+
const description = kindMatch ? kindMatch[2].trim() : ""
|
98
|
+
|
99
|
+
exNumber = exNumber.trim()
|
100
|
+
// Clean title
|
101
|
+
exTitle = exTitle.replace(kindMatch?.[0] || "", "").trim()
|
102
|
+
exTitle = slugify(exTitle)
|
103
|
+
|
104
|
+
return {
|
105
|
+
exNumber,
|
106
|
+
kind,
|
107
|
+
description,
|
108
|
+
exTitle,
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
export function extractImagesFromMarkdown(markdown: string) {
|
113
|
+
const imageRegex = /!\[([^\]]*)]\(([^)]+)\)/g
|
114
|
+
const images = []
|
115
|
+
let match
|
116
|
+
|
117
|
+
while ((match = imageRegex.exec(markdown)) !== null) {
|
118
|
+
const altText = match[1]
|
119
|
+
const url = match[2]
|
120
|
+
images.push({ alt: altText, url: url })
|
121
|
+
}
|
122
|
+
|
123
|
+
return images
|
124
|
+
}
|
125
|
+
|
126
|
+
export function getFilenameFromUrl(url: string) {
|
127
|
+
return path.basename(url)
|
128
|
+
}
|
129
|
+
|
130
|
+
export const makePackageInfo = (choices: any) => {
|
131
|
+
const packageInfo = {
|
132
|
+
grading: choices.grading,
|
133
|
+
difficulty: choices.difficulty,
|
134
|
+
duration: parseInt(choices.duration),
|
135
|
+
description: {
|
136
|
+
us: choices.description,
|
137
|
+
},
|
138
|
+
title: {
|
139
|
+
us: choices.title,
|
140
|
+
},
|
141
|
+
slug: choices.title
|
142
|
+
.toLowerCase()
|
143
|
+
.replace(/ /g, "-")
|
144
|
+
.replace(/[^\w-]+/g, ""),
|
145
|
+
}
|
146
|
+
return packageInfo
|
147
|
+
}
|
package/src/utils/rigoActions.ts
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
import axios from "axios"
|
2
2
|
import { writeFile } from "fs"
|
3
3
|
import Console from "../utils/console"
|
4
|
+
import { PackageInfo } from "./creatorUtilities"
|
5
|
+
import * as fs from "fs"
|
6
|
+
import * as path from "path"
|
4
7
|
|
5
8
|
const RIGOBOT_HOST = "https://rigobot.herokuapp.com"
|
6
9
|
|
@@ -188,6 +191,7 @@ export const interactiveCreation = async (
|
|
188
191
|
|
189
192
|
type TCreateCodeFileInputs = {
|
190
193
|
readme: string
|
194
|
+
tutorial_info: string
|
191
195
|
}
|
192
196
|
|
193
197
|
export const createCodeFile = async (
|
@@ -262,7 +266,7 @@ export const readmeCreator = async (
|
|
262
266
|
return createReadme(token, createReadmeInputs)
|
263
267
|
}
|
264
268
|
|
265
|
-
|
269
|
+
if (inputs.kind === "code") {
|
266
270
|
return createCodingReadme(token, {
|
267
271
|
title: inputs.title,
|
268
272
|
output_lang: inputs.output_lang,
|
@@ -271,7 +275,21 @@ export const readmeCreator = async (
|
|
271
275
|
lesson_description: inputs.lesson_description,
|
272
276
|
})
|
273
277
|
}
|
274
|
-
|
275
|
-
|
276
|
-
|
278
|
+
|
279
|
+
throw new Error("Invalid kind of lesson")
|
280
|
+
}
|
281
|
+
|
282
|
+
export async function createPreviewReadme(
|
283
|
+
tutorialDir: string,
|
284
|
+
packageInfo: PackageInfo,
|
285
|
+
rigoToken: string,
|
286
|
+
readmeContents: string[]
|
287
|
+
) {
|
288
|
+
const readmeFilename = `README.md`
|
289
|
+
|
290
|
+
const readmeContent = await generateCourseIntroduction(rigoToken, {
|
291
|
+
course_title: packageInfo.title.us,
|
292
|
+
lessons_context: readmeContents.join("\n"),
|
293
|
+
})
|
294
|
+
fs.writeFileSync(path.join(tutorialDir, readmeFilename), readmeContent.answer)
|
277
295
|
}
|