@learnpack/learnpack 5.0.354 → 5.0.357
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/serve.d.ts +0 -17
- package/lib/commands/serve.js +160 -130
- package/lib/creatorDist/assets/{index-DnthLsvb.js → index-D6pmbMe9.js} +14030 -13903
- package/lib/creatorDist/assets/index-zrPponAn.css +1701 -0
- package/lib/creatorDist/index.html +2 -2
- package/lib/models/creator.d.ts +1 -1
- package/lib/services/ingest/adapters/gcsCourseStorage.d.ts +13 -0
- package/lib/services/ingest/adapters/gcsCourseStorage.js +58 -0
- package/lib/services/ingest/adapters/rigoUserTokenAuth.d.ts +15 -0
- package/lib/services/ingest/adapters/rigoUserTokenAuth.js +46 -0
- package/lib/services/ingest/adapters/rigobotPackageRegistry.d.ts +7 -0
- package/lib/services/ingest/adapters/rigobotPackageRegistry.js +119 -0
- package/lib/services/ingest/core/buildIngestConfig.d.ts +27 -0
- package/lib/services/ingest/core/buildIngestConfig.js +40 -0
- package/lib/services/ingest/core/buildInitialSyllabus.d.ts +24 -0
- package/lib/services/ingest/core/buildInitialSyllabus.js +104 -0
- package/lib/services/ingest/core/buildSidebar.d.ts +13 -0
- package/lib/services/ingest/core/buildSidebar.js +28 -0
- package/lib/services/ingest/core/coursePaths.d.ts +31 -0
- package/lib/services/ingest/core/coursePaths.js +50 -0
- package/lib/services/ingest/core/ingestCoursePackage.d.ts +52 -0
- package/lib/services/ingest/core/ingestCoursePackage.js +217 -0
- package/lib/services/ingest/core/normalizeLearnJson.d.ts +25 -0
- package/lib/services/ingest/core/normalizeLearnJson.js +29 -0
- package/lib/services/ingest/core/pairLessons.d.ts +29 -0
- package/lib/services/ingest/core/pairLessons.js +102 -0
- package/lib/services/ingest/core/planWrites.d.ts +44 -0
- package/lib/services/ingest/core/planWrites.js +59 -0
- package/lib/services/ingest/core/resolveSlug.d.ts +53 -0
- package/lib/services/ingest/core/resolveSlug.js +93 -0
- package/lib/services/ingest/core/types.d.ts +53 -0
- package/lib/services/ingest/core/types.js +2 -0
- package/lib/services/ingest/core/validatePackage.d.ts +38 -0
- package/lib/services/ingest/core/validatePackage.js +97 -0
- package/lib/services/ingest/createIngestService.d.ts +34 -0
- package/lib/services/ingest/createIngestService.js +38 -0
- package/lib/services/ingest/errors.d.ts +22 -0
- package/lib/services/ingest/errors.js +34 -0
- package/lib/services/ingest/http/errorMapping.d.ts +8 -0
- package/lib/services/ingest/http/errorMapping.js +40 -0
- package/lib/services/ingest/http/router.d.ts +32 -0
- package/lib/services/ingest/http/router.js +124 -0
- package/lib/services/ingest/http/zipPackageReader.d.ts +26 -0
- package/lib/services/ingest/http/zipPackageReader.js +62 -0
- package/lib/services/ingest/ports/courseStorage.d.ts +25 -0
- package/lib/services/ingest/ports/courseStorage.js +2 -0
- package/lib/services/ingest/ports/packageRegistry.d.ts +58 -0
- package/lib/services/ingest/ports/packageRegistry.js +2 -0
- package/lib/services/ingest/ports/requestAuthenticator.d.ts +25 -0
- package/lib/services/ingest/ports/requestAuthenticator.js +2 -0
- package/lib/utils/coursePackage/bucketIo.d.ts +23 -0
- package/lib/utils/coursePackage/bucketIo.js +36 -0
- package/lib/utils/coursePackage/learnJson.d.ts +26 -0
- package/lib/utils/coursePackage/learnJson.js +36 -0
- package/lib/utils/coursePackage/sidebar.d.ts +15 -0
- package/lib/utils/coursePackage/sidebar.js +26 -0
- package/lib/utils/creatorSocket.js +15 -21
- package/lib/utils/gcpCredentials.d.ts +18 -0
- package/lib/utils/gcpCredentials.js +27 -0
- package/lib/utils/rigoActions.d.ts +16 -0
- package/lib/utils/rigoActions.js +102 -1
- package/lib/utils/socketRegistry.d.ts +46 -0
- package/lib/utils/socketRegistry.js +83 -0
- package/package.json +1 -1
- package/src/commands/serve.ts +163 -115
- package/src/creator/README.md +63 -51
- package/src/creator/package-lock.json +1188 -8
- package/src/creator/package.json +5 -1
- package/src/creator/src/App.tsx +120 -54
- package/src/creator/src/components/FileUploader.tsx +1 -12
- package/src/creator/src/components/NotificationListener.tsx +1 -8
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +101 -36
- package/src/creator/src/locales/en.json +8 -0
- package/src/creator/src/locales/es.json +8 -0
- package/src/creator/src/utils/completionResult.test.ts +144 -0
- package/src/creator/src/utils/completionResult.ts +154 -0
- package/src/creator/src/utils/constants.ts +17 -4
- package/src/creator/src/utils/socket.test.ts +113 -0
- package/src/creator/src/utils/socket.ts +60 -37
- package/src/creator/src/utils/store.ts +84 -67
- package/src/creator/src/utils/useCompletionWatchdog.test.ts +99 -0
- package/src/creator/src/utils/useCompletionWatchdog.ts +37 -0
- package/src/creator/vitest.config.ts +16 -0
- package/src/creatorDist/assets/{index-DnthLsvb.js → index-D6pmbMe9.js} +14030 -13903
- package/src/creatorDist/assets/index-zrPponAn.css +1701 -0
- package/src/creatorDist/index.html +2 -2
- package/src/models/creator.ts +1 -1
- package/src/services/ingest/adapters/gcsCourseStorage.ts +70 -0
- package/src/services/ingest/adapters/rigoUserTokenAuth.ts +65 -0
- package/src/services/ingest/adapters/rigobotPackageRegistry.ts +161 -0
- package/src/services/ingest/core/buildIngestConfig.ts +51 -0
- package/src/services/ingest/core/buildInitialSyllabus.ts +122 -0
- package/src/services/ingest/core/buildSidebar.ts +32 -0
- package/src/services/ingest/core/coursePaths.ts +59 -0
- package/src/services/ingest/core/ingestCoursePackage.ts +343 -0
- package/src/services/ingest/core/normalizeLearnJson.ts +47 -0
- package/src/services/ingest/core/pairLessons.ts +132 -0
- package/src/services/ingest/core/planWrites.ts +91 -0
- package/src/services/ingest/core/resolveSlug.ts +119 -0
- package/src/services/ingest/core/types.ts +57 -0
- package/src/services/ingest/core/validatePackage.ts +132 -0
- package/src/services/ingest/createIngestService.ts +66 -0
- package/src/services/ingest/errors.ts +45 -0
- package/src/services/ingest/http/errorMapping.ts +49 -0
- package/src/services/ingest/http/router.ts +156 -0
- package/src/services/ingest/http/zipPackageReader.ts +81 -0
- package/src/services/ingest/ports/courseStorage.ts +25 -0
- package/src/services/ingest/ports/packageRegistry.ts +60 -0
- package/src/services/ingest/ports/requestAuthenticator.ts +28 -0
- package/src/ui/_app/app.js +325 -325
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/coursePackage/bucketIo.ts +46 -0
- package/src/utils/coursePackage/learnJson.ts +37 -0
- package/src/utils/coursePackage/sidebar.ts +28 -0
- package/src/utils/creatorSocket.ts +16 -22
- package/src/utils/gcpCredentials.ts +38 -0
- package/src/utils/rigoActions.ts +163 -0
- package/src/utils/socketRegistry.ts +85 -0
- package/lib/creatorDist/assets/index-CjddKHB_.css +0 -1
- package/src/creatorDist/assets/index-CjddKHB_.css +0 -1
package/lib/commands/serve.d.ts
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
1
|
import SessionCommand from "../utils/SessionCommand";
|
|
2
|
-
import { FormState } from "../models/creator";
|
|
3
|
-
export declare const createLearnJson: (courseInfo: FormState) => {
|
|
4
|
-
slug: string;
|
|
5
|
-
title: {
|
|
6
|
-
[x: string]: string;
|
|
7
|
-
};
|
|
8
|
-
technologies: string[];
|
|
9
|
-
difficulty: string;
|
|
10
|
-
description: {
|
|
11
|
-
[x: string]: string;
|
|
12
|
-
};
|
|
13
|
-
grading: string;
|
|
14
|
-
telemetry: {
|
|
15
|
-
batch: string;
|
|
16
|
-
};
|
|
17
|
-
preview: string;
|
|
18
|
-
};
|
|
19
2
|
export declare const processImage: (url: string, description: string, rigoToken: string, courseSlug: string) => Promise<boolean>;
|
|
20
3
|
declare class ServeCommand extends SessionCommand {
|
|
21
4
|
static description: string;
|
package/lib/commands/serve.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.processImage =
|
|
3
|
+
exports.processImage = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const command_1 = require("@oclif/command");
|
|
6
6
|
const buffer_1 = require("buffer");
|
|
@@ -33,6 +33,10 @@ const creatorUtilities_2 = require("../utils/creatorUtilities");
|
|
|
33
33
|
const sidebarGenerator_1 = require("../utils/sidebarGenerator");
|
|
34
34
|
const publish_1 = require("./publish");
|
|
35
35
|
const export_1 = require("../utils/export");
|
|
36
|
+
const router_1 = require("../services/ingest/http/router");
|
|
37
|
+
const bucketIo_1 = require("../utils/coursePackage/bucketIo");
|
|
38
|
+
const learnJson_1 = require("../utils/coursePackage/learnJson");
|
|
39
|
+
const sidebar_1 = require("../utils/coursePackage/sidebar");
|
|
36
40
|
const packageManifest_1 = require("../utils/packageManifest");
|
|
37
41
|
const gcsBucketName_1 = require("../utils/gcsBucketName");
|
|
38
42
|
const syllabusSync_1 = require("../utils/syllabusSync");
|
|
@@ -86,39 +90,6 @@ async function serializeByCourse(courseSlug, fn) {
|
|
|
86
90
|
courseQueues.set(courseSlug, next);
|
|
87
91
|
return next;
|
|
88
92
|
}
|
|
89
|
-
const createLearnJson = (courseInfo) => {
|
|
90
|
-
// console.log("courseInfo to create learn json", courseInfo)
|
|
91
|
-
const expectedPreviewUrl = `https://${courseInfo.slug}.learn-pack.com/preview.png`;
|
|
92
|
-
const language = courseInfo.language || "en";
|
|
93
|
-
const learnJson = {
|
|
94
|
-
slug: courseInfo.slug,
|
|
95
|
-
title: {
|
|
96
|
-
[language]: courseInfo.title,
|
|
97
|
-
},
|
|
98
|
-
technologies: courseInfo.technologies || [],
|
|
99
|
-
difficulty: "beginner",
|
|
100
|
-
description: {
|
|
101
|
-
[language]: courseInfo.description,
|
|
102
|
-
},
|
|
103
|
-
grading: "isolated",
|
|
104
|
-
telemetry: {
|
|
105
|
-
batch: api_1.BREATHECODE_TELEMETRY_URL,
|
|
106
|
-
},
|
|
107
|
-
preview: expectedPreviewUrl,
|
|
108
|
-
};
|
|
109
|
-
return learnJson;
|
|
110
|
-
};
|
|
111
|
-
exports.createLearnJson = createLearnJson;
|
|
112
|
-
const uploadFileToBucket = async (bucket, file, path) => {
|
|
113
|
-
const isReadme = /readme(\.\w+)?\.md$/i.test(path);
|
|
114
|
-
const content = isReadme ? (0, readmeSanitizer_1.sanitizeReadmeNewlines)(file) : file;
|
|
115
|
-
const fileRef = bucket.file(path);
|
|
116
|
-
await fileRef.save(buffer_1.Buffer.from(content, "utf8"));
|
|
117
|
-
};
|
|
118
|
-
const uploadBinaryToBucket = async (bucket, buffer, path, contentType) => {
|
|
119
|
-
const fileRef = bucket.file(path);
|
|
120
|
-
await fileRef.save(buffer, Object.assign({ resumable: false }, (contentType && { contentType })));
|
|
121
|
-
};
|
|
122
93
|
const getGithubCredentials = () => {
|
|
123
94
|
var _a, _b;
|
|
124
95
|
const token = (_a = process.env.GITHUB_TOKEN) === null || _a === void 0 ? void 0 : _a.trim();
|
|
@@ -331,15 +302,6 @@ const processImage = async (url, description, rigoToken, courseSlug) => {
|
|
|
331
302
|
}
|
|
332
303
|
};
|
|
333
304
|
exports.processImage = processImage;
|
|
334
|
-
const createInitialSidebar = async (slugs, initialLanguage = "en") => {
|
|
335
|
-
const sidebar = {};
|
|
336
|
-
for (const slug of slugs) {
|
|
337
|
-
sidebar[slug] = {
|
|
338
|
-
[initialLanguage]: slug,
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
return sidebar;
|
|
342
|
-
};
|
|
343
305
|
const uploadInitialReadme = async (bucket, exSlug, targetDir, packageContext) => {
|
|
344
306
|
const isGeneratingText = `
|
|
345
307
|
\`\`\`loader slug="${exSlug}"
|
|
@@ -347,7 +309,7 @@ const uploadInitialReadme = async (bucket, exSlug, targetDir, packageContext) =>
|
|
|
347
309
|
\`\`\`
|
|
348
310
|
`;
|
|
349
311
|
const readmeFilename = `README${(0, creatorUtilities_1.getReadmeExtension)(packageContext.language || "en")}`;
|
|
350
|
-
await uploadFileToBucket(bucket, isGeneratingText, `${targetDir}/${readmeFilename}`);
|
|
312
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, isGeneratingText, `${targetDir}/${readmeFilename}`);
|
|
351
313
|
};
|
|
352
314
|
const cleanFormState = (formState) => {
|
|
353
315
|
const { description, technologies, purpose, hasContentIndex, duration, isCompleted, variables, currentStep, language } = formState, rest = tslib_1.__rest(formState, ["description", "technologies", "purpose", "hasContentIndex", "duration", "isCompleted", "variables", "currentStep", "language"]);
|
|
@@ -581,7 +543,7 @@ function sanitizeSyllabusFromUnknown(syllabus) {
|
|
|
581
543
|
}
|
|
582
544
|
async function saveSyllabus(courseSlug, syllabus, bucket) {
|
|
583
545
|
sanitizeSyllabusFromUnknown(syllabus);
|
|
584
|
-
await uploadFileToBucket(bucket, JSON.stringify(syllabus), `courses/${courseSlug}/.learn/initialSyllabus.json`);
|
|
546
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(syllabus), `courses/${courseSlug}/.learn/initialSyllabus.json`);
|
|
585
547
|
}
|
|
586
548
|
// Instrumented syllabus operations for race condition observability
|
|
587
549
|
async function getSyllabusWithLog(courseSlug, bucket, context) {
|
|
@@ -786,29 +748,68 @@ const getTitleFromHTML = (html) => {
|
|
|
786
748
|
const titleMatch = html.match(titleRegex);
|
|
787
749
|
return titleMatch ? titleMatch[1] : null;
|
|
788
750
|
};
|
|
751
|
+
/** How often the notification is touched while a translation is running. */
|
|
752
|
+
const SYNC_HEARTBEAT_INTERVAL_MS = 60 * 1000;
|
|
753
|
+
/**
|
|
754
|
+
* A synchronization with no news for this long is considered stuck: the GET
|
|
755
|
+
* endpoint flags it as failed and it can be accepted again.
|
|
756
|
+
*/
|
|
757
|
+
const SYNC_PROCESSING_TIMEOUT_MS = 3 * 60 * 1000;
|
|
758
|
+
// True when a processing notification stopped reporting progress.
|
|
759
|
+
function isSyncProcessingStale(notification) {
|
|
760
|
+
const lastUpdate = notification.processingLastUpdate || notification.updatedAt;
|
|
761
|
+
return Date.now() - lastUpdate > SYNC_PROCESSING_TIMEOUT_MS;
|
|
762
|
+
}
|
|
763
|
+
function findSyncNotification(syllabus, exerciseSlug, notificationId) {
|
|
764
|
+
var _a;
|
|
765
|
+
const lesson = syllabus.lessons.find(lesson => (0, creatorUtilities_2.slugify)(lesson.id + "-" + lesson.title) === exerciseSlug);
|
|
766
|
+
return (_a = lesson === null || lesson === void 0 ? void 0 : lesson.syncNotifications) === null || _a === void 0 ? void 0 : _a.find(n => n.id === notificationId);
|
|
767
|
+
}
|
|
789
768
|
async function processSyncTranslationsSequentially(courseSlug, exerciseSlug, notificationId, sourceReadmeContent, targetLanguages, rigoToken, bucket, historyManager) {
|
|
790
|
-
var _a, _b, _c, _d, _e, _f;
|
|
769
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
791
770
|
try {
|
|
792
771
|
const sanitizedSource = (0, readmeSanitizer_1.sanitizeReadmeNewlines)(sourceReadmeContent);
|
|
772
|
+
let lastHeartbeatAt = Date.now();
|
|
773
|
+
// Keeps `processingLastUpdate` fresh while a language is being translated,
|
|
774
|
+
// otherwise the PROCESSING_TIMEOUT in GET /sync-notifications can flag a
|
|
775
|
+
// running synchronization as failed. Throttled, since every beat rewrites
|
|
776
|
+
// the syllabus in the bucket.
|
|
777
|
+
const heartbeat = async (targetLang) => {
|
|
778
|
+
if (Date.now() - lastHeartbeatAt < SYNC_HEARTBEAT_INTERVAL_MS) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
lastHeartbeatAt = Date.now();
|
|
782
|
+
try {
|
|
783
|
+
await serializeByCourse(courseSlug, async () => {
|
|
784
|
+
const syllabus = await getSyllabus(courseSlug, bucket);
|
|
785
|
+
const notification = findSyncNotification(syllabus, exerciseSlug, notificationId);
|
|
786
|
+
if (!notification) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
notification.processingLastUpdate = Date.now();
|
|
790
|
+
if (notification.syncProgress) {
|
|
791
|
+
notification.syncProgress.currentLanguage = targetLang;
|
|
792
|
+
}
|
|
793
|
+
await saveSyllabus(courseSlug, syllabus, bucket);
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
catch (error) {
|
|
797
|
+
console.warn(`🔄 SYNC: heartbeat failed for ${exerciseSlug}:`, error.message);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
793
800
|
// Process translations sequentially (no race conditions)
|
|
794
801
|
for (const targetLang of targetLanguages) {
|
|
795
802
|
try {
|
|
796
|
-
|
|
803
|
+
const languageStartedAt = Date.now();
|
|
804
|
+
// The translation runs as an asynchronous Rigobot job that we poll:
|
|
805
|
+
// waiting for it inside a single request takes 28-45s for a full
|
|
806
|
+
// README and Heroku kills anything above 30s.
|
|
797
807
|
// eslint-disable-next-line no-await-in-loop
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
include_purpose_objective: false,
|
|
804
|
-
execute_async: false, // Synchronous execution
|
|
805
|
-
}, {
|
|
806
|
-
headers: {
|
|
807
|
-
"Content-Type": "application/json",
|
|
808
|
-
Authorization: "Token " + rigoToken,
|
|
809
|
-
},
|
|
810
|
-
});
|
|
811
|
-
const translationResult = response.data;
|
|
808
|
+
const translationResult = await (0, rigoActions_1.translateExerciseAndWait)(rigoToken, {
|
|
809
|
+
text_to_translate: sanitizedSource,
|
|
810
|
+
output_language: targetLang,
|
|
811
|
+
}, { onPoll: () => heartbeat(targetLang) });
|
|
812
|
+
console.log(`🔄 SYNC: ${targetLang} translated in ${Math.round((Date.now() - languageStartedAt) / 1000)}s (job ${translationResult.id})`);
|
|
812
813
|
// Check if translation was successful
|
|
813
814
|
if (!((_a = translationResult.parsed) === null || _a === void 0 ? void 0 : _a.translation)) {
|
|
814
815
|
throw new Error("Translation result is empty");
|
|
@@ -897,10 +898,20 @@ async function processSyncTranslationsSequentially(courseSlug, exerciseSlug, not
|
|
|
897
898
|
const totalProcessed = notification.syncProgress.completedLanguages.length +
|
|
898
899
|
(((_e = notification.syncProgress.failedLanguages) === null || _e === void 0 ? void 0 : _e.length) || 0);
|
|
899
900
|
if (totalProcessed === notification.syncProgress.totalLanguages) {
|
|
900
|
-
notification.
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
901
|
+
const completedCount = notification.syncProgress.completedLanguages.length;
|
|
902
|
+
const failedCount = ((_f = notification.syncProgress.failedLanguages) === null || _f === void 0 ? void 0 : _f.length) || 0;
|
|
903
|
+
// A partial result stays actionable: the notification remains
|
|
904
|
+
// visible so the user can retry just the languages that failed.
|
|
905
|
+
if (completedCount === 0) {
|
|
906
|
+
notification.status = "error";
|
|
907
|
+
}
|
|
908
|
+
else if (failedCount > 0) {
|
|
909
|
+
notification.status = "partial";
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
notification.status = "completed";
|
|
913
|
+
}
|
|
914
|
+
notification.updatedAt = Date.now();
|
|
904
915
|
await saveSyllabus(courseSlug, syllabus, bucket);
|
|
905
916
|
console.log(`🔄 SYNC: ✅ All translations completed for ${exerciseSlug} - Status: ${notification.status}`);
|
|
906
917
|
(0, creatorSocket_1.emitToCourse)(courseSlug, "sync-notification-completed", {
|
|
@@ -908,7 +919,7 @@ async function processSyncTranslationsSequentially(courseSlug, exerciseSlug, not
|
|
|
908
919
|
notificationId,
|
|
909
920
|
status: notification.status,
|
|
910
921
|
completed: notification.syncProgress.completedLanguages.length,
|
|
911
|
-
failed: ((
|
|
922
|
+
failed: ((_g = notification.syncProgress.failedLanguages) === null || _g === void 0 ? void 0 : _g.length) || 0,
|
|
912
923
|
});
|
|
913
924
|
}
|
|
914
925
|
}
|
|
@@ -1021,6 +1032,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
1021
1032
|
// app.use(express.static(distPath))
|
|
1022
1033
|
app.use(express.json({ limit: "20mb" }));
|
|
1023
1034
|
app.use(cors());
|
|
1035
|
+
app.use("/generation", (0, router_1.createIngestRouter)());
|
|
1024
1036
|
const appPath = path.resolve(__dirname, "../ui/_app");
|
|
1025
1037
|
const tarPath = path.resolve(__dirname, "../ui/app.tar.gz");
|
|
1026
1038
|
if (fs.existsSync(appPath)) {
|
|
@@ -1207,7 +1219,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
1207
1219
|
});
|
|
1208
1220
|
}
|
|
1209
1221
|
const filePath = `courses/${courseSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(langCode)}`;
|
|
1210
|
-
await uploadFileToBucket(bucket, content, filePath);
|
|
1222
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, content, filePath);
|
|
1211
1223
|
res.json({ status: "SUCCESS" });
|
|
1212
1224
|
});
|
|
1213
1225
|
app.post("/webhooks/:courseSlug/images/:imageId", async (req, res) => {
|
|
@@ -1302,7 +1314,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
1302
1314
|
if (fileObj.name && fileObj.content) {
|
|
1303
1315
|
const filePath = `${exerciseDir}/${flatFileName}`;
|
|
1304
1316
|
// eslint-disable-next-line no-await-in-loop
|
|
1305
|
-
await uploadFileToBucket(bucket, fileObj.content, filePath);
|
|
1317
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, fileObj.content, filePath);
|
|
1306
1318
|
console.log(`✅ Saved file: ${filePath}`);
|
|
1307
1319
|
}
|
|
1308
1320
|
// Save the solution file if it exists
|
|
@@ -1314,7 +1326,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
1314
1326
|
const solutionFileName = `${baseName}.solution.hide.${extension}`;
|
|
1315
1327
|
const solutionFilePath = `${exerciseDir}/${solutionFileName}`;
|
|
1316
1328
|
// eslint-disable-next-line no-await-in-loop
|
|
1317
|
-
await uploadFileToBucket(bucket, fileObj.solution, solutionFilePath);
|
|
1329
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, fileObj.solution, solutionFilePath);
|
|
1318
1330
|
console.log(`✅ Saved solution file: ${solutionFilePath}`);
|
|
1319
1331
|
}
|
|
1320
1332
|
else {
|
|
@@ -1322,7 +1334,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
1322
1334
|
const solutionFileName = `${flatFileName}.solution.hide`;
|
|
1323
1335
|
const solutionFilePath = `${exerciseDir}/${solutionFileName}`;
|
|
1324
1336
|
// eslint-disable-next-line no-await-in-loop
|
|
1325
|
-
await uploadFileToBucket(bucket, fileObj.solution, solutionFilePath);
|
|
1337
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, fileObj.solution, solutionFilePath);
|
|
1326
1338
|
console.log(`✅ Saved solution file: ${solutionFilePath}`);
|
|
1327
1339
|
}
|
|
1328
1340
|
}
|
|
@@ -1870,7 +1882,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
1870
1882
|
const readmeFilename = `README${(0, creatorUtilities_1.getReadmeExtension)(response.parsed.output_language ||
|
|
1871
1883
|
syllabus.courseInfo.language ||
|
|
1872
1884
|
"en")}`;
|
|
1873
|
-
await uploadFileToBucket(bucket, readability.newMarkdown, `${targetDir}/${readmeFilename}`);
|
|
1885
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, readability.newMarkdown, `${targetDir}/${readmeFilename}`);
|
|
1874
1886
|
// Update used components if provided by the AI
|
|
1875
1887
|
if (response.parsed.used_components &&
|
|
1876
1888
|
Array.isArray(response.parsed.used_components)) {
|
|
@@ -1994,7 +2006,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
1994
2006
|
}
|
|
1995
2007
|
// Translation successful
|
|
1996
2008
|
const readmePath = `courses/${courseSlug}/exercises/${exSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(body.parsed.output_language_code)}`;
|
|
1997
|
-
await uploadFileToBucket(bucket, body.parsed.translation, readmePath);
|
|
2009
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, body.parsed.translation, readmePath);
|
|
1998
2010
|
// Verify file exists before updating syllabus (resilience: ensure file was actually saved)
|
|
1999
2011
|
const [fileExists] = await bucket.file(readmePath).exists();
|
|
2000
2012
|
// Update syllabus with completed status only if file was successfully saved
|
|
@@ -2132,7 +2144,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
2132
2144
|
console.log("BODY", body);
|
|
2133
2145
|
const readmePath = `courses/${courseSlug}/exercises/${exSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(lang)}`;
|
|
2134
2146
|
if (body.parsed.content) {
|
|
2135
|
-
await uploadFileToBucket(bucket, body.parsed.content, readmePath);
|
|
2147
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, body.parsed.content, readmePath);
|
|
2136
2148
|
}
|
|
2137
2149
|
(0, creatorSocket_1.emitToNotification)(notificationId, {
|
|
2138
2150
|
status: "SUCCESS",
|
|
@@ -2178,7 +2190,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
2178
2190
|
const config = await mergeConfigPreservingGithub(bucket, courseSlug, builtConfig);
|
|
2179
2191
|
res.set("X-Creator-Web", "true");
|
|
2180
2192
|
res.set("Access-Control-Expose-Headers", "X-Creator-Web");
|
|
2181
|
-
await uploadFileToBucket(bucket, JSON.stringify({ config, exercises }), `courses/${courseSlug}/.learn/config.json`);
|
|
2193
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify({ config, exercises }), `courses/${courseSlug}/.learn/config.json`);
|
|
2182
2194
|
res.json({ config, exercises });
|
|
2183
2195
|
}
|
|
2184
2196
|
catch (error) {
|
|
@@ -2590,7 +2602,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
2590
2602
|
completedAt: 0,
|
|
2591
2603
|
},
|
|
2592
2604
|
};
|
|
2593
|
-
await uploadFileToBucket(bucket, JSON.stringify(Object.assign(Object.assign({}, initialSyllabus), { lessons: newLessons })), `courses/${courseSlug}/.learn/initialSyllabus.json`);
|
|
2605
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(Object.assign(Object.assign({}, initialSyllabus), { lessons: newLessons })), `courses/${courseSlug}/.learn/initialSyllabus.json`);
|
|
2594
2606
|
const targetDir = `courses/${courseSlug}/exercises/${stepSlug}`;
|
|
2595
2607
|
await uploadInitialReadme(bucket, stepSlug, targetDir, initialSyllabus.courseInfo);
|
|
2596
2608
|
res.json({ status: "SUCCESS", message: "Exercise generati on started!" });
|
|
@@ -2633,20 +2645,20 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
2633
2645
|
if (!learnJson.title)
|
|
2634
2646
|
learnJson.title = {};
|
|
2635
2647
|
learnJson.title[language] = title;
|
|
2636
|
-
await uploadFileToBucket(bucket, JSON.stringify(learnJson), `courses/${courseSlug}/learn.json`);
|
|
2648
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(learnJson), `courses/${courseSlug}/learn.json`);
|
|
2637
2649
|
const configFile = bucket.file(`courses/${courseSlug}/.learn/config.json`);
|
|
2638
2650
|
const [configContent] = await configFile.download();
|
|
2639
2651
|
const configJson = JSON.parse(configContent.toString());
|
|
2640
2652
|
configJson.config = configJson.config || {};
|
|
2641
2653
|
configJson.config.title = Object.assign(Object.assign({}, configJson.config.title), learnJson.title);
|
|
2642
|
-
await uploadFileToBucket(bucket, JSON.stringify(configJson), `courses/${courseSlug}/.learn/config.json`);
|
|
2654
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(configJson), `courses/${courseSlug}/.learn/config.json`);
|
|
2643
2655
|
try {
|
|
2644
2656
|
const syllabusFile = bucket.file(`courses/${courseSlug}/.learn/initialSyllabus.json`);
|
|
2645
2657
|
const [syllabusContent] = await syllabusFile.download();
|
|
2646
2658
|
const syllabusJson = JSON.parse(syllabusContent.toString());
|
|
2647
2659
|
if (syllabusJson === null || syllabusJson === void 0 ? void 0 : syllabusJson.courseInfo) {
|
|
2648
2660
|
syllabusJson.courseInfo.title = title;
|
|
2649
|
-
await uploadFileToBucket(bucket, JSON.stringify(syllabusJson), `courses/${courseSlug}/.learn/initialSyllabus.json`);
|
|
2661
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(syllabusJson), `courses/${courseSlug}/.learn/initialSyllabus.json`);
|
|
2650
2662
|
}
|
|
2651
2663
|
}
|
|
2652
2664
|
catch (error) {
|
|
@@ -2664,9 +2676,9 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
2664
2676
|
if ((_a = result === null || result === void 0 ? void 0 : result.parsed) === null || _a === void 0 ? void 0 : _a.title) {
|
|
2665
2677
|
const translatedTitle = JSON.parse(result.parsed.title);
|
|
2666
2678
|
learnJson.title = Object.assign(Object.assign(Object.assign({}, learnJson.title), translatedTitle), { [language]: title });
|
|
2667
|
-
await uploadFileToBucket(bucket, JSON.stringify(learnJson), `courses/${courseSlug}/learn.json`);
|
|
2679
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(learnJson), `courses/${courseSlug}/learn.json`);
|
|
2668
2680
|
configJson.config.title = Object.assign(Object.assign({}, configJson.config.title), learnJson.title);
|
|
2669
|
-
await uploadFileToBucket(bucket, JSON.stringify(configJson), `courses/${courseSlug}/.learn/config.json`);
|
|
2681
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(configJson), `courses/${courseSlug}/.learn/config.json`);
|
|
2670
2682
|
}
|
|
2671
2683
|
}
|
|
2672
2684
|
catch (translationError) {
|
|
@@ -2791,7 +2803,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
2791
2803
|
const translatedDescription = JSON.parse(result.parsed.description);
|
|
2792
2804
|
courseJson.title = translatedTitle;
|
|
2793
2805
|
courseJson.description = translatedDescription;
|
|
2794
|
-
await uploadFileToBucket(bucket, JSON.stringify(courseJson), `courses/${courseSlug}/learn.json`);
|
|
2806
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(courseJson), `courses/${courseSlug}/learn.json`);
|
|
2795
2807
|
currentLanguages = Object.keys(courseJson.title);
|
|
2796
2808
|
}
|
|
2797
2809
|
const missingReadmeTranslations = [];
|
|
@@ -2989,37 +3001,35 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
2989
3001
|
if (!syllabus) {
|
|
2990
3002
|
return res.status(404).json({ error: "Syllabus not found" });
|
|
2991
3003
|
}
|
|
2992
|
-
const PROCESSING_TIMEOUT = 3 * 60 * 1000; // 3 minutes
|
|
2993
3004
|
let modified = false;
|
|
2994
|
-
// Collect active notifications (pending, processing, or error)
|
|
3005
|
+
// Collect active notifications (pending, processing, partial or error)
|
|
2995
3006
|
const notifications = [];
|
|
2996
3007
|
for (const lesson of syllabus.lessons) {
|
|
2997
3008
|
if (lesson.syncNotifications && lesson.syncNotifications.length > 0) {
|
|
2998
3009
|
for (const notification of lesson.syncNotifications) {
|
|
2999
3010
|
// Check for timeout in processing notifications
|
|
3000
|
-
if (notification.status === "processing"
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
notificationId: notification.id,
|
|
3016
|
-
error: "Processing timeout",
|
|
3017
|
-
});
|
|
3018
|
-
}
|
|
3011
|
+
if (notification.status === "processing" &&
|
|
3012
|
+
isSyncProcessingStale(notification)) {
|
|
3013
|
+
notification.status = "error";
|
|
3014
|
+
notification.error = {
|
|
3015
|
+
message: "Synchronization timeout - process took too long",
|
|
3016
|
+
code: "PROCESSING_TIMEOUT",
|
|
3017
|
+
timestamp: Date.now(),
|
|
3018
|
+
};
|
|
3019
|
+
modified = true;
|
|
3020
|
+
// Emit error event
|
|
3021
|
+
(0, creatorSocket_1.emitToCourse)(courseSlug, "sync-notification-error", {
|
|
3022
|
+
exerciseSlug: (0, creatorUtilities_2.slugify)(lesson.id + "-" + lesson.title),
|
|
3023
|
+
notificationId: notification.id,
|
|
3024
|
+
error: "Processing timeout",
|
|
3025
|
+
});
|
|
3019
3026
|
}
|
|
3020
|
-
// Include active notifications (pending, processing, or
|
|
3027
|
+
// Include active notifications (pending, processing, partial or
|
|
3028
|
+
// error): a partial sync is still actionable, the user can retry
|
|
3029
|
+
// the languages that failed.
|
|
3021
3030
|
if (notification.status === "pending" ||
|
|
3022
3031
|
notification.status === "processing" ||
|
|
3032
|
+
notification.status === "partial" ||
|
|
3023
3033
|
notification.status === "error") {
|
|
3024
3034
|
notifications.push(Object.assign(Object.assign({}, notification), { lessonSlug: (0, creatorUtilities_2.slugify)(lesson.id + "-" + lesson.title), lessonTitle: lesson.title }));
|
|
3025
3035
|
}
|
|
@@ -3083,7 +3093,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
3083
3093
|
});
|
|
3084
3094
|
// Accept sync notification and start synchronization
|
|
3085
3095
|
app.post("/courses/:courseSlug/lessons/:exerciseSlug/sync-notification/:notificationId/accept", express.json(), async (req, res) => {
|
|
3086
|
-
var _a, _b;
|
|
3096
|
+
var _a, _b, _c;
|
|
3087
3097
|
console.log("POST /courses/:courseSlug/lessons/:exerciseSlug/sync-notification/:notificationId/accept");
|
|
3088
3098
|
const { courseSlug, exerciseSlug, notificationId } = req.params;
|
|
3089
3099
|
const rigoToken = req.header("x-rigo-token");
|
|
@@ -3111,9 +3121,21 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
3111
3121
|
if (!notification) {
|
|
3112
3122
|
return res.status(404).json({ error: "Notification not found" });
|
|
3113
3123
|
}
|
|
3114
|
-
|
|
3124
|
+
// A synchronization can be accepted again unless it is actively
|
|
3125
|
+
// running: after a failure or a partial result this same
|
|
3126
|
+
// notification is what the Retry button acts on.
|
|
3127
|
+
if (notification.status === "processing" &&
|
|
3128
|
+
!isSyncProcessingStale(notification)) {
|
|
3129
|
+
return res.status(409).json({
|
|
3130
|
+
error: "Notification is already being synchronized",
|
|
3131
|
+
code: "ALREADY_PROCESSING",
|
|
3132
|
+
currentStatus: notification.status,
|
|
3133
|
+
});
|
|
3134
|
+
}
|
|
3135
|
+
if (notification.status === "completed") {
|
|
3115
3136
|
return res.status(400).json({
|
|
3116
|
-
error: "Notification is
|
|
3137
|
+
error: "Notification is already synchronized",
|
|
3138
|
+
code: "ALREADY_COMPLETED",
|
|
3117
3139
|
currentStatus: notification.status,
|
|
3118
3140
|
});
|
|
3119
3141
|
}
|
|
@@ -3131,9 +3153,13 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
3131
3153
|
code: "SOURCE_README_ERROR",
|
|
3132
3154
|
});
|
|
3133
3155
|
}
|
|
3134
|
-
// Determine target languages
|
|
3156
|
+
// Determine target languages. On a retry only what is still missing
|
|
3157
|
+
// is translated again, so the languages that already succeeded are
|
|
3158
|
+
// neither paid for twice nor overwritten.
|
|
3159
|
+
const alreadyCompleted = ((_b = notification.syncProgress) === null || _b === void 0 ? void 0 : _b.completedLanguages) || [];
|
|
3135
3160
|
const availableLanguages = Object.keys(lesson.translations || {});
|
|
3136
|
-
const targetLanguages = availableLanguages.filter(lang => lang !== notification.sourceLanguage
|
|
3161
|
+
const targetLanguages = availableLanguages.filter(lang => lang !== notification.sourceLanguage &&
|
|
3162
|
+
!alreadyCompleted.includes(lang));
|
|
3137
3163
|
if (targetLanguages.length === 0) {
|
|
3138
3164
|
return res.status(400).json({
|
|
3139
3165
|
error: "No target languages found",
|
|
@@ -3142,10 +3168,14 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
3142
3168
|
}
|
|
3143
3169
|
// Remove all other notifications for this lesson
|
|
3144
3170
|
lesson.syncNotifications =
|
|
3145
|
-
((
|
|
3171
|
+
((_c = lesson.syncNotifications) === null || _c === void 0 ? void 0 : _c.filter(n => n.id === notificationId)) ||
|
|
3146
3172
|
[];
|
|
3173
|
+
// Start from a clean slate so a previous failure does not leak into
|
|
3174
|
+
// this run.
|
|
3147
3175
|
notification.status = "processing";
|
|
3176
|
+
notification.updatedAt = Date.now();
|
|
3148
3177
|
notification.processingLastUpdate = Date.now();
|
|
3178
|
+
delete notification.error;
|
|
3149
3179
|
notification.syncProgress = {
|
|
3150
3180
|
totalLanguages: targetLanguages.length,
|
|
3151
3181
|
completedLanguages: [], // Array of completed language codes
|
|
@@ -3391,7 +3421,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
3391
3421
|
const removedCount = initialCount - kept.length;
|
|
3392
3422
|
exercise.files = kept;
|
|
3393
3423
|
if (removedCount > 0) {
|
|
3394
|
-
await uploadFileToBucket(bucket, JSON.stringify({ config: configJson.config, exercises }), `courses/${courseSlug}/.learn/config.json`);
|
|
3424
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify({ config: configJson.config, exercises }), `courses/${courseSlug}/.learn/config.json`);
|
|
3395
3425
|
}
|
|
3396
3426
|
if (movedCount > 0 || removedCount > 0) {
|
|
3397
3427
|
console.log(`[sync-lesson-files] ${lessonSlug}: ${movedCount} moved from subdir, ${removedCount} removed from config`);
|
|
@@ -3427,7 +3457,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
3427
3457
|
const { exercises } = await (0, configBuilder_1.buildConfig)(bucket, courseSlug);
|
|
3428
3458
|
const { fixedSidebar, valid } = await (0, sidebarGenerator_1.checkAndFixSidebarPure)(sidebar, exercises, rigoToken);
|
|
3429
3459
|
if (fixedSidebar) {
|
|
3430
|
-
await uploadFileToBucket(bucket, JSON.stringify(fixedSidebar), `courses/${courseSlug}/.learn/sidebar.json`);
|
|
3460
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(fixedSidebar), `courses/${courseSlug}/.learn/sidebar.json`);
|
|
3431
3461
|
return res.json(fixedSidebar);
|
|
3432
3462
|
}
|
|
3433
3463
|
}
|
|
@@ -3437,12 +3467,12 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
3437
3467
|
if (error.code === 404) {
|
|
3438
3468
|
const { exercises } = await (0, configBuilder_1.buildConfig)(bucket, courseSlug);
|
|
3439
3469
|
const exerciseSlugsArray = exercises.map(exercise => exercise.slug);
|
|
3440
|
-
const sidebar = await createInitialSidebar(exerciseSlugsArray);
|
|
3441
|
-
await uploadFileToBucket(bucket, JSON.stringify(sidebar), `courses/${courseSlug}/.learn/sidebar.json`);
|
|
3470
|
+
const sidebar = await (0, sidebar_1.createInitialSidebar)(exerciseSlugsArray);
|
|
3471
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(sidebar), `courses/${courseSlug}/.learn/sidebar.json`);
|
|
3442
3472
|
if (rigoToken) {
|
|
3443
3473
|
const { fixedSidebar } = await (0, sidebarGenerator_1.checkAndFixSidebarPure)(sidebar, exercises, rigoToken);
|
|
3444
3474
|
if (fixedSidebar) {
|
|
3445
|
-
await uploadFileToBucket(bucket, JSON.stringify(fixedSidebar), `courses/${courseSlug}/.learn/sidebar.json`);
|
|
3475
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(fixedSidebar), `courses/${courseSlug}/.learn/sidebar.json`);
|
|
3446
3476
|
}
|
|
3447
3477
|
return res.status(200).json(fixedSidebar);
|
|
3448
3478
|
}
|
|
@@ -3486,7 +3516,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
3486
3516
|
}
|
|
3487
3517
|
const courseSlug = syllabus.courseInfo.slug;
|
|
3488
3518
|
const tutorialDir = `courses/${courseSlug}`;
|
|
3489
|
-
const learnJson = (0,
|
|
3519
|
+
const learnJson = (0, learnJson_1.createLearnJson)(syllabus.courseInfo);
|
|
3490
3520
|
try {
|
|
3491
3521
|
await api_1.default.createRigoPackage(rigoToken, courseSlug, learnJson);
|
|
3492
3522
|
}
|
|
@@ -3495,7 +3525,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
3495
3525
|
return res.status(400).json({ error: "Failed to create Rigo package" });
|
|
3496
3526
|
}
|
|
3497
3527
|
try {
|
|
3498
|
-
await uploadFileToBucket(bucket, JSON.stringify(learnJson), `${tutorialDir}/learn.json`);
|
|
3528
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(learnJson), `${tutorialDir}/learn.json`);
|
|
3499
3529
|
}
|
|
3500
3530
|
catch (error) {
|
|
3501
3531
|
console.error("Failed to upload learn.json:", error);
|
|
@@ -3509,15 +3539,15 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
3509
3539
|
// eslint-disable-next-line no-await-in-loop
|
|
3510
3540
|
await uploadInitialReadme(bucket, exSlug, targetDir, syllabus.courseInfo);
|
|
3511
3541
|
}
|
|
3512
|
-
const sidebar = await createInitialSidebar(syllabus.lessons.map(lesson => (0, creatorUtilities_2.slugify)(lesson.id + "-" + lesson.title)), syllabus.courseInfo.language);
|
|
3542
|
+
const sidebar = await (0, sidebar_1.createInitialSidebar)(syllabus.lessons.map(lesson => (0, creatorUtilities_2.slugify)(lesson.id + "-" + lesson.title)), syllabus.courseInfo.language);
|
|
3513
3543
|
const initialSyllabus = Object.assign(Object.assign({}, syllabus), { lessons: syllabus.lessons.map((lesson, index) => {
|
|
3514
3544
|
if (index < 1) {
|
|
3515
3545
|
return Object.assign(Object.assign({}, lesson), { generated: false, status: "GENERATING" });
|
|
3516
3546
|
}
|
|
3517
3547
|
return Object.assign(Object.assign({}, lesson), { generated: false, status: "PENDING" });
|
|
3518
3548
|
}) });
|
|
3519
|
-
await uploadFileToBucket(bucket, JSON.stringify(initialSyllabus), `${tutorialDir}/.learn/initialSyllabus.json`);
|
|
3520
|
-
await uploadFileToBucket(bucket, JSON.stringify(sidebar), `${tutorialDir}/.learn/sidebar.json`);
|
|
3549
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(initialSyllabus), `${tutorialDir}/.learn/initialSyllabus.json`);
|
|
3550
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(sidebar), `${tutorialDir}/.learn/sidebar.json`);
|
|
3521
3551
|
const firstLesson = syllabus.lessons[0];
|
|
3522
3552
|
const lastResult = "---";
|
|
3523
3553
|
// Use new two-phase generation workflow
|
|
@@ -4220,19 +4250,19 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
4220
4250
|
const [learnJsonContent] = await learnJsonFile.download();
|
|
4221
4251
|
const learnJson = JSON.parse(learnJsonContent.toString());
|
|
4222
4252
|
learnJson.slug = newSlug;
|
|
4223
|
-
await uploadFileToBucket(bucket, JSON.stringify(learnJson), `${newPrefix}learn.json`);
|
|
4253
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(learnJson), `${newPrefix}learn.json`);
|
|
4224
4254
|
// Update initialSyllabus.json with new slug
|
|
4225
4255
|
const syllabusFile = bucket.file(`${newPrefix}.learn/initialSyllabus.json`);
|
|
4226
4256
|
const [syllabusContent] = await syllabusFile.download();
|
|
4227
4257
|
const syllabus = JSON.parse(syllabusContent.toString());
|
|
4228
4258
|
syllabus.courseInfo.slug = newSlug;
|
|
4229
|
-
await uploadFileToBucket(bucket, JSON.stringify(syllabus), `${newPrefix}.learn/initialSyllabus.json`);
|
|
4259
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(syllabus), `${newPrefix}.learn/initialSyllabus.json`);
|
|
4230
4260
|
// Update config.json with new slug
|
|
4231
4261
|
const configFile = bucket.file(`${newPrefix}.learn/config.json`);
|
|
4232
4262
|
const [configContent] = await configFile.download();
|
|
4233
4263
|
const config = JSON.parse(configContent.toString());
|
|
4234
4264
|
config.config.slug = newSlug;
|
|
4235
|
-
await uploadFileToBucket(bucket, JSON.stringify(config), `${newPrefix}.learn/config.json`);
|
|
4265
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(config), `${newPrefix}.learn/config.json`);
|
|
4236
4266
|
// Update Rigobot package slug
|
|
4237
4267
|
const updateUrl = `${api_1.RIGOBOT_HOST}/v1/learnpack/package/${currentSlug}/`;
|
|
4238
4268
|
await axios_1.default.put(updateUrl, { new_slug: newSlug }, {
|
|
@@ -4394,7 +4424,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
4394
4424
|
defaultBranch,
|
|
4395
4425
|
lastSyncSHA: commitRes.sha,
|
|
4396
4426
|
};
|
|
4397
|
-
await uploadFileToBucket(bucket, JSON.stringify(configJson), `courses/${courseSlug}/.learn/config.json`);
|
|
4427
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(configJson), `courses/${courseSlug}/.learn/config.json`);
|
|
4398
4428
|
return res.json({
|
|
4399
4429
|
success: true,
|
|
4400
4430
|
repository,
|
|
@@ -4568,7 +4598,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
4568
4598
|
compareRes.files.length === 0) {
|
|
4569
4599
|
configJson.config = configJson.config || {};
|
|
4570
4600
|
configJson.config.github = Object.assign(Object.assign({}, configJson.config.github), { lastSyncSHA: targetSHA });
|
|
4571
|
-
await uploadFileToBucket(bucket, JSON.stringify(configJson), `courses/${courseSlug}/.learn/config.json`);
|
|
4601
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(configJson), `courses/${courseSlug}/.learn/config.json`);
|
|
4572
4602
|
return res.json({
|
|
4573
4603
|
success: true,
|
|
4574
4604
|
syncedLessons: [],
|
|
@@ -4698,14 +4728,14 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
4698
4728
|
continue;
|
|
4699
4729
|
}
|
|
4700
4730
|
const buffer = buffer_1.Buffer.from(content, "base64");
|
|
4701
|
-
if (isImageFile(item.relativePath)) {
|
|
4731
|
+
if (item.isAsset || isImageFile(item.relativePath)) {
|
|
4702
4732
|
// eslint-disable-next-line no-await-in-loop -- Sequential processing to avoid rate limits
|
|
4703
|
-
await uploadBinaryToBucket(bucket, buffer, item.bucketPath, "application/octet-stream");
|
|
4733
|
+
await (0, bucketIo_1.uploadBinaryToBucket)(bucket, buffer, item.bucketPath, "application/octet-stream");
|
|
4704
4734
|
}
|
|
4705
4735
|
else {
|
|
4706
4736
|
const text = buffer.toString("utf8");
|
|
4707
4737
|
// eslint-disable-next-line no-await-in-loop -- Sequential processing to avoid rate limits
|
|
4708
|
-
await uploadFileToBucket(bucket, text, item.bucketPath);
|
|
4738
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, text, item.bucketPath);
|
|
4709
4739
|
}
|
|
4710
4740
|
if (item.isAsset) {
|
|
4711
4741
|
syncedAssetsUploaded++;
|
|
@@ -4718,7 +4748,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
4718
4748
|
}
|
|
4719
4749
|
configJson.config = configJson.config || {};
|
|
4720
4750
|
configJson.config.github = Object.assign(Object.assign({}, configJson.config.github), { lastSyncSHA: targetSHA });
|
|
4721
|
-
await uploadFileToBucket(bucket, JSON.stringify(configJson), `courses/${courseSlug}/.learn/config.json`);
|
|
4751
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(configJson), `courses/${courseSlug}/.learn/config.json`);
|
|
4722
4752
|
return res.json({
|
|
4723
4753
|
success: true,
|
|
4724
4754
|
syncedLessons: [...syncedLessonSlugs],
|
|
@@ -4787,7 +4817,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
4787
4817
|
});
|
|
4788
4818
|
configJson.config = configJson.config || {};
|
|
4789
4819
|
configJson.config.github = Object.assign(Object.assign({}, configJson.config.github), { lastSyncSHA: commitRes.sha });
|
|
4790
|
-
await uploadFileToBucket(bucket, JSON.stringify(configJson), `courses/${courseSlug}/.learn/config.json`);
|
|
4820
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, JSON.stringify(configJson), `courses/${courseSlug}/.learn/config.json`);
|
|
4791
4821
|
return res.json({
|
|
4792
4822
|
success: true,
|
|
4793
4823
|
repository,
|
|
@@ -4862,7 +4892,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
4862
4892
|
}
|
|
4863
4893
|
try {
|
|
4864
4894
|
const memoryBankPath = `courses/${courseSlug}/.learn/memory_bank.txt`;
|
|
4865
|
-
await uploadFileToBucket(bucket, content, memoryBankPath);
|
|
4895
|
+
await (0, bucketIo_1.uploadFileToBucket)(bucket, content, memoryBankPath);
|
|
4866
4896
|
console.log(`✅ Memory bank updated for course: ${courseSlug}`);
|
|
4867
4897
|
res.json({ message: "Memory bank updated successfully" });
|
|
4868
4898
|
}
|