11import matter from "gray-matter"
22
3- // ─── New API ────────────────────────────────────────────────────────────────
4-
53export interface SourceFrontMatter {
64 slug : string
75 title : string
8- "chapter-title" ?: string
9- "intro-title" ?: string
106 description ?: string
117 index : number
128 "is-advanced" ?: boolean
@@ -17,70 +13,96 @@ export interface TranslationFrontMatter {
1713 translates : string
1814 "translated-from-revision" : string
1915 title ?: string
20- "chapter-title" ?: string
21- "intro-title" ?: string
2216 description ?: string
2317 banner ?: { src : string ; alt ?: string }
2418}
2519
26- interface ParseSourceFrontMatterOptions {
27- allowTitlelessFolder ?: boolean
20+ export interface SourceReadmeFrontMatter {
21+ slug : string
22+ "chapter-title" : string
23+ "intro-title" ?: string
24+ index : number
2825}
2926
30- // ─── Allowed / legacy key sets ─────────────────────────────────────────────
27+ export interface TranslationReadmeFrontMatter {
28+ translates : string
29+ "translated-from-revision" : string
30+ "chapter-title" : string
31+ "intro-title" ?: string
32+ }
33+
34+ type BannerFrontMatter = { src : string ; alt ?: string }
3135
3236const SOURCE_ALLOWED_KEYS = new Set ( [
3337 "slug" ,
3438 "title" ,
35- "chapter-title" ,
36- "intro-title" ,
3739 "description" ,
3840 "index" ,
3941 "is-advanced" ,
4042 "banner" ,
41- "author" ,
4243] )
4344
4445const TRANSLATION_ALLOWED_KEYS = new Set ( [
4546 "translates" ,
4647 "translated-from-revision" ,
4748 "title" ,
48- "chapter-title" ,
49- "intro-title" ,
5049 "description" ,
5150 "banner" ,
5251] )
5352
54- const LEGACY_KEYS = new Set ( [
55- "title-en" ,
56- "chapter-title-en" ,
57- "intro-title-en" ,
58- "date" ,
59- "lastmod" ,
60- "co-authors" ,
53+ const SOURCE_README_ALLOWED_KEYS = new Set ( [
54+ "slug" ,
55+ "chapter-title" ,
56+ "intro-title" ,
57+ "index" ,
6158] )
6259
63- // ─── Helpers ────────────────────────────────────────────────────────────────
60+ const TRANSLATION_README_ALLOWED_KEYS = new Set ( [
61+ "translates" ,
62+ "translated-from-revision" ,
63+ "chapter-title" ,
64+ "intro-title" ,
65+ ] )
6466
65- function checkLegacyKeys ( data : Record < string , unknown > ) : void {
66- for ( const key of Object . keys ( data ) ) {
67- if ( LEGACY_KEYS . has ( key ) ) {
68- throw new Error ( `legacy key '${ key } ' not allowed` )
69- }
70- }
71- }
67+ // ─── Helpers ────────────────────────────────────────────────────────────────
7268
7369function checkAdditionalProperties (
7470 data : Record < string , unknown > ,
7571 allowedKeys : Set < string >
7672) : void {
7773 for ( const key of Object . keys ( data ) ) {
78- if ( ! allowedKeys . has ( key ) && ! LEGACY_KEYS . has ( key ) ) {
74+ if ( ! allowedKeys . has ( key ) ) {
7975 throw new Error ( `unknown key '${ key } ' not allowed` )
8076 }
8177 }
8278}
8379
80+ function parseRequiredString (
81+ data : Record < string , unknown > ,
82+ key : string
83+ ) : string {
84+ const value = data [ key ]
85+ if ( typeof value !== "string" ) {
86+ throw new Error ( `missing required key '${ key } '` )
87+ }
88+ return value
89+ }
90+
91+ function parseRequiredNonEmptyString (
92+ data : Record < string , unknown > ,
93+ key : string
94+ ) : string {
95+ const value = parseRequiredString ( data , key )
96+ if ( value === "" ) {
97+ throw new Error ( `missing required key '${ key } '` )
98+ }
99+ return value
100+ }
101+
102+ function parseOptionalString ( value : unknown ) : string | undefined {
103+ return typeof value === "string" ? value : undefined
104+ }
105+
84106function parseIndex ( value : unknown ) : number {
85107 if ( typeof value === "number" && Number . isInteger ( value ) ) {
86108 return value
@@ -96,7 +118,7 @@ function parseIndex(value: unknown): number {
96118
97119function parseBanner (
98120 value : unknown
99- ) : { src : string ; alt ?: string } | undefined {
121+ ) : BannerFrontMatter | undefined {
100122 if ( typeof value !== "object" || value === null ) return undefined
101123 const obj = value as Record < string , unknown >
102124 if ( typeof obj . src !== "string" ) return undefined
@@ -108,36 +130,19 @@ function parseBanner(
108130
109131// ─── Parsers ────────────────────────────────────────────────────────────────
110132
111- export function parseSourceFrontMatter (
112- content : string ,
113- options : ParseSourceFrontMatterOptions = { }
114- ) : SourceFrontMatter {
133+ function parseFrontMatterData ( content : string ) : Record < string , unknown > {
115134 const { data } = matter ( content )
116- const raw = data as Record < string , unknown >
135+ return data as Record < string , unknown >
136+ }
117137
118- checkLegacyKeys ( raw )
138+ export function parseSourceFrontMatter ( content : string ) : SourceFrontMatter {
139+ const raw = parseFrontMatterData ( content )
119140 checkAdditionalProperties ( raw , SOURCE_ALLOWED_KEYS )
120141
121- if ( typeof raw . slug !== "string" || raw . slug === "" ) {
122- throw new Error ( "missing required key 'slug'" )
123- }
124- const title = typeof raw . title === "string" ? raw . title : ""
125-
126- if ( ! options . allowTitlelessFolder && title === "" ) {
127- throw new Error ( "missing required key 'title'" )
128- }
129-
130142 return {
131- slug : raw . slug ,
132- title,
133- "chapter-title" :
134- typeof raw [ "chapter-title" ] === "string"
135- ? raw [ "chapter-title" ]
136- : undefined ,
137- "intro-title" :
138- typeof raw [ "intro-title" ] === "string" ? raw [ "intro-title" ] : undefined ,
139- description :
140- typeof raw . description === "string" ? raw . description : undefined ,
143+ slug : parseRequiredNonEmptyString ( raw , "slug" ) ,
144+ title : parseRequiredNonEmptyString ( raw , "title" ) ,
145+ description : parseOptionalString ( raw . description ) ,
141146 index : parseIndex ( raw . index ) ,
142147 "is-advanced" : raw [ "is-advanced" ] === true ? true : undefined ,
143148 banner : parseBanner ( raw . banner ) ,
@@ -147,34 +152,48 @@ export function parseSourceFrontMatter(
147152export function parseTranslationFrontMatter (
148153 content : string
149154) : TranslationFrontMatter {
150- const { data } = matter ( content )
151- const raw = data as Record < string , unknown >
152-
153- checkLegacyKeys ( raw )
155+ const raw = parseFrontMatterData ( content )
154156 checkAdditionalProperties ( raw , TRANSLATION_ALLOWED_KEYS )
155157
156- if ( typeof raw . translates !== "string" || raw . translates === "" ) {
157- throw new Error ( "missing required key 'translates'" )
158+ return {
159+ translates : parseRequiredNonEmptyString ( raw , "translates" ) ,
160+ "translated-from-revision" : parseRequiredNonEmptyString (
161+ raw ,
162+ "translated-from-revision"
163+ ) ,
164+ title : parseOptionalString ( raw . title ) ,
165+ description : parseOptionalString ( raw . description ) ,
166+ banner : parseBanner ( raw . banner ) ,
158167 }
159- if (
160- typeof raw [ "translated-from-revision" ] !== "string" ||
161- raw [ "translated-from-revision" ] === ""
162- ) {
163- throw new Error ( "missing required key 'translated-from-revision'" )
168+ }
169+
170+ export function parseSourceReadmeFrontMatter (
171+ content : string
172+ ) : SourceReadmeFrontMatter {
173+ const raw = parseFrontMatterData ( content )
174+ checkAdditionalProperties ( raw , SOURCE_README_ALLOWED_KEYS )
175+
176+ return {
177+ slug : parseRequiredString ( raw , "slug" ) ,
178+ "chapter-title" : parseRequiredNonEmptyString ( raw , "chapter-title" ) ,
179+ "intro-title" : parseOptionalString ( raw [ "intro-title" ] ) ,
180+ index : parseIndex ( raw . index ) ,
164181 }
182+ }
183+
184+ export function parseTranslationReadmeFrontMatter (
185+ content : string
186+ ) : TranslationReadmeFrontMatter {
187+ const raw = parseFrontMatterData ( content )
188+ checkAdditionalProperties ( raw , TRANSLATION_README_ALLOWED_KEYS )
165189
166190 return {
167- translates : raw . translates ,
168- "translated-from-revision" : raw [ "translated-from-revision" ] ,
169- title : typeof raw . title === "string" ? raw . title : undefined ,
170- "chapter-title" :
171- typeof raw [ "chapter-title" ] === "string"
172- ? raw [ "chapter-title" ]
173- : undefined ,
174- "intro-title" :
175- typeof raw [ "intro-title" ] === "string" ? raw [ "intro-title" ] : undefined ,
176- description :
177- typeof raw . description === "string" ? raw . description : undefined ,
178- banner : parseBanner ( raw . banner ) ,
191+ translates : parseRequiredNonEmptyString ( raw , "translates" ) ,
192+ "translated-from-revision" : parseRequiredNonEmptyString (
193+ raw ,
194+ "translated-from-revision"
195+ ) ,
196+ "chapter-title" : parseRequiredNonEmptyString ( raw , "chapter-title" ) ,
197+ "intro-title" : parseOptionalString ( raw [ "intro-title" ] ) ,
179198 }
180199}
0 commit comments