@learnpack/learnpack 5.0.346 → 5.0.348
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/lib/commands/publish.d.ts +1 -1
- package/lib/commands/publish.js +9 -2
- package/lib/commands/serve.js +120 -19
- package/lib/utils/api.d.ts +25 -1
- package/lib/utils/api.js +34 -1
- package/package.json +1 -1
- package/src/commands/publish.ts +15 -0
- package/src/commands/serve.ts +139 -36
- package/src/ui/_app/app.js +2076 -2076
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/api.ts +43 -2
package/src/commands/serve.ts
CHANGED
|
@@ -55,6 +55,8 @@ import api, {
|
|
|
55
55
|
RIGOBOT_REALTIME_HOST,
|
|
56
56
|
listUserAcademies,
|
|
57
57
|
doesAssetExists,
|
|
58
|
+
type AssetSyncError,
|
|
59
|
+
resolveLearnpackPackageId,
|
|
58
60
|
} from "../utils/api"
|
|
59
61
|
import {
|
|
60
62
|
createUploadMiddleware,
|
|
@@ -583,6 +585,16 @@ const getLocalizedValue = (
|
|
|
583
585
|
return typeof first === "string" ? first : ""
|
|
584
586
|
}
|
|
585
587
|
|
|
588
|
+
function assetSyncErrorDetail(err: any): string {
|
|
589
|
+
if (typeof err?.detail === "string") return err.detail
|
|
590
|
+
if (typeof err?.message === "string") return err.message
|
|
591
|
+
try {
|
|
592
|
+
return JSON.stringify(err)
|
|
593
|
+
} catch {
|
|
594
|
+
return String(err)
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
586
598
|
const createMultiLangAsset = async (
|
|
587
599
|
bucket: Bucket,
|
|
588
600
|
rigoToken: string,
|
|
@@ -591,12 +603,27 @@ const createMultiLangAsset = async (
|
|
|
591
603
|
courseJson: any,
|
|
592
604
|
deployUrl: string,
|
|
593
605
|
academyId?: number
|
|
594
|
-
): Promise<{ errors:
|
|
606
|
+
): Promise<{ errors: AssetSyncError[] }> => {
|
|
607
|
+
const learnpackId = await resolveLearnpackPackageId(rigoToken, courseSlug)
|
|
608
|
+
if (learnpackId === null) {
|
|
609
|
+
return {
|
|
610
|
+
errors: [
|
|
611
|
+
{
|
|
612
|
+
kind: "package_error",
|
|
613
|
+
error: {
|
|
614
|
+
detail:
|
|
615
|
+
"Could not resolve Learnpack package id; assets not synced to Breathecode.",
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
],
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
595
622
|
const availableLangs = Object.keys(courseJson.title)
|
|
596
623
|
console.log("AVAILABLE LANGUAGES to upload asset", availableLangs)
|
|
597
624
|
|
|
598
625
|
const all_translations: string[] = []
|
|
599
|
-
const errors:
|
|
626
|
+
const errors: AssetSyncError[] = []
|
|
600
627
|
|
|
601
628
|
for (const lang of availableLangs) {
|
|
602
629
|
// eslint-disable-next-line no-await-in-loop
|
|
@@ -625,14 +652,16 @@ const createMultiLangAsset = async (
|
|
|
625
652
|
deployUrl,
|
|
626
653
|
b64IndexReadme,
|
|
627
654
|
academyId,
|
|
655
|
+
learnpackId,
|
|
628
656
|
undefined,
|
|
629
657
|
all_translations
|
|
630
658
|
)
|
|
631
659
|
|
|
632
660
|
if (!asset) {
|
|
633
661
|
errors.push({
|
|
662
|
+
kind: "lang_error",
|
|
634
663
|
lang,
|
|
635
|
-
error: { detail: "Failed to create asset"
|
|
664
|
+
error: { detail: "Failed to create asset" },
|
|
636
665
|
})
|
|
637
666
|
console.log("No se pudo crear el asset, saltando idioma", lang)
|
|
638
667
|
continue
|
|
@@ -644,7 +673,11 @@ const createMultiLangAsset = async (
|
|
|
644
673
|
error && typeof error === "object" && "response" in error ?
|
|
645
674
|
(error as any).response?.data || error :
|
|
646
675
|
error
|
|
647
|
-
errors.push({
|
|
676
|
+
errors.push({
|
|
677
|
+
kind: "lang_error",
|
|
678
|
+
lang,
|
|
679
|
+
error: { detail: assetSyncErrorDetail(errorData) },
|
|
680
|
+
})
|
|
648
681
|
console.error(`Error creating asset for language ${lang}:`, error)
|
|
649
682
|
}
|
|
650
683
|
}
|
|
@@ -4708,6 +4741,7 @@ class ServeCommand extends SessionCommand {
|
|
|
4708
4741
|
}> = []
|
|
4709
4742
|
let repairedTranslationsInLessons = 0
|
|
4710
4743
|
let repairedTranslationEntries = 0
|
|
4744
|
+
let fixedLessons = 0
|
|
4711
4745
|
|
|
4712
4746
|
console.log(
|
|
4713
4747
|
`📋 Checking ${syllabus.lessons.length} lessons in syllabus...`
|
|
@@ -4950,6 +4984,35 @@ class ServeCommand extends SessionCommand {
|
|
|
4950
4984
|
repairedTranslationsInLessons += 1
|
|
4951
4985
|
}
|
|
4952
4986
|
}
|
|
4987
|
+
|
|
4988
|
+
// Fifth pass: fix lessons stuck in GENERATING or ERROR when the file exists in the bucket
|
|
4989
|
+
const primaryLanguage = syllabus.courseInfo.language || "en"
|
|
4990
|
+
for (const lesson of syllabus.lessons) {
|
|
4991
|
+
if (lesson.generated !== false) continue
|
|
4992
|
+
if (lesson.status !== "GENERATING" && lesson.status !== "ERROR")
|
|
4993
|
+
continue
|
|
4994
|
+
|
|
4995
|
+
const candidateSlugs = [
|
|
4996
|
+
slugify(lesson.id + "-" + lesson.title),
|
|
4997
|
+
lesson.uid,
|
|
4998
|
+
].filter(Boolean) as string[]
|
|
4999
|
+
|
|
5000
|
+
const matchedSlug = candidateSlugs.find(s =>
|
|
5001
|
+
translationsBySlug.has(s)
|
|
5002
|
+
)
|
|
5003
|
+
if (!matchedSlug) continue
|
|
5004
|
+
|
|
5005
|
+
const langs = translationsBySlug.get(matchedSlug) || []
|
|
5006
|
+
if (!langs.includes(primaryLanguage)) continue
|
|
5007
|
+
|
|
5008
|
+
const prevStatus = lesson.status
|
|
5009
|
+
lesson.generated = true
|
|
5010
|
+
lesson.status = "DONE"
|
|
5011
|
+
fixedLessons += 1
|
|
5012
|
+
console.log(
|
|
5013
|
+
`🔧 Fixed lesson: ${lesson.id} - "${lesson.title}" (was generated:false status:${prevStatus}, primary language "${primaryLanguage}" exists in bucket)`
|
|
5014
|
+
)
|
|
5015
|
+
}
|
|
4953
5016
|
} catch (error) {
|
|
4954
5017
|
console.error(
|
|
4955
5018
|
"⚠️ Could not reconcile lesson translations during syllabus sync:",
|
|
@@ -4960,11 +5023,12 @@ class ServeCommand extends SessionCommand {
|
|
|
4960
5023
|
if (
|
|
4961
5024
|
totalRemoved > 0 ||
|
|
4962
5025
|
addedLessons.length > 0 ||
|
|
4963
|
-
repairedTranslationsInLessons > 0
|
|
5026
|
+
repairedTranslationsInLessons > 0 ||
|
|
5027
|
+
fixedLessons > 0
|
|
4964
5028
|
) {
|
|
4965
5029
|
await saveSyllabus(courseSlug, syllabus, bucket)
|
|
4966
5030
|
console.log(
|
|
4967
|
-
`✅ Syllabus synchronized. Removed ${removedLessons.length} non-existent, ${duplicatesRemoved.length} duplicate(s); added ${addedLessons.length} from bucket; repaired translations in ${repairedTranslationsInLessons} lesson(s).`
|
|
5031
|
+
`✅ Syllabus synchronized. Removed ${removedLessons.length} non-existent, ${duplicatesRemoved.length} duplicate(s); added ${addedLessons.length} from bucket; repaired translations in ${repairedTranslationsInLessons} lesson(s); fixed ${fixedLessons} stuck lesson(s).`
|
|
4968
5032
|
)
|
|
4969
5033
|
} else {
|
|
4970
5034
|
console.log(`✅ Syllabus is already in sync. No changes.`)
|
|
@@ -4978,6 +5042,7 @@ class ServeCommand extends SessionCommand {
|
|
|
4978
5042
|
removedLessons: removedLessons.length,
|
|
4979
5043
|
duplicatesResolved: duplicatesRemoved.length,
|
|
4980
5044
|
addedLessons: addedLessons.length,
|
|
5045
|
+
fixedLessons,
|
|
4981
5046
|
repairedTranslationsInLessons,
|
|
4982
5047
|
repairedTranslationEntries,
|
|
4983
5048
|
removed: removedLessons,
|
|
@@ -5620,38 +5685,76 @@ class ServeCommand extends SessionCommand {
|
|
|
5620
5685
|
const archive = archiver("zip", { zlib: { level: 9 } })
|
|
5621
5686
|
|
|
5622
5687
|
output.on("close", async () => {
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5688
|
+
let rigoPublishUrl: string | undefined
|
|
5689
|
+
try {
|
|
5690
|
+
// 10) Subir ZIP a RigoBot
|
|
5691
|
+
const form = new FormData()
|
|
5692
|
+
form.append("file", fs.createReadStream(zipPath))
|
|
5693
|
+
form.append("config", JSON.stringify(config))
|
|
5694
|
+
|
|
5695
|
+
const rigoRes = await axios.post(
|
|
5696
|
+
`${RIGOBOT_HOST}/v1/learnpack/upload`,
|
|
5697
|
+
form,
|
|
5698
|
+
{
|
|
5699
|
+
headers: {
|
|
5700
|
+
...form.getHeaders(),
|
|
5701
|
+
Authorization: "Token " + rigoToken.trim(),
|
|
5702
|
+
},
|
|
5703
|
+
}
|
|
5704
|
+
)
|
|
5705
|
+
rigoPublishUrl = rigoRes.data.url
|
|
5638
5706
|
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5707
|
+
let errors: AssetSyncError[]
|
|
5708
|
+
try {
|
|
5709
|
+
const assetResults = await createMultiLangAsset(
|
|
5710
|
+
bucket,
|
|
5711
|
+
rigoToken,
|
|
5712
|
+
bcToken,
|
|
5713
|
+
slug,
|
|
5714
|
+
fullConfig.config,
|
|
5715
|
+
rigoRes.data.url,
|
|
5716
|
+
academyId
|
|
5717
|
+
)
|
|
5718
|
+
errors = assetResults.errors
|
|
5719
|
+
} catch (error) {
|
|
5720
|
+
console.error("Asset sync failed unexpectedly:", error)
|
|
5721
|
+
errors = [
|
|
5722
|
+
{
|
|
5723
|
+
kind: "package_error",
|
|
5724
|
+
error: { detail: "Asset sync failed unexpectedly." },
|
|
5725
|
+
},
|
|
5726
|
+
]
|
|
5727
|
+
}
|
|
5648
5728
|
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5729
|
+
if (res.headersSent) return
|
|
5730
|
+
console.log("RigoRes", rigoRes.data)
|
|
5731
|
+
res.json({
|
|
5732
|
+
url: rigoPublishUrl,
|
|
5733
|
+
errors,
|
|
5734
|
+
})
|
|
5735
|
+
} catch (error) {
|
|
5736
|
+
console.error(error)
|
|
5737
|
+
if (res.headersSent) return
|
|
5738
|
+
if (rigoPublishUrl !== undefined) {
|
|
5739
|
+
res.json({
|
|
5740
|
+
url: rigoPublishUrl,
|
|
5741
|
+
errors: [
|
|
5742
|
+
{
|
|
5743
|
+
kind: "package_error",
|
|
5744
|
+
error: { detail: "Asset sync failed unexpectedly." },
|
|
5745
|
+
},
|
|
5746
|
+
],
|
|
5747
|
+
})
|
|
5748
|
+
} else {
|
|
5749
|
+
res.status(500).json({ error: (error as Error).message })
|
|
5750
|
+
}
|
|
5751
|
+
} finally {
|
|
5752
|
+
try {
|
|
5753
|
+
rimraf.sync(tmpRoot)
|
|
5754
|
+
} catch (error) {
|
|
5755
|
+
console.error("rimraf tmpRoot:", error)
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5655
5758
|
})
|
|
5656
5759
|
|
|
5657
5760
|
archive.on("error", err => {
|