planning
All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 35s

This commit is contained in:
2024-10-14 09:15:30 +02:00
parent bcba00a730
commit 6e64e138e2
21059 changed files with 2317811 additions and 1 deletions

View File

@@ -0,0 +1,87 @@
import { Map } from 'immutable';
import { extensionFormatters, resolveFormat } from '../formats';
import { registerCustomFormat } from '../../lib/registry';
describe('custom formats', () => {
const testEntry = {
collection: 'testCollection',
data: { x: 1 },
isModification: false,
label: 'testLabel',
mediaFiles: [],
meta: {},
newRecord: true,
partial: false,
path: 'testPath1',
raw: 'testRaw',
slug: 'testSlug',
author: 'testAuthor',
updatedOn: 'testUpdatedOn',
};
it('resolves builtint formats', () => {
const collection = Map({
name: 'posts',
});
expect(resolveFormat(collection, { ...testEntry, path: 'test.yml' })).toEqual(
extensionFormatters.yml,
);
expect(resolveFormat(collection, { ...testEntry, path: 'test.yaml' })).toEqual(
extensionFormatters.yml,
);
expect(resolveFormat(collection, { ...testEntry, path: 'test.toml' })).toEqual(
extensionFormatters.toml,
);
expect(resolveFormat(collection, { ...testEntry, path: 'test.json' })).toEqual(
extensionFormatters.json,
);
expect(resolveFormat(collection, { ...testEntry, path: 'test.md' })).toEqual(
extensionFormatters.md,
);
expect(resolveFormat(collection, { ...testEntry, path: 'test.markdown' })).toEqual(
extensionFormatters.markdown,
);
expect(resolveFormat(collection, { ...testEntry, path: 'test.html' })).toEqual(
extensionFormatters.html,
);
});
it('resolves custom format', () => {
registerCustomFormat('txt-querystring', 'txt', {
fromFile: file => Object.fromEntries(new URLSearchParams(file)),
toFile: value => new URLSearchParams(value).toString(),
});
const collection = Map({
name: 'posts',
format: 'txt-querystring',
});
const formatter = resolveFormat(collection, { ...testEntry, path: 'test.txt' });
expect(formatter.toFile({ foo: 'bar' })).toEqual('foo=bar');
expect(formatter.fromFile('foo=bar')).toEqual({ foo: 'bar' });
});
it('can override existing formatters', () => {
// simplified version of a more realistic use case: using a different yaml library like js-yaml
// to make netlify-cms play nice with other tools that edit content and spit out yaml
registerCustomFormat('bad-yaml', 'yml', {
fromFile: file => Object.fromEntries(file.split('\n').map(line => line.split(': '))),
toFile: value =>
Object.entries(value)
.map(([k, v]) => `${k}: ${v}`)
.join('\n'),
});
const collection = Map({
name: 'posts',
format: 'bad-yaml',
});
const formatter = resolveFormat(collection, { ...testEntry, path: 'test.txt' });
expect(formatter.toFile({ a: 'b', c: 'd' })).toEqual('a: b\nc: d');
expect(formatter.fromFile('a: b\nc: d')).toEqual({ a: 'b', c: 'd' });
});
});

View File

