Added reader and diagnostics
This commit is contained in:
45
src/lib/content/load.ts
Normal file
45
src/lib/content/load.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { ARTICLES, PAGES, TAGS } from '../constants/collections'
|
||||||
|
import { createDiagnostics } from '../diagnostics'
|
||||||
|
import type { Diagnostics } from '../diagnostics/types'
|
||||||
|
import { createContentReader } from './reader'
|
||||||
|
import type { ArticleEntry, Content, PageEntry, TagEntry } from './types'
|
||||||
|
|
||||||
|
interface CollectionReader<T> {
|
||||||
|
list(): Promise<string[]>
|
||||||
|
read(id: string): Promise<T | null>
|
||||||
|
}
|
||||||
|
|
||||||
|
function reason(error: unknown): string {
|
||||||
|
const [first, ...rest] = (error instanceof Error ? error.message : String(error)).split('\n')
|
||||||
|
return first.endsWith(':') && rest.length ? rest.join('; ') : first
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readAll<T>(
|
||||||
|
reader: CollectionReader<T>,
|
||||||
|
collection: string,
|
||||||
|
problems: Diagnostics,
|
||||||
|
): Promise<Map<string, T>> {
|
||||||
|
const entries = new Map<string, T>()
|
||||||
|
await Promise.all(
|
||||||
|
(await reader.list()).map(async (id) => {
|
||||||
|
try {
|
||||||
|
const entry = await reader.read(id)
|
||||||
|
if (entry) entries.set(id, entry)
|
||||||
|
} catch (error) {
|
||||||
|
problems.error(collection, id, reason(error))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadContent(): Promise<Content> {
|
||||||
|
const { collections } = createContentReader()
|
||||||
|
const problems = createDiagnostics()
|
||||||
|
const [articles, pages, tags] = await Promise.all([
|
||||||
|
readAll<ArticleEntry>(collections.articles, ARTICLES, problems),
|
||||||
|
readAll<PageEntry>(collections.pages, PAGES, problems),
|
||||||
|
readAll<TagEntry>(collections.tags, TAGS, problems),
|
||||||
|
])
|
||||||
|
return { articles, pages, tags, diagnostics: problems.list }
|
||||||
|
}
|
||||||
5
src/lib/content/reader.ts
Normal file
5
src/lib/content/reader.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { createReader } from '@keystatic/core/reader'
|
||||||
|
|
||||||
|
import config from '~/keystatic.config'
|
||||||
|
|
||||||
|
export const createContentReader = () => createReader('.', config)
|
||||||
18
src/lib/content/types.ts
Normal file
18
src/lib/content/types.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import type { Entry } from '@keystatic/core/reader'
|
||||||
|
|
||||||
|
import type config from '~/keystatic.config'
|
||||||
|
|
||||||
|
import type { Diagnostic } from '../diagnostics/types'
|
||||||
|
|
||||||
|
type Collections = typeof config.collections
|
||||||
|
|
||||||
|
export type ArticleEntry = Entry<Collections['articles']>
|
||||||
|
export type PageEntry = Entry<Collections['pages']>
|
||||||
|
export type TagEntry = Entry<Collections['tags']>
|
||||||
|
|
||||||
|
export interface Content {
|
||||||
|
articles: Map<string, ArticleEntry>
|
||||||
|
pages: Map<string, PageEntry>
|
||||||
|
tags: Map<string, TagEntry>
|
||||||
|
diagnostics: Diagnostic[]
|
||||||
|
}
|
||||||
22
src/lib/diagnostics/index.ts
Normal file
22
src/lib/diagnostics/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import type { Diagnostic, Diagnostics } from './types'
|
||||||
|
|
||||||
|
export function createDiagnostics(): Diagnostics {
|
||||||
|
const list: Diagnostic[] = []
|
||||||
|
return {
|
||||||
|
list,
|
||||||
|
error: (collection, id, message) =>
|
||||||
|
list.push({
|
||||||
|
level: 'error',
|
||||||
|
collection,
|
||||||
|
id,
|
||||||
|
message,
|
||||||
|
}),
|
||||||
|
warning: (collection, id, message) =>
|
||||||
|
list.push({
|
||||||
|
level: 'warning',
|
||||||
|
collection,
|
||||||
|
id,
|
||||||
|
message,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/lib/diagnostics/report.ts
Normal file
23
src/lib/diagnostics/report.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Diagnostic } from './types'
|
||||||
|
|
||||||
|
const SYMBOL = {
|
||||||
|
error: '✖',
|
||||||
|
warning: '▲',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const byLevelThenEntry = (a: Diagnostic, b: Diagnostic) =>
|
||||||
|
(a.level === b.level ? 0 : a.level === 'error' ? -1 : 1) ||
|
||||||
|
a.collection.localeCompare(b.collection) ||
|
||||||
|
(a.id ?? '').localeCompare(b.id ?? '')
|
||||||
|
|
||||||
|
export function formatDiagnostics(list: Diagnostic[]): string {
|
||||||
|
return [...list]
|
||||||
|
.sort(byLevelThenEntry)
|
||||||
|
.map((d) => `${SYMBOL[d.level]} ${d.collection}/${d.id ?? '*'} ${d.message}`)
|
||||||
|
.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertNoErrors(list: Diagnostic[]): void {
|
||||||
|
const errors = list.filter((d) => d.level === 'error').length
|
||||||
|
if (errors) throw new Error(`Content has ${errors} error(s):\n${formatDiagnostics(list)}`)
|
||||||
|
}
|
||||||
14
src/lib/diagnostics/types.ts
Normal file
14
src/lib/diagnostics/types.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export type Level = 'error' | 'warning'
|
||||||
|
|
||||||
|
export interface Diagnostic {
|
||||||
|
level: Level
|
||||||
|
collection: string
|
||||||
|
id: string | null
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Diagnostics {
|
||||||
|
list: Diagnostic[]
|
||||||
|
error(collection: string, id: string | null, message: string): void
|
||||||
|
warning(collection: string, id: string | null, message: string): void
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user