Skip to content

Commit d46de8f

Browse files
committed
fix(cli): enhance lesson URL formatting and data structure in launch readiness
- Updated the formatProductLessonUrl function to include sectionSlug in the generated URLs. - Modified fetchRemoteWorkshopLessonSlugs to return lessons with associated sectionSlugs. - Adjusted launch readiness checks to utilize the new lessons structure for improved URL generation. - Enhanced tests to validate the new lesson and sectionSlug handling.
1 parent 65a778c commit d46de8f

2 files changed

Lines changed: 57 additions & 22 deletions

File tree

packages/workshop-cli/src/commands/admin/launch-readiness.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,12 @@ test('remote lesson check fails when product lesson slug not represented locally
176176
JSON.stringify({
177177
resources: [
178178
{ _type: 'lesson', _id: '1', slug: 'workshop-intro' },
179-
{ _type: 'lesson', _id: '2', slug: 'missing-lesson' },
179+
{
180+
_type: 'section',
181+
_id: 's1',
182+
slug: 'functions-section',
183+
lessons: [{ _type: 'lesson', _id: '2', slug: 'missing-lesson' }],
184+
},
180185
],
181186
}),
182187
{ status: 200 },
@@ -199,7 +204,7 @@ test('remote lesson check fails when product lesson slug not represented locally
199204
expect(output).toContain('Missing videos in workshop for product lessons:')
200205
expect(output).toContain('missing-lesson')
201206
expect(output).toContain(
202-
`https://${productHost}/workshops/${productSlug}/missing-lesson`,
207+
`https://${productHost}/workshops/${productSlug}/functions-section/missing-lesson`,
203208
)
204209
} finally {
205210
logSpy.mockRestore()

packages/workshop-cli/src/commands/admin/launch-readiness.ts

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,17 @@ function formatProductLessonUrl({
116116
productHost,
117117
productSlug,
118118
lessonSlug,
119+
sectionSlug,
119120
}: {
120121
productHost: string
121122
productSlug: string
122123
lessonSlug: string
124+
sectionSlug: string | null
123125
}) {
124126
// The product site will typically redirect to a section-specific path when needed.
125-
return `https://${productHost}/workshops/${productSlug}/${lessonSlug}`
127+
return sectionSlug
128+
? `https://${productHost}/workshops/${productSlug}/${sectionSlug}/${lessonSlug}`
129+
: `https://${productHost}/workshops/${productSlug}/${lessonSlug}`
126130
}
127131

128132
function formatIssue(issue: Issue, workshopRoot: string) {
@@ -347,7 +351,10 @@ async function fetchRemoteWorkshopLessonSlugs({
347351
productHost: string
348352
workshopSlug: string
349353
}): Promise<
350-
| { status: 'success'; lessonSlugs: Array<string> }
354+
| {
355+
status: 'success'
356+
lessons: Array<{ slug: string; sectionSlug: string | null }>
357+
}
351358
| { status: 'error'; message: string }
352359
> {
353360
const url = `https://${productHost}/api/workshops/${encodeURIComponent(workshopSlug)}`
@@ -420,30 +427,36 @@ async function fetchRemoteWorkshopLessonSlugs({
420427
}
421428
}
422429

423-
const lessonSlugs: Array<string> = []
430+
const lessons: Array<{ slug: string; sectionSlug: string | null }> = []
424431
for (const resource of resources) {
425432
if (!resource || typeof resource !== 'object') continue
426433
const r = resource as Record<string, unknown>
427434

428435
if (r._type === 'lesson') {
429436
const slug = r.slug
430-
if (typeof slug === 'string') lessonSlugs.push(slug)
437+
if (typeof slug === 'string') lessons.push({ slug, sectionSlug: null })
431438
continue
432439
}
433440

434441
if (r._type === 'section') {
435-
const lessons = r.lessons
436-
if (!Array.isArray(lessons)) continue
437-
for (const lesson of lessons) {
442+
const sectionSlug =
443+
typeof r.slug === 'string' && r.slug.trim().length > 0
444+
? r.slug.trim()
445+
: null
446+
const sectionLessons = r.lessons
447+
if (!Array.isArray(sectionLessons)) continue
448+
for (const lesson of sectionLessons) {
438449
if (!lesson || typeof lesson !== 'object') continue
439450
const l = lesson as Record<string, unknown>
440451
const slug = l.slug
441-
if (typeof slug === 'string') lessonSlugs.push(slug)
452+
if (typeof slug === 'string') {
453+
lessons.push({ slug, sectionSlug })
454+
}
442455
}
443456
}
444457
}
445458

446-
return { status: 'success', lessonSlugs }
459+
return { status: 'success', lessons }
447460
}
448461

449462
async function checkMinContentLength({
@@ -1020,9 +1033,25 @@ export async function launchReadiness(
10201033
message: remote.message,
10211034
})
10221035
} else {
1023-
const remoteLessonSlugs = Array.from(
1024-
new Set(remote.lessonSlugs.map(stripEpicAiSlugSuffix)),
1025-
)
1036+
const remoteLessons = remote.lessons
1037+
.map((l) => ({
1038+
slug: stripEpicAiSlugSuffix(l.slug),
1039+
sectionSlug: l.sectionSlug
1040+
? stripEpicAiSlugSuffix(l.sectionSlug)
1041+
: null,
1042+
}))
1043+
.filter((l) => l.slug.trim().length > 0)
1044+
1045+
// Preserve the first sectionSlug seen for a given lesson slug.
1046+
const remoteLessonBySlug = new Map<
1047+
string,
1048+
{ slug: string; sectionSlug: string | null }
1049+
>()
1050+
for (const l of remoteLessons) {
1051+
if (!remoteLessonBySlug.has(l.slug)) remoteLessonBySlug.set(l.slug, l)
1052+
}
1053+
1054+
const remoteLessonSlugs = [...remoteLessonBySlug.keys()]
10261055

10271056
if (remoteLessonSlugs.length === 0) {
10281057
issues.push({
@@ -1039,14 +1068,15 @@ export async function launchReadiness(
10391068
if (missing.length) {
10401069
const formatted = missing
10411070
.sort()
1042-
.map(
1043-
(slug) =>
1044-
`- ${slug}: ${formatProductLessonUrl({
1045-
productHost,
1046-
productSlug,
1047-
lessonSlug: slug,
1048-
})}`,
1049-
)
1071+
.map((slug) => {
1072+
const remoteLesson = remoteLessonBySlug.get(slug)
1073+
return `- ${slug}: ${formatProductLessonUrl({
1074+
productHost,
1075+
productSlug,
1076+
lessonSlug: slug,
1077+
sectionSlug: remoteLesson?.sectionSlug ?? null,
1078+
})}`
1079+
})
10501080
.join('\n')
10511081
issues.push({
10521082
level: 'error',

0 commit comments

Comments
 (0)