@@ -0,0 +1,429 @@
import {
FrontmatterInfer,
frontmatterJSON,
frontmatterTOML,
frontmatterYAML,
} from '../frontmatter';
describe('Frontmatter', () => {
describe('yaml', () => {
it('should parse YAML with --- delimiters', () => {
expect(
FrontmatterInfer.fromFile('---\ntitle: YAML\ndescription: Something longer\n---\nContent'),
).toEqual({
title: 'YAML',
description: 'Something longer',
body: 'Content',
});
});
it('should parse YAML with --- delimiters when it is explicitly set as the format without a custom delimiter', () => {
expect(
frontmatterYAML().fromFile('---\ntitle: YAML\ndescription: Something longer\n---\nContent'),
).toEqual({
title: 'YAML',
description: 'Something longer',
body: 'Content',
});
});
it('should parse YAML with custom delimiters when it is explicitly set as the format with a custom delimiter', () => {
expect(
frontmatterYAML('~~~').fromFile(
'~~~\ntitle: YAML\ndescription: Something longer\n~~~\nContent',
),
).toEqual({
title: 'YAML',
description: 'Something longer',
body: 'Content',
});
});
it('should parse YAML with custom delimiters when it is explicitly set as the format with different custom delimiters', () => {
expect(
frontmatterYAML(['~~~', '^^^']).fromFile(
'~~~\ntitle: YAML\ndescription: Something longer\n^^^\nContent',
),
).toEqual({
title: 'YAML',
description: 'Something longer',
body: 'Content',
});
});
it('should parse YAML with ---yaml delimiters', () => {
expect(
FrontmatterInfer.fromFile(
'---yaml\ntitle: YAML\ndescription: Something longer\n---\nContent',
),
).toEqual({
title: 'YAML',
description: 'Something longer',
body: 'Content',
});
});
it('should overwrite any body param in the front matter', () => {
expect(
FrontmatterInfer.fromFile('---\ntitle: The Title\nbody: Something longer\n---\nContent'),
).toEqual({
title: 'The Title',
body: 'Content',
});
});
it('should stringify YAML with --- delimiters', () => {
expect(
FrontmatterInfer.toFile({
body: 'Some content\nOn another line',
tags: ['front matter', 'yaml'],
title: 'YAML',
}),
).toEqual(
[
'---',
'tags:',
' - front matter',
' - yaml',
'title: YAML',
'---',
'Some content',
'On another line',
].join('\n'),
);
});
it('should stringify YAML with missing body', () => {
expect(FrontmatterInfer.toFile({ tags: ['front matter', 'yaml'], title: 'YAML' })).toEqual(
['---', 'tags:', ' - front matter', ' - yaml', 'title: YAML', '---', ''].join('\n'),
);
});
it('should stringify YAML with --- delimiters when it is explicitly set as the format without a custom delimiter', () => {
expect(
frontmatterYAML().toFile({
body: 'Some content\nOn another line',
tags: ['front matter', 'yaml'],
title: 'YAML',
}),
).toEqual(
[
'---',
'tags:',
' - front matter',
' - yaml',
'title: YAML',
'---',
'Some content',
'On another line',
].join('\n'),
);
});
it('should stringify YAML with --- delimiters when it is explicitly set as the format with a custom delimiter', () => {
expect(
frontmatterYAML('~~~').toFile({
body: 'Some content\nOn another line',
tags: ['front matter', 'yaml'],
title: 'YAML',
}),
).toEqual(
[
'~~~',
'tags:',
' - front matter',
' - yaml',
'title: YAML',
'~~~',
'Some content',
'On another line',
].join('\n'),
);
});
it('should stringify YAML with --- delimiters when it is explicitly set as the format with different custom delimiters', () => {
expect(
frontmatterYAML(['~~~', '^^^']).toFile({
body: 'Some content\nOn another line',
tags: ['front matter', 'yaml'],
title: 'YAML',
}),
).toEqual(
[
'~~~',
'tags:',
' - front matter',
' - yaml',
'title: YAML',
'^^^',
'Some content',
'On another line',
].join('\n'),
);
});
it('should trim last line break if added by grey-matter', () => {
expect(
frontmatterYAML().toFile({
body: 'noLineBreak',
}),
).toEqual('noLineBreak');
});
it('should not trim last line break if not added by grey-matter', () => {
expect(
frontmatterYAML().toFile({
body: 'withLineBreak\n',
}),
).toEqual('withLineBreak\n');
});
it('should keep field types', () => {
const frontmatter = frontmatterYAML();
const file = frontmatter.toFile({
number: 1,
string: 'Hello World!',
date: new Date('2020-01-01'),
array: ['1', new Date('2020-01-01')],
body: 'Content',
});
expect(frontmatter.fromFile(file)).toEqual({
number: 1,
string: 'Hello World!',
date: new Date('2020-01-01'),
array: ['1', new Date('2020-01-01')],
body: 'Content',
});
});
});
describe('toml', () => {
it('should parse TOML with +++ delimiters', () => {
expect(
FrontmatterInfer.fromFile(
'+++\ntitle = "TOML"\ndescription = "Front matter"\n+++\nContent',
),
).toEqual({
title: 'TOML',
description: 'Front matter',
body: 'Content',
});
});
it('should parse TOML with 0.5 style dates', () => {
expect(
FrontmatterInfer.fromFile('+++\ntitle = "TOML"\ndate = 2018-12-24\n+++\nContent'),
).toEqual({
title: 'TOML',
date: new Date('2018-12-24T00:00:00.000Z'),
body: 'Content',
});
});
it('should parse TOML with +++ delimiters when it is explicitly set as the format without a custom delimiter', () => {
expect(
frontmatterTOML('~~~').fromFile(
'~~~\ntitle = "TOML"\ndescription = "Front matter"\n~~~\nContent',
),
).toEqual({
title: 'TOML',
description: 'Front matter',
body: 'Content',
});
});
it('should parse TOML with ---toml delimiters', () => {
expect(
FrontmatterInfer.fromFile(
'---toml\ntitle = "TOML"\ndescription = "Something longer"\n---\nContent',
),
).toEqual({
title: 'TOML',
description: 'Something longer',
body: 'Content',
});
});
it('should stringify TOML with +++ delimiters when it is explicitly set as the format without a custom delimiter', () => {
expect(
frontmatterTOML().toFile({
body: 'Some content\nOn another line',
tags: ['front matter', 'toml'],
title: 'TOML',
}),
).toEqual(
[
'+++',
'tags = ["front matter", "toml"]',
'title = "TOML"',
'+++',
'Some content',
'On another line',
].join('\n'),
);
});
it('should stringify TOML with +++ delimiters when it is explicitly set as the format with a custom delimiter', () => {
expect(
frontmatterTOML('~~~').toFile({
body: 'Some content\nOn another line',
tags: ['front matter', 'toml'],
title: 'TOML',
}),
).toEqual(
[
'~~~',
'tags = ["front matter", "toml"]',
'title = "TOML"',
'~~~',
'Some content',
'On another line',
].join('\n'),
);
});
it('should keep field types', () => {
const frontmatter = frontmatterTOML();
const file = frontmatter.toFile({
number: 1,
string: 'Hello World!',
date: new Date('2020-01-01'),
// in toml arrays must contain the same type
array: ['1', new Date('2020-01-01').toISOString()],
body: 'Content',
});
expect(frontmatter.fromFile(file)).toEqual({
number: 1,
string: 'Hello World!',
date: new Date('2020-01-01'),
array: ['1', new Date('2020-01-01').toISOString()],
body: 'Content',
});
});
});
describe('json', () => {
it('should parse JSON with { } delimiters', () => {
expect(
FrontmatterInfer.fromFile(
'{\n"title": "The Title",\n"description": "Something longer"\n}\nContent',
),
).toEqual({
title: 'The Title',
description: 'Something longer',
body: 'Content',
});
});
it('should parse JSON with { } delimiters when it is explicitly set as the format without a custom delimiter', () => {
expect(
frontmatterJSON().fromFile(
'{\n"title": "The Title",\n"description": "Something longer"\n}\nContent',
),
).toEqual({
title: 'The Title',
description: 'Something longer',
body: 'Content',
});
});
it('should parse JSON with { } delimiters when it is explicitly set as the format with a custom delimiter', () => {
expect(
frontmatterJSON('~~~').fromFile(
'~~~\n"title": "The Title",\n"description": "Something longer"\n~~~\nContent',
),
).toEqual({
title: 'The Title',
description: 'Something longer',
body: 'Content',
});
});
it('should parse JSON with ---json delimiters', () => {
expect(
FrontmatterInfer.fromFile(
'---json\n{\n"title": "The Title",\n"description": "Something longer"\n}\n---\nContent',
),
).toEqual({
title: 'The Title',
description: 'Something longer',
body: 'Content',
});
});
it('should parse JSON with { } delimiters ending with a nested object', () => {
expect(
FrontmatterInfer.fromFile(
'{\n "title": "The Title",\n "nested": {\n "inside": "Inside prop"\n }\n}\nContent',
),
).toEqual({
title: 'The Title',
nested: { inside: 'Inside prop' },
body: 'Content',
});
});
it('should stringify JSON with { } delimiters when it is explicitly set as the format without a custom delimiter', () => {
expect(
frontmatterJSON().toFile({
body: 'Some content\nOn another line',
tags: ['front matter', 'json'],
title: 'JSON',
}),
).toEqual(
[
'{',
'"tags": [',
' "front matter",',
' "json"',
' ],',
' "title": "JSON"',
'}',
'Some content',
'On another line',
].join('\n'),
);
});
it('should stringify JSON with { } delimiters when it is explicitly set as the format with a custom delimiter', () => {
expect(
frontmatterJSON('~~~').toFile({
body: 'Some content\nOn another line',
tags: ['front matter', 'json'],
title: 'JSON',
}),
).toEqual(
[
'~~~',
'"tags": [',
' "front matter",',
' "json"',
' ],',
' "title": "JSON"',
'~~~',
'Some content',
'On another line',
].join('\n'),
);
});
it('should keep field types', () => {
const frontmatter = frontmatterJSON();
const file = frontmatter.toFile({
number: 1,
string: 'Hello World!',
// no way to represent date in JSON
date: new Date('2020-01-01').toISOString(),
array: ['1', new Date('2020-01-01').toISOString()],
body: 'Content',
});
expect(frontmatter.fromFile(file)).toEqual({
number: 1,
string: 'Hello World!',
date: new Date('2020-01-01').toISOString(),
array: ['1', new Date('2020-01-01').toISOString()],
body: 'Content',
});
});
});
});

