├── .all-contributorsrc ├── .eslintignore ├── .eslintrc.cjs ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── __tests__ └── index.ts ├── jest.config.js ├── package-lock.json ├── package.json ├── source └── index.ts └── tsconfig.json /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "parse-md", 3 | "projectOwner": "Robert Pearce ", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": true, 11 | "commitConvention": "none", 12 | "contributors": [ 13 | { 14 | "login": "rpearce", 15 | "name": "Robert Pearce", 16 | "avatar_url": "https://avatars2.githubusercontent.com/u/592876?v=4", 17 | "profile": "https://robertwpearce.com", 18 | "contributions": [ 19 | "code", 20 | "doc", 21 | "example", 22 | "ideas", 23 | "test" 24 | ] 25 | }, 26 | { 27 | "login": "justinchan23", 28 | "name": "Justin Chan", 29 | "avatar_url": "https://avatars3.githubusercontent.com/u/45015017?v=4", 30 | "profile": "https://www.justinchan.ca", 31 | "contributions": [ 32 | "bug" 33 | ] 34 | }, 35 | { 36 | "login": "alexghr", 37 | "name": "Alex Gherghisan", 38 | "avatar_url": "https://avatars.githubusercontent.com/u/3816165?v=4", 39 | "profile": "https://www.alexghr.me", 40 | "contributions": [ 41 | "code", 42 | "bug", 43 | "ideas" 44 | ] 45 | } 46 | ], 47 | "contributorsPerLine": 7 48 | } 49 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | es6: true, 5 | jest: true, 6 | node: true, 7 | }, 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | ], 11 | overrides: [], 12 | parserOptions: { 13 | ecmaVersion: 2020, 14 | sourceType: 'module', 15 | }, 16 | parser: '@typescript-eslint/parser', 17 | plugins: [ 18 | '@typescript-eslint', 19 | 'jest', 20 | ], 21 | rules: { 22 | '@typescript-eslint/no-unused-vars': 'error', 23 | '@typescript-eslint/no-use-before-define': 'off', 24 | 'comma-dangle': ['error', { 25 | arrays: 'always-multiline', 26 | exports: 'always-multiline', 27 | functions: 'ignore', 28 | imports: 'always-multiline', 29 | objects: 'always-multiline', 30 | }], 31 | curly: ['error', 'all'], 32 | eqeqeq: ['error', 'always', { null: 'ignore' }], 33 | 'eol-last': 'error', 34 | indent: ['error', 2, { 'SwitchCase': 1 }], 35 | 'keyword-spacing': 'error', 36 | 'linebreak-style': ['error', 'unix'], 37 | 'max-len': [ 38 | 'error', 39 | { 40 | code: 80, 41 | ignoreComments: true, 42 | ignoreTemplateLiterals: true, 43 | ignoreStrings: true, 44 | }, 45 | ], 46 | 'no-trailing-spaces': 'error', 47 | 'no-use-before-define': ['error', { 48 | functions: false, 49 | classes: true, 50 | variables: false, 51 | }], 52 | 'object-curly-spacing': ['error', 'always'], 53 | quotes: ['error', 'single', { allowTemplateLiterals: true }], 54 | semi: ['error', 'never'], 55 | 'space-in-parens': ['error', 'never'], 56 | 'space-infix-ops': ['error'], 57 | 'no-use-before-define': 'off', 58 | '@typescript-eslint/no-use-before-define': ['error'], 59 | }, 60 | } 61 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: rpearce 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout repo under GH workspace 11 | uses: actions/checkout@v2 12 | 13 | - name: Use nodejs 14 | uses: actions/setup-node@v2 15 | with: 16 | node-version: '16' 17 | cache: 'npm' 18 | 19 | - name: Install deps without updating package-lock.json 20 | run: npm i --no-save 21 | 22 | - name: Run the CI build 23 | run: npm run ci 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.tmp 2 | .DS_Store 3 | coverage/ 4 | dist/ 5 | node_modules/ 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## [3.0.3] - 2022-01-08 9 | 10 | ### Changed 11 | 12 | * Updated README with important importing info 13 | 14 | ## [3.0.2] - 2022-01-08 15 | 16 | ### Fixed 17 | 18 | * Actually drop commonjs support 19 | * Drop `tslib` dependency 20 | * Fix typescript declaration importing `js-yaml` 21 | 22 | ## [3.0.1] - 2022-01-04 23 | 24 | ### Fixed 25 | 26 | * Conditional exports fields not pointing to the right files 27 | * Set `"type": "module"` in package.json since we can't output `.mjs` with TS 28 | 29 | ## [3.0.0] - 2022-01-02 30 | 31 | ### Changed 32 | 33 | * Rewrote in TypeScript (PR #27) 34 | * BREAKING CHANGE: commonjs no longer outputted in v3.x 35 | 36 | ## [2.0.5] - 2021-12-31 37 | 38 | ### Fixed 39 | 40 | * Can't use properly inside a node ESM package (#24) 41 | 42 | ## [2.0.4] - 2020-01-05 43 | 44 | ### Changed 45 | 46 | * bump devDependencies 47 | 48 | ## [2.0.3] - 2019-12-05 49 | 50 | ### Changed 51 | 52 | * `dist/` output now contains `esm/` and `cjs/` folders with `index.js` (and 53 | `index.map.js`) instead of `.cjs.js` and `.esm.js` files 54 | * fixed copy-paste issues in `rollup.config.js` & `package.json` 55 | * bumped a number of dev dependencies 56 | 57 | ## [2.0.1] - 2019-09-11 58 | 59 | ### Changed 60 | 61 | * bumped `sspk` for security fix 62 | 63 | ## [2.0.0] - 2019-09-11 64 | 65 | ### Changed 66 | 67 | * using rollup to built ESM & commonjs output to `dist/` folder 68 | * security fixes 69 | * new dev experience 70 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at me@robertwpearce.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 1. Check out the [issues](https://github.com/rpearce/parse-md/issues) 4 | 1. [Fork](https://guides.github.com/activities/forking/) this repository 5 | 1. [Clone](https://help.github.com/articles/cloning-a-repository/) your fork 6 | 1. Add the upstream project (this one) as a git remote: 7 | ```sh 8 | git remote add upstream git@github.com:rpearce/parse-md.git 9 | git fetch upstream 10 | git rebase upstream/main 11 | ``` 12 | 1. Check out a feature branch 13 | ```sh 14 | git checkout -b my-feature 15 | ``` 16 | 1. Make your changes 17 | 1. Push your branch to your GitHub repo 18 | ```sh 19 | git push origin my-feature 20 | ``` 21 | 1. Create a [pull request](https://help.github.com/articles/about-pull-requests/) 22 | from your branch to this repo's `main` branch 23 | 1. When all is merged, pull down the upstream changes to your main 24 | ```sh 25 | git fetch upstream 26 | git rebase upstream/main 27 | ``` 28 | 1. Delete your feature branch (locally and then on GitHub) 29 | ```sh 30 | git branch -D my-feature 31 | git push origin :my-feature 32 | ``` 33 | 34 | ## Testing 35 | 36 | Tests are located in the `__tests__/` folder. Here's how to run them: 37 | 38 | ```sh 39 | npm test 40 | ``` 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Robert Pearce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parseMD 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-) [![npm version](https://img.shields.io/npm/v/parse-md.svg?style=flat-square)](https://www.npmjs.com/package/parse-md) [![npm downloads](https://img.shields.io/npm/dm/parse-md.svg?style=flat-square)](https://www.npmjs.com/package/parse-md) [![bundlephobia size](https://flat.badgen.net/bundlephobia/minzip/parse-md)](https://bundlephobia.com/result?p=parse-md) 4 | 5 | This library exists as a way to pass a markdown file's content and have its 6 | metadata and markdown returned as an object containing `metadata` and `content` 7 | keys. 8 | 9 | Note that it is not trying to do anything but solve the markdown metadata vs. 10 | content parsing problem and is _not parsing the markdown body, itself._ You can 11 | use something like [marked](https://github.com/chjj/marked) for that. 12 | 13 | ## What It Does 14 | 15 | For example, 16 | 17 | ```md 18 | --- 19 | title: This is a test 20 | description: Once upon a time... 21 | --- 22 | # Title of my great post 23 | Lorem ipsum dolor... 24 | ``` 25 | 26 | would be parsed as 27 | 28 | ```js 29 | { 30 | metadata: { 31 | title: "This is a test", 32 | description: "Once upon a time..." 33 | }, 34 | content: "# Title of my great post\nLorem ipsum dolor..." 35 | } 36 | ``` 37 | 38 | _Note: This tool expects that your Markdown metadata has `---` boundaries, as 39 | shown above._ 40 | 41 | ## Usage 42 | 43 | Installation: 44 | 45 | ```sh 46 | npm i parse-md 47 | ``` 48 | 49 | Import it where you need it, and then pass it a Markdown file's content: 50 | 51 | ```javascript 52 | import fs from 'fs' 53 | import parseMD from 'parse-md' 54 | 55 | const fileContents = fs.readFileSync('posts/first.md', 'utf8') 56 | const { metadata, content } = parseMD(fileContents) 57 | 58 | console.log(metadata) // { title: 'Great first post', description: 'This is my first great post. Rawr' } 59 | console.log(content) // "# My first post..." 60 | ``` 61 | 62 | ## Links 63 | 64 | * [`Changelog`](./CHANGELOG.md) 65 | * [`Contributing`](./CONTRIBUTING.md) 66 | * [`Code of Conduct`](./CODE_OF_CONDUCT.md) 67 | 68 | ## Note about CommonJS 69 | 70 | If you need to CommonJS module support, use version `2.x`, and require it like 71 | this: 72 | 73 | ```javascript 74 | const parseMD = require('parse-md').default 75 | ``` 76 | 77 | ## Contributors ✨ 78 | 79 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |

Robert Pearce

💻 📖 💡 🤔 ⚠️

Justin Chan

🐛

Alex Gherghisan

💻 🐛 🤔
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 103 | -------------------------------------------------------------------------------- /__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import parseMD from '../source' 2 | 3 | type ExpectedMetadata = { title: string, description: string } 4 | 5 | test('returns meta & raw content when meta present', () => { 6 | const metaTitle = 'Glorious Title' 7 | const metaDescription = 'ABC123' 8 | const body = '# This is our house' 9 | const markdown = `--- 10 | title: ${metaTitle} 11 | description: ${metaDescription} 12 | --- 13 | ${body}` 14 | const { metadata, content } = parseMD(markdown) 15 | const { title, description } = metadata as ExpectedMetadata 16 | 17 | expect(title).toEqual(metaTitle) 18 | expect(description).toEqual(metaDescription) 19 | expect(content).toEqual(body) 20 | }) 21 | 22 | test('returns raw content when meta absent', () => { 23 | const markdown = '# This is our house' 24 | const { metadata, content } = parseMD(markdown) 25 | 26 | expect(metadata).toEqual({}) 27 | expect(content).toEqual(markdown) 28 | }) 29 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | clearMocks: true, 3 | collectCoverage: true, 4 | collectCoverageFrom: ['/source/index.ts'], 5 | coveragePathIgnorePatterns: ['/node_modules/'], 6 | moduleNameMapper: {}, 7 | preset: 'ts-jest', 8 | setupFilesAfterEnv: [], 9 | testEnvironment: 'node', 10 | verbose: true, 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parse-md", 3 | "version": "3.0.3", 4 | "description": "Separate markdown file's metadata from its content", 5 | "main": "./dist/index.js", 6 | "exports": { 7 | ".": "./dist/index.js" 8 | }, 9 | "type": "module", 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:rpearce/parse-md.git" 13 | }, 14 | "homepage": "https://github.com/rpearce/parse-md", 15 | "bugs": "https://github.com/rpearce/parse-md/issues", 16 | "author": "Robert Pearce ", 17 | "contributors": [ 18 | "Robert Pearce (https://robertwpearce.com)", 19 | "Alex Gherghisan <> (https://www.alexghr.me)" 20 | ], 21 | "license": "MIT", 22 | "keywords": [ 23 | "markdown", 24 | "metadata", 25 | "md", 26 | "meta", 27 | "parse" 28 | ], 29 | "tags": [ 30 | "markdown", 31 | "metadata", 32 | "md", 33 | "meta", 34 | "parse" 35 | ], 36 | "files": [ 37 | "LICENSE", 38 | "README.md", 39 | "dist/" 40 | ], 41 | "sideEffects": false, 42 | "scripts": { 43 | "build": "rm -rf dist && tsc", 44 | "ci": "run-p lint test build", 45 | "contributors:add": "all-contributors add", 46 | "contributors:generate": "all-contributors generate", 47 | "lint": "eslint .", 48 | "prepublishOnly": "run-p lint test && npm run build", 49 | "test": "jest" 50 | }, 51 | "dependencies": { 52 | "js-yaml": "^4.1.0" 53 | }, 54 | "devDependencies": { 55 | "@types/jest": "^27.0.2", 56 | "@types/js-yaml": "^4.0.5", 57 | "@types/node": "^17.0.8", 58 | "@typescript-eslint/eslint-plugin": "^5.9.0", 59 | "@typescript-eslint/parser": "^5.9.0", 60 | "all-contributors-cli": "^6.12.0", 61 | "eslint": "^8.1.0", 62 | "eslint-plugin-jest": "^25.2.2", 63 | "jest": "^27.4.7", 64 | "npm-run-all": "^4.1.5", 65 | "typescript": "^4.4.4", 66 | "ts-jest": "^27.0.7" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /source/index.ts: -------------------------------------------------------------------------------- 1 | import { load } from 'js-yaml' 2 | 3 | // ============================================================================= 4 | type Content = string 5 | type EmptyObject = Record 6 | type InputContent = string 7 | type Line = string 8 | type MetaIndex = number 9 | 10 | // ============================================================================= 11 | interface FindMetaIndices { 12 | (mem: MetaIndex[], item: Line, i: MetaIndex): MetaIndex[] 13 | } 14 | 15 | const findMetaIndices: FindMetaIndices = (mem, item, i) => { 16 | if (/^---/.test(item)) { 17 | mem.push(i) 18 | } 19 | 20 | return mem 21 | } 22 | 23 | // ============================================================================= 24 | interface ParseMeta { 25 | ({ lines, metaIndices }: { 26 | lines: Line[], 27 | metaIndices: MetaIndex[], 28 | }): EmptyObject | unknown 29 | } 30 | 31 | const emptyObject = {} 32 | 33 | const parseMeta: ParseMeta = ({ lines, metaIndices }) => { 34 | if (metaIndices.length > 0) { 35 | const metadata = lines.slice(metaIndices[0] + 1, metaIndices[1]) 36 | return load(metadata.join('\n')) 37 | } 38 | 39 | return emptyObject 40 | } 41 | 42 | // ============================================================================= 43 | interface ParseContent { 44 | ({ lines, metaIndices }: { 45 | lines: Line[], 46 | metaIndices: MetaIndex[], 47 | }): Content 48 | } 49 | 50 | const parseContent: ParseContent = ({ lines, metaIndices }) => { 51 | if (metaIndices.length > 0) { 52 | lines = lines.slice(metaIndices[1] + 1, lines.length) 53 | } 54 | 55 | return lines.join('\n') 56 | } 57 | 58 | // ============================================================================= 59 | export interface ParseMD { 60 | (contents: InputContent): { 61 | metadata: ReturnType, 62 | content: ReturnType, 63 | } 64 | } 65 | 66 | const parseMD: ParseMD = contents => { 67 | const lines = contents.split('\n') 68 | const metaIndices = lines.reduce(findMetaIndices, []) 69 | const metadata = parseMeta({ lines, metaIndices }) 70 | const content = parseContent({ lines, metaIndices }) 71 | 72 | return { metadata, content } 73 | } 74 | 75 | export default parseMD 76 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "allowSyntheticDefaultImports": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "importHelpers": true, 8 | "lib": ["es2020"], 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "noImplicitAny": true, 12 | "outDir": "./dist/", 13 | "sourceMap": false, 14 | "strict": true, 15 | "target": "es2015" 16 | }, 17 | "include": ["./source/index.ts"] 18 | } 19 | --------------------------------------------------------------------------------