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'
|
title: 'Introduction to Nx'
|
||||||
description: 'New to Nx? Then this is where you should start.'
|
description: 'New to Nx? Then this is where you should start.'
|
||||||
authors: [Juri Strumpflohner]
|
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.
|
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.'
|
description: 'Learn how to transform a PNPM workspace monorepo into a high-performance distributed CI setup using Nx.'
|
||||||
authors: [Juri Strumpflohner]
|
authors: [Juri Strumpflohner]
|
||||||
repository: 'https://github.com/nrwl/nx-course-pnpm-nx'
|
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.
|
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[];
|
authors: BlogAuthor[];
|
||||||
repository?: string;
|
repository?: string;
|
||||||
lessons: Lesson[];
|
lessons: Lesson[];
|
||||||
|
lessonCount?: number;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
|
externalLink?: string;
|
||||||
totalDuration: string;
|
totalDuration: string;
|
||||||
|
order?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Lesson {
|
export interface Lesson {
|
||||||
|
|||||||
@ -29,7 +29,17 @@ export class CoursesApi {
|
|||||||
})
|
})
|
||||||
.map((folder) => this.getCourse(folder))
|
.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> {
|
async getCourse(folderName: string): Promise<Course> {
|
||||||
@ -42,8 +52,10 @@ export class CoursesApi {
|
|||||||
const content = await readFile(courseFilePath, 'utf-8');
|
const content = await readFile(courseFilePath, 'utf-8');
|
||||||
const frontmatter = extractFrontmatter(content);
|
const frontmatter = extractFrontmatter(content);
|
||||||
|
|
||||||
|
let lessons: Lesson[] = [];
|
||||||
|
if (!frontmatter.externalLink) {
|
||||||
const lessonFolders = await readdir(coursePath);
|
const lessonFolders = await readdir(coursePath);
|
||||||
const lessons = await Promise.all(
|
const tmpLessons = await Promise.all(
|
||||||
lessonFolders
|
lessonFolders
|
||||||
.filter((folder) => {
|
.filter((folder) => {
|
||||||
const stat = lstatSync(join(coursePath, folder));
|
const stat = lstatSync(join(coursePath, folder));
|
||||||
@ -51,7 +63,8 @@ export class CoursesApi {
|
|||||||
})
|
})
|
||||||
.map((folder) => this.getLessons(folderName, folder))
|
.map((folder) => this.getLessons(folderName, folder))
|
||||||
);
|
);
|
||||||
const flattenedLessons = lessons.flat();
|
lessons = tmpLessons.flat();
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: folderName,
|
id: folderName,
|
||||||
@ -62,9 +75,12 @@ export class CoursesApi {
|
|||||||
frontmatter.authors.includes(author.name)
|
frontmatter.authors.includes(author.name)
|
||||||
),
|
),
|
||||||
repository: frontmatter.repository,
|
repository: frontmatter.repository,
|
||||||
lessons: flattenedLessons,
|
lessons,
|
||||||
filePath: courseFilePath,
|
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) => (
|
{courses.map((course) => (
|
||||||
<Link
|
<Link
|
||||||
key={course.id}
|
key={course.id}
|
||||||
href={`/courses/${course.id}`}
|
href={course.externalLink || `/courses/${course.id}`}
|
||||||
className="block h-full transform-gpu"
|
className="block h-full transform-gpu"
|
||||||
prefetch={false}
|
prefetch={false}
|
||||||
>
|
>
|
||||||
@ -43,14 +43,20 @@ export function CourseOverview({ courses }: CourseOverviewProps): JSX.Element {
|
|||||||
</span>
|
</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 className="text-slate-300 dark:text-slate-600">
|
||||||
•
|
•
|
||||||
</span>
|
</span>
|
||||||
|
{course.lessons.length > 0 && (
|
||||||
<span className="flex items-center gap-1">
|
<span className="flex items-center gap-1">
|
||||||
<ClockIcon className="h-3 w-3" />
|
<ClockIcon className="h-3 w-3" />
|
||||||
{course.totalDuration}
|
{course.totalDuration}
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user