View File

@@ -0,0 +1,9 @@
import tomlFormatter from '../toml';
describe('tomlFormatter', () => {
it('should output TOML integer values without decimals', () => {
expect(tomlFormatter.toFile({ testFloat: 123.456, testInteger: 789, title: 'TOML' })).toEqual(
['testFloat = 123.456', 'testInteger = 789', 'title = "TOML"'].join('\n'),
);
});
});

View File

@@ -0,0 +1,162 @@
import { stripIndent } from 'common-tags';
import yaml from '../yaml';
describe('yaml', () => {
describe('fromFile', () => {
test('loads valid yaml', () => {
expect(yaml.fromFile('[]')).toEqual([]);
const result = yaml.fromFile(stripIndent`
date: 2020-04-02T16:08:03.327Z
dateString: 2020-04-02
boolean: true
number: 1
`);
expect(result).toEqual({
date: new Date('2020-04-02T16:08:03.327Z'),
dateString: '2020-04-02',
boolean: true,
number: 1,
});
expect(yaml.fromFile('# Comment a\na: a\nb:\n # Comment c\n c:\n d: d\n')).toEqual({
a: 'a',
b: { c: { d: 'd' } },
});
expect(
yaml.fromFile(stripIndent`
# template comment
template: post
# title comment
title: title
# image comment
image: /media/netlify.png
# date comment
date: 2020-04-02T13:27:48.617Z
# object comment
object:
# object_title comment
object_title: object_title
# object_list comment
object_list:
- object_list_item_1: "1"
object_list_item_2: "2"
# list comment
list:
- "1"
`),
).toEqual({
list: ['1'],
object: {
object_title: 'object_title',
object_list: [{ object_list_item_1: '1', object_list_item_2: '2' }],
},
date: new Date('2020-04-02T13:27:48.617Z'),
image: '/media/netlify.png',
title: 'title',
template: 'post',
});
});
test('does not fail on closing separator', () => {
expect(yaml.fromFile('---\n[]\n---')).toEqual([]);
});
test('parses single quoted string as string', () => {
expect(yaml.fromFile('name: y')).toEqual({ name: 'y' });
});
test('parses ISO date string as date', () => {
expect(yaml.fromFile('date: 2020-04-02T16:08:03.327Z')).toEqual({
date: new Date('2020-04-02T16:08:03.327Z'),
});
});
test('parses partial date string as string', () => {
expect(yaml.fromFile('date: 2020-06-12')).toEqual({
date: '2020-06-12',
});
expect(yaml.fromFile('date: 12-06-2012')).toEqual({
date: '12-06-2012',
});
});
test('parses partial time value as string', () => {
expect(yaml.fromFile('time: 10:05')).toEqual({
time: '10:05',
});
});
});
describe('toFile', () => {
test('outputs valid yaml', () => {
expect(yaml.toFile([])).toEqual('[]\n');
});
test('should sort keys', () => {
expect(yaml.toFile({ a: 'a', b: 'b', c: 'c', d: 'd' })).toEqual('a: a\nb: b\nc: c\nd: d\n');
expect(yaml.toFile({ a: 'a', b: 'b', c: 'c', d: 'd' }, ['d', 'b', 'a', 'c'])).toEqual(
'd: d\nb: b\na: a\nc: c\n',
);
expect(yaml.toFile({ a: 'a', b: 'b', c: 'c', d: 'd' }, ['d', 'b', 'c'])).toEqual(
'a: a\nd: d\nb: b\nc: c\n',
);
});
test('should add comments', () => {
expect(
yaml.toFile({ a: 'a', b: { c: { d: 'd' } } }, [], { a: 'Comment a', 'b.c': 'Comment c' }),
).toEqual('# Comment a\na: a\nb:\n # Comment c\n c:\n d: d\n');
const expected = `# template comment
template: post
# title comment
title: title
# image comment
image: /media/netlify.png
# date comment
date: 2020-04-02T13:27:48.617Z
# object comment
object:
# object_title comment
object_title: object_title
# object_list comment
object_list:
- object_list_item_1: "1"
object_list_item_2: "2"
# list comment
list:
- "1"
`;
const result = yaml.toFile(
{
list: ['1'],
object: {
object_title: 'object_title',
object_list: [{ object_list_item_1: '1', object_list_item_2: '2' }],
},
date: new Date('2020-04-02T13:27:48.617Z'),
image: '/media/netlify.png',
title: 'title',
template: 'post',
},
['template', 'title', 'image', 'date', 'object', 'list'],
{
list: 'list comment',
object: 'object comment',
'object.object_title': 'object_title comment',
'object.object_list': 'object_list comment',
date: 'date comment',
image: 'image comment',
title: 'title comment',
template: 'template comment',
},
);
expect(result).toEqual(expected);
expect(yaml.toFile({ a: 'a' }, [], { a: 'line 1\\nline 2' })).toEqual(
'# line 1\n# line 2\na: a\n',
);
});
});
});

