Skip to content

Commit 866ad29

Browse files
committed
fix(tutorial): restore missing workspace helper functions
- Add generateSkillMDX, saveSkillToWorkspace, deleteSkillFromWorkspace - Add saveCourseToWorkspace, deleteCourseFromWorkspace - Add addSkillToCourse, removeSkillFromCourse, reorderCourseSkills - Fixes build error: missing exports from tutorial-scanner.ts
1 parent 4657b19 commit 866ad29

1 file changed

Lines changed: 209 additions & 0 deletions

File tree

playground/apps/desktop/src/lib/tutorial-scanner.ts

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,212 @@ export async function scanWorkspace(workspacePath: string): Promise<ScanResult>
233233
return { courses: [], skills: [] };
234234
}
235235
}
236+
237+
// ─── Workspace File Helpers ──────────────────────────────
238+
239+
export function generateSkillMDX({
240+
title,
241+
description,
242+
difficulty,
243+
duration,
244+
category,
245+
tags,
246+
content,
247+
}: {
248+
title: string;
249+
description: string;
250+
difficulty: string;
251+
duration: number;
252+
category: string;
253+
tags: string[];
254+
content: string;
255+
}): string {
256+
const tagsStr = tags.length > 0 ? JSON.stringify(tags) : "[]";
257+
return `---
258+
title: "${title}"
259+
description: "${description}"
260+
difficulty: ${difficulty}
261+
duration: ${duration}
262+
category: ${category}
263+
tags: ${tagsStr}
264+
---
265+
266+
${content}
267+
`;
268+
}
269+
270+
export async function saveSkillToWorkspace(
271+
workspacePath: string,
272+
slug: string,
273+
content: string
274+
): Promise<void> {
275+
if (!("__TAURI_INTERNALS__" in window)) return;
276+
const { writeFile, mkdir } = await import("@tauri-apps/plugin-fs");
277+
const skillsDir = `${workspacePath}/skills`;
278+
try {
279+
await mkdir(skillsDir, { recursive: true });
280+
} catch {
281+
// dir may already exist
282+
}
283+
const path = `${skillsDir}/${slug}.md`;
284+
const encoder = new TextEncoder();
285+
await writeFile(path, encoder.encode(content));
286+
}
287+
288+
export async function deleteSkillFromWorkspace(
289+
workspacePath: string,
290+
slug: string
291+
): Promise<void> {
292+
if (!("__TAURI_INTERNALS__" in window)) return;
293+
const { remove } = await import("@tauri-apps/plugin-fs");
294+
const possiblePaths = [
295+
`${workspacePath}/skills/${slug}.md`,
296+
`${workspacePath}/skills/${slug}.mdx`,
297+
`${workspacePath}/lessons/${slug}.md`,
298+
`${workspacePath}/lessons/${slug}.mdx`,
299+
`${workspacePath}/${slug}.md`,
300+
`${workspacePath}/${slug}.mdx`,
301+
];
302+
for (const p of possiblePaths) {
303+
try {
304+
await remove(p);
305+
} catch {
306+
// file may not exist
307+
}
308+
}
309+
}
310+
311+
export async function saveCourseToWorkspace(
312+
workspacePath: string,
313+
course: CourseFile
314+
): Promise<void> {
315+
if (!("__TAURI_INTERNALS__" in window)) return;
316+
const { writeFile, readFile, mkdir } = await import("@tauri-apps/plugin-fs");
317+
const coursesDir = `${workspacePath}/courses`;
318+
try {
319+
await mkdir(coursesDir, { recursive: true });
320+
} catch {
321+
// dir may already exist
322+
}
323+
324+
const path = `${coursesDir}/${course.id}.json`;
325+
let courses: CourseFile[] = [];
326+
try {
327+
const bytes = await readFile(`${workspacePath}/_courses.json`);
328+
const text = new TextDecoder().decode(bytes);
329+
courses = JSON.parse(text);
330+
} catch {
331+
// file may not exist
332+
}
333+
334+
const idx = courses.findIndex((c) => c.id === course.id);
335+
if (idx >= 0) {
336+
courses[idx] = course;
337+
} else {
338+
courses.push(course);
339+
}
340+
341+
const encoder = new TextEncoder();
342+
await writeFile(`${workspacePath}/_courses.json`, encoder.encode(JSON.stringify(courses, null, 2)));
343+
await writeFile(path, encoder.encode(JSON.stringify(course, null, 2)));
344+
}
345+
346+
export async function deleteCourseFromWorkspace(
347+
workspacePath: string,
348+
id: string
349+
): Promise<void> {
350+
if (!("__TAURI_INTERNALS__" in window)) return;
351+
const { remove, readFile, writeFile } = await import("@tauri-apps/plugin-fs");
352+
353+
try {
354+
await remove(`${workspacePath}/courses/${id}.json`);
355+
} catch {
356+
// file may not exist
357+
}
358+
359+
try {
360+
const bytes = await readFile(`${workspacePath}/_courses.json`);
361+
const text = new TextDecoder().decode(bytes);
362+
const courses: CourseFile[] = JSON.parse(text);
363+
const filtered = courses.filter((c) => c.id !== id);
364+
const encoder = new TextEncoder();
365+
await writeFile(`${workspacePath}/_courses.json`, encoder.encode(JSON.stringify(filtered, null, 2)));
366+
} catch {
367+
// file may not exist
368+
}
369+
}
370+
371+
export async function addSkillToCourse(
372+
workspacePath: string,
373+
courseId: string,
374+
slug: string,
375+
_content: string,
376+
order: number
377+
): Promise<void> {
378+
if (!("__TAURI_INTERNALS__" in window)) return;
379+
const { readFile, writeFile } = await import("@tauri-apps/plugin-fs");
380+
const path = `${workspacePath}/courses/${courseId}.json`;
381+
382+
try {
383+
const bytes = await readFile(path);
384+
const text = new TextDecoder().decode(bytes);
385+
const course: CourseFile & { skills?: CourseSkill[] } = JSON.parse(text);
386+
if (!course.skills) course.skills = [];
387+
if (!course.skills.find((s) => s.slug === slug)) {
388+
course.skills.push({ slug, order });
389+
}
390+
const encoder = new TextEncoder();
391+
await writeFile(path, encoder.encode(JSON.stringify(course, null, 2)));
392+
} catch {
393+
// course may not exist
394+
}
395+
}
396+
397+
export async function removeSkillFromCourse(
398+
workspacePath: string,
399+
courseId: string,
400+
slug: string
401+
): Promise<void> {
402+
if (!("__TAURI_INTERNALS__" in window)) return;
403+
const { readFile, writeFile } = await import("@tauri-apps/plugin-fs");
404+
const path = `${workspacePath}/courses/${courseId}.json`;
405+
406+
try {
407+
const bytes = await readFile(path);
408+
const text = new TextDecoder().decode(bytes);
409+
const course: CourseFile & { skills?: CourseSkill[] } = JSON.parse(text);
410+
if (course.skills) {
411+
course.skills = course.skills.filter((s) => s.slug !== slug);
412+
}
413+
const encoder = new TextEncoder();
414+
await writeFile(path, encoder.encode(JSON.stringify(course, null, 2)));
415+
} catch {
416+
// course may not exist
417+
}
418+
}
419+
420+
export async function reorderCourseSkills(
421+
workspacePath: string,
422+
courseId: string,
423+
slugs: string[]
424+
): Promise<void> {
425+
if (!("__TAURI_INTERNALS__" in window)) return;
426+
const { readFile, writeFile } = await import("@tauri-apps/plugin-fs");
427+
const path = `${workspacePath}/courses/${courseId}.json`;
428+
429+
try {
430+
const bytes = await readFile(path);
431+
const text = new TextDecoder().decode(bytes);
432+
const course: CourseFile & { skills?: CourseSkill[] } = JSON.parse(text);
433+
if (course.skills) {
434+
course.skills = slugs.map((slug, idx) => ({
435+
slug,
436+
order: idx + 1,
437+
}));
438+
}
439+
const encoder = new TextEncoder();
440+
await writeFile(path, encoder.encode(JSON.stringify(course, null, 2)));
441+
} catch {
442+
// course may not exist
443+
}
444+
}

0 commit comments

Comments
 (0)