@learnpack/learnpack 5.0.358 → 5.0.360
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 +0 -10
- package/lib/commands/publish.js +52 -168
- package/lib/commands/serve.js +52 -116
- package/lib/creatorDist/assets/{index-zrPponAn.css → index-B7s5Nmq8.css} +59 -0
- package/lib/creatorDist/assets/{index-D6pmbMe9.js → index-GYxGB_X0.js} +22609 -22363
- package/lib/creatorDist/index.html +2 -2
- package/lib/utils/api.d.ts +62 -15
- package/lib/utils/api.js +124 -45
- package/lib/utils/assetResolver.d.ts +116 -0
- package/lib/utils/assetResolver.js +268 -0
- package/lib/utils/assetSync.d.ts +48 -0
- package/lib/utils/assetSync.js +267 -0
- package/lib/utils/creatorUtilities.d.ts +1 -0
- package/lib/utils/creatorUtilities.js +17 -1
- package/package.json +1 -1
- package/src/commands/publish.ts +61 -255
- package/src/commands/serve.ts +65 -140
- package/src/creator/src/App.test.tsx +338 -0
- package/src/creator/src/App.tsx +152 -26
- package/src/creator/src/components/CourseLanguagePicker.tsx +51 -0
- package/src/creator/src/components/LanguageDetectionModal.tsx +29 -9
- package/src/creator/src/components/StepWizard.tsx +11 -3
- package/src/creator/src/components/Uploader.test.tsx +65 -0
- package/src/creator/src/components/Uploader.tsx +9 -3
- package/src/creator/src/locales/en.json +7 -0
- package/src/creator/src/locales/es.json +7 -0
- package/src/creator/src/utils/courseLanguage.test.ts +215 -0
- package/src/creator/src/utils/courseLanguage.ts +173 -0
- package/src/creator/src/utils/creationRequest.test.ts +104 -0
- package/src/creator/src/utils/creationRequest.ts +63 -0
- package/src/creator/src/utils/store.ts +7 -0
- package/src/creatorDist/assets/{index-zrPponAn.css → index-B7s5Nmq8.css} +59 -0
- package/src/creatorDist/assets/{index-D6pmbMe9.js → index-GYxGB_X0.js} +22609 -22363
- package/src/creatorDist/index.html +2 -2
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +2105 -2105
- package/src/ui/_app/sw.js +13 -0
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/api.ts +157 -42
- package/src/utils/assetResolver.ts +368 -0
- package/src/utils/assetSync.ts +343 -0
- package/src/utils/awsCredentials.ts +57 -57
- package/src/utils/creatorUtilities.ts +16 -0
- package/src/utils/descriptions/publishStage.ts +394 -394
- package/src/utils/descriptions/resumePublication.ts +217 -217
- package/src/utils/descriptions/s3Storage.ts +214 -214
- package/src/utils/s3/packageManifestBackfill.ts +771 -771
package/src/commands/publish.ts
CHANGED
|
@@ -19,282 +19,78 @@ import api, { getConsumable, RIGOBOT_HOST, TAcademy } from "../utils/api"
|
|
|
19
19
|
import * as prompts from "prompts"
|
|
20
20
|
import { isValidRigoToken } from "../utils/rigoActions"
|
|
21
21
|
import { minutesToISO8601Duration } from "../utils/misc"
|
|
22
|
-
import { getReadmeExtension
|
|
22
|
+
import { getReadmeExtension } from "../utils/creatorUtilities"
|
|
23
23
|
import { getFallbackPreviewUrl } from "../utils/branding"
|
|
24
|
+
import {
|
|
25
|
+
determineAcademyMode,
|
|
26
|
+
getAvailableLangs,
|
|
27
|
+
PackageAssetsResolution,
|
|
28
|
+
resolvePackageAssets,
|
|
29
|
+
} from "../utils/assetResolver"
|
|
30
|
+
import { getLocalizedValue, syncPackageAssets } from "../utils/assetSync"
|
|
24
31
|
|
|
25
32
|
const uploadZipEndpont = RIGOBOT_HOST + "/v1/learnpack/upload"
|
|
26
33
|
|
|
27
|
-
const getAvailableLangs = (learnJson: any): string[] => {
|
|
28
|
-
const langs = Object.keys(learnJson?.title || {})
|
|
29
|
-
return langs.filter((l) => typeof l === "string" && l.length > 0)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
34
|
const getDefaultLang = (learnJson: any): string => {
|
|
33
35
|
const availableLangs = getAvailableLangs(learnJson)
|
|
34
36
|
if (availableLangs.length === 0) return "en"
|
|
35
37
|
return availableLangs.includes("en") ? "en" : availableLangs[0]
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
if (!translations || typeof translations !== "object") return ""
|
|
44
|
-
|
|
45
|
-
const direct = translations[lang]
|
|
46
|
-
if (typeof direct === "string" && direct.trim().length > 0) return direct
|
|
47
|
-
|
|
48
|
-
for (const fb of fallbackLangs) {
|
|
49
|
-
const v = translations[fb]
|
|
50
|
-
if (typeof v === "string" && v.trim().length > 0) return v
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const firstKey = Object.keys(translations)[0]
|
|
54
|
-
const first = firstKey ? translations[firstKey] : ""
|
|
55
|
-
return typeof first === "string" ? first : ""
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
type ExistingAssetInfo = {
|
|
59
|
-
lang: string;
|
|
60
|
-
slug: string;
|
|
61
|
-
exists: boolean;
|
|
62
|
-
academyId?: number;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
type AcademyMode =
|
|
66
|
-
| { type: "select" }
|
|
67
|
-
| { type: "locked"; academyId: number }
|
|
68
|
-
| { type: "conflict"; academies: number[] };
|
|
69
|
-
|
|
70
|
-
const getExistingAssetsInfo = async (
|
|
71
|
-
token: string,
|
|
72
|
-
learnJson: any
|
|
73
|
-
): Promise<ExistingAssetInfo[]> => {
|
|
74
|
-
const availableLangs = getAvailableLangs(learnJson)
|
|
75
|
-
const results: ExistingAssetInfo[] = []
|
|
76
|
-
|
|
77
|
-
for (const lang of availableLangs) {
|
|
78
|
-
const assetTitle = getLocalizedValue(learnJson?.title, lang)
|
|
79
|
-
if (!assetTitle) continue
|
|
80
|
-
|
|
81
|
-
let slug = slugify(assetTitle).slice(0, 47)
|
|
82
|
-
slug = `${slug}-${lang}`
|
|
83
|
-
|
|
84
|
-
// eslint-disable-next-line no-await-in-loop
|
|
85
|
-
const { exists, academyId } = await api.doesAssetExists(token, slug)
|
|
86
|
-
results.push({ lang, slug, exists, academyId })
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return results
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const determinePublishAcademyMode = (
|
|
93
|
-
existingAssets: ExistingAssetInfo[]
|
|
94
|
-
): AcademyMode => {
|
|
95
|
-
const academyIds = existingAssets
|
|
96
|
-
.filter((a) => a.exists && a.academyId !== undefined)
|
|
97
|
-
.map((a) => a.academyId as number)
|
|
98
|
-
|
|
99
|
-
const unique = [...new Set(academyIds)]
|
|
100
|
-
|
|
101
|
-
if (unique.length === 0) return { type: "select" }
|
|
102
|
-
if (unique.length === 1) return { type: "locked", academyId: unique[0] }
|
|
103
|
-
return { type: "conflict", academies: unique }
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export const handleAssetCreation = async (
|
|
107
|
-
sessionPayload: { token: string; rigobotToken: string },
|
|
108
|
-
learnJson: any,
|
|
109
|
-
selectedLang: string,
|
|
110
|
-
learnpackDeployUrl: string,
|
|
111
|
-
b64IndexReadme: string,
|
|
112
|
-
academyId: number | undefined,
|
|
113
|
-
learnpackId: number,
|
|
114
|
-
preflightInfo?: ExistingAssetInfo,
|
|
115
|
-
all_translations: string[] = []
|
|
116
|
-
) => {
|
|
117
|
-
const category = "uncategorized"
|
|
118
|
-
|
|
40
|
+
const readReadmeFromDisk = async (lang: string): Promise<string> => {
|
|
41
|
+
const readmePath = path.join(
|
|
42
|
+
process.cwd(),
|
|
43
|
+
`README${getReadmeExtension(lang)}`
|
|
44
|
+
)
|
|
119
45
|
try {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const assetDescription = getLocalizedValue(
|
|
124
|
-
learnJson?.description,
|
|
125
|
-
selectedLang
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
if (!assetTitle) {
|
|
129
|
-
throw new Error(
|
|
130
|
-
`Missing learn.json title for language "${selectedLang}"`
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
let slug = slugify(assetTitle).slice(0, 47)
|
|
135
|
-
slug = `${slug}-${selectedLang}`
|
|
136
|
-
|
|
137
|
-
// Use pre-flight info when available to avoid an extra GET request
|
|
138
|
-
const { exists, academyId: existingAcademyId } =
|
|
139
|
-
preflightInfo ?? (await api.doesAssetExists(sessionPayload.token, slug))
|
|
140
|
-
|
|
141
|
-
// const technologies: unknown[] = Array.isArray(learnJson?.technologies) ?
|
|
142
|
-
// learnJson.technologies :
|
|
143
|
-
// []
|
|
144
|
-
|
|
145
|
-
if (!exists) {
|
|
146
|
-
Console.info("Asset does not exist in this academy, creating it")
|
|
147
|
-
const assetPayload: any = {
|
|
148
|
-
slug: slug,
|
|
149
|
-
title: assetTitle,
|
|
150
|
-
lang: selectedLang,
|
|
151
|
-
graded: true,
|
|
152
|
-
description: assetDescription,
|
|
153
|
-
learnpack_deploy_url: learnpackDeployUrl,
|
|
154
|
-
// technologies: technologies.map((tech: unknown) =>
|
|
155
|
-
// String(tech).toLowerCase().replace(/\s+/g, "-")
|
|
156
|
-
// ),
|
|
157
|
-
technologies: [],
|
|
158
|
-
url: learnpackDeployUrl,
|
|
159
|
-
category: category,
|
|
160
|
-
owner: user.id,
|
|
161
|
-
author: user.id,
|
|
162
|
-
preview: learnJson.preview,
|
|
163
|
-
readme_raw: b64IndexReadme,
|
|
164
|
-
all_translations,
|
|
165
|
-
learnpack_id: learnpackId,
|
|
166
|
-
}
|
|
167
|
-
if (academyId !== undefined) {
|
|
168
|
-
assetPayload.academy_id = academyId
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const asset = await api.createAsset(sessionPayload.token, assetPayload)
|
|
172
|
-
try {
|
|
173
|
-
await api.updateRigoPackage(
|
|
174
|
-
sessionPayload.rigobotToken.trim(),
|
|
175
|
-
learnJson.slug,
|
|
176
|
-
{
|
|
177
|
-
asset_id: asset.id,
|
|
178
|
-
}
|
|
179
|
-
)
|
|
180
|
-
} catch (error) {
|
|
181
|
-
Console.error("Error updating Rigo package:", error)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
Console.info("Asset created with id", asset.id)
|
|
185
|
-
return asset
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
Console.info("Asset exists, updating it")
|
|
189
|
-
const updatePayload: any = {
|
|
190
|
-
graded: true,
|
|
191
|
-
learnpack_deploy_url: learnpackDeployUrl,
|
|
192
|
-
title: assetTitle,
|
|
193
|
-
category: category,
|
|
194
|
-
description: assetDescription,
|
|
195
|
-
all_translations,
|
|
196
|
-
learnpack_id: learnpackId,
|
|
197
|
-
}
|
|
198
|
-
// Only set academy when the asset has none yet and the user selected one
|
|
199
|
-
if (existingAcademyId === undefined && academyId !== undefined) {
|
|
200
|
-
updatePayload.academy_id = academyId
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const asset = await api.updateAsset(
|
|
204
|
-
sessionPayload.token,
|
|
205
|
-
slug,
|
|
206
|
-
updatePayload
|
|
207
|
-
)
|
|
208
|
-
try {
|
|
209
|
-
await api.updateRigoPackage(
|
|
210
|
-
sessionPayload.rigobotToken.trim(),
|
|
211
|
-
learnJson.slug,
|
|
212
|
-
{
|
|
213
|
-
asset_id: asset.id,
|
|
214
|
-
}
|
|
215
|
-
)
|
|
216
|
-
} catch (error) {
|
|
217
|
-
Console.error("Error updating Rigo package:", error)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
Console.info("Asset updated with id", asset.id)
|
|
221
|
-
return asset
|
|
46
|
+
return fs.existsSync(readmePath) ?
|
|
47
|
+
fs.readFileSync(readmePath, "utf-8") :
|
|
48
|
+
""
|
|
222
49
|
} catch (error) {
|
|
223
|
-
Console.error("Error
|
|
224
|
-
|
|
50
|
+
Console.error("Error reading index readme:", error)
|
|
51
|
+
return ""
|
|
225
52
|
}
|
|
226
53
|
}
|
|
227
54
|
|
|
228
|
-
const
|
|
55
|
+
const syncAssetsFromDisk = async (
|
|
229
56
|
sessionPayload: { token: string; rigobotToken: string },
|
|
230
57
|
learnJson: any,
|
|
231
58
|
deployUrl: string,
|
|
232
|
-
selectedAcademyId
|
|
233
|
-
|
|
59
|
+
selectedAcademyId: number | undefined,
|
|
60
|
+
resolution: PackageAssetsResolution
|
|
234
61
|
) => {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (availableLangs.length === 0) {
|
|
62
|
+
if (getAvailableLangs(learnJson).length === 0) {
|
|
238
63
|
Console.error(
|
|
239
64
|
"No languages found in learn.json.title. Add at least one language (e.g. title.en)."
|
|
240
65
|
)
|
|
241
66
|
return
|
|
242
67
|
}
|
|
243
68
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
69
|
+
// The pre-upload resolution is reused unless the package did not exist in
|
|
70
|
+
// Rigobot yet (first publish): the upload has just created it.
|
|
71
|
+
const result = await syncPackageAssets({
|
|
72
|
+
bcToken: sessionPayload.token,
|
|
73
|
+
rigoToken: sessionPayload.rigobotToken,
|
|
74
|
+
packageSlug: learnJson.slug,
|
|
75
|
+
learnJson,
|
|
76
|
+
deployUrl,
|
|
77
|
+
academyId: selectedAcademyId,
|
|
78
|
+
readReadme: readReadmeFromDisk,
|
|
79
|
+
resolution: resolution.learnpackId === null ? undefined : resolution,
|
|
80
|
+
})
|
|
254
81
|
|
|
255
|
-
const
|
|
256
|
-
for (const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
`README${getReadmeExtension(lang)}`
|
|
82
|
+
for (const warning of result.warnings) Console.warning(warning)
|
|
83
|
+
for (const synced of result.synced) {
|
|
84
|
+
Console.info(
|
|
85
|
+
`Asset ${synced.action} for "${synced.lang}": #${synced.asset.id} ${synced.asset.slug}`
|
|
260
86
|
)
|
|
87
|
+
}
|
|
261
88
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
} catch (error) {
|
|
268
|
-
Console.error("Error reading index readme:", error)
|
|
269
|
-
indexReadmeString = ""
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64")
|
|
273
|
-
const preflightInfo = existingAssetsInfo.find((a) => a.lang === lang)
|
|
274
|
-
|
|
275
|
-
try {
|
|
276
|
-
// eslint-disable-next-line no-await-in-loop
|
|
277
|
-
const asset = await handleAssetCreation(
|
|
278
|
-
sessionPayload,
|
|
279
|
-
learnJson,
|
|
280
|
-
lang,
|
|
281
|
-
deployUrl,
|
|
282
|
-
b64IndexReadme,
|
|
283
|
-
selectedAcademyId,
|
|
284
|
-
learnpackId,
|
|
285
|
-
preflightInfo,
|
|
286
|
-
all_translations
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
if (!asset) {
|
|
290
|
-
Console.debug("Could not create/update asset for lang", lang)
|
|
291
|
-
continue
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
all_translations.push(asset.slug)
|
|
295
|
-
} catch (error) {
|
|
296
|
-
Console.error("Error creating asset for language", lang, error)
|
|
297
|
-
// Continue with other languages
|
|
89
|
+
for (const error of result.errors) {
|
|
90
|
+
if (error.kind === "lang_error") {
|
|
91
|
+
Console.error(`Asset for "${error.lang}": ${error.error.detail}`)
|
|
92
|
+
} else {
|
|
93
|
+
Console.error(error.error.detail)
|
|
298
94
|
}
|
|
299
95
|
}
|
|
300
96
|
}
|
|
@@ -488,11 +284,21 @@ class BuildCommand extends SessionCommand {
|
|
|
488
284
|
}
|
|
489
285
|
|
|
490
286
|
Console.info("Checking existing assets...")
|
|
491
|
-
const
|
|
492
|
-
sessionPayload.token,
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
287
|
+
const resolution = await resolvePackageAssets({
|
|
288
|
+
bcToken: sessionPayload.token,
|
|
289
|
+
rigoToken: rigoToken,
|
|
290
|
+
packageSlug: learnJson.slug,
|
|
291
|
+
learnJson,
|
|
292
|
+
})
|
|
293
|
+
for (const warning of resolution.warnings) Console.warning(warning)
|
|
294
|
+
if (resolution.packageState === "unknown") {
|
|
295
|
+
Console.error(
|
|
296
|
+
`${resolution.packageError}. Existing assets cannot be checked, so publishing now could create duplicates. Try again later.`
|
|
297
|
+
)
|
|
298
|
+
process.exit(1)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const academyMode = determineAcademyMode(resolution)
|
|
496
302
|
|
|
497
303
|
let selectedAcademyId: number | undefined
|
|
498
304
|
|
|
@@ -688,12 +494,12 @@ class BuildCommand extends SessionCommand {
|
|
|
688
494
|
fs.unlinkSync(zipFilePath)
|
|
689
495
|
this.removeDirectory(buildDir)
|
|
690
496
|
|
|
691
|
-
await
|
|
497
|
+
await syncAssetsFromDisk(
|
|
692
498
|
{ token: sessionPayload.token, rigobotToken: rigoToken },
|
|
693
499
|
learnJson,
|
|
694
500
|
res.data.url,
|
|
695
501
|
selectedAcademyId,
|
|
696
|
-
|
|
502
|
+
resolution
|
|
697
503
|
)
|
|
698
504
|
} catch (error) {
|
|
699
505
|
if (axios.isAxiosError(error)) {
|
package/src/commands/serve.ts
CHANGED
|
@@ -55,9 +55,7 @@ import api, {
|
|
|
55
55
|
RIGOBOT_HOST,
|
|
56
56
|
RIGOBOT_REALTIME_HOST,
|
|
57
57
|
listUserAcademies,
|
|
58
|
-
doesAssetExists,
|
|
59
58
|
type AssetSyncError,
|
|
60
|
-
resolveLearnpackPackageId,
|
|
61
59
|
} from "../utils/api"
|
|
62
60
|
import {
|
|
63
61
|
createUploadMiddleware,
|
|
@@ -66,7 +64,12 @@ import {
|
|
|
66
64
|
import { buildConfig } from "../utils/configBuilder"
|
|
67
65
|
import { checkReadability, slugify } from "../utils/creatorUtilities"
|
|
68
66
|
import { checkAndFixSidebarPure } from "../utils/sidebarGenerator"
|
|
69
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
determineAcademyMode,
|
|
69
|
+
hasPublishedAsset,
|
|
70
|
+
resolvePackageAssets,
|
|
71
|
+
} from "../utils/assetResolver"
|
|
72
|
+
import { syncPackageAssets } from "../utils/assetSync"
|
|
70
73
|
import {
|
|
71
74
|
FormState,
|
|
72
75
|
Lesson,
|
|
@@ -538,36 +541,6 @@ const cleanFormStateForSyllabus = (formState: FormState) => {
|
|
|
538
541
|
}
|
|
539
542
|
}
|
|
540
543
|
|
|
541
|
-
const getLocalizedValue = (
|
|
542
|
-
translations: Record<string, any> | undefined,
|
|
543
|
-
lang: string,
|
|
544
|
-
fallbackLangs: string[] = ["en", "us"]
|
|
545
|
-
): string => {
|
|
546
|
-
if (!translations || typeof translations !== "object") return ""
|
|
547
|
-
|
|
548
|
-
const direct = translations[lang]
|
|
549
|
-
if (typeof direct === "string" && direct.trim().length > 0) return direct
|
|
550
|
-
|
|
551
|
-
for (const fb of fallbackLangs) {
|
|
552
|
-
const v = translations[fb]
|
|
553
|
-
if (typeof v === "string" && v.trim().length > 0) return v
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const firstKey = Object.keys(translations)[0]
|
|
557
|
-
const first = firstKey ? translations[firstKey] : ""
|
|
558
|
-
return typeof first === "string" ? first : ""
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
function assetSyncErrorDetail(err: any): string {
|
|
562
|
-
if (typeof err?.detail === "string") return err.detail
|
|
563
|
-
if (typeof err?.message === "string") return err.message
|
|
564
|
-
try {
|
|
565
|
-
return JSON.stringify(err)
|
|
566
|
-
} catch {
|
|
567
|
-
return String(err)
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
544
|
const createMultiLangAsset = async (
|
|
572
545
|
bucket: Bucket,
|
|
573
546
|
rigoToken: string,
|
|
@@ -577,85 +550,38 @@ const createMultiLangAsset = async (
|
|
|
577
550
|
deployUrl: string,
|
|
578
551
|
academyId?: number
|
|
579
552
|
): Promise<{ errors: AssetSyncError[] }> => {
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
return {
|
|
583
|
-
errors: [
|
|
584
|
-
{
|
|
585
|
-
kind: "package_error",
|
|
586
|
-
error: {
|
|
587
|
-
detail:
|
|
588
|
-
"Could not resolve Learnpack package id; assets not synced to Breathecode.",
|
|
589
|
-
},
|
|
590
|
-
},
|
|
591
|
-
],
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
const availableLangs = Object.keys(courseJson.title)
|
|
596
|
-
console.log("AVAILABLE LANGUAGES to upload asset", availableLangs)
|
|
597
|
-
|
|
598
|
-
const all_translations: string[] = []
|
|
599
|
-
const errors: AssetSyncError[] = []
|
|
600
|
-
|
|
601
|
-
for (const lang of availableLangs) {
|
|
602
|
-
// eslint-disable-next-line no-await-in-loop
|
|
603
|
-
const indexReadme = await bucket.file(
|
|
553
|
+
const readReadme = async (lang: string): Promise<string> => {
|
|
554
|
+
const indexReadme = bucket.file(
|
|
604
555
|
`courses/${courseSlug}/README${getReadmeExtension(lang)}`
|
|
605
556
|
)
|
|
606
|
-
let indexReadmeString = ""
|
|
607
557
|
try {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
indexReadmeString = indexReadmeContent.toString()
|
|
558
|
+
const [content] = await indexReadme.download()
|
|
559
|
+
return content.toString()
|
|
611
560
|
} catch (error) {
|
|
612
561
|
console.error("Error downloading index readme", error)
|
|
613
562
|
// TODO: Trigger generation of the index readme
|
|
614
|
-
|
|
563
|
+
return ""
|
|
615
564
|
}
|
|
565
|
+
}
|
|
616
566
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
b64IndexReadme,
|
|
627
|
-
academyId,
|
|
628
|
-
learnpackId,
|
|
629
|
-
undefined,
|
|
630
|
-
all_translations
|
|
631
|
-
)
|
|
632
|
-
|
|
633
|
-
if (!asset) {
|
|
634
|
-
errors.push({
|
|
635
|
-
kind: "lang_error",
|
|
636
|
-
lang,
|
|
637
|
-
error: { detail: "Failed to create asset" },
|
|
638
|
-
})
|
|
639
|
-
console.log("No se pudo crear el asset, saltando idioma", lang)
|
|
640
|
-
continue
|
|
641
|
-
}
|
|
567
|
+
const result = await syncPackageAssets({
|
|
568
|
+
bcToken,
|
|
569
|
+
rigoToken,
|
|
570
|
+
packageSlug: courseSlug,
|
|
571
|
+
learnJson: courseJson,
|
|
572
|
+
deployUrl,
|
|
573
|
+
academyId,
|
|
574
|
+
readReadme,
|
|
575
|
+
})
|
|
642
576
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
error
|
|
649
|
-
errors.push({
|
|
650
|
-
kind: "lang_error",
|
|
651
|
-
lang,
|
|
652
|
-
error: { detail: assetSyncErrorDetail(errorData) },
|
|
653
|
-
})
|
|
654
|
-
console.error(`Error creating asset for language ${lang}:`, error)
|
|
655
|
-
}
|
|
577
|
+
for (const warning of result.warnings) console.warn("[assets]", warning)
|
|
578
|
+
for (const synced of result.synced) {
|
|
579
|
+
console.log(
|
|
580
|
+
`[assets] ${synced.action} "${synced.lang}": #${synced.asset.id} ${synced.asset.slug}`
|
|
581
|
+
)
|
|
656
582
|
}
|
|
657
583
|
|
|
658
|
-
return { errors }
|
|
584
|
+
return { errors: result.errors }
|
|
659
585
|
}
|
|
660
586
|
|
|
661
587
|
const lessonCleaner = (lesson: Lesson) => {
|
|
@@ -5392,6 +5318,10 @@ class ServeCommand extends SessionCommand {
|
|
|
5392
5318
|
})
|
|
5393
5319
|
}
|
|
5394
5320
|
|
|
5321
|
+
// Older IDEs only send the Breathecode token; without the Rigobot
|
|
5322
|
+
// token the lookup falls back to the title-derived slugs.
|
|
5323
|
+
const rigoToken = req.header("x-rigo-token")
|
|
5324
|
+
|
|
5395
5325
|
const configFile = await bucket.file(
|
|
5396
5326
|
`courses/${slug}/.learn/config.json`
|
|
5397
5327
|
)
|
|
@@ -5399,56 +5329,51 @@ class ServeCommand extends SessionCommand {
|
|
|
5399
5329
|
const configJson = JSON.parse(configContent.toString())
|
|
5400
5330
|
const { config } = configJson
|
|
5401
5331
|
|
|
5402
|
-
const
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
const
|
|
5409
|
-
.
|
|
5410
|
-
|
|
5411
|
-
if (!assetTitle) return null
|
|
5412
|
-
const assetSlug = slugify(assetTitle).slice(0, 47)
|
|
5413
|
-
return `${assetSlug}-${lang}`
|
|
5414
|
-
})
|
|
5415
|
-
.filter((slug): slug is string => slug !== null)
|
|
5332
|
+
const resolution = await resolvePackageAssets({
|
|
5333
|
+
bcToken,
|
|
5334
|
+
rigoToken,
|
|
5335
|
+
packageSlug: slug,
|
|
5336
|
+
learnJson: config,
|
|
5337
|
+
})
|
|
5338
|
+
for (const warning of resolution.warnings) {
|
|
5339
|
+
console.warn("[package-academy]", warning)
|
|
5340
|
+
}
|
|
5416
5341
|
|
|
5417
|
-
const
|
|
5418
|
-
|
|
5419
|
-
|
|
5342
|
+
const isPublished = hasPublishedAsset(resolution)
|
|
5343
|
+
const academyMode = determineAcademyMode(resolution)
|
|
5344
|
+
const warnings = [...resolution.warnings]
|
|
5345
|
+
if (resolution.packageState === "unknown") {
|
|
5346
|
+
warnings.push(
|
|
5347
|
+
`${resolution.packageError}; existing assets could not be checked.`
|
|
5348
|
+
)
|
|
5349
|
+
}
|
|
5420
5350
|
|
|
5421
|
-
for (const
|
|
5422
|
-
if (
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5351
|
+
for (const [lang, entry] of resolution.byLang) {
|
|
5352
|
+
if (entry.status === "unknown") {
|
|
5353
|
+
warnings.push(`"${lang}": ${entry.error}`)
|
|
5354
|
+
} else if (entry.status === "ambiguous") {
|
|
5355
|
+
warnings.push(
|
|
5356
|
+
`"${lang}": ${
|
|
5357
|
+
entry.candidates.length
|
|
5358
|
+
} assets found (${entry.candidates
|
|
5359
|
+
.map(c => `#${c.id} ${c.slug}`)
|
|
5360
|
+
.join(", ")})`
|
|
5361
|
+
)
|
|
5427
5362
|
}
|
|
5428
5363
|
}
|
|
5429
5364
|
|
|
5430
|
-
const
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
let conflictAcademies: number[] | undefined
|
|
5436
|
-
|
|
5437
|
-
if (uniqueAcademies.length === 0) {
|
|
5438
|
-
mode = "select"
|
|
5439
|
-
} else if (uniqueAcademies.length === 1) {
|
|
5440
|
-
mode = "locked"
|
|
5441
|
-
lockedAcademyId = uniqueAcademies[0]
|
|
5442
|
-
} else {
|
|
5443
|
-
mode = "conflict"
|
|
5444
|
-
conflictAcademies = uniqueAcademies
|
|
5445
|
-
}
|
|
5365
|
+
const mode = academyMode.type
|
|
5366
|
+
const lockedAcademyId =
|
|
5367
|
+
academyMode.type === "locked" ? academyMode.academyId : undefined
|
|
5368
|
+
const conflictAcademies =
|
|
5369
|
+
academyMode.type === "conflict" ? academyMode.academies : undefined
|
|
5446
5370
|
|
|
5447
5371
|
return res.json({
|
|
5448
5372
|
isPublished,
|
|
5449
5373
|
mode,
|
|
5450
5374
|
lockedAcademyId,
|
|
5451
5375
|
conflictAcademies,
|
|
5376
|
+
warnings,
|
|
5452
5377
|
})
|
|
5453
5378
|
} catch (error) {
|
|
5454
5379
|
console.error("Error fetching package academy:", error)
|