97
node_modules/decap-cms-core/src/formats/formats.ts generated vendored Normal file
View File

@@ -0,0 +1,97 @@
import { List } from 'immutable';
import { get } from 'lodash';
import yamlFormatter from './yaml';
import tomlFormatter from './toml';
import jsonFormatter from './json';
import { FrontmatterInfer, frontmatterJSON, frontmatterTOML, frontmatterYAML } from './frontmatter';
import { getCustomFormatsExtensions, getCustomFormatsFormatters } from '../lib/registry';
import type { Delimiter } from './frontmatter';
import type { Collection, EntryObject, Format } from '../types/redux';
import type { EntryValue } from '../valueObjects/Entry';
import type { Formatter } from 'decap-cms-core';
export const frontmatterFormats = ['yaml-frontmatter', 'toml-frontmatter', 'json-frontmatter'];
export const formatExtensions = {
yml: 'yml',
yaml: 'yml',
toml: 'toml',
json: 'json',
frontmatter: 'md',
'json-frontmatter': 'md',
'toml-frontmatter': 'md',
'yaml-frontmatter': 'md',
};
export function getFormatExtensions() {
return { ...formatExtensions, ...getCustomFormatsExtensions() };
}
export const extensionFormatters = {
yml: yamlFormatter,
yaml: yamlFormatter,
toml: tomlFormatter,
json: jsonFormatter,
md: FrontmatterInfer,
markdown: FrontmatterInfer,
html: FrontmatterInfer,
};
function formatByName(name: Format, customDelimiter?: Delimiter): Formatter {
const formatters: Record<string, Formatter> = {
yml: yamlFormatter,
yaml: yamlFormatter,
toml: tomlFormatter,
json: jsonFormatter,
frontmatter: FrontmatterInfer,
'json-frontmatter': frontmatterJSON(customDelimiter),
'toml-frontmatter': frontmatterTOML(customDelimiter),
'yaml-frontmatter': frontmatterYAML(customDelimiter),
...getCustomFormatsFormatters(),
};
if (name in formatters) {
return formatters[name];
}
throw new Error(`No formatter available with name: ${name}`);
}
function frontmatterDelimiterIsList(
frontmatterDelimiter?: Delimiter | List<string>,
): frontmatterDelimiter is List<string> {
return List.isList(frontmatterDelimiter);
}
export function resolveFormat(collection: Collection, entry: EntryObject | EntryValue) {
// Check for custom delimiter
const frontmatter_delimiter = collection.get('frontmatter_delimiter');
const customDelimiter = frontmatterDelimiterIsList(frontmatter_delimiter)
? (frontmatter_delimiter.toArray() as [string, string])
: frontmatter_delimiter;
// If the format is specified in the collection, use that format.
const formatSpecification = collection.get('format');
if (formatSpecification) {
return formatByName(formatSpecification, customDelimiter);
}
// If a file already exists, infer the format from its file extension.
const filePath = entry && entry.path;
if (filePath) {
const fileExtension = filePath.split('.').pop();
if (fileExtension) {
return get(extensionFormatters, fileExtension);
}
}
// If creating a new file, and an `extension` is specified in the
// collection config, infer the format from that extension.
const extension = collection.get('extension');
if (extension) {
return get(extensionFormatters, extension);
}
// If no format is specified and it cannot be inferred, return the default.
return formatByName('frontmatter', customDelimiter);
}

