Skip to content

Commit 999dcfb

Browse files
authored
feat(nx-dev): add epic nx release course (#29777)
1 parent 7d864c8 commit 999dcfb

File tree

6 files changed

+53
-19
lines changed

6 files changed

+53
-19
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: 'Versioning and Releasing NPM packages with Nx'
3+
description: 'Learn how Nx Release automates package versioning, changelog generation, and publishing workflows, making releases faster and more reliable.'
4+
authors: [Juri Strumpflohner]
5+
externalLink: 'https://www.epicweb.dev/tutorials/versioning-and-releasing-npm-packages-with-nx'
6+
lessonCount: 20
7+
---

docs/courses/explore-nx/course.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
title: 'Introduction to Nx'
33
description: 'New to Nx? Then this is where you should start.'
44
authors: [Juri Strumpflohner]
5+
order: 1
56
---
67

78
This course gives you a quick high-level overview of Nx, how running tasks works, task caching, how Nx provides code scaffolding functionality and how you can use `nx migrate` to automatically update your workspace dependencies and code across breaking changes.

docs/courses/pnpm-nx-next/course.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ title: 'From PNPM Workspaces to Distributed CI'
33
description: 'Learn how to transform a PNPM workspace monorepo into a high-performance distributed CI setup using Nx.'
44
authors: [Juri Strumpflohner]
55
repository: 'https://github.com/nrwl/nx-course-pnpm-nx'
6+
order: 2
67
---
78

89
In this course, we'll walk through a step-by-step guide using the Tasker application as our example. Tasker is a task management app built with Next.js, structured as a PNPM workspace monorepo. The monorepo contains the Next.js application which is modularized into packages that handle data access via Prisma to a local DB, UI components, and more.

nx-dev/data-access-courses/src/lib/course.types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ export interface Course {
88
authors: BlogAuthor[];
99
repository?: string;
1010
lessons: Lesson[];
11+
lessonCount?: number;
1112
filePath: string;
13+
externalLink?: string;
1214
totalDuration: string;
15+
order?: number;
1316
}
1417

1518
export interface Lesson {

nx-dev/data-access-courses/src/lib/courses.api.ts

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,17 @@ export class CoursesApi {
2929
})
3030
.map((folder) => this.getCourse(folder))
3131
);
32-
return courses;
32+
return courses.sort((a, b) => {
33+
// If both courses have order, sort by order
34+
if (a.order !== undefined && b.order !== undefined) {
35+
return a.order - b.order;
36+
}
37+
// If only one has order, prioritize the one with order
38+
if (a.order !== undefined) return -1;
39+
if (b.order !== undefined) return 1;
40+
// If neither has order, sort by id (folder name)
41+
return a.id.localeCompare(b.id);
42+
});
3343
}
3444

3545
async getCourse(folderName: string): Promise<Course> {
@@ -42,16 +52,19 @@ export class CoursesApi {
4252
const content = await readFile(courseFilePath, 'utf-8');
4353
const frontmatter = extractFrontmatter(content);
4454

45-
const lessonFolders = await readdir(coursePath);
46-
const lessons = await Promise.all(
47-
lessonFolders
48-
.filter((folder) => {
49-
const stat = lstatSync(join(coursePath, folder));
50-
return stat.isDirectory();
51-
})
52-
.map((folder) => this.getLessons(folderName, folder))
53-
);
54-
const flattenedLessons = lessons.flat();
55+
let lessons: Lesson[] = [];
56+
if (!frontmatter.externalLink) {
57+
const lessonFolders = await readdir(coursePath);
58+
const tmpLessons = await Promise.all(
59+
lessonFolders
60+
.filter((folder) => {
61+
const stat = lstatSync(join(coursePath, folder));
62+
return stat.isDirectory();
63+
})
64+
.map((folder) => this.getLessons(folderName, folder))
65+
);
66+
lessons = tmpLessons.flat();
67+
}
5568

5669
return {
5770
id: folderName,
@@ -62,9 +75,12 @@ export class CoursesApi {
6275
frontmatter.authors.includes(author.name)
6376
),
6477
repository: frontmatter.repository,
65-
lessons: flattenedLessons,
78+
lessons,
6679
filePath: courseFilePath,
67-
totalDuration: calculateTotalDuration(flattenedLessons),
80+
totalDuration: calculateTotalDuration(lessons),
81+
lessonCount: frontmatter.lessonCount,
82+
externalLink: frontmatter.externalLink,
83+
order: frontmatter.order,
6884
};
6985
}
7086

nx-dev/ui-video-courses/src/lib/course-overview.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function CourseOverview({ courses }: CourseOverviewProps): JSX.Element {
1515
{courses.map((course) => (
1616
<Link
1717
key={course.id}
18-
href={`/courses/${course.id}`}
18+
href={course.externalLink || `/courses/${course.id}`}
1919
className="block h-full transform-gpu"
2020
prefetch={false}
2121
>
@@ -43,14 +43,20 @@ export function CourseOverview({ courses }: CourseOverviewProps): JSX.Element {
4343
</span>
4444
</>
4545
)}
46-
<span>{course.lessons.length} lessons</span>
46+
<span>
47+
{course.lessons.length > 0
48+
? `${course.lessons.length} lessons`
49+
: `${course.lessonCount} lessons`}
50+
</span>
4751
<span className="text-slate-300 dark:text-slate-600">
4852
4953
</span>
50-
<span className="flex items-center gap-1">
51-
<ClockIcon className="h-3 w-3" />
52-
{course.totalDuration}
53-
</span>
54+
{course.lessons.length > 0 && (
55+
<span className="flex items-center gap-1">
56+
<ClockIcon className="h-3 w-3" />
57+
{course.totalDuration}
58+
</span>
59+
)}
5460
</div>
5561

5662
<div className="mt-4">

0 commit comments

Comments
 (0)