From 6d7cdd7d41ccb6d6d4aaab2ef019580f2afa7c92 Mon Sep 17 00:00:00 2001 From: Nicholas Cunningham Date: Thu, 15 Aug 2024 12:32:48 -0600 Subject: [PATCH] Podcast page (#26971) --- .../data-access-documents/src/lib/blog.api.ts | 69 ++++-------------- .../src/lib/podcast.api.ts | 16 ++++ .../src/lib/podcast.model.ts | 5 ++ .../data-access-documents/src/node.index.ts | 2 + nx-dev/nx-dev/app/blog/page.tsx | 2 +- nx-dev/nx-dev/app/podcast/page.tsx | 39 ++++++++++ nx-dev/nx-dev/lib/podcast.api.ts | 4 + .../public/images/podcast/podcast-hero.avif | Bin 0 -> 23416 bytes .../ui-common/src/lib/headers/menu-items.ts | 24 +++++- .../src/lib/headers/sections-menu.tsx | 2 +- nx-dev/ui-icons/src/index.ts | 6 ++ .../src/lib/podcasts/amazon-music.tsx | 18 +++++ .../src/lib/podcasts/apple-podcasts.tsx | 20 +++++ .../src/lib/podcasts/i-heart-radio.tsx | 20 +++++ nx-dev/ui-icons/src/lib/podcasts/spotify.tsx | 20 +++++ nx-dev/ui-podcast/.babelrc | 12 +++ nx-dev/ui-podcast/.eslintrc.json | 18 +++++ nx-dev/ui-podcast/README.md | 7 ++ nx-dev/ui-podcast/project.json | 9 +++ nx-dev/ui-podcast/src/index.ts | 2 + nx-dev/ui-podcast/src/lib/hero.tsx | 35 +++++++++ nx-dev/ui-podcast/src/lib/listen-on.tsx | 53 ++++++++++++++ .../ui-podcast/src/lib/podcast-list-item.tsx | 33 +++++++++ nx-dev/ui-podcast/src/lib/podcast-list.tsx | 27 +++++++ nx-dev/ui-podcast/tsconfig.json | 17 +++++ nx-dev/ui-podcast/tsconfig.lib.json | 24 ++++++ tsconfig.base.json | 1 + 27 files changed, 424 insertions(+), 61 deletions(-) create mode 100644 nx-dev/data-access-documents/src/lib/podcast.api.ts create mode 100644 nx-dev/data-access-documents/src/lib/podcast.model.ts create mode 100644 nx-dev/nx-dev/app/podcast/page.tsx create mode 100644 nx-dev/nx-dev/lib/podcast.api.ts create mode 100644 nx-dev/nx-dev/public/images/podcast/podcast-hero.avif create mode 100644 nx-dev/ui-icons/src/lib/podcasts/amazon-music.tsx create mode 100644 nx-dev/ui-icons/src/lib/podcasts/apple-podcasts.tsx create mode 100644 nx-dev/ui-icons/src/lib/podcasts/i-heart-radio.tsx create mode 100644 nx-dev/ui-icons/src/lib/podcasts/spotify.tsx create mode 100644 nx-dev/ui-podcast/.babelrc create mode 100644 nx-dev/ui-podcast/.eslintrc.json create mode 100644 nx-dev/ui-podcast/README.md create mode 100644 nx-dev/ui-podcast/project.json create mode 100644 nx-dev/ui-podcast/src/index.ts create mode 100644 nx-dev/ui-podcast/src/lib/hero.tsx create mode 100644 nx-dev/ui-podcast/src/lib/listen-on.tsx create mode 100644 nx-dev/ui-podcast/src/lib/podcast-list-item.tsx create mode 100644 nx-dev/ui-podcast/src/lib/podcast-list.tsx create mode 100644 nx-dev/ui-podcast/tsconfig.json create mode 100644 nx-dev/ui-podcast/tsconfig.lib.json diff --git a/nx-dev/data-access-documents/src/lib/blog.api.ts b/nx-dev/data-access-documents/src/lib/blog.api.ts index 4dfd0469be..f1fd9eb33a 100644 --- a/nx-dev/data-access-documents/src/lib/blog.api.ts +++ b/nx-dev/data-access-documents/src/lib/blog.api.ts @@ -1,4 +1,4 @@ -import { readFileSync, readdirSync, accessSync, constants } from 'fs'; +import { readFileSync, accessSync, constants } from 'fs'; import { join, basename, parse, resolve } from 'path'; import { extractFrontmatter } from '@nx/nx-dev/ui-markdoc'; import { sortPosts } from './blog.util'; @@ -19,7 +19,6 @@ export class BlogApi { throw new Error('public blog root cannot be undefined'); } } - async getBlogTags(): Promise { const blogs = await this.getBlogs(); const tags = new Set(); @@ -28,8 +27,15 @@ export class BlogApi { }); return Array.from(tags); } + async getBlogs( + filterFn?: (post: BlogPostDataEntry) => boolean + ): Promise { + return await this.getAllBlogs(filterFn); + } - async getBlogs(): Promise { + async getAllBlogs( + filterFn?: (post: BlogPostDataEntry) => boolean + ): Promise { const files: string[] = await readdir(this.options.blogRoot); const authors = JSON.parse( readFileSync(join(this.options.blogRoot, 'authors.json'), 'utf8') @@ -63,65 +69,16 @@ export class BlogApi { filePath, slug, }; - if (!frontmatter.draft || process.env.NODE_ENV === 'development') { + const isDevelopment = process.env.NODE_ENV === 'development'; + const shouldIncludePost = !frontmatter.draft || isDevelopment; + + if (shouldIncludePost && (!filterFn || filterFn(post))) { allPosts.push(post); } } return sortPosts(allPosts); } - getBlogPosts(): BlogPostDataEntry[] { - const files: string[] = readdirSync(this.options.blogRoot); - const authors = JSON.parse( - readFileSync(join(this.options.blogRoot, 'authors.json'), 'utf8') - ); - const allPosts: BlogPostDataEntry[] = []; - - for (const file of files) { - const filePath = join(this.options.blogRoot, file); - // filter out directories (e.g. images) - if (!filePath.endsWith('.md')) continue; - - const content = readFileSync(filePath, 'utf8'); - const frontmatter = extractFrontmatter(content); - const slug = this.calculateSlug(filePath, frontmatter); - const { image, type } = this.determineOgImage(frontmatter.cover_image); - const post = { - content, - title: frontmatter.title ?? null, - description: frontmatter.description ?? null, - authors: authors.filter((author) => - frontmatter.authors.includes(author.name) - ), - date: this.calculateDate(file, frontmatter), - cover_image: frontmatter.cover_image - ? `/documentation${frontmatter.cover_image}` // Match the prefix used by markdown parser - : null, - tags: frontmatter.tags ?? [], - reposts: frontmatter.reposts ?? [], - pinned: frontmatter.pinned ?? false, - ogImage: image, - ogImageType: type, - filePath, - slug, - }; - - if (!frontmatter.draft || process.env.NODE_ENV === 'development') { - allPosts.push(post); - } - } - - return sortPosts(allPosts); - } - - getBlogPost(slug: string): BlogPostDataEntry { - const blogs = this.getBlogPosts(); - const blog = blogs.find((b) => b.slug === slug); - if (!blog) { - throw new Error(`Could not find blog post with slug: ${slug}`); - } - return blog; - } // Optimize this so we don't read the FS multiple times async getBlogPostBySlug( slug: string | null diff --git a/nx-dev/data-access-documents/src/lib/podcast.api.ts b/nx-dev/data-access-documents/src/lib/podcast.api.ts new file mode 100644 index 0000000000..0952154dc7 --- /dev/null +++ b/nx-dev/data-access-documents/src/lib/podcast.api.ts @@ -0,0 +1,16 @@ +import { BlogApi } from './blog.api'; +import { PodcastDataEntry } from './podcast.model'; + +export class PodcastApi { + _blogApi: BlogApi; + + constructor(options: { blogApi: BlogApi }) { + this._blogApi = options.blogApi; + } + + async getPodcastBlogs(): Promise { + return await this._blogApi.getBlogs((post) => + post.tags.map((t) => t.toLowerCase()).includes('podcast') + ); + } +} diff --git a/nx-dev/data-access-documents/src/lib/podcast.model.ts b/nx-dev/data-access-documents/src/lib/podcast.model.ts new file mode 100644 index 0000000000..1a9f44442a --- /dev/null +++ b/nx-dev/data-access-documents/src/lib/podcast.model.ts @@ -0,0 +1,5 @@ +import { BlogPostDataEntry } from './blog.model'; + +export interface PodcastDataEntry extends BlogPostDataEntry { + duration?: string; +} diff --git a/nx-dev/data-access-documents/src/node.index.ts b/nx-dev/data-access-documents/src/node.index.ts index c829d2e6a5..7534024a3d 100644 --- a/nx-dev/data-access-documents/src/node.index.ts +++ b/nx-dev/data-access-documents/src/node.index.ts @@ -4,3 +4,5 @@ export * from './lib/blog.util'; export * from './lib/blog.api'; export * from './lib/blog.model'; export * from './lib/tags.api'; +export * from './lib/podcast.model'; +export * from './lib/podcast.api'; diff --git a/nx-dev/nx-dev/app/blog/page.tsx b/nx-dev/nx-dev/app/blog/page.tsx index 9ab79c2337..dea815931d 100644 --- a/nx-dev/nx-dev/app/blog/page.tsx +++ b/nx-dev/nx-dev/app/blog/page.tsx @@ -26,7 +26,7 @@ export const metadata: Metadata = { }, }; async function getBlogs() { - return await blogApi.getBlogPosts(); + return await blogApi.getBlogs(); } async function getBlogTags() { diff --git a/nx-dev/nx-dev/app/podcast/page.tsx b/nx-dev/nx-dev/app/podcast/page.tsx new file mode 100644 index 0000000000..edcaa81c93 --- /dev/null +++ b/nx-dev/nx-dev/app/podcast/page.tsx @@ -0,0 +1,39 @@ +import { Metadata } from 'next'; +import { podcastApi } from '../../lib/podcast.api'; +import { DefaultLayout } from '@nx/nx-dev/ui-common'; +import { Hero, PodcastList } from '@nx/nx-dev/ui-podcast'; + +export const metadata: Metadata = { + title: 'Nx Podcast - Updates from the Nx & Nx Cloud team', + description: 'Latest podcasts from the Nx & Nx Cloud core team', + openGraph: { + url: 'https://nx.dev/podcast', + title: 'Nx Podcast - Updates from the Nx & Nx Cloud team', + description: + 'Stay updated with the latest podcasts from the Nx & Nx Cloud team.', + images: [ + { + url: 'https://nx.dev/socials/nx-media.png', + width: 800, + height: 421, + alt: 'Nx: Smart Monorepos ยท Fast CI', + type: 'image/jpeg', + }, + ], + siteName: 'NxDev', + type: 'website', + }, +}; + +async function getPodcasts() { + return await podcastApi.getPodcastBlogs(); +} +export default async function Page() { + const podcasts = await getPodcasts(); + return ( + + + + + ); +} diff --git a/nx-dev/nx-dev/lib/podcast.api.ts b/nx-dev/nx-dev/lib/podcast.api.ts new file mode 100644 index 0000000000..dea27dbc7b --- /dev/null +++ b/nx-dev/nx-dev/lib/podcast.api.ts @@ -0,0 +1,4 @@ +import { blogApi } from './blog.api'; +import { PodcastApi } from '@nx/nx-dev/data-access-documents/node-only'; + +export const podcastApi = new PodcastApi({ blogApi }); diff --git a/nx-dev/nx-dev/public/images/podcast/podcast-hero.avif b/nx-dev/nx-dev/public/images/podcast/podcast-hero.avif new file mode 100644 index 0000000000000000000000000000000000000000..b863e4de486fd520d332e6b822dfa02d6c11efa4 GIT binary patch literal 23416 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 RQSSL09`r=U+Fy7FCcqlFu^9jW literal 0 HcmV?d00001 diff --git a/nx-dev/ui-common/src/lib/headers/menu-items.ts b/nx-dev/ui-common/src/lib/headers/menu-items.ts index 65499de13f..4ffff65af1 100644 --- a/nx-dev/ui-common/src/lib/headers/menu-items.ts +++ b/nx-dev/ui-common/src/lib/headers/menu-items.ts @@ -13,6 +13,8 @@ import { UserGroupIcon, ComputerDesktopIcon, GlobeAltIcon, + MicrophoneIcon, + VideoCameraIcon, } from '@heroicons/react/24/outline'; import { FC, SVGProps } from 'react'; import { DiscordIcon } from '../discord-icon'; @@ -172,6 +174,22 @@ export const learnItems: MenuItem[] = [ isNew: false, isHighlight: false, }, + { + name: 'Podcasts', + description: null, + href: '/podcast', + icon: MicrophoneIcon, + isNew: false, + isHighlight: false, + }, + { + name: 'Webinars', + description: null, + href: 'https://go.nx.dev/webinar', + icon: ComputerDesktopIcon, + isNew: false, + isHighlight: false, + }, { name: 'Video tutorials', description: null, @@ -215,10 +233,10 @@ export const eventItems: MenuItem[] = [ isHighlight: false, }, { - name: 'Webinars', + name: 'Live Streams', description: null, - href: 'https://go.nx.dev/webinar', - icon: ComputerDesktopIcon, + href: 'https://www.youtube.com/@nxdevtools/streams', + icon: VideoCameraIcon, isNew: false, isHighlight: false, }, diff --git a/nx-dev/ui-common/src/lib/headers/sections-menu.tsx b/nx-dev/ui-common/src/lib/headers/sections-menu.tsx index 5cbbcb73df..646dea834f 100644 --- a/nx-dev/ui-common/src/lib/headers/sections-menu.tsx +++ b/nx-dev/ui-common/src/lib/headers/sections-menu.tsx @@ -10,7 +10,7 @@ export function SectionsMenu({
{Object.keys(sections).map((section) => ( -
+
{section}
diff --git a/nx-dev/ui-icons/src/index.ts b/nx-dev/ui-icons/src/index.ts index 217df3dc7d..86209781a8 100644 --- a/nx-dev/ui-icons/src/index.ts +++ b/nx-dev/ui-icons/src/index.ts @@ -119,3 +119,9 @@ export * from './lib/technologies/vite'; export * from './lib/technologies/vitest'; export * from './lib/technologies/vue'; export * from './lib/technologies/webpack'; + +// PODCASTS +export * from './lib/podcasts/amazon-music'; +export * from './lib/podcasts/apple-podcasts'; +export * from './lib/podcasts/i-heart-radio'; +export * from './lib/podcasts/spotify'; diff --git a/nx-dev/ui-icons/src/lib/podcasts/amazon-music.tsx b/nx-dev/ui-icons/src/lib/podcasts/amazon-music.tsx new file mode 100644 index 0000000000..62e24e0952 --- /dev/null +++ b/nx-dev/ui-icons/src/lib/podcasts/amazon-music.tsx @@ -0,0 +1,18 @@ +import { FC, SVGProps } from 'react'; +/** + * Color: #46C3D0 + */ +export const AmazonMusicIcon: FC> = (props) => { + return ( + + Amazon Music + + + ); +}; diff --git a/nx-dev/ui-icons/src/lib/podcasts/apple-podcasts.tsx b/nx-dev/ui-icons/src/lib/podcasts/apple-podcasts.tsx new file mode 100644 index 0000000000..be24dbe168 --- /dev/null +++ b/nx-dev/ui-icons/src/lib/podcasts/apple-podcasts.tsx @@ -0,0 +1,20 @@ +import { FC, SVGProps } from 'react'; + +/** + * Color: #9933CC + */ + +export const ApplePodcastsIcon: FC> = (props) => { + return ( + + Apple Podcasts + + + ); +}; diff --git a/nx-dev/ui-icons/src/lib/podcasts/i-heart-radio.tsx b/nx-dev/ui-icons/src/lib/podcasts/i-heart-radio.tsx new file mode 100644 index 0000000000..5be59cf530 --- /dev/null +++ b/nx-dev/ui-icons/src/lib/podcasts/i-heart-radio.tsx @@ -0,0 +1,20 @@ +import { FC, SVGProps } from 'react'; + +/** + * Color: #C6002B + */ + +export const IHeartRadioIcon: FC> = (props) => { + return ( + + iHeartRadio + + + ); +}; diff --git a/nx-dev/ui-icons/src/lib/podcasts/spotify.tsx b/nx-dev/ui-icons/src/lib/podcasts/spotify.tsx new file mode 100644 index 0000000000..71987398f5 --- /dev/null +++ b/nx-dev/ui-icons/src/lib/podcasts/spotify.tsx @@ -0,0 +1,20 @@ +import { FC, SVGProps } from 'react'; + +/** + * Color: #1DB954 + */ + +export const SpotifyIcon: FC> = (props) => { + return ( + + Spotify + + + ); +}; diff --git a/nx-dev/ui-podcast/.babelrc b/nx-dev/ui-podcast/.babelrc new file mode 100644 index 0000000000..1ea870ead4 --- /dev/null +++ b/nx-dev/ui-podcast/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/nx-dev/ui-podcast/.eslintrc.json b/nx-dev/ui-podcast/.eslintrc.json new file mode 100644 index 0000000000..a39ac5d057 --- /dev/null +++ b/nx-dev/ui-podcast/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/nx-dev/ui-podcast/README.md b/nx-dev/ui-podcast/README.md new file mode 100644 index 0000000000..f687f62b87 --- /dev/null +++ b/nx-dev/ui-podcast/README.md @@ -0,0 +1,7 @@ +# ui-podcast + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test ui-podcast` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/nx-dev/ui-podcast/project.json b/nx-dev/ui-podcast/project.json new file mode 100644 index 0000000000..fd32f08b2a --- /dev/null +++ b/nx-dev/ui-podcast/project.json @@ -0,0 +1,9 @@ +{ + "name": "ui-podcast", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "nx-dev/ui-podcast/src", + "projectType": "library", + "tags": [], + "// targets": "to see all targets run: nx show project ui-podcast --web", + "targets": {} +} diff --git a/nx-dev/ui-podcast/src/index.ts b/nx-dev/ui-podcast/src/index.ts new file mode 100644 index 0000000000..d961e66414 --- /dev/null +++ b/nx-dev/ui-podcast/src/index.ts @@ -0,0 +1,2 @@ +export * from './lib/hero'; +export * from './lib/podcast-list'; diff --git a/nx-dev/ui-podcast/src/lib/hero.tsx b/nx-dev/ui-podcast/src/lib/hero.tsx new file mode 100644 index 0000000000..9ecbaf7845 --- /dev/null +++ b/nx-dev/ui-podcast/src/lib/hero.tsx @@ -0,0 +1,35 @@ +import { SectionHeading } from '@nx/nx-dev/ui-common'; +import { ListenOn } from './listen-on'; + +export function Hero(): JSX.Element { + return ( +
+
+
+ + The Enterprise Software Podcast + +
+ + Listen in to some exciting conversations with the hidden + architects of enterprise software. + +
+

+ Available On:{' '} +

+ +
+
+
+
+ Illustration of a microphone +
+
+
+ ); +} diff --git a/nx-dev/ui-podcast/src/lib/listen-on.tsx b/nx-dev/ui-podcast/src/lib/listen-on.tsx new file mode 100644 index 0000000000..da771c7736 --- /dev/null +++ b/nx-dev/ui-podcast/src/lib/listen-on.tsx @@ -0,0 +1,53 @@ +import { + AmazonMusicIcon, + ApplePodcastsIcon, + IHeartRadioIcon, + SpotifyIcon, +} from '@nx/nx-dev/ui-icons'; + +export function ListenOn(): JSX.Element { + const platforms = [ + { + name: 'Amazon Music', + url: 'https://music.amazon.com/podcasts/a221fdad-36fd-4695-a5b4-038d7b99d284/the-enterprise-software-podcast-by-nx', + icon: AmazonMusicIcon, + }, + { + name: 'Apple Podcasts', + url: 'https://podcasts.apple.com/us/podcast/the-enterprise-software-podcast-by-nx/id1752704996', + icon: ApplePodcastsIcon, + }, + { + name: 'iHeartRadio', + url: 'https://www.iheart.com/podcast/269-the-enterprise-software-po-186891508/', + icon: IHeartRadioIcon, + }, + { + name: 'Spotify', + url: 'https://open.spotify.com/show/6Axjn4Qh7PUWlGbNqzE7J4', + icon: SpotifyIcon, + }, + ]; + + return ( +
    + {platforms.map((platform) => { + return ( +
  • + + + +
  • + ); + })} +
+ ); +} diff --git a/nx-dev/ui-podcast/src/lib/podcast-list-item.tsx b/nx-dev/ui-podcast/src/lib/podcast-list-item.tsx new file mode 100644 index 0000000000..377c61d6bf --- /dev/null +++ b/nx-dev/ui-podcast/src/lib/podcast-list-item.tsx @@ -0,0 +1,33 @@ +import { BlogAuthors } from '@nx/nx-dev/ui-blog'; +import Link from 'next/link'; +import type { PodcastDataEntry } from '@nx/nx-dev/data-access-documents/node-only'; + +export interface PodcastListItemProps { + podcast: PodcastDataEntry; + episode: number; +} +export function PodcastListItem({ podcast, episode }: PodcastListItemProps) { + const formattedDate = new Date(podcast.date).toLocaleDateString('en-US', { + month: 'short', + day: '2-digit', + year: 'numeric', + }); + return ( + + + Episode {episode}: {podcast.title} + + + + + + + + + ); +} diff --git a/nx-dev/ui-podcast/src/lib/podcast-list.tsx b/nx-dev/ui-podcast/src/lib/podcast-list.tsx new file mode 100644 index 0000000000..047fda6396 --- /dev/null +++ b/nx-dev/ui-podcast/src/lib/podcast-list.tsx @@ -0,0 +1,27 @@ +import { PodcastDataEntry } from '@nx/nx-dev/data-access-documents/node-only'; +import { PodcastListItem } from './podcast-list-item'; + +export interface PodcastListProps { + podcasts: PodcastDataEntry[]; +} + +export function PodcastList({ podcasts }: PodcastListProps): JSX.Element { + return podcasts.length < 1 ? ( +
+

+ No podcasts as yet but stay tuned! +

+
+ ) : ( +
+
+

Podcasts

+
+
+ {podcasts?.map((post, index) => ( + + ))} +
+
+ ); +} diff --git a/nx-dev/ui-podcast/tsconfig.json b/nx-dev/ui-podcast/tsconfig.json new file mode 100644 index 0000000000..95cfeb243d --- /dev/null +++ b/nx-dev/ui-podcast/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ], + "extends": "../../tsconfig.base.json" +} diff --git a/nx-dev/ui-podcast/tsconfig.lib.json b/nx-dev/ui-podcast/tsconfig.lib.json new file mode 100644 index 0000000000..cfc4843293 --- /dev/null +++ b/nx-dev/ui-podcast/tsconfig.lib.json @@ -0,0 +1,24 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": [ + "node", + + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts" + ] + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index e93b67d768..007ac4e0bb 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -108,6 +108,7 @@ "@nx/nx-dev/ui-icons": ["nx-dev/ui-icons/src/index.ts"], "@nx/nx-dev/ui-markdoc": ["nx-dev/ui-markdoc/src/index.ts"], "@nx/nx-dev/ui-member-card": ["nx-dev/ui-member-card/src/index.ts"], + "@nx/nx-dev/ui-podcast": ["nx-dev/ui-podcast/src/index.ts"], "@nx/nx-dev/ui-pricing": ["nx-dev/ui-pricing/src/index.ts"], "@nx/nx-dev/ui-primitives": ["nx-dev/ui-primitives/src/index.ts"], "@nx/nx-dev/ui-references": ["nx-dev/ui-references/src/index.ts"],