150
node_modules/decap-cms-core/src/formats/frontmatter.ts generated vendored Normal file
View File

@@ -0,0 +1,150 @@
import matter from 'gray-matter';
import tomlFormatter from './toml';
import yamlFormatter from './yaml';
import jsonFormatter from './json';
const Languages = {
YAML: 'yaml',
TOML: 'toml',
JSON: 'json',
} as const;
type Language = (typeof Languages)[keyof typeof Languages];
export type Delimiter = string | [string, string];
type Format = { language: Language; delimiters: Delimiter };
const parsers = {
toml: {
parse: (input: string) => tomlFormatter.fromFile(input),
stringify: (metadata: object, opts?: { sortedKeys?: string[] }) => {
const { sortedKeys } = opts || {};
return tomlFormatter.toFile(metadata, sortedKeys);
},
},
json: {
parse: (input: string) => {
let JSONinput = input.trim();
// Fix JSON if leading and trailing brackets were trimmed.
if (JSONinput.slice(0, 1) !== '{') {
JSONinput = '{' + JSONinput + '}';
}
return jsonFormatter.fromFile(JSONinput);
},
stringify: (metadata: object) => {
let JSONoutput = jsonFormatter.toFile(metadata).trim();
// Trim leading and trailing brackets.
if (JSONoutput.slice(0, 1) === '{' && JSONoutput.slice(-1) === '}') {
JSONoutput = JSONoutput.slice(1, -1);
}
return JSONoutput;
},
},
yaml: {
parse: (input: string) => yamlFormatter.fromFile(input),
stringify: (
metadata: object,
opts?: { sortedKeys?: string[]; comments?: Record<string, string> },
) => {
const { sortedKeys, comments } = opts || {};
return yamlFormatter.toFile(metadata, sortedKeys, comments);
},
},
};
function inferFrontmatterFormat(str: string) {
const lineEnd = str.indexOf('\n');
const firstLine = str.slice(0, lineEnd !== -1 ? lineEnd : 0).trim();
if (firstLine.length > 3 && firstLine.slice(0, 3) === '---') {
// No need to infer, `gray-matter` will handle things like `---toml` for us.
return;
}
switch (firstLine) {
case '---':
return getFormatOpts(Languages.YAML);
case '+++':
return getFormatOpts(Languages.TOML);
case '{':
return getFormatOpts(Languages.JSON);
default:
console.warn('Unrecognized front-matter format.');
}
}
export function getFormatOpts(format?: Language, customDelimiter?: Delimiter) {
if (!format) {
return undefined;
}
const formats: { [key in Language]: Format } = {
yaml: { language: Languages.YAML, delimiters: '---' },
toml: { language: Languages.TOML, delimiters: '+++' },
json: { language: Languages.JSON, delimiters: ['{', '}'] },
};
const { language, delimiters } = formats[format];
return {
language,
delimiters: customDelimiter || delimiters,
};
}
export class FrontmatterFormatter {
format?: Format;
constructor(format?: Language, customDelimiter?: Delimiter) {
this.format = getFormatOpts(format, customDelimiter);
}
fromFile(content: string) {
const format = this.format || inferFrontmatterFormat(content);
const result = matter(content, { engines: parsers, ...format });
// in the absent of a body when serializing an entry we use an empty one
// when calling `toFile`, so we don't want to add it when parsing.
return {
...result.data,
...(result.content.trim() && { body: result.content }),
};
}
toFile(
data: { body?: string } & Record<string, unknown>,
sortedKeys?: string[],
comments?: Record<string, string>,
) {
const { body = '', ...meta } = data;
// Stringify to YAML if the format was not set
const format = this.format || getFormatOpts(Languages.YAML);
// gray-matter always adds a line break at the end which trips our
// change detection logic
// https://github.com/jonschlinkert/gray-matter/issues/96
const trimLastLineBreak = body.slice(-1) !== '\n';
const file = matter.stringify(body, meta, {
engines: parsers,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore `sortedKeys` is not recognized by gray-matter, so it gets passed through to the parser
sortedKeys,
comments,
...format,
});
return trimLastLineBreak && file.slice(-1) === '\n' ? file.slice(0, -1) : file;
}
}
export const FrontmatterInfer = new FrontmatterFormatter();
export function frontmatterYAML(customDelimiter?: Delimiter) {
return new FrontmatterFormatter(Languages.YAML, customDelimiter);
}
export function frontmatterTOML(customDelimiter?: Delimiter) {
return new FrontmatterFormatter(Languages.TOML, customDelimiter);
}
export function frontmatterJSON(customDelimiter?: Delimiter) {
return new FrontmatterFormatter(Languages.JSON, customDelimiter);
}

14
node_modules/decap-cms-core/src/formats/helpers.ts generated vendored Normal file
View File

@@ -0,0 +1,14 @@
export function sortKeys<Item>(
sortedKeys: string[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
selector: (a: Item) => string = (a: any) => a,
) {
return (a: Item, b: Item) => {
const idxA = sortedKeys.indexOf(selector(a));
const idxB = sortedKeys.indexOf(selector(b));
if (idxA === -1 || idxB === -1) return 0;
if (idxA > idxB) return 1;
if (idxA < idxB) return -1;
return 0;
};
}

9
node_modules/decap-cms-core/src/formats/json.ts generated vendored Normal file
View File

@@ -0,0 +1,9 @@
export default {
fromFile(content: string) {
return JSON.parse(content);
},
toFile(data: object) {
return JSON.stringify(data, null, 2);
},
};

33
node_modules/decap-cms-core/src/formats/toml.ts generated vendored Normal file
View File

@@ -0,0 +1,33 @@
import toml from '@iarna/toml';
import tomlify from 'tomlify-j0.4';
import dayjs from 'dayjs';
import AssetProxy from '../valueObjects/AssetProxy';
import { sortKeys } from './helpers';
function outputReplacer(_key: string, value: unknown) {
if (dayjs.isDayjs(value)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return value.format(value._f);
}
if (value instanceof AssetProxy) {
return `${value.path}`;
}
if (typeof value === 'number' && Number.isInteger(value)) {
// Return the string representation of integers so tomlify won't render with tenths (".0")
return value.toString();
}
// Return `false` to use default (`undefined` would delete key).
return false;
}
export default {
fromFile(content: string) {
return toml.parse(content);
},
toFile(data: object, sortedKeys: string[] = []) {
return tomlify.toToml(data, { replace: outputReplacer, sort: sortKeys(sortedKeys) });
},
};

58
node_modules/decap-cms-core/src/formats/yaml.ts generated vendored Normal file
View File

@@ -0,0 +1,58 @@
import yaml from 'yaml';
import { sortKeys } from './helpers';
import type { YAMLMap, YAMLSeq, Pair, Node } from 'yaml/types';
function addComments(items: Array<Pair>, comments: Record<string, string>, prefix = '') {
items.forEach(item => {
if (item.key !== undefined) {
const itemKey = item.key.toString();
const key = prefix ? `${prefix}.${itemKey}` : itemKey;
if (comments[key]) {
const value = comments[key].split('\\n').join('\n ');
item.commentBefore = ` ${value}`;
}
if (Array.isArray(item.value?.items)) {
addComments(item.value.items, comments, key);
}
}
});
}
const timestampTag = {
identify: (value: unknown) => value instanceof Date,
default: true,
tag: '!timestamp',
test: RegExp(
'^' +
'([0-9]{4})-([0-9]{2})-([0-9]{2})' + // YYYY-MM-DD
'T' + // T
'([0-9]{2}):([0-9]{2}):([0-9]{2}(\\.[0-9]+)?)' + // HH:MM:SS(.ss)?
'Z' + // Z
'$',
),
resolve: (str: string) => new Date(str),
stringify: (value: Node) => (value as Date).toISOString(),
} as const;
export default {
fromFile(content: string) {
if (content && content.trim().endsWith('---')) {
content = content.trim().slice(0, -3);
}
return yaml.parse(content, { customTags: [timestampTag] });
},
toFile(data: object, sortedKeys: string[] = [], comments: Record<string, string> = {}) {
const contents = yaml.createNode(data) as YAMLMap | YAMLSeq;
addComments(contents.items, comments);
contents.items.sort(sortKeys(sortedKeys, item => item.key?.toString()));
const doc = new yaml.Document();
doc.contents = contents;
return doc.toString();
},
};