feat(nx-dev): add epic nx release course (#29777)
This commit is contained in:
parent
7d864c8db8
commit
999dcfbb0f
7
docs/courses/epic-nx-release/course.md
Normal file
7
docs/courses/epic-nx-release/course.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
title: 'Versioning and Releasing NPM packages with Nx'
|
||||
description: 'Learn how Nx Release automates package versioning, changelog generation, and publishing workflows, making releases faster and more reliable.'
|
||||
authors: [Juri Strumpflohner]
|
||||
externalLink: 'https://www.epicweb.dev/tutorials/versioning-and-releasing-npm-packages-with-nx'
|
||||
lessonCount: 20
|
||||
---
|
||||
@ -2,6 +2,7 @@
|
||||
title: 'Introduction to Nx'
|
||||
description: 'New to Nx? Then this is where you should start.'
|
||||
authors: [Juri Strumpflohner]
|
||||
order: 1
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
@ -3,6 +3,7 @@ title: 'From PNPM Workspaces to Distributed CI'
|
||||
description: 'Learn how to transform a PNPM workspace monorepo into a high-performance distributed CI setup using Nx.'
|
||||
authors: [Juri Strumpflohner]
|
||||
repository: 'https://github.com/nrwl/nx-course-pnpm-nx'
|
||||
order: 2
|
||||
---
|
||||
|
||||
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.
|
||||
|
||||
@ -8,8 +8,11 @@ export interface Course {
|
||||
authors: BlogAuthor[];
|
||||
repository?: string;
|
||||
lessons: Lesson[];
|
||||
lessonCount?: number;
|
||||
filePath: string;
|
||||
externalLink?: string;
|
||||
totalDuration: string;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
export interface Lesson {
|
||||
|
||||
@ -29,7 +29,17 @@ export class CoursesApi {
|
||||
})
|
||||
.map((folder) => this.getCourse(folder))
|
||||
);
|
||||
return courses;
|
||||
return courses.sort((a, b) => {
|
||||
// If both courses have order, sort by order
|
||||
if (a.order !== undefined && b.order !== undefined) {
|
||||
return a.order - b.order;
|
||||
}
|
||||
// If only one has order, prioritize the one with order
|
||||
if (a.order !== undefined) return -1;
|
||||
if (b.order !== undefined) return 1;
|
||||
// If neither has order, sort by id (folder name)
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
}
|
||||
|
||||
async getCourse(folderName: string): Promise<Course> {
|
||||
@ -42,16 +52,19 @@ export class CoursesApi {
|
||||
const content = await readFile(courseFilePath, 'utf-8');
|
||||
const frontmatter = extractFrontmatter(content);
|
||||
|
||||
const lessonFolders = await readdir(coursePath);
|
||||
const lessons = await Promise.all(
|
||||
lessonFolders
|
||||
.filter((folder) => {
|
||||
const stat = lstatSync(join(coursePath, folder));
|
||||
return stat.isDirectory();
|
||||
})
|
||||
.map((folder) => this.getLessons(folderName, folder))
|
||||
);
|
||||
const flattenedLessons = lessons.flat();
|
||||
let lessons: Lesson[] = [];
|
||||
if (!frontmatter.externalLink) {
|
||||
const lessonFolders = await readdir(coursePath);
|
||||
const tmpLessons = await Promise.all(
|
||||
lessonFolders
|
||||
.filter((folder) => {
|
||||
const stat = lstatSync(join(coursePath, folder));
|
||||
return stat.isDirectory();
|
||||
})
|
||||
.map((folder) => this.getLessons(folderName, folder))
|
||||
);
|
||||
lessons = tmpLessons.flat();
|
||||
}
|
||||
|
||||
return {
|
||||
id: folderName,
|
||||
@ -62,9 +75,12 @@ export class CoursesApi {
|
||||
frontmatter.authors.includes(author.name)
|
||||
),
|
||||
repository: frontmatter.repository,
|
||||
lessons: flattenedLessons,
|
||||
lessons,
|
||||
filePath: courseFilePath,
|
||||
totalDuration: calculateTotalDuration(flattenedLessons),
|
||||
totalDuration: calculateTotalDuration(lessons),
|
||||
lessonCount: frontmatter.lessonCount,
|
||||
externalLink: frontmatter.externalLink,
|
||||
order: frontmatter.order,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ export function CourseOverview({ courses }: CourseOverviewProps): JSX.Element {
|
||||
{courses.map((course) => (
|
||||
<Link
|
||||
key={course.id}
|
||||
href={`/courses/${course.id}`}
|
||||
href={course.externalLink || `/courses/${course.id}`}
|
||||
className="block h-full transform-gpu"
|
||||
prefetch={false}
|
||||
>
|
||||
@ -43,14 +43,20 @@ export function CourseOverview({ courses }: CourseOverviewProps): JSX.Element {
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<span>{course.lessons.length} lessons</span>
|
||||
<span>
|
||||
{course.lessons.length > 0
|
||||
? `${course.lessons.length} lessons`
|
||||
: `${course.lessonCount} lessons`}
|
||||
</span>
|
||||
<span className="text-slate-300 dark:text-slate-600">
|
||||
•
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<ClockIcon className="h-3 w-3" />
|
||||
{course.totalDuration}
|
||||
</span>
|
||||
{course.lessons.length > 0 && (
|
||||
<span className="flex items-center gap-1">
|
||||
<ClockIcon className="h-3 w-3" />
|
||||
{course.totalDuration}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user