@learnpack/learnpack 5.0.343 → 5.0.346
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 +7 -1
- package/lib/commands/publish.js +64 -21
- package/lib/commands/serve.js +810 -15
- package/lib/creatorDist/assets/{index-BhqDgBS9.js → index-DnthLsvb.js} +4731 -4730
- package/lib/creatorDist/index.html +1 -1
- package/lib/utils/api.d.ts +1 -1
- package/lib/utils/api.js +38 -18
- package/package.json +1 -1
- package/src/commands/publish.ts +107 -30
- package/src/commands/serve.ts +1194 -16
- package/src/creator/src/components/FileUploader.tsx +1 -2
- package/src/creator/src/utils/rigo.ts +1 -2
- package/src/creatorDist/assets/{index-BhqDgBS9.js → index-DnthLsvb.js} +4731 -4730
- package/src/creatorDist/index.html +1 -1
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +2114 -2113
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/api.ts +52 -20
|
@@ -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-
|
|
13
|
+
<script type="module" crossorigin src="/creator/assets/index-DnthLsvb.js"></script>
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/creator/assets/index-CjddKHB_.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
package/lib/utils/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const RIGOBOT_HOST
|
|
1
|
+
export declare const RIGOBOT_HOST: string;
|
|
2
2
|
export declare const RIGOBOT_REALTIME_HOST = "https://ai.4geeks.com";
|
|
3
3
|
type TConsumableSlug = "ai-conversation-message" | "ai-compilation" | "ai-tutorial-generation" | "ai-generation" | "learnpack-publish";
|
|
4
4
|
export declare const countConsumables: (consumables: any, consumableSlug?: TConsumableSlug) => any;
|
package/lib/utils/api.js
CHANGED
|
@@ -8,7 +8,7 @@ const axios_1 = require("axios");
|
|
|
8
8
|
const dotenv = require("dotenv");
|
|
9
9
|
dotenv.config();
|
|
10
10
|
const HOST = "https://breathecode.herokuapp.com";
|
|
11
|
-
exports.RIGOBOT_HOST = "https://rigobot.herokuapp.com";
|
|
11
|
+
exports.RIGOBOT_HOST = process.env.RIGOBOT_HOST || "https://rigobot.herokuapp.com";
|
|
12
12
|
exports.RIGOBOT_REALTIME_HOST = "https://ai.4geeks.com";
|
|
13
13
|
// export const RIGOBOT_REALTIME_HOST = "http://127.0.0.1:8003"
|
|
14
14
|
// export const RIGOBOT_HOST = "https://rigobot-test-cca7d841c9d8.herokuapp.com"
|
|
@@ -278,36 +278,48 @@ const getConsumable = async (token, consumableSlug = "ai-generation") => {
|
|
|
278
278
|
}
|
|
279
279
|
};
|
|
280
280
|
exports.getConsumable = getConsumable;
|
|
281
|
-
const
|
|
282
|
-
"add_asset",
|
|
283
|
-
"change_asset",
|
|
284
|
-
"view_asset",
|
|
285
|
-
"delete_asset",
|
|
286
|
-
];
|
|
281
|
+
const CRUD_ASSET_CAPABILITY_URL = `${HOST}/v1/auth/user/me/capability/crud_asset`;
|
|
287
282
|
const listUserAcademies = async (breathecodeToken) => {
|
|
288
|
-
const
|
|
283
|
+
const meUrl = `${HOST}/v1/auth/user/me`;
|
|
289
284
|
try {
|
|
290
|
-
const response = await axios_1.default.get(
|
|
285
|
+
const response = await axios_1.default.get(meUrl, {
|
|
291
286
|
headers: {
|
|
292
287
|
Authorization: `Token ${breathecodeToken}`,
|
|
293
288
|
},
|
|
294
289
|
});
|
|
295
290
|
const data = response.data;
|
|
296
291
|
const academiesMap = new Map();
|
|
292
|
+
if (!Array.isArray(data.roles)) {
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
297
295
|
for (const role of data.roles) {
|
|
298
296
|
const academy = role.academy;
|
|
297
|
+
if (!academy)
|
|
298
|
+
continue;
|
|
299
299
|
if (!academiesMap.has(academy.id)) {
|
|
300
300
|
academiesMap.set(academy.id, academy);
|
|
301
301
|
}
|
|
302
302
|
}
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
303
|
+
const academies = [...academiesMap.values()];
|
|
304
|
+
const allowed = await Promise.all(academies.map(async (academy) => {
|
|
305
|
+
const capResponse = await axios_1.default.get(CRUD_ASSET_CAPABILITY_URL, {
|
|
306
|
+
headers: {
|
|
307
|
+
Authorization: `Token ${breathecodeToken}`,
|
|
308
|
+
Academy: academy.id,
|
|
309
|
+
},
|
|
310
|
+
validateStatus: () => true,
|
|
311
|
+
});
|
|
312
|
+
if (capResponse.status === 200) {
|
|
313
|
+
return academy;
|
|
314
|
+
}
|
|
315
|
+
if (capResponse.status !== 403) {
|
|
316
|
+
console.warn(`listUserAcademies: unexpected status ${capResponse.status} for academy ${academy.id}`);
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
}));
|
|
320
|
+
const filtered = allowed.filter((a) => a !== null);
|
|
321
|
+
filtered.sort((a, b) => a.name.localeCompare(b.name));
|
|
322
|
+
return filtered;
|
|
311
323
|
}
|
|
312
324
|
catch (error) {
|
|
313
325
|
console.error("Failed to fetch user academies:", error);
|
|
@@ -330,6 +342,7 @@ const validateToken = async (token) => {
|
|
|
330
342
|
};
|
|
331
343
|
exports.validateToken = validateToken;
|
|
332
344
|
const createAsset = async (token, asset) => {
|
|
345
|
+
var _a;
|
|
333
346
|
const body = {
|
|
334
347
|
slug: asset.slug,
|
|
335
348
|
title: asset.title,
|
|
@@ -367,6 +380,7 @@ const createAsset = async (token, asset) => {
|
|
|
367
380
|
url = `https://breathecode.herokuapp.com/v1/registry/academy/asset`;
|
|
368
381
|
headers.Academy = String(asset.academy_id);
|
|
369
382
|
}
|
|
383
|
+
console.log("[BC] POST", url, "| academy_id:", (_a = asset.academy_id) !== null && _a !== void 0 ? _a : "none", "| slug:", asset.slug);
|
|
370
384
|
try {
|
|
371
385
|
const response = await axios_1.default.post(url, body, { headers });
|
|
372
386
|
return response.data;
|
|
@@ -398,10 +412,12 @@ const doesAssetExists = async (token, assetSlug) => {
|
|
|
398
412
|
};
|
|
399
413
|
exports.doesAssetExists = doesAssetExists;
|
|
400
414
|
const updateAsset = async (token, assetSlug, asset) => {
|
|
415
|
+
var _a;
|
|
401
416
|
const url = `https://breathecode.herokuapp.com/v1/registry/asset/me/${assetSlug}`;
|
|
402
417
|
const headers = {
|
|
403
418
|
Authorization: `Token ${token}`,
|
|
404
419
|
};
|
|
420
|
+
console.log("[BC] PUT", url, "| academy_id:", (_a = asset.academy_id) !== null && _a !== void 0 ? _a : "none");
|
|
405
421
|
try {
|
|
406
422
|
const response = await axios_1.default.put(url, asset, { headers });
|
|
407
423
|
return response.data;
|
|
@@ -460,7 +476,11 @@ const createRigoPackage = async (token, slug, config) => {
|
|
|
460
476
|
}
|
|
461
477
|
};
|
|
462
478
|
let technologiesCache = [];
|
|
463
|
-
/**
|
|
479
|
+
/**
|
|
480
|
+
* Strip .env comments (e.g. "token # comment") so the token is sent without spaces.
|
|
481
|
+
* @param token - Raw token value from env, possibly with trailing comment.
|
|
482
|
+
* @returns Trimmed token with inline `#` comments removed, or empty string if missing.
|
|
483
|
+
*/
|
|
464
484
|
function sanitizeToken(token) {
|
|
465
485
|
if (!token)
|
|
466
486
|
return "";
|
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.
|
|
4
|
+
"version": "5.0.346",
|
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
|
6
6
|
"contributors": [
|
|
7
7
|
{
|
package/src/commands/publish.ts
CHANGED
|
@@ -54,6 +54,54 @@ const getLocalizedValue = (
|
|
|
54
54
|
return typeof first === "string" ? first : ""
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
type ExistingAssetInfo = {
|
|
58
|
+
lang: string;
|
|
59
|
+
slug: string;
|
|
60
|
+
exists: boolean;
|
|
61
|
+
academyId?: number;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type AcademyMode =
|
|
65
|
+
| { type: "select" }
|
|
66
|
+
| { type: "locked"; academyId: number }
|
|
67
|
+
| { type: "conflict"; academies: number[] };
|
|
68
|
+
|
|
69
|
+
const getExistingAssetsInfo = async (
|
|
70
|
+
token: string,
|
|
71
|
+
learnJson: any
|
|
72
|
+
): Promise<ExistingAssetInfo[]> => {
|
|
73
|
+
const availableLangs = getAvailableLangs(learnJson)
|
|
74
|
+
const results: ExistingAssetInfo[] = []
|
|
75
|
+
|
|
76
|
+
for (const lang of availableLangs) {
|
|
77
|
+
const assetTitle = getLocalizedValue(learnJson?.title, lang)
|
|
78
|
+
if (!assetTitle) continue
|
|
79
|
+
|
|
80
|
+
let slug = slugify(assetTitle).slice(0, 47)
|
|
81
|
+
slug = `${slug}-${lang}`
|
|
82
|
+
|
|
83
|
+
// eslint-disable-next-line no-await-in-loop
|
|
84
|
+
const { exists, academyId } = await api.doesAssetExists(token, slug)
|
|
85
|
+
results.push({ lang, slug, exists, academyId })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return results
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const determinePublishAcademyMode = (
|
|
92
|
+
existingAssets: ExistingAssetInfo[]
|
|
93
|
+
): AcademyMode => {
|
|
94
|
+
const academyIds = existingAssets
|
|
95
|
+
.filter((a) => a.exists && a.academyId !== undefined)
|
|
96
|
+
.map((a) => a.academyId as number)
|
|
97
|
+
|
|
98
|
+
const unique = [...new Set(academyIds)]
|
|
99
|
+
|
|
100
|
+
if (unique.length === 0) return { type: "select" }
|
|
101
|
+
if (unique.length === 1) return { type: "locked", academyId: unique[0] }
|
|
102
|
+
return { type: "conflict", academies: unique }
|
|
103
|
+
}
|
|
104
|
+
|
|
57
105
|
export const handleAssetCreation = async (
|
|
58
106
|
sessionPayload: { token: string; rigobotToken: string },
|
|
59
107
|
learnJson: any,
|
|
@@ -61,6 +109,7 @@ export const handleAssetCreation = async (
|
|
|
61
109
|
learnpackDeployUrl: string,
|
|
62
110
|
b64IndexReadme: string,
|
|
63
111
|
academyId: number | undefined,
|
|
112
|
+
preflightInfo?: ExistingAssetInfo,
|
|
64
113
|
all_translations: string[] = []
|
|
65
114
|
) => {
|
|
66
115
|
const category = "uncategorized"
|
|
@@ -83,23 +132,9 @@ export const handleAssetCreation = async (
|
|
|
83
132
|
let slug = slugify(assetTitle).slice(0, 47)
|
|
84
133
|
slug = `${slug}-${selectedLang}`
|
|
85
134
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
slug
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
// Compare academy IDs if asset exists and academyId is provided
|
|
92
|
-
if (
|
|
93
|
-
exists &&
|
|
94
|
-
existingAcademyId !== undefined &&
|
|
95
|
-
academyId !== undefined &&
|
|
96
|
-
existingAcademyId !== academyId
|
|
97
|
-
) {
|
|
98
|
-
Console.warning(
|
|
99
|
-
`Asset exists in academy ${existingAcademyId}, but attempting to publish to academy ${academyId}. ` +
|
|
100
|
-
`The asset will be updated in its current academy (${existingAcademyId}).`
|
|
101
|
-
)
|
|
102
|
-
}
|
|
135
|
+
// Use pre-flight info when available to avoid an extra GET request
|
|
136
|
+
const { exists, academyId: existingAcademyId } =
|
|
137
|
+
preflightInfo ?? (await api.doesAssetExists(sessionPayload.token, slug))
|
|
103
138
|
|
|
104
139
|
// const technologies: unknown[] = Array.isArray(learnJson?.technologies) ?
|
|
105
140
|
// learnJson.technologies :
|
|
@@ -148,14 +183,24 @@ export const handleAssetCreation = async (
|
|
|
148
183
|
}
|
|
149
184
|
|
|
150
185
|
Console.info("Asset exists, updating it")
|
|
151
|
-
const
|
|
186
|
+
const updatePayload: any = {
|
|
152
187
|
graded: true,
|
|
153
188
|
learnpack_deploy_url: learnpackDeployUrl,
|
|
154
189
|
title: assetTitle,
|
|
155
190
|
category: category,
|
|
156
191
|
description: assetDescription,
|
|
157
192
|
all_translations,
|
|
158
|
-
}
|
|
193
|
+
}
|
|
194
|
+
// Only set academy when the asset has none yet and the user selected one
|
|
195
|
+
if (existingAcademyId === undefined && academyId !== undefined) {
|
|
196
|
+
updatePayload.academy_id = academyId
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const asset = await api.updateAsset(
|
|
200
|
+
sessionPayload.token,
|
|
201
|
+
slug,
|
|
202
|
+
updatePayload
|
|
203
|
+
)
|
|
159
204
|
try {
|
|
160
205
|
await api.updateRigoPackage(
|
|
161
206
|
sessionPayload.rigobotToken.trim(),
|
|
@@ -179,7 +224,9 @@ export const handleAssetCreation = async (
|
|
|
179
224
|
const createMultiLangAssetFromDisk = async (
|
|
180
225
|
sessionPayload: { token: string; rigobotToken: string },
|
|
181
226
|
learnJson: any,
|
|
182
|
-
deployUrl: string
|
|
227
|
+
deployUrl: string,
|
|
228
|
+
selectedAcademyId?: number,
|
|
229
|
+
existingAssetsInfo: ExistingAssetInfo[] = []
|
|
183
230
|
) => {
|
|
184
231
|
const availableLangs = getAvailableLangs(learnJson)
|
|
185
232
|
|
|
@@ -208,6 +255,7 @@ const createMultiLangAssetFromDisk = async (
|
|
|
208
255
|
}
|
|
209
256
|
|
|
210
257
|
const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64")
|
|
258
|
+
const preflightInfo = existingAssetsInfo.find((a) => a.lang === lang)
|
|
211
259
|
|
|
212
260
|
try {
|
|
213
261
|
// eslint-disable-next-line no-await-in-loop
|
|
@@ -217,7 +265,8 @@ const createMultiLangAssetFromDisk = async (
|
|
|
217
265
|
lang,
|
|
218
266
|
deployUrl,
|
|
219
267
|
b64IndexReadme,
|
|
220
|
-
|
|
268
|
+
selectedAcademyId,
|
|
269
|
+
preflightInfo,
|
|
221
270
|
all_translations
|
|
222
271
|
)
|
|
223
272
|
|
|
@@ -406,6 +455,13 @@ class BuildCommand extends SessionCommand {
|
|
|
406
455
|
this.configManager?.buildIndex()
|
|
407
456
|
}
|
|
408
457
|
|
|
458
|
+
const learnJsonPath = path.join(process.cwd(), "learn.json")
|
|
459
|
+
if (!fs.existsSync(learnJsonPath)) {
|
|
460
|
+
this.error("learn.json not found")
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const learnJson = JSON.parse(fs.readFileSync(learnJsonPath, "utf-8"))
|
|
464
|
+
|
|
409
465
|
const academies = await api.listUserAcademies(sessionPayload.token)
|
|
410
466
|
|
|
411
467
|
if (academies.length === 0) {
|
|
@@ -415,17 +471,36 @@ class BuildCommand extends SessionCommand {
|
|
|
415
471
|
process.exit(1)
|
|
416
472
|
}
|
|
417
473
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
sessionPayload.token
|
|
474
|
+
Console.info("Checking existing assets...")
|
|
475
|
+
const existingAssetsInfo = await getExistingAssetsInfo(
|
|
476
|
+
sessionPayload.token,
|
|
477
|
+
learnJson
|
|
421
478
|
)
|
|
479
|
+
const academyMode = determinePublishAcademyMode(existingAssetsInfo)
|
|
422
480
|
|
|
423
|
-
|
|
424
|
-
if (!fs.existsSync(learnJsonPath)) {
|
|
425
|
-
this.error("learn.json not found")
|
|
426
|
-
}
|
|
481
|
+
let selectedAcademyId: number | undefined
|
|
427
482
|
|
|
428
|
-
|
|
483
|
+
if (academyMode.type === "conflict") {
|
|
484
|
+
Console.warning(
|
|
485
|
+
`Some of your assets are associated with different academies ` +
|
|
486
|
+
`(${academyMode.academies.join(", ")}). ` +
|
|
487
|
+
`Academy assignment will be skipped to avoid conflicts.`
|
|
488
|
+
)
|
|
489
|
+
} else if (academyMode.type === "locked") {
|
|
490
|
+
const lockedAcademy = academies.find(
|
|
491
|
+
(a) => a.id === academyMode.academyId
|
|
492
|
+
)
|
|
493
|
+
Console.info(
|
|
494
|
+
`This package is associated with academy: ${
|
|
495
|
+
lockedAcademy?.name ?? academyMode.academyId
|
|
496
|
+
}. Academy cannot be changed.`
|
|
497
|
+
)
|
|
498
|
+
selectedAcademyId = academyMode.academyId
|
|
499
|
+
} else {
|
|
500
|
+
// mode === "select": all existing assets have no academy, user picks one
|
|
501
|
+
const { academy } = await selectAcademy(academies, sessionPayload.token)
|
|
502
|
+
selectedAcademyId = academy?.id
|
|
503
|
+
}
|
|
429
504
|
|
|
430
505
|
const zipFilePath = path.join(process.cwd(), `${learnJson.slug}.zip`)
|
|
431
506
|
|
|
@@ -595,7 +670,9 @@ class BuildCommand extends SessionCommand {
|
|
|
595
670
|
await createMultiLangAssetFromDisk(
|
|
596
671
|
{ token: sessionPayload.token, rigobotToken: rigoToken },
|
|
597
672
|
learnJson,
|
|
598
|
-
res.data.url
|
|
673
|
+
res.data.url,
|
|
674
|
+
selectedAcademyId,
|
|
675
|
+
existingAssetsInfo
|
|
599
676
|
)
|
|
600
677
|
} catch (error) {
|
|
601
678
|
if (axios.isAxiosError(error)) {
|