[READ-ONLY] a fast, modern browser for the npm registry

refactor: use `gray-matter` for parsing frontmatter (#1168)

authored by

Daniel Roe and committed by
GitHub
24094a12 7b624596

+73 -112
+2 -4
modules/standard-site-sync.ts
··· 1 1 import process from 'node:process' 2 - import { readFileSync } from 'node:fs' 3 2 import { createHash } from 'node:crypto' 4 3 import { defineNuxtModule, useNuxt, createResolver } from 'nuxt/kit' 5 4 import { safeParse } from 'valibot' 6 5 import * as site from '../shared/types/lexicons/site' 7 6 import { BlogPostSchema } from '../shared/schemas/blog' 8 7 import { NPMX_SITE } from '../shared/utils/constants' 9 - import { parseBasicFrontmatter } from '../shared/utils/parse-basic-frontmatter' 8 + import { read } from 'gray-matter' 10 9 import { TID } from '@atproto/common' 11 10 import { Client } from '@atproto/lex' 12 11 ··· 77 76 * WARN: DOES NOT CATCH ERRORS, THIS MUST BE HANDLED 78 77 */ 79 78 const syncFile = async (filePath: string, siteUrl: string, client: Client) => { 80 - const fileContent = readFileSync(filePath, 'utf-8') 81 - const frontmatter = parseBasicFrontmatter(fileContent) 79 + const { data: frontmatter } = read(filePath) 82 80 83 81 // Schema expects 'path' & frontmatter provides 'slug' 84 82 const normalizedFrontmatter = {
+1
package.json
··· 81 81 "defu": "6.1.4", 82 82 "fast-npm-meta": "1.0.0", 83 83 "focus-trap": "^7.8.0", 84 + "gray-matter": "4.0.3", 84 85 "marked": "17.0.1", 85 86 "module-replacements": "2.11.0", 86 87 "nuxt": "4.3.0",
+70
pnpm-lock.yaml
··· 131 131 focus-trap: 132 132 specifier: ^7.8.0 133 133 version: 7.8.0 134 + gray-matter: 135 + specifier: 4.0.3 136 + version: 4.0.3 134 137 marked: 135 138 specifier: 17.0.1 136 139 version: 17.0.1 ··· 4688 4691 resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} 4689 4692 engines: {node: '>= 14'} 4690 4693 4694 + argparse@1.0.10: 4695 + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} 4696 + 4691 4697 argparse@2.0.1: 4692 4698 resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 4693 4699 ··· 5722 5728 exsolve@1.0.8: 5723 5729 resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} 5724 5730 5731 + extend-shallow@2.0.1: 5732 + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} 5733 + engines: {node: '>=0.10.0'} 5734 + 5725 5735 extend@3.0.2: 5726 5736 resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} 5727 5737 ··· 6043 6053 graceful-fs@4.2.11: 6044 6054 resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 6045 6055 6056 + gray-matter@4.0.3: 6057 + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} 6058 + engines: {node: '>=6.0'} 6059 + 6046 6060 gzip-size@6.0.0: 6047 6061 resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} 6048 6062 engines: {node: '>=10'} ··· 6359 6373 engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 6360 6374 hasBin: true 6361 6375 6376 + is-extendable@0.1.1: 6377 + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} 6378 + engines: {node: '>=0.10.0'} 6379 + 6362 6380 is-extglob@2.1.1: 6363 6381 resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 6364 6382 engines: {node: '>=0.10.0'} ··· 6596 6614 js-tokens@9.0.1: 6597 6615 resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} 6598 6616 6617 + js-yaml@3.14.2: 6618 + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} 6619 + hasBin: true 6620 + 6599 6621 js-yaml@4.1.1: 6600 6622 resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} 6601 6623 hasBin: true ··· 6653 6675 6654 6676 keyv@4.5.4: 6655 6677 resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 6678 + 6679 + kind-of@6.0.3: 6680 + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} 6681 + engines: {node: '>=0.10.0'} 6656 6682 6657 6683 kleur@3.0.3: 6658 6684 resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} ··· 8303 8329 scule@1.3.0: 8304 8330 resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} 8305 8331 8332 + section-matter@1.0.0: 8333 + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} 8334 + engines: {node: '>=4'} 8335 + 8306 8336 semver@6.3.1: 8307 8337 resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 8308 8338 hasBin: true ··· 8490 8520 resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} 8491 8521 engines: {node: '>= 10.x'} 8492 8522 8523 + sprintf-js@1.0.3: 8524 + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} 8525 + 8493 8526 srvx@0.10.1: 8494 8527 resolution: {integrity: sha512-A//xtfak4eESMWWydSRFUVvCTQbSwivnGCEf8YGPe2eHU0+Z6znfUTCPF0a7oV3sObSOcrXHlL6Bs9vVctfXdg==} 8495 8528 engines: {node: '>=20.16.0'} ··· 8572 8605 resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} 8573 8606 engines: {node: '>=12'} 8574 8607 8608 + strip-bom-string@1.0.0: 8609 + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} 8610 + engines: {node: '>=0.10.0'} 8611 + 8575 8612 strip-comments@2.0.1: 8576 8613 resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==} 8577 8614 engines: {node: '>=10'} ··· 14675 14712 - bare-abort-controller 14676 14713 - react-native-b4a 14677 14714 14715 + argparse@1.0.10: 14716 + dependencies: 14717 + sprintf-js: 1.0.3 14718 + 14678 14719 argparse@2.0.1: {} 14679 14720 14680 14721 aria-hidden@1.2.6: ··· 15931 15972 15932 15973 exsolve@1.0.8: {} 15933 15974 15975 + extend-shallow@2.0.1: 15976 + dependencies: 15977 + is-extendable: 0.1.1 15978 + 15934 15979 extend@3.0.2: {} 15935 15980 15936 15981 fake-indexeddb@6.2.5: {} ··· 16340 16385 gopd@1.2.0: {} 16341 16386 16342 16387 graceful-fs@4.2.11: {} 16388 + 16389 + gray-matter@4.0.3: 16390 + dependencies: 16391 + js-yaml: 3.14.2 16392 + kind-of: 6.0.3 16393 + section-matter: 1.0.0 16394 + strip-bom-string: 1.0.0 16343 16395 16344 16396 gzip-size@6.0.0: 16345 16397 dependencies: ··· 16772 16824 is-docker@2.2.1: {} 16773 16825 16774 16826 is-docker@3.0.0: {} 16827 + 16828 + is-extendable@0.1.1: {} 16775 16829 16776 16830 is-extglob@2.1.1: {} 16777 16831 ··· 16987 17041 16988 17042 js-tokens@9.0.1: {} 16989 17043 17044 + js-yaml@3.14.2: 17045 + dependencies: 17046 + argparse: 1.0.10 17047 + esprima: 4.0.1 17048 + 16990 17049 js-yaml@4.1.1: 16991 17050 dependencies: 16992 17051 argparse: 2.0.1 ··· 17041 17100 keyv@4.5.4: 17042 17101 dependencies: 17043 17102 json-buffer: 3.0.1 17103 + 17104 + kind-of@6.0.3: {} 17044 17105 17045 17106 kleur@3.0.3: {} 17046 17107 ··· 19641 19702 19642 19703 scule@1.3.0: {} 19643 19704 19705 + section-matter@1.0.0: 19706 + dependencies: 19707 + extend-shallow: 2.0.1 19708 + kind-of: 6.0.3 19709 + 19644 19710 semver@6.3.1: {} 19645 19711 19646 19712 semver@7.7.3: {} ··· 19891 19957 19892 19958 split2@4.2.0: {} 19893 19959 19960 + sprintf-js@1.0.3: {} 19961 + 19894 19962 srvx@0.10.1: {} 19895 19963 19896 19964 standard-as-callback@2.1.0: {} ··· 20005 20073 strip-ansi@7.1.2: 20006 20074 dependencies: 20007 20075 ansi-regex: 6.2.2 20076 + 20077 + strip-bom-string@1.0.0: {} 20008 20078 20009 20079 strip-comments@2.0.1: {} 20010 20080
-108
test/unit/shared/utils/parse-basic-frontmatter.spec.ts
··· 1 - import { describe, expect, it } from 'vitest' 2 - import { parseBasicFrontmatter } from '../../../../shared/utils/parse-basic-frontmatter' 3 - 4 - describe('parseBasicFrontmatter', () => { 5 - it('returns empty object for content without frontmatter', () => { 6 - expect(parseBasicFrontmatter('just some text')).toEqual({}) 7 - }) 8 - 9 - it('returns empty object for empty string', () => { 10 - expect(parseBasicFrontmatter('')).toEqual({}) 11 - }) 12 - 13 - it('returns empty object for empty frontmatter block', () => { 14 - expect(parseBasicFrontmatter('---\n---\n')).toEqual({}) 15 - }) 16 - 17 - it('parses string values', () => { 18 - const input = '---\ntitle: Hello World\nauthor: James\n---\n' 19 - expect(parseBasicFrontmatter(input)).toEqual({ 20 - title: 'Hello World', 21 - author: 'James', 22 - }) 23 - }) 24 - 25 - it('strips surrounding quotes from values', () => { 26 - const input = '---\ntitle: "Hello World"\nauthor: \'James\'\n---\n' 27 - expect(parseBasicFrontmatter(input)).toEqual({ 28 - title: 'Hello World', 29 - author: 'James', 30 - }) 31 - }) 32 - 33 - it('parses boolean true', () => { 34 - const input = '---\ndraft: true\n---\n' 35 - expect(parseBasicFrontmatter(input)).toEqual({ draft: true }) 36 - }) 37 - 38 - it('parses boolean false', () => { 39 - const input = '---\ndraft: false\n---\n' 40 - expect(parseBasicFrontmatter(input)).toEqual({ draft: false }) 41 - }) 42 - 43 - it('parses integer values', () => { 44 - const input = '---\ncount: 42\nnegative: -7\n---\n' 45 - expect(parseBasicFrontmatter(input)).toEqual({ count: 42, negative: -7 }) 46 - }) 47 - 48 - it('parses float values', () => { 49 - const input = '---\nrating: 4.5\nnegative: -3.14\n---\n' 50 - expect(parseBasicFrontmatter(input)).toEqual({ rating: 4.5, negative: -3.14 }) 51 - }) 52 - 53 - it('parses array values', () => { 54 - const input = '---\ntags: [foo, bar, baz]\n---\n' 55 - expect(parseBasicFrontmatter(input)).toEqual({ 56 - tags: ['foo', 'bar', 'baz'], 57 - }) 58 - }) 59 - 60 - it('strips quotes from array items', () => { 61 - const input = '---\ntags: ["foo", \'bar\']\n---\n' 62 - expect(parseBasicFrontmatter(input)).toEqual({ 63 - tags: ['foo', 'bar'], 64 - }) 65 - }) 66 - 67 - it('does not support nested arrays', () => { 68 - const input = '---\nmatrix: [[1, 2], [3, 4]]\n---\n' 69 - const result = parseBasicFrontmatter(input) 70 - expect(result.matrix).toEqual(['[1', '2]', '[3', '4]']) 71 - }) 72 - 73 - it('handles values with colons', () => { 74 - const input = '---\nurl: https://example.com\n---\n' 75 - expect(parseBasicFrontmatter(input)).toEqual({ 76 - url: 'https://example.com', 77 - }) 78 - }) 79 - 80 - it('skips lines without colons', () => { 81 - const input = '---\ntitle: Hello\ninvalid line\nauthor: James\n---\n' 82 - expect(parseBasicFrontmatter(input)).toEqual({ 83 - title: 'Hello', 84 - author: 'James', 85 - }) 86 - }) 87 - 88 - it('trims keys and values', () => { 89 - const input = '---\n title : Hello \n---\n' 90 - expect(parseBasicFrontmatter(input)).toEqual({ title: 'Hello' }) 91 - }) 92 - 93 - it('handles frontmatter at end of file without trailing newline', () => { 94 - const input = '---\ntitle: Hello\n---' 95 - expect(parseBasicFrontmatter(input)).toEqual({ title: 'Hello' }) 96 - }) 97 - 98 - it('handles mixed types', () => { 99 - const input = '---\ntitle: My Post\ncount: 5\nrating: 9.8\npublished: true\ntags: [a, b]\n---\n' 100 - expect(parseBasicFrontmatter(input)).toEqual({ 101 - title: 'My Post', 102 - count: 5, 103 - rating: 9.8, 104 - published: true, 105 - tags: ['a', 'b'], 106 - }) 107 - }) 108 - })