@github-docs/frontmatter
Advanced tools
Comparing version
16
index.js
@@ -8,4 +8,5 @@ const matter = require('gray-matter') | ||
const filepath = opts.filepath || null | ||
const { content, data } = matter(markdown) | ||
const allowedKeys = Object.keys(schema.properties) | ||
const { content, data } = matter(markdown) | ||
const existingKeys = Object.keys(data) | ||
@@ -19,5 +20,4 @@ let { errors } = revalidator.validate(data, schema) | ||
// check for keys | ||
// validate key names | ||
if (opts.validateKeyNames) { | ||
const existingKeys = Object.keys(data) | ||
const invalidKeys = difference(existingKeys, allowedKeys) | ||
@@ -34,3 +34,13 @@ invalidKeys.forEach(key => { | ||
// validate key order | ||
if (opts.validateKeyOrder && allowedKeys.join('') !== existingKeys.join('')) { | ||
const error = { | ||
property: 'keys', | ||
message: `keys must be in order. Current: ${existingKeys.join(',')}; Expected: ${allowedKeys.join(',')}` | ||
} | ||
if (filepath) error.filepath = filepath | ||
errors.push(error) | ||
} | ||
return { content, data, errors } | ||
} |
{ | ||
"name": "@github-docs/frontmatter", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "Parse and validate YAML frontmatter", | ||
@@ -5,0 +5,0 @@ "repository": "https://github.com/docs/frontmatter", |
@@ -16,2 +16,11 @@ # @github-docs/frontmatter | ||
## Features | ||
- Make frontmatter entries required or optional | ||
- Validate value type, length, pattern, etc. See the [revalidator#schema](https://github.com/flatiron/revalidator#schema). | ||
- Validate urls, emails, IP addresses, dates, times, etc. See [revalidator#format](https://github.com/flatiron/revalidator#format). | ||
- Set an explicit list of allowable values with [`enum`](https://github.com/flatiron/revalidator#enum). | ||
- Enforce a specific order of frontmatter values with `validateKeyOrder` | ||
- Disallow values that are not specified in the schema with `validateKeyNames` | ||
## Usage | ||
@@ -18,0 +27,0 @@ |
const parse = require('..') | ||
const filepath = 'path/to/file.md' | ||
const fixture1 = `--- | ||
@@ -12,15 +12,61 @@ title: Hello, World | ||
describe('frontmatter', () => { | ||
it('parses frontmatter and content in a given string', () => { | ||
const { data, content } = parse(fixture1) | ||
it('parses frontmatter and content in a given string (no options required)', () => { | ||
const { data, content, errors } = parse(fixture1) | ||
expect(data.title).toBe('Hello, World') | ||
expect(data.meaning_of_life).toBe(42) | ||
expect(content.trim()).toBe('I am content.') | ||
expect(errors.length).toBe(0) | ||
}) | ||
it('accepts an optional schema for validation', () => { | ||
describe('schema', () => { | ||
it('is optional', () => { | ||
const schema = { | ||
properties: { | ||
title: { | ||
type: 'string' | ||
}, | ||
meaning_of_life: { | ||
type: 'number' | ||
} | ||
} | ||
} | ||
const { data, content, errors } = parse(fixture1, { schema }) | ||
expect(data.title).toBe('Hello, World') | ||
expect(data.meaning_of_life).toBe(42) | ||
expect(content.trim()).toBe('I am content.') | ||
expect(errors.length).toBe(0) | ||
}) | ||
it('creates errors if frontmatter does not conform to schema', () => { | ||
const schema = { | ||
properties: { | ||
meaning_of_life: { | ||
type: 'number', | ||
minimum: 50 | ||
} | ||
} | ||
} | ||
const { data, content, errors } = parse(fixture1, { schema }) | ||
expect(data.title).toBe('Hello, World') | ||
expect(data.meaning_of_life).toBe(42) | ||
expect(content.trim()).toBe('I am content.') | ||
expect(errors.length).toBe(1) | ||
const expectedError = { | ||
attribute: 'minimum', | ||
property: 'meaning_of_life', | ||
expected: 50, | ||
actual: 42, | ||
message: 'must be greater than or equal to 50' | ||
} | ||
expect(errors[0]).toEqual(expectedError) | ||
}) | ||
}) | ||
describe('validateKeyNames', () => { | ||
const schema = { | ||
properties: { | ||
meaning_of_life: { | ||
type: 'number', | ||
minimum: 50 | ||
age: { | ||
type: 'number' | ||
} | ||
@@ -30,25 +76,64 @@ } | ||
const { data, content, errors } = parse(fixture1, { schema }) | ||
expect(data.title).toBe('Hello, World') | ||
expect(data.meaning_of_life).toBe(42) | ||
expect(errors.length).toBe(1) | ||
const expectedError = { | ||
attribute: 'minimum', | ||
property: 'meaning_of_life', | ||
expected: 50, | ||
actual: 42, | ||
message: 'must be greater than or equal to 50' | ||
} | ||
expect(errors[0]).toEqual(expectedError) | ||
it('creates errors for undocumented keys if `validateKeyNames` is true', () => { | ||
const { errors } = parse(fixture1, { schema, validateKeyNames: true, filepath }) | ||
expect(errors.length).toBe(2) | ||
const expectedErrors = [ | ||
{ | ||
property: 'title', | ||
message: 'not allowed. Allowed properties are: age', | ||
filepath: 'path/to/file.md' | ||
}, | ||
{ | ||
property: 'meaning_of_life', | ||
message: 'not allowed. Allowed properties are: age', | ||
filepath: 'path/to/file.md' | ||
} | ||
] | ||
expect(errors).toEqual(expectedErrors) | ||
}) | ||
expect(content.trim()).toBe('I am content.') | ||
it('does not create errors for undocumented keys if `validateKeyNames` is false', () => { | ||
const { errors } = parse(fixture1, { schema, validateKeyNames: false }) | ||
expect(errors.length).toBe(0) | ||
}) | ||
}) | ||
it.todo('creates errors for undocumented keys if `validateKeyNames` is true') | ||
describe('validateKeyOrder', () => { | ||
it('creates errors if `validateKeyOrder` is true and keys are not in order', () => { | ||
const schema = { | ||
properties: { | ||
meaning_of_life: { | ||
type: 'number' | ||
}, | ||
title: { | ||
type: 'string' | ||
} | ||
} | ||
} | ||
const { errors } = parse(fixture1, { schema, validateKeyOrder: true, filepath }) | ||
const expectedErrors = [ | ||
{ | ||
property: 'keys', | ||
message: 'keys must be in order. Current: title,meaning_of_life; Expected: meaning_of_life,title', | ||
filepath: 'path/to/file.md' | ||
} | ||
] | ||
expect(errors).toEqual(expectedErrors) | ||
}) | ||
it.todo('validates key order if `validateKeyOrder` is true') | ||
it.todo('includes filepath in schema validation errors, if specified') | ||
it.todo('includes filepath in key name validation errors, if specified') | ||
it('does not create errors if `validateKeyOrder` is true and keys are in order', () => { | ||
const schema = { | ||
properties: { | ||
title: { | ||
type: 'string' | ||
}, | ||
meaning_of_life: { | ||
type: 'number' | ||
} | ||
} | ||
} | ||
const { errors } = parse(fixture1, { schema, validateKeyOrder: true }) | ||
expect(errors.length).toBe(0) | ||
}) | ||
}) | ||
}) |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
12941
150.55%8
33.33%174
114.81%71
14.52%2
100%