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