refactor: improve type safety (#155)

* Fix to make it type safe.

* The build is failing, fix it.
This commit is contained in:
Katsuyuki Karasawa 2024-08-28 01:19:26 +09:00 committed by GitHub
parent f79ee3482d
commit e9c8930559
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 489 additions and 133 deletions

View File

@ -8,12 +8,13 @@
"build": "astro build && pagefind --site dist", "build": "astro build && pagefind --site dist",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro", "astro": "astro",
"type-check": "tsc --noEmit --isolatedDeclarations",
"new-post": "node scripts/new-post.js", "new-post": "node scripts/new-post.js",
"format": "biome format --write ./src", "format": "biome format --write ./src",
"lint": "biome check --apply ./src" "lint": "biome check --apply ./src"
}, },
"dependencies": { "dependencies": {
"@astrojs/check": "^0.8.3", "@astrojs/check": "^0.9.2",
"@astrojs/rss": "^4.0.7", "@astrojs/rss": "^4.0.7",
"@astrojs/sitemap": "^3.1.6", "@astrojs/sitemap": "^3.1.6",
"@astrojs/svelte": "^5.7.0", "@astrojs/svelte": "^5.7.0",
@ -21,7 +22,7 @@
"@fontsource-variable/jetbrains-mono": "^5.0.21", "@fontsource-variable/jetbrains-mono": "^5.0.21",
"@fontsource/roboto": "^5.0.13", "@fontsource/roboto": "^5.0.13",
"@swup/astro": "^1.4.1", "@swup/astro": "^1.4.1",
"astro": "^4.12.2", "astro": "^4.14.2",
"astro-compress": "^2.3.0", "astro-compress": "^2.3.0",
"astro-icon": "1.1.0", "astro-icon": "1.1.0",
"colorjs.io": "^0.5.2", "colorjs.io": "^0.5.2",
@ -46,7 +47,7 @@
"unist-util-visit": "^5.0.0" "unist-util-visit": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/ts-plugin": "^1.9.0", "@astrojs/ts-plugin": "^1.10.0",
"@biomejs/biome": "1.8.3", "@biomejs/biome": "1.8.3",
"@iconify-json/fa6-brands": "^1.1.21", "@iconify-json/fa6-brands": "^1.1.21",
"@iconify-json/fa6-regular": "^1.1.21", "@iconify-json/fa6-regular": "^1.1.21",

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
--- ---
import { UNCATEGORIZED } from '@constants/constants'
import I18nKey from '../i18n/i18nKey'
import { i18n } from '../i18n/translation'
import { getSortedPosts } from '../utils/content-utils' import { getSortedPosts } from '../utils/content-utils'
import { getPostUrlBySlug } from '../utils/url-utils' import { getPostUrlBySlug } from '../utils/url-utils'
import { i18n } from '../i18n/translation'
import I18nKey from '../i18n/i18nKey'
import { UNCATEGORIZED } from '@constants/constants'
interface Props { interface Props {
keyword?: string keyword?: string
@ -30,7 +30,7 @@ if (Array.isArray(categories) && categories.length > 0) {
) )
} }
const groups: { year: number; posts: typeof posts }[] = (function () { const groups: { year: number; posts: typeof posts }[] = (() => {
const groupedPosts = posts.reduce( const groupedPosts = posts.reduce(
(grouped: { [year: number]: typeof posts }, post) => { (grouped: { [year: number]: typeof posts }, post) => {
const year = post.data.published.getFullYear() const year = post.data.published.getFullYear()
@ -45,8 +45,8 @@ const groups: { year: number; posts: typeof posts }[] = (function () {
// convert the object to an array // convert the object to an array
const groupedPostsArray = Object.keys(groupedPosts).map(key => ({ const groupedPostsArray = Object.keys(groupedPosts).map(key => ({
year: parseInt(key), year: Number.parseInt(key),
posts: groupedPosts[parseInt(key)], posts: groupedPosts[Number.parseInt(key)],
})) }))
// sort years by latest first // sort years by latest first

View File

@ -1,27 +1,27 @@
--- ---
import { getSortedPosts } from '@utils/content-utils'
import MainGridLayout from '@layouts/MainGridLayout.astro'
import ArchivePanel from '@components/ArchivePanel.astro' import ArchivePanel from '@components/ArchivePanel.astro'
import { i18n } from '@i18n/translation'
import I18nKey from '@i18n/i18nKey' import I18nKey from '@i18n/i18nKey'
import { i18n } from '@i18n/translation'
import MainGridLayout from '@layouts/MainGridLayout.astro'
import { getSortedPosts } from '@utils/content-utils'
export async function getStaticPaths() { export async function getStaticPaths() {
let posts = await getSortedPosts() const posts = await getSortedPosts()
const allTags = posts.reduce((acc, post) => { // タグを集めるための Set の型を指定
const allTags = posts.reduce<Set<string>>((acc, post) => {
// biome-ignore lint/complexity/noForEach: <explanation>
post.data.tags.forEach(tag => acc.add(tag)) post.data.tags.forEach(tag => acc.add(tag))
return acc return acc
}, new Set()) }, new Set())
const allTagsArray = Array.from(allTags) const allTagsArray = Array.from(allTags)
return allTagsArray.map(tag => { return allTagsArray.map(tag => ({
return { params: {
params: { tag: tag,
tag: tag, },
}, }))
}
})
} }
const tag = Astro.params.tag as string const tag = Astro.params.tag as string
@ -29,4 +29,4 @@ const tag = Astro.params.tag as string
<MainGridLayout title={i18n(I18nKey.archive)} description={i18n(I18nKey.archive)}> <MainGridLayout title={i18n(I18nKey.archive)} description={i18n(I18nKey.archive)}>
<ArchivePanel tags={[tag]}></ArchivePanel> <ArchivePanel tags={[tag]}></ArchivePanel>
</MainGridLayout> </MainGridLayout>

View File

@ -1,25 +1,31 @@
import rss from '@astrojs/rss'
import { siteConfig } from '@/config' import { siteConfig } from '@/config'
import sanitizeHtml from 'sanitize-html' import rss from '@astrojs/rss'
import { getSortedPosts } from '@utils/content-utils'
import type { APIContext } from 'astro'
import MarkdownIt from 'markdown-it' import MarkdownIt from 'markdown-it'
import { getSortedPosts } from '@utils/content-utils.ts' import sanitizeHtml from 'sanitize-html'
const parser = new MarkdownIt() const parser = new MarkdownIt()
export async function GET(context: any) { export async function GET(context: APIContext) {
const blog = await getSortedPosts() const blog = await getSortedPosts()
return rss({ return rss({
title: siteConfig.title, title: siteConfig.title,
description: siteConfig.subtitle || 'No description', description: siteConfig.subtitle || 'No description',
site: context.site, site: context.site ?? 'https://fuwari.vercel.app',
items: blog.map(post => ({ items: blog.map(post => {
title: post.data.title, const body = typeof post.data.body === 'string' ? post.data.body : ''
pubDate: post.data.published, return {
description: post.data.description, title: post.data.title,
link: `/posts/${post.slug}/`, pubDate: post.data.published,
content: sanitizeHtml(parser.render(post.body), { description: post.data.description || '',
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']), link: `/posts/${post.slug}/`,
}), content: sanitizeHtml(parser.render(body), {
})), allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
}),
}
}),
customData: `<language>${siteConfig.lang}</language>`, customData: `<language>${siteConfig.lang}</language>`,
}) })
} }

View File

@ -4,7 +4,7 @@ import { visit } from 'unist-util-visit'
export function parseDirectiveNode() { export function parseDirectiveNode() {
return (tree, { data }) => { return (tree, { data }) => {
visit(tree, function (node) { visit(tree, node => {
if ( if (
node.type === 'containerDirective' || node.type === 'containerDirective' ||
node.type === 'leafDirective' || node.type === 'leafDirective' ||

View File

@ -1,4 +1,4 @@
import type { LIGHT_MODE, DARK_MODE, AUTO_MODE } from '@constants/constants' import type { AUTO_MODE, DARK_MODE, LIGHT_MODE } from '@constants/constants'
export type SiteConfig = { export type SiteConfig = {
title: string title: string
@ -67,3 +67,18 @@ export type LIGHT_DARK_MODE =
| typeof LIGHT_MODE | typeof LIGHT_MODE
| typeof DARK_MODE | typeof DARK_MODE
| typeof AUTO_MODE | typeof AUTO_MODE
export type BlogPostData = {
body: string
title: string
published: Date
description: string
tags: string[]
draft?: boolean
image?: string
category?: string
prevTitle?: string
prevSlug?: string
nextTitle?: string
nextSlug?: string
}

View File

@ -1,16 +1,22 @@
import { getCollection } from 'astro:content'
import type { BlogPostData } from '@/types/config'
import I18nKey from '@i18n/i18nKey' import I18nKey from '@i18n/i18nKey'
import { i18n } from '@i18n/translation' import { i18n } from '@i18n/translation'
import { getCollection } from 'astro:content'
export async function getSortedPosts() { export async function getSortedPosts(): Promise<
const allBlogPosts = await getCollection('posts', ({ data }) => { { data: BlogPostData; slug: string }[]
> {
const allBlogPosts = (await getCollection('posts', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true return import.meta.env.PROD ? data.draft !== true : true
}) })) as unknown as { data: BlogPostData; slug: string }[]
const sorted = allBlogPosts.sort((a, b) => {
const dateA = new Date(a.data.published) const sorted = allBlogPosts.sort(
const dateB = new Date(b.data.published) (a: { data: { published: Date } }, b: { data: { published: Date } }) => {
return dateA > dateB ? -1 : 1 const dateA = new Date(a.data.published)
}) const dateB = new Date(b.data.published)
return dateA > dateB ? -1 : 1
},
)
for (let i = 1; i < sorted.length; i++) { for (let i = 1; i < sorted.length; i++) {
sorted[i].data.nextSlug = sorted[i - 1].slug sorted[i].data.nextSlug = sorted[i - 1].slug
@ -30,12 +36,12 @@ export type Tag = {
} }
export async function getTagList(): Promise<Tag[]> { export async function getTagList(): Promise<Tag[]> {
const allBlogPosts = await getCollection('posts', ({ data }) => { const allBlogPosts = await getCollection<'posts'>('posts', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true return import.meta.env.PROD ? data.draft !== true : true
}) })
const countMap: { [key: string]: number } = {} const countMap: { [key: string]: number } = {}
allBlogPosts.map(post => { allBlogPosts.map((post: { data: { tags: string[] } }) => {
post.data.tags.map((tag: string) => { post.data.tags.map((tag: string) => {
if (!countMap[tag]) countMap[tag] = 0 if (!countMap[tag]) countMap[tag] = 0
countMap[tag]++ countMap[tag]++
@ -56,11 +62,11 @@ export type Category = {
} }
export async function getCategoryList(): Promise<Category[]> { export async function getCategoryList(): Promise<Category[]> {
const allBlogPosts = await getCollection('posts', ({ data }) => { const allBlogPosts = await getCollection<'posts'>('posts', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true return import.meta.env.PROD ? data.draft !== true : true
}) })
const count: { [key: string]: number } = {} const count: { [key: string]: number } = {}
allBlogPosts.map(post => { allBlogPosts.map((post: { data: { category: string | number } }) => {
if (!post.data.category) { if (!post.data.category) {
const ucKey = i18n(I18nKey.uncategorized) const ucKey = i18n(I18nKey.uncategorized)
count[ucKey] = count[ucKey] ? count[ucKey] + 1 : 1 count[ucKey] = count[ucKey] ? count[ucKey] + 1 : 1

View File

@ -1,20 +1,20 @@
import type { LIGHT_DARK_MODE } from '@/types/config'
import { import {
AUTO_MODE, AUTO_MODE,
DARK_MODE, DARK_MODE,
DEFAULT_THEME, DEFAULT_THEME,
LIGHT_MODE, LIGHT_MODE,
} from '@constants/constants.ts' } from '@constants/constants.ts'
import type { LIGHT_DARK_MODE } from '@/types/config'
export function getDefaultHue(): number { export function getDefaultHue(): number {
const fallback = '250' const fallback = '250'
const configCarrier = document.getElementById('config-carrier') const configCarrier = document.getElementById('config-carrier')
return parseInt(configCarrier?.dataset.hue || fallback) return Number.parseInt(configCarrier?.dataset.hue || fallback)
} }
export function getHue(): number { export function getHue(): number {
const stored = localStorage.getItem('hue') const stored = localStorage.getItem('hue')
return stored ? parseInt(stored) : getDefaultHue() return stored ? Number.parseInt(stored) : getDefaultHue()
} }
export function setHue(hue: number): void { export function setHue(hue: number): void {

View File

@ -3,7 +3,8 @@
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"strictNullChecks": true, "strictNullChecks": true,
"allowJs": true, "allowJs": false,
"declaration": true,
"plugins": [ "plugins": [
{ {
"name": "@astrojs/ts-plugin" "name": "@astrojs/ts-plugin"