1- import { writeFile , readFile , copyFile } from 'node:fs/promises'
1+ import { cp , readFile , writeFile } from 'node:fs/promises'
22import { dirname , join } from 'node:path'
33import { fileURLToPath } from 'node:url'
4+ // biome-ignore lint/nursery/noUndeclaredDependencies: <explanation>
5+ import markdownit from 'markdown-it'
46import { mkdirp } from 'mkdirp'
57import slugify from 'slugify'
68import { parse } from 'yaml'
7- import quotes , { type Quote , type Author , type AuthorDescription , type RawQuote , type AuthorWithQuotes } from '../src/quotes.js'
89
9- const REPO_URL = 'https://github.com/FullStackBulletin/tech-quotes'
10- const GH_PAGES_URL = 'https://fullStackbulletin.github.io/tech-quotes'
10+ const slug = slugify . default
11+ const md = markdownit ( )
12+ const REPO_URL = 'https://github.com/FullStackBulletin/fullstack-books'
13+ const GH_PAGES_URL = 'https://fullStackbulletin.github.io/fullstack-books'
1114const baseUrl = process . env . BASE_URL ?? GH_PAGES_URL
1215
13- const currentDir = dirname ( fileURLToPath ( import . meta. url ) )
14- const destPath : string = join ( currentDir , '..' , 'dist' )
15- const quotesPath : string = join ( currentDir , '..' , 'dist' , 'quotes' )
16- const authorsPath : string = join ( currentDir , '..' , 'dist' , 'authors' )
16+ const __dirname = dirname ( fileURLToPath ( import . meta. url ) )
17+ const destPath = join ( __dirname , '..' , 'dist' )
18+ const booksPath = join ( __dirname , '..' , 'dist' , 'books' )
19+ const authorsPath = join ( __dirname , '..' , 'dist' , 'authors' )
20+ const srcCoversPath = join ( __dirname , '..' , 'src' , 'covers' )
21+ const coversPath = join ( __dirname , '..' , 'dist' , 'covers' )
1722
1823// Creates the `dest` and `dest/quotes` folders if they don't exist
19- await Promise . all ( [
20- mkdirp ( destPath ) ,
21- mkdirp ( quotesPath ) ,
22- mkdirp ( authorsPath )
23- ] )
24+ await Promise . all ( [ mkdirp ( destPath ) , mkdirp ( booksPath ) , mkdirp ( authorsPath ) ] )
25+ await cp ( srcCoversPath , coversPath )
26+ console . log ( `Copied ${ srcCoversPath } to ${ coversPath } ` )
27+
28+ // load and parse raw data from source file
29+ const sourcePath = join ( __dirname , '..' , 'src' , 'books.yml' )
30+ const rawData = parse ( await readFile ( sourcePath , 'utf-8' ) )
31+
32+ function mapAuthor ( author : string ) {
33+ const authorSlug = slug ( author , {
34+ lower : true ,
35+ strict : true ,
36+ } )
37+
38+ return {
39+ name : author ,
40+ slug : authorSlug ,
41+ url : `${ baseUrl } /authors/authorSlug.json` ,
42+ }
43+ }
44+
45+ const books = rawData . map (
46+ ( book : {
47+ title : string
48+ description : string
49+ authors : string [ ]
50+ cover : string
51+ } ) => {
52+ return {
53+ ...book ,
54+ url : `${ baseUrl } /books/${ slug ( book . title ) } .json` ,
55+ cover : `${ baseUrl } /covers/${ book . cover } ` ,
56+ descriptionHtml : md ( book . description ) ,
57+ authors : book . authors . map ( mapAuthor ) ,
58+ }
59+ } ,
60+ )
61+
62+ const authors = [ ]
63+ const booksByAuthor : Record < string , any > = { }
64+ for ( const book of books ) {
65+ for ( const author of book . authors ) {
66+ if ( ! booksByAuthor [ author . slug ] ) {
67+ booksByAuthor [ author . slug ] = [ ]
68+ authors . push ( author )
69+ }
70+ booksByAuthor [ author . slug ] . push ( book )
71+ }
72+ }
2473
2574// Creates an index.html file that redirects to the GitHub repo
2675const index = `<html>
@@ -41,118 +90,57 @@ const fourOhFour = `<html>
4190 <body>Not found</body>
4291</html>`
4392
93+ // create books files
4494await writeFile ( `${ destPath } /404.html` , fourOhFour )
4595console . log ( `Written ${ destPath } /404.html` )
4696
47- const stats = {
48- total : quotes . length ,
49- all : `${ baseUrl } /quotes/all.json` ,
50- first : `${ baseUrl } /quotes/0.json` ,
51- last : `${ baseUrl } /quotes/${ quotes . length - 1 } .json` ,
52- urlPrefix : `${ baseUrl } /quotes`
53- }
54-
55- await writeFile ( `${ quotesPath } /stats.json` , JSON . stringify ( stats , null , 2 ) )
56- console . log ( `Written ${ quotesPath } /stats.json` )
57-
58- // Creates a JSON file for each quote and an all.json file with all the quotes
59- function mapQuote ( id : string , quote : RawQuote ) : Quote {
60- const idAsNumber = Number ( id )
61- return {
62- id : idAsNumber ,
63- text : quote . text ,
64- author : makeAuthor ( quote . authorName , quote . authorDescription , quote . authorWiki ) ,
65- url : `${ baseUrl } /quotes/${ idAsNumber } .json`
66- }
67- }
68-
69- function makeAuthor ( name : string , description : AuthorDescription , wiki ?: `https://en.wikipedia.org/wiki/${string } `) : Author {
70- const slug = slugify . default ( name , { lower : true , strict : true } )
71-
72- return {
73- id : slug ,
74- name,
75- description,
76- wiki,
77- url : `${ baseUrl } /authors/${ slug } .json`
78- }
79- }
80-
81- function removeAuthorFromQuote ( quote : Quote ) : Omit < Quote , 'author' > {
82- const { author, ...rest } = quote
83- return rest
84- }
85-
86- const all = {
87- metadata : {
88- total : quotes . length ,
89- first : 0 ,
90- last : quotes . length - 1
91- } ,
92- quotes : Object . entries ( quotes ) . map ( ( [ id , quote ] ) => mapQuote ( id , quote ) )
97+ const bookStats = {
98+ total : books . length ,
99+ all : `${ baseUrl } /books/all.json` ,
100+ ids : `${ baseUrl } /books/ids.json` ,
101+ urlPrefix : `${ baseUrl } /books` ,
93102}
94103
95- await writeFile ( `${ quotesPath } /all.json` , JSON . stringify ( all , null , 2 ) )
96- console . log ( `Written ${ quotesPath } /all.json` )
97-
98- // As it goes through the various quotes starts to accumulate authors and quotes
99- const authorsWithQuotes = new Map < string , AuthorWithQuotes > ( )
104+ await writeFile ( `${ booksPath } /stats.json` , JSON . stringify ( bookStats , null , 2 ) )
105+ console . log ( `Written ${ booksPath } /stats.json` )
100106
101- for ( const quote of all . quotes ) {
102- const dest = join ( quotesPath , `${ String ( quote . id ) } .json` )
103- await writeFile ( dest , JSON . stringify ( quote , null , 2 ) )
104-
105- const authorEntry = authorsWithQuotes . get ( quote . author . id )
106- if ( typeof authorEntry !== 'undefined' ) {
107- authorEntry . quotes . push ( removeAuthorFromQuote ( quote ) )
108- } else {
109- authorsWithQuotes . set ( quote . author . id , {
110- ...quote . author ,
111- quotes : [ removeAuthorFromQuote ( quote ) ]
112- } )
113- }
107+ const bookIds = books . map ( ( book : { slug : string } ) => book . slug )
108+ await writeFile ( `${ booksPath } /ids.json` , JSON . stringify ( bookIds , null , 2 ) )
109+ console . log ( `Written ${ booksPath } /ids.json` )
114110
115- console . log ( `Written ${ String ( dest ) } ` )
116- }
111+ await writeFile ( ` ${ booksPath } /all.json` , JSON . stringify ( books , null , 2 ) )
112+ console . log ( `Written ${ booksPath } /all.json` )
117113
118- // persists authors
119- let totalAuthors = 0
120- for ( const author of authorsWithQuotes . values ( ) ) {
121- const dest = join ( authorsPath , `${ author . id } .json` )
122- await writeFile ( dest , JSON . stringify ( author , null , 2 ) )
123- console . log ( `Written ${ String ( dest ) } ` )
124- totalAuthors ++
114+ for ( const book of books ) {
115+ const dest = join ( booksPath , `${ book . slug } .json` )
116+ await writeFile ( dest , JSON . stringify ( book , null , 2 ) )
117+ console . log ( `Written ${ dest } ` )
125118}
126119
127- // creates stats.json for authors
128- const authorsStats = {
129- total : totalAuthors ,
120+ // create authors files
121+ const authorStats = {
122+ total : authors . length ,
130123 all : `${ baseUrl } /authors/all.json` ,
131- urlPrefix : `${ baseUrl } /authors`
124+ ids : `${ baseUrl } /authors/ids.json` ,
125+ urlPrefix : `${ baseUrl } /authors` ,
132126}
133127
134- await writeFile ( `${ authorsPath } /stats.json` , JSON . stringify ( authorsStats , null , 2 ) )
128+ await writeFile (
129+ `${ authorsPath } /stats.json` ,
130+ JSON . stringify ( authorStats , null , 2 ) ,
131+ )
135132console . log ( `Written ${ authorsPath } /stats.json` )
136133
137- // Create all.json for authors
138- const allAuthors = {
139- metadata : {
140- total : totalAuthors
141- } ,
142- authors : [ ...authorsWithQuotes . keys ( ) ]
143- }
134+ const authorIds = authors . map ( ( author : { slug : string } ) => author . slug )
135+ await writeFile ( `${ authorsPath } /ids.json` , JSON . stringify ( authorIds , null , 2 ) )
136+ console . log ( `Written ${ destPath } /ids.json` )
144137
145- await writeFile ( `${ authorsPath } /all.json` , JSON . stringify ( allAuthors , null , 2 ) )
138+ await writeFile ( `${ authorsPath } /all.json` , JSON . stringify ( authors , null , 2 ) )
146139console . log ( `Written ${ authorsPath } /all.json` )
147140
148- // copy open api file and converts it to json
149- const openApiPath = join ( currentDir , '..' , 'src' , 'openapi.yml' )
150- const openApiDestYaml = join ( destPath , 'openapi.yml' )
151- await copyFile ( openApiPath , openApiDestYaml )
152- console . log ( `Written ${ openApiDestYaml } ` )
153-
154- const openApiYaml = await readFile ( openApiPath , 'utf-8' )
155- const openApiJson = parse ( openApiYaml )
156- const openApiDestJson = join ( destPath , 'openapi.json' )
157- await writeFile ( openApiDestJson , JSON . stringify ( openApiJson , null , 2 ) )
158- console . log ( `Written ${ openApiDestJson } ` )
141+ for ( const author of authors ) {
142+ const authorWithBooks = { ...author , books : booksByAuthor [ author . slug ] }
143+ const dest = join ( authorsPath , `${ author . slug } .json` )
144+ await writeFile ( dest , JSON . stringify ( authorWithBooks , null , 2 ) )
145+ console . log ( `Written ${ dest } ` )
146+ }
0 commit comments