@learnpack/learnpack 5.0.29 → 5.0.31
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 +15 -15
- package/lib/commands/init.js +39 -15
- package/lib/commands/logout.js +6 -11
- package/lib/commands/start.js +0 -3
- package/lib/commands/translate.js +2 -10
- package/lib/managers/config/index.js +102 -77
- package/lib/managers/file.js +1 -1
- package/lib/managers/server/routes.js +68 -0
- package/lib/managers/session.js +3 -0
- package/lib/models/config-manager.d.ts +2 -0
- package/lib/utils/creatorUtilities.d.ts +2 -0
- package/lib/utils/creatorUtilities.js +48 -7
- package/lib/utils/rigoActions.d.ts +1 -0
- package/lib/utils/rigoActions.js +13 -2
- package/oclif.manifest.json +1 -1
- package/package.json +3 -2
- package/src/commands/audit.ts +449 -449
- package/src/commands/breakToken.ts +36 -36
- package/src/commands/init.ts +48 -17
- package/src/commands/logout.ts +38 -43
- package/src/commands/publish.ts +312 -312
- package/src/commands/start.ts +0 -3
- package/src/commands/translate.ts +1 -20
- package/src/managers/config/index.ts +742 -715
- package/src/managers/file.ts +1 -1
- package/src/managers/server/routes.ts +103 -1
- package/src/managers/session.ts +4 -0
- package/src/models/config-manager.ts +25 -23
- package/src/utils/console.ts +24 -24
- package/src/utils/creatorUtilities.ts +66 -6
- package/src/utils/rigoActions.ts +13 -1
- package/src/utils/templates/incremental/.github/workflows/learnpack-audit.yml +29 -0
- package/src/utils/templates/isolated/.github/workflows/learnpack-audit.yml +29 -0
package/src/managers/file.ts
CHANGED
@@ -123,7 +123,7 @@ export const download = (url: string, dest: string) => {
|
|
123
123
|
})
|
124
124
|
file.on("error", err => {
|
125
125
|
file.close()
|
126
|
-
if (err.
|
126
|
+
if (err.name === "EEXIST") {
|
127
127
|
Console.debug("File already exists")
|
128
128
|
resolve("File already exists")
|
129
129
|
} else {
|
@@ -12,7 +12,9 @@ import { IConfigManager } from "../../models/config-manager"
|
|
12
12
|
import { IExercise } from "../../models/exercise-obj"
|
13
13
|
import SessionManager from "../../managers/session"
|
14
14
|
import TelemetryManager from "../telemetry"
|
15
|
-
import {
|
15
|
+
import { saveTranslatedReadme } from "../../utils/creatorUtilities"
|
16
|
+
import { translateExercise } from "../../utils/rigoActions"
|
17
|
+
// import { eventManager } from "../../utils/osOperations"
|
16
18
|
|
17
19
|
const withHandler =
|
18
20
|
(func: (req: express.Request, res: express.Response) => void) =>
|
@@ -381,6 +383,106 @@ throw new Error("File not found: " + filePath)
|
|
381
383
|
})
|
382
384
|
)
|
383
385
|
|
386
|
+
app.delete(
|
387
|
+
"/exercise/:slug/delete",
|
388
|
+
withHandler(async (req: express.Request, res: express.Response) => {
|
389
|
+
const exerciseDeleted = configManager.deleteExercise(req.params.slug)
|
390
|
+
if (exerciseDeleted) {
|
391
|
+
configManager.buildIndex()
|
392
|
+
res.json({ status: "ok" })
|
393
|
+
} else {
|
394
|
+
res.status(500).json({ error: "Failed to delete exercise" })
|
395
|
+
}
|
396
|
+
})
|
397
|
+
)
|
398
|
+
|
399
|
+
app.post(
|
400
|
+
"/actions/translate",
|
401
|
+
jsonBodyParser,
|
402
|
+
withHandler(async (req: express.Request, res: express.Response) => {
|
403
|
+
const { exerciseSlugs, languages } = req.body
|
404
|
+
|
405
|
+
const session = await SessionManager.getPayload()
|
406
|
+
const rigoToken = session?.rigobot?.key
|
407
|
+
|
408
|
+
if (!rigoToken) {
|
409
|
+
return res.status(400).json({ error: "RigoToken not found" })
|
410
|
+
}
|
411
|
+
|
412
|
+
const languagesToTranslate: string[] = languages.split(",")
|
413
|
+
|
414
|
+
try {
|
415
|
+
await Promise.all(
|
416
|
+
exerciseSlugs.map(async (slug: string) => {
|
417
|
+
const exercise = configManager.getExercise(slug)
|
418
|
+
if (!exercise) {
|
419
|
+
throw new Error(`Exercise ${slug} not found`)
|
420
|
+
}
|
421
|
+
|
422
|
+
if (exercise.getReadme) {
|
423
|
+
const readme = exercise.getReadme(null)
|
424
|
+
|
425
|
+
await Promise.all(
|
426
|
+
languagesToTranslate.map(async (language: string) => {
|
427
|
+
const response = await translateExercise(rigoToken, {
|
428
|
+
text_to_translate: readme.body,
|
429
|
+
output_language: language,
|
430
|
+
})
|
431
|
+
|
432
|
+
await saveTranslatedReadme(
|
433
|
+
slug,
|
434
|
+
response.parsed.output_language_code,
|
435
|
+
response.parsed.translation
|
436
|
+
)
|
437
|
+
|
438
|
+
Console.success(
|
439
|
+
`Translated ${slug} to ${language} successfully`
|
440
|
+
)
|
441
|
+
})
|
442
|
+
)
|
443
|
+
}
|
444
|
+
})
|
445
|
+
)
|
446
|
+
|
447
|
+
configManager.buildIndex()
|
448
|
+
|
449
|
+
return res.status(200).json({ message: "Translated exercises" })
|
450
|
+
} catch (error) {
|
451
|
+
console.log(error, "ERROR")
|
452
|
+
return res.status(400).json({ error: (error as Error).message })
|
453
|
+
}
|
454
|
+
})
|
455
|
+
)
|
456
|
+
|
457
|
+
app.post(
|
458
|
+
"/exercise/:slug/create",
|
459
|
+
jsonBodyParser,
|
460
|
+
withHandler(async (req: express.Request, res: express.Response) => {
|
461
|
+
const { title, readme, language } = req.body
|
462
|
+
const { slug } = req.params
|
463
|
+
|
464
|
+
if (!title || !readme || !language) {
|
465
|
+
return res.status(400).json({ error: "Missing required fields" })
|
466
|
+
}
|
467
|
+
|
468
|
+
try {
|
469
|
+
const exerciseCreated = await configManager.createExercise(
|
470
|
+
slug,
|
471
|
+
readme,
|
472
|
+
language
|
473
|
+
)
|
474
|
+
if (exerciseCreated) {
|
475
|
+
configManager.buildIndex()
|
476
|
+
res.json({ status: "ok" })
|
477
|
+
} else {
|
478
|
+
res.status(500).json({ error: "Failed to create exercise" })
|
479
|
+
}
|
480
|
+
} catch {
|
481
|
+
res.status(500).json({ error: "Failed to create exercise" })
|
482
|
+
}
|
483
|
+
})
|
484
|
+
)
|
485
|
+
|
384
486
|
const textBodyParser = bodyParser.text()
|
385
487
|
app.put(
|
386
488
|
"/exercise/:slug/file/:fileName",
|
package/src/managers/session.ts
CHANGED
@@ -1,23 +1,25 @@
|
|
1
|
-
import { IConfigObj, TGrading } from "./config"
|
2
|
-
import { IExercise } from "./exercise-obj"
|
3
|
-
|
4
|
-
export interface IConfigManagerAttributes {
|
5
|
-
grading: TGrading
|
6
|
-
disableGrading: boolean
|
7
|
-
version: string
|
8
|
-
mode?: string
|
9
|
-
}
|
10
|
-
|
11
|
-
export interface IConfigManager {
|
12
|
-
validLanguages?: any
|
13
|
-
get: () => IConfigObj
|
14
|
-
clean: () => void
|
15
|
-
getExercise: (slug: string | undefined) => IExercise
|
16
|
-
startExercise: (slug: string) => IExercise
|
17
|
-
reset: (slug: string) => void
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
1
|
+
import { IConfigObj, TGrading } from "./config"
|
2
|
+
import { IExercise } from "./exercise-obj"
|
3
|
+
|
4
|
+
export interface IConfigManagerAttributes {
|
5
|
+
grading: TGrading
|
6
|
+
disableGrading: boolean
|
7
|
+
version: string
|
8
|
+
mode?: string
|
9
|
+
}
|
10
|
+
|
11
|
+
export interface IConfigManager {
|
12
|
+
validLanguages?: any
|
13
|
+
get: () => IConfigObj
|
14
|
+
clean: () => void
|
15
|
+
getExercise: (slug: string | undefined) => IExercise
|
16
|
+
startExercise: (slug: string) => IExercise
|
17
|
+
reset: (slug: string) => void
|
18
|
+
createExercise: (slug: string, content: string, language: string) => boolean
|
19
|
+
deleteExercise: (slug: string) => boolean
|
20
|
+
buildIndex: () => boolean | void
|
21
|
+
watchIndex: (onChange: (...args: Array<any>) => void) => void
|
22
|
+
save: () => void
|
23
|
+
noCurrentExercise: () => void
|
24
|
+
getAllExercises: () => IExercise[]
|
25
|
+
}
|
package/src/utils/console.ts
CHANGED
@@ -1,24 +1,24 @@
|
|
1
|
-
import * as chalk from "chalk"
|
2
|
-
|
3
|
-
export default {
|
4
|
-
// _debug: true,
|
5
|
-
_debug: process.env.DEBUG === "true",
|
6
|
-
startDebug: function () {
|
7
|
-
this._debug = true
|
8
|
-
},
|
9
|
-
log: (msg: string | Array<string>, ...args: Array<any>) =>
|
10
|
-
console.log(chalk.gray(msg), ...args),
|
11
|
-
error: (msg: string, ...args: Array<any>) =>
|
12
|
-
console.log(chalk.red("⨉ " + msg), ...args),
|
13
|
-
success: (msg: string, ...args: Array<any>) =>
|
14
|
-
console.log(chalk.green("✓ " + msg), ...args),
|
15
|
-
info: (msg: any, ...args: Array<any>) =>
|
16
|
-
console.log(chalk.blue("ⓘ " + msg), ...args),
|
17
|
-
help: (msg: string) =>
|
18
|
-
console.log(`${chalk.white.bold("⚠ help:")} ${chalk.white(msg)}`),
|
19
|
-
debug(...args: Array<any>) {
|
20
|
-
this._debug && console.log(chalk.magentaBright("⚠ debug: "), args)
|
21
|
-
},
|
22
|
-
warning: (msg: string) =>
|
23
|
-
console.log(`${chalk.yellow("⚠ warning:")} ${chalk.yellow(msg)}`),
|
24
|
-
}
|
1
|
+
import * as chalk from "chalk"
|
2
|
+
|
3
|
+
export default {
|
4
|
+
// _debug: true,
|
5
|
+
_debug: process.env.DEBUG === "true",
|
6
|
+
startDebug: function () {
|
7
|
+
this._debug = true
|
8
|
+
},
|
9
|
+
log: (msg: string | Array<string>, ...args: Array<any>) =>
|
10
|
+
console.log(chalk.gray(msg), ...args),
|
11
|
+
error: (msg: string, ...args: Array<any>) =>
|
12
|
+
console.log(chalk.red("⨉ " + msg), ...args),
|
13
|
+
success: (msg: string, ...args: Array<any>) =>
|
14
|
+
console.log(chalk.green("✓ " + msg), ...args),
|
15
|
+
info: (msg: any, ...args: Array<any>) =>
|
16
|
+
console.log(chalk.blue("ⓘ " + msg), ...args),
|
17
|
+
help: (msg: string) =>
|
18
|
+
console.log(`${chalk.white.bold("⚠ help:")} ${chalk.white(msg)}`),
|
19
|
+
debug(...args: Array<any>) {
|
20
|
+
this._debug && console.log(chalk.magentaBright("⚠ debug: "), args)
|
21
|
+
},
|
22
|
+
warning: (msg: string) =>
|
23
|
+
console.log(`${chalk.yellow("⚠ warning:")} ${chalk.yellow(msg)}`),
|
24
|
+
}
|
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
|
2
2
|
const frontMatter = require("front-matter")
|
3
|
+
|
3
4
|
import * as path from "path"
|
4
5
|
import * as fs from "fs"
|
5
6
|
import { homedir } from "os"
|
@@ -61,11 +62,13 @@ export function checkReadingTime(
|
|
61
62
|
exceedsThreshold: boolean
|
62
63
|
minutes: number
|
63
64
|
body: string
|
65
|
+
// readingEase: number
|
64
66
|
} {
|
65
67
|
const parsed = frontMatter(markdown)
|
66
68
|
|
67
69
|
const readingTime = estimateReadingTime(parsed.body, wordsPerMinute)
|
68
70
|
|
71
|
+
// const readingEase = estimateReadingEase(parsed.body)
|
69
72
|
let attributes = parsed.attributes ? parsed.attributes : {}
|
70
73
|
|
71
74
|
if (typeof parsed.attributes !== "object") {
|
@@ -75,6 +78,7 @@ export function checkReadingTime(
|
|
75
78
|
const updatedAttributes = {
|
76
79
|
...attributes,
|
77
80
|
readingTime,
|
81
|
+
// readingEase,
|
78
82
|
}
|
79
83
|
|
80
84
|
let yamlFrontMatter = ""
|
@@ -86,6 +90,7 @@ export function checkReadingTime(
|
|
86
90
|
exceedsThreshold: false,
|
87
91
|
minutes: 0,
|
88
92
|
body: "",
|
93
|
+
// readingEase: 0,
|
89
94
|
}
|
90
95
|
}
|
91
96
|
|
@@ -97,6 +102,7 @@ export function checkReadingTime(
|
|
97
102
|
exceedsThreshold: readingTime.minutes > maxMinutes,
|
98
103
|
minutes: readingTime.minutes,
|
99
104
|
body: parsed.body,
|
105
|
+
// readingEase: 0,
|
100
106
|
}
|
101
107
|
}
|
102
108
|
|
@@ -189,11 +195,13 @@ export function estimateDuration(listOfSteps: string[]): number {
|
|
189
195
|
const writeFilePromise = promisify(fs.writeFile)
|
190
196
|
const execPromise = promisify(exec)
|
191
197
|
|
192
|
-
const example_content = `
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
198
|
+
const example_content = `Write or paste your table of content below this line, each topic should be defined on a new line, here is an example:
|
199
|
+
|
200
|
+
Introduction to AI: Explain what is AI and its applications
|
201
|
+
Introduction to Machine Learning: Explain what is machine learning and its applications
|
202
|
+
What is an AI Model: Explain what is an AI model and its applications
|
203
|
+
How to use an AI Model: Different APIs, local models, etc.
|
204
|
+
How to build an AI Model: Fine-tuning, data collection, cleaning and more.
|
197
205
|
`
|
198
206
|
|
199
207
|
export async function createFileOnDesktop() {
|
@@ -239,3 +247,55 @@ export function getContentIndex() {
|
|
239
247
|
const content = fs.readFileSync(filePath, "utf8")
|
240
248
|
return content
|
241
249
|
}
|
250
|
+
|
251
|
+
// export function fleschKincaidReadingEase(text: string): number {
|
252
|
+
// const sentences = text.split(/[.!?]/).filter((s) => s.trim().length > 0)
|
253
|
+
// const words = text.split(/\s+/).filter((w) => w.trim().length > 0)
|
254
|
+
// const totalSyllables = words.reduce((sum, word) => sum + syllable(word), 0)
|
255
|
+
|
256
|
+
// const ASL = words.length / sentences.length // Average Sentence Length
|
257
|
+
// const ASW = totalSyllables / words.length // Average Syllables per Word
|
258
|
+
|
259
|
+
// return Math.round(206.835 - 1.015 * ASL - 84.6 * ASW)
|
260
|
+
// }
|
261
|
+
|
262
|
+
export function extractTextFromMarkdown(mdContent: string): string {
|
263
|
+
let content = mdContent.replace(/!\[.*?]\(.*?\)/g, "")
|
264
|
+
content = content.replace(/\[.*?]\(.*?\)/g, "")
|
265
|
+
content = content.replace(/`.*?`/g, "")
|
266
|
+
content = content.replace(/```[\S\s]*?```/g, "")
|
267
|
+
|
268
|
+
return content.trim()
|
269
|
+
}
|
270
|
+
|
271
|
+
// export const estimateReadingEase = async (text: string): Promise<number> => {
|
272
|
+
// const cleanedText = extractTextFromMarkdown(text)
|
273
|
+
// // @ts-ignore
|
274
|
+
// const rs = (await import("text-readability")).default;
|
275
|
+
// // return fleschKincaidReadingEase(cleanedText)
|
276
|
+
// // const score = readability(cleanedText)
|
277
|
+
// // console.log(score)
|
278
|
+
// // return score.fleschKincaid ?? 0
|
279
|
+
// const score = rs.fleschReadingEase(cleanedText)
|
280
|
+
// console.log(score, "SCORE FLESCH READING EASE")
|
281
|
+
// return score
|
282
|
+
// }
|
283
|
+
|
284
|
+
const cleanReadme = (readme: string) => {
|
285
|
+
// Replace <text> and </text> with nothing
|
286
|
+
return readme.replace(/<text>/g, "").replace(/<\/text>/g, "")
|
287
|
+
}
|
288
|
+
|
289
|
+
export const saveTranslatedReadme = async (
|
290
|
+
exercise: string,
|
291
|
+
languageCode: string,
|
292
|
+
readme: string
|
293
|
+
) => {
|
294
|
+
const readmePath = path.join(
|
295
|
+
process.cwd(),
|
296
|
+
"exercises",
|
297
|
+
exercise,
|
298
|
+
`README.${languageCode}.md`
|
299
|
+
)
|
300
|
+
fs.writeFileSync(readmePath, cleanReadme(readme))
|
301
|
+
}
|
package/src/utils/rigoActions.ts
CHANGED
@@ -62,7 +62,7 @@ return false
|
|
62
62
|
Console.debug("The user is a creator! 🎉")
|
63
63
|
return true
|
64
64
|
} catch (error) {
|
65
|
-
Console.
|
65
|
+
Console.error(error as string)
|
66
66
|
return false
|
67
67
|
}
|
68
68
|
}
|
@@ -323,3 +323,15 @@ export async function reduceReadme(
|
|
323
323
|
return null
|
324
324
|
}
|
325
325
|
}
|
326
|
+
|
327
|
+
export const isValidRigoToken = async (rigobotToken: string) => {
|
328
|
+
const rigoUrl = `${RIGOBOT_HOST}/v1/auth/token/${rigobotToken}`
|
329
|
+
const rigoResp = await fetch(rigoUrl)
|
330
|
+
if (!rigoResp.ok) {
|
331
|
+
Console.debug("Invalid Rigobot token", rigoResp.status)
|
332
|
+
Console.debug(rigoResp.statusText)
|
333
|
+
return false
|
334
|
+
}
|
335
|
+
|
336
|
+
return true
|
337
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
2
|
+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
3
|
+
|
4
|
+
name: Learnpack audit
|
5
|
+
|
6
|
+
on:
|
7
|
+
push:
|
8
|
+
branches: [ main ]
|
9
|
+
pull_request:
|
10
|
+
branches: [ main ]
|
11
|
+
|
12
|
+
jobs:
|
13
|
+
build:
|
14
|
+
|
15
|
+
runs-on: ubuntu-latest
|
16
|
+
|
17
|
+
strategy:
|
18
|
+
matrix:
|
19
|
+
node-version: [20.x]
|
20
|
+
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
21
|
+
|
22
|
+
steps:
|
23
|
+
- uses: actions/checkout@v2
|
24
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
25
|
+
uses: actions/setup-node@v2
|
26
|
+
with:
|
27
|
+
node-version: ${{ matrix.node-version }}
|
28
|
+
- run: npm install @learnpack/learnpack@latest -g
|
29
|
+
- run: learnpack audit
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
2
|
+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
3
|
+
|
4
|
+
name: Learnpack audit
|
5
|
+
|
6
|
+
on:
|
7
|
+
push:
|
8
|
+
branches: [ main ]
|
9
|
+
pull_request:
|
10
|
+
branches: [ main ]
|
11
|
+
|
12
|
+
jobs:
|
13
|
+
build:
|
14
|
+
|
15
|
+
runs-on: ubuntu-latest
|
16
|
+
|
17
|
+
strategy:
|
18
|
+
matrix:
|
19
|
+
node-version: [20.x]
|
20
|
+
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
21
|
+
|
22
|
+
steps:
|
23
|
+
- uses: actions/checkout@v2
|
24
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
25
|
+
uses: actions/setup-node@v2
|
26
|
+
with:
|
27
|
+
node-version: ${{ matrix.node-version }}
|
28
|
+
- run: npm install @learnpack/learnpack@latest -g
|
29
|
+
- run: learnpack audit
|