1- import fs from 'fs' ;
2- import path from 'path' ;
3- import { fileURLToPath } from 'url' ;
1+ import fs from 'node: fs' ;
2+ import path from 'node: path' ;
3+ import { fileURLToPath } from 'node: url' ;
44import { resources } from '../src/data/resources.js' ;
55import { EU_COUNTRIES } from '../src/data/constants.js' ;
66
@@ -17,136 +17,136 @@ function generateStatsMarkdown() {
1717
1818 const resourcesByCountry = resources . reduce ( ( acc , resource ) => {
1919 if ( resource . countries && resource . countries . length > 0 ) {
20- resource . countries . forEach ( country => {
20+ for ( const country of resource . countries ) {
2121 const countryName = country === 'European Union' || EU_COUNTRIES . includes ( country ) ? 'European Union' : country ;
2222 acc [ countryName ] = ( acc [ countryName ] || 0 ) + 1 ;
23- } ) ;
23+ }
2424 } else {
2525 acc [ 'Worldwide' ] = ( acc [ 'Worldwide' ] || 0 ) + 1 ;
2626 }
2727 return acc ;
2828 } , { } ) ;
2929
3030 const resourcesByDataType = resources . reduce ( ( acc , resource ) => {
31- ( resource . dataTypes || [ ] ) . forEach ( type => {
32- const baseType = type . startsWith ( 'Wearable data' ) ? 'Wearable data' : type ;
33- acc [ baseType ] = ( acc [ baseType ] || 0 ) + 1 ;
34- } ) ;
35- return acc ;
31+ for ( const type of ( resource . dataTypes || [ ] ) ) {
32+ const baseType = type . startsWith ( 'Wearable data' ) ? 'Wearable data' : type ;
33+ acc [ baseType ] = ( acc [ baseType ] || 0 ) + 1 ;
34+ }
35+ return acc ;
3636 } , { } ) ;
3737
3838 const resourcesByCompensation = resources . reduce ( ( acc , resource ) => {
39- const type = resource . compensationType || 'donation' ;
40- acc [ type ] = ( acc [ type ] || 0 ) + 1 ;
41- return acc ;
39+ const type = resource . compensationType || 'donation' ;
40+ acc [ type ] = ( acc [ type ] || 0 ) + 1 ;
41+ return acc ;
4242 } , { } ) ;
4343
4444 const resourcesByEntityType = resources . reduce ( ( acc , resource ) => {
45- const category = resource . entityCategory || 'Other' ;
46- const subType = resource . entitySubType || 'Not Specified' ;
45+ const category = resource . entityCategory || 'Other' ;
46+ const subType = resource . entitySubType || 'Not Specified' ;
4747
48- if ( ! acc [ category ] ) {
49- acc [ category ] = { count : 0 , subTypes : { } } ;
50- }
51- acc [ category ] . count ++ ;
52- acc [ category ] . subTypes [ subType ] = ( acc [ category ] . subTypes [ subType ] || 0 ) + 1 ;
53- return acc ;
48+ if ( ! acc [ category ] ) {
49+ acc [ category ] = { count : 0 , subTypes : { } } ;
50+ }
51+ acc [ category ] . count ++ ;
52+ acc [ category ] . subTypes [ subType ] = ( acc [ category ] . subTypes [ subType ] || 0 ) + 1 ;
53+ return acc ;
5454 } , { } ) ;
55-
55+
5656 const topCountries = Object . entries ( resourcesByCountry )
57- . sort ( ( [ , a ] , [ , b ] ) => b - a )
58- . slice ( 0 , 10 ) ;
57+ . sort ( ( [ , a ] , [ , b ] ) => b - a )
58+ . slice ( 0 , 10 ) ;
5959
6060 const dataTypesDistribution = Object . entries ( resourcesByDataType )
61- . sort ( ( [ , a ] , [ , b ] ) => b - a ) ;
61+ . sort ( ( [ , a ] , [ , b ] ) => b - a ) ;
6262
6363 const compensationDistribution = Object . entries ( resourcesByCompensation )
64- . sort ( ( [ , a ] , [ , b ] ) => b - a ) ;
65-
64+ . sort ( ( [ , a ] , [ , b ] ) => b - a ) ;
65+
6666 const entityTypeDistribution = Object . entries ( resourcesByEntityType )
67- . map ( ( [ name , data ] ) => ( {
68- name,
69- count : data . count ,
70- subTypes : Object . entries ( data . subTypes ) . sort ( ( [ , a ] , [ , b ] ) => b - a ) ,
71- } ) )
72- . sort ( ( a , b ) => b . count - a . count ) ;
67+ . map ( ( [ name , data ] ) => ( {
68+ name,
69+ count : data . count ,
70+ subTypes : Object . entries ( data . subTypes ) . sort ( ( [ , a ] , [ , b ] ) => b - a ) ,
71+ } ) )
72+ . sort ( ( a , b ) => b . count - a . count ) ;
7373
7474 const euBreakdownStats = Object . entries ( resources . reduce ( ( acc , resource ) => {
75- if ( resource . countries ) {
76- resource . countries . forEach ( country => {
77- if ( country === 'European Union' ) {
78- acc [ 'EU-Wide' ] = ( acc [ 'EU-Wide' ] || 0 ) + 1 ;
79- } else if ( EU_COUNTRIES . includes ( country ) ) {
80- acc [ country ] = ( acc [ country ] || 0 ) + 1 ;
81- }
82- } ) ;
75+ if ( resource . countries ) {
76+ for ( const country of resource . countries ) {
77+ if ( country === 'European Union' ) {
78+ acc [ 'EU-Wide' ] = ( acc [ 'EU-Wide' ] || 0 ) + 1 ;
79+ } else if ( EU_COUNTRIES . includes ( country ) ) {
80+ acc [ country ] = ( acc [ country ] || 0 ) + 1 ;
81+ }
8382 }
84- return acc ;
83+ }
84+ return acc ;
8585 } , { } ) ) . sort ( ( [ , a ] , [ , b ] ) => b - a ) ;
8686
8787
8888 // --- Generate Markdown Content ---
8989
9090 let mdContent = `# Project Statistics\n\n` ;
9191 mdContent += `An overview of the resources available on Yourself to Science, providing insights into the landscape of citizen science contribution.\n\n` ;
92-
92+
9393 mdContent += `## Overview\n\n` ;
9494 mdContent += `- **Total Resources:** ${ totalResources } \n\n` ;
9595
9696 mdContent += `## Compensation Types\n\n` ;
97- compensationDistribution . forEach ( ( [ type , count ] ) => {
97+ for ( const [ type , count ] of compensationDistribution ) {
9898 mdContent += `- **${ type . charAt ( 0 ) . toUpperCase ( ) + type . slice ( 1 ) } :** ${ count } \n` ;
99- } ) ;
99+ }
100100 mdContent += `\n` ;
101101
102102 mdContent += `## Top 10 Service Availability by Country\n\n` ;
103- topCountries . forEach ( ( [ country , count ] ) => {
103+ for ( const [ country , count ] of topCountries ) {
104104 mdContent += `- **${ country } :** ${ count } \n` ;
105105 if ( country === 'European Union' ) {
106106 mdContent += ` - **Breakdown:**\n` ;
107- euBreakdownStats . forEach ( ( [ euCountry , euCount ] ) => {
107+ for ( const [ euCountry , euCount ] of euBreakdownStats ) {
108108 mdContent += ` - **${ euCountry } :** ${ euCount } \n` ;
109- } ) ;
109+ }
110110 }
111- } ) ;
111+ }
112112 mdContent += `\n` ;
113113
114114 mdContent += `## Data Type Distribution\n\n` ;
115- dataTypesDistribution . forEach ( ( [ type , count ] ) => {
115+ for ( const [ type , count ] of dataTypesDistribution ) {
116116 mdContent += `- **${ type } :** ${ count } \n` ;
117- } ) ;
117+ }
118118 mdContent += `\n` ;
119-
119+
120120 mdContent += `## Entity Type Distribution\n\n` ;
121- entityTypeDistribution . forEach ( entity => {
122- mdContent += `- **${ entity . name } :** ${ entity . count } \n` ;
123- if ( entity . subTypes . length > 1 ) {
124- entity . subTypes . forEach ( ( [ subType , subCount ] ) => {
125- mdContent += ` - **${ subType } :** ${ subCount } \n` ;
126- } ) ;
121+ for ( const entity of entityTypeDistribution ) {
122+ mdContent += `- **${ entity . name } :** ${ entity . count } \n` ;
123+ if ( entity . subTypes . length > 1 ) {
124+ for ( const [ subType , subCount ] of entity . subTypes ) {
125+ mdContent += ` - **${ subType } :** ${ subCount } \n` ;
127126 }
128- } ) ;
127+ }
128+ }
129129 mdContent += `\n` ;
130130
131131 mdContent += `## Live Data Access\n\n` ;
132132 mdContent += `Use these persistent URLs for automated access to always get the latest version of the dataset.\n\n` ;
133133 mdContent += `- **CSV Endpoint:** https://yourselftoscience.org/resources.csv\n` ;
134134 mdContent += `- **JSON Endpoint:** https://yourselftoscience.org/resources.json\n` ;
135-
135+
136136 const outputPath = path . join ( __dirname , '../public/stats.md' ) ;
137137 fs . writeFileSync ( outputPath , mdContent ) ;
138138 console . log ( `Successfully generated ${ outputPath } ` ) ;
139139}
140140
141141function generateHomepageMarkdown ( ) {
142142 console . log ( 'Generating index.html.md...' ) ;
143-
143+
144144 let mdContent = `# Yourself to Science: Contribute to Science\n\n` ;
145- mdContent += `YourselfToScience.org is an open-source website providing a comprehensive list of services that allow individuals to contribute to scientific research with their data, genome, body, and more.\n\n` ;
145+ mdContent += `Yourself to Science™ is an open-source project providing a comprehensive list of services that allow individuals to contribute to scientific research with their data, genome, body, and more.\n\n` ;
146146 mdContent += `This page provides a filterable list of all resources. You can also download the full dataset as CSV or JSON.\n\n` ;
147147 mdContent += `## All Resources\n\n` ;
148148
149- resources . forEach ( resource => {
149+ for ( const [ index , resource ] of resources . entries ( ) ) {
150150 mdContent += `### [${ resource . title } ](https://yourselftoscience.org/resource/${ resource . slug } )\n\n` ;
151151 mdContent += `*Description:* ${ resource . description } \n\n` ;
152152 if ( resource . dataTypes ) {
@@ -158,72 +158,80 @@ function generateHomepageMarkdown() {
158158 if ( resource . compensationType ) {
159159 mdContent += `*Compensation:* ${ resource . compensationType . charAt ( 0 ) . toUpperCase ( ) + resource . compensationType . slice ( 1 ) } \n\n` ;
160160 }
161- mdContent += `---\n\n` ;
162- } ) ;
161+ // Add separator only if it's not the last item
162+ if ( index < resources . length - 1 ) {
163+ mdContent += `---\n\n` ;
164+ } else {
165+ mdContent += `---` ; // End with separator but no extra newlines
166+ }
167+ }
168+
169+ // Ensure single trailing newline
170+ mdContent += '\n' ;
163171
164172 const outputPath = path . join ( __dirname , '../public/index.html.md' ) ;
165173 fs . writeFileSync ( outputPath , mdContent ) ;
166174 console . log ( `Successfully generated ${ outputPath } ` ) ;
167175}
168176
169177function generateClinicalTrialsMarkdown ( ) {
170- console . log ( 'Generating clinical-trials.md...' ) ;
171- const clinicalTrialResources = resources . filter ( r => r . dataTypes . includes ( 'Clinical trials' ) ) ;
172-
173- let mdContent = `# Clinical Trials\n\n` ;
174- mdContent += `This page lists resources related to contributing to clinical trials.\n\n` ;
175- mdContent += `## Clinical Trial Resources\n\n` ;
176-
177- clinicalTrialResources . forEach ( resource => {
178- mdContent += `### [${ resource . title } ](https://yourselftoscience.org/resource/${ resource . slug } )\n\n` ;
179- mdContent += `*Description:* ${ resource . description } \n\n` ;
180- if ( resource . dataTypes ) {
181- mdContent += `*Data Types:* ${ resource . dataTypes . join ( ', ' ) } \n\n` ;
182- }
183- if ( resource . countries ) {
184- mdContent += `*Countries:* ${ resource . countries . join ( ', ' ) } \n\n` ;
185- }
186- if ( resource . compensationType ) {
187- mdContent += `*Compensation:* ${ resource . compensationType . charAt ( 0 ) . toUpperCase ( ) + resource . compensationType . slice ( 1 ) } \n\n` ;
188- }
189- mdContent += `---\n\n` ;
190- } ) ;
178+ console . log ( 'Generating clinical-trials.md...' ) ;
179+ const clinicalTrialResources = resources . filter ( r => r . dataTypes . includes ( 'Clinical trials' ) ) ;
180+
181+ let mdContent = `# Clinical Trials\n\n` ;
182+ mdContent += `This page lists resources related to contributing to clinical trials.\n\n` ;
183+ mdContent += `## Clinical Trial Resources\n\n` ;
184+
185+ for ( const resource of clinicalTrialResources ) {
186+ mdContent += `### [${ resource . title } ](https://yourselftoscience.org/resource/${ resource . slug } )\n\n` ;
187+ mdContent += `*Description:* ${ resource . description } \n\n` ;
188+ if ( resource . dataTypes ) {
189+ mdContent += `*Data Types:* ${ resource . dataTypes . join ( ', ' ) } \n\n` ;
190+ }
191+ if ( resource . countries ) {
192+ mdContent += `*Countries:* ${ resource . countries . join ( ', ' ) } \n\n` ;
193+ }
194+ if ( resource . compensationType ) {
195+ mdContent += `*Compensation:* ${ resource . compensationType . charAt ( 0 ) . toUpperCase ( ) + resource . compensationType . slice ( 1 ) } \n\n` ;
196+ }
197+ mdContent += `---\n\n` ;
198+ }
191199
192- const outputPath = path . join ( __dirname , '../public/clinical-trials.md' ) ;
193- fs . writeFileSync ( outputPath , mdContent ) ;
194- console . log ( `Successfully generated ${ outputPath } ` ) ;
200+ const outputPath = path . join ( __dirname , '../public/clinical-trials.md' ) ;
201+ fs . writeFileSync ( outputPath , mdContent ) ;
202+ console . log ( `Successfully generated ${ outputPath } ` ) ;
195203}
196204
197205function generateOrganBodyTissueDonationMarkdown ( ) {
198- console . log ( 'Generating organ-body-tissue-donation.md...' ) ;
199- const donationResources = resources . filter ( r =>
200- r . dataTypes . includes ( 'Organ' ) ||
201- r . dataTypes . includes ( 'Body' ) ||
202- r . dataTypes . includes ( 'Tissue' )
203- ) ;
204-
205- let mdContent = `# Organ, Body & Tissue Donation\n\n` ;
206- mdContent += `This page lists resources related to donating organs, bodies, or tissues for scientific research.\n\n` ;
207- mdContent += `## Organ, Body & Tissue Donation Resources\n\n` ;
208-
209- donationResources . forEach ( resource => {
210- mdContent += `### [${ resource . title } ](https://yourselftoscience.org/resource/${ resource . slug } )\n\n` ;
211- mdContent += `*Description:* ${ resource . description } \n\n` ;
212- if ( resource . dataTypes ) {
213- mdContent += `*Data Types:* ${ resource . dataTypes . join ( ', ' ) } \n\n` ;
214- }
215- if ( resource . countries ) {
216- mdContent += `*Countries:* ${ resource . countries . join ( ', ' ) } \n\n` ;
217- }
218- if ( resource . compensationType ) {
219- mdContent += `*Compensation:* ${ resource . compensationType . charAt ( 0 ) . toUpperCase ( ) + resource . compensationType . slice ( 1 ) } \n\n` ;
220- }
221- mdContent += `---\n\n` ;
222- } ) ;
206+ console . log ( 'Generating organ-body-tissue-donation.md...' ) ;
207+ const donationResources = resources . filter ( r =>
208+ r . dataTypes . includes ( 'Organ' ) ||
209+ r . dataTypes . includes ( 'Body' ) ||
210+ r . dataTypes . includes ( 'Tissue' )
211+ ) ;
212+
213+ let mdContent = `# Organ, Body & Tissue Donation\n\n` ;
214+ mdContent += `This page lists resources related to donating organs, bodies, or tissues for scientific research.\n\n` ;
215+ mdContent += `## Organ, Body & Tissue Donation Resources\n\n` ;
216+
217+ for ( const resource of donationResources ) {
218+ mdContent += `### [${ resource . title } ](https://yourselftoscience.org/resource/${ resource . slug } )\n\n` ;
219+ mdContent += `*Description:* ${ resource . description } \n\n` ;
220+ if ( resource . dataTypes ) {
221+ mdContent += `*Data Types:* ${ resource . dataTypes . join ( ', ' ) } \n\n` ;
222+ }
223+ if ( resource . countries ) {
224+ mdContent += `*Countries:* ${ resource . countries . join ( ', ' ) } \n\n` ;
225+ }
226+ if ( resource . compensationType ) {
227+ mdContent += `*Compensation:* ${ resource . compensationType . charAt ( 0 ) . toUpperCase ( ) + resource . compensationType . slice ( 1 ) } \n\n` ;
228+ }
229+ mdContent += `---\n\n` ;
230+ }
223231
224- const outputPath = path . join ( __dirname , '../public/organ-body-tissue-donation.md' ) ;
225- fs . writeFileSync ( outputPath , mdContent ) ;
226- console . log ( `Successfully generated ${ outputPath } ` ) ;
232+ const outputPath = path . join ( __dirname , '../public/organ-body-tissue-donation.md' ) ;
233+ fs . writeFileSync ( outputPath , mdContent ) ;
234+ console . log ( `Successfully generated ${ outputPath } ` ) ;
227235}
228236
229237function generateGetInvolvedMarkdown ( ) {
@@ -234,18 +242,18 @@ function generateGetInvolvedMarkdown() {
234242
235243 let mdContent = `# Get Involved\n\n` ;
236244 mdContent += `Our mission is to provide a transparent, accessible, and comprehensive list of services to advance scientific research. This project is built by the community, for the community, and every contribution is incredibly valuable.\n\n` ;
237-
245+
238246 mdContent += `## Join the Discussion\n\n` ;
239247 mdContent += `The best place to start. Suggest new services, share ideas, and get feedback from the community. Perfect for all users.\n\n` ;
240248 mdContent += `[Suggest on Reddit](${ redditSuggestUrl } )\n\n` ;
241-
249+
242250 mdContent += `## Go Direct\n\n` ;
243251 mdContent += `For developers and those comfortable with GitHub. Use our template to add your suggestion directly to our project tracker.\n\n` ;
244252 mdContent += `[Suggest on GitHub](${ githubSuggestUrl } )\n\n` ;
245253
246254 mdContent += `## Contact Us\n\n` ;
247255 mdContent += `Have a question, suggestion, or collaboration idea? We’d love to hear from you. Email us at [[email protected] ](mailto:[email protected] ).\n\n` 248-
256+
249257 mdContent += `Want to help in other ways? Explore our [project repository](https://github.com/yourselftoscience/yourselftoscience.org) for documentation, code, and more.\n` ;
250258
251259 const outputPath = path . join ( __dirname , '../public/get-involved.md' ) ;
@@ -255,11 +263,11 @@ function generateGetInvolvedMarkdown() {
255263
256264
257265function generateAllMarkdown ( ) {
258- generateStatsMarkdown ( ) ;
259- generateHomepageMarkdown ( ) ;
260- generateClinicalTrialsMarkdown ( ) ;
261- generateOrganBodyTissueDonationMarkdown ( ) ;
262- generateGetInvolvedMarkdown ( ) ;
266+ generateStatsMarkdown ( ) ;
267+ generateHomepageMarkdown ( ) ;
268+ generateClinicalTrialsMarkdown ( ) ;
269+ generateOrganBodyTissueDonationMarkdown ( ) ;
270+ generateGetInvolvedMarkdown ( ) ;
263271}
264272
265273generateAllMarkdown ( ) ;
0 commit comments