diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index c60b55323..000000000 --- a/.eslintrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": ["@diplodoc/eslint-config"], - "env": { - "node": true - } -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..ccda1b82e --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,11 @@ +module.exports = { + "extends": ["@diplodoc/eslint-config"], + "root": true, + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + "env": { + "node": true + } +} diff --git a/src/models.ts b/src/models.ts index 2f78c192c..bbbf7d050 100644 --- a/src/models.ts +++ b/src/models.ts @@ -7,7 +7,13 @@ import {ChangelogItem} from '@diplodoc/transform/lib/plugins/changelog/types'; export type VarsPreset = 'internal' | 'external'; -export type YfmPreset = Record; +export type VarsMetadata = { + [field: string]: string; +}[]; + +export type YfmPreset = Record & { + __metadata?: VarsMetadata; +}; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type Metadata = Record; @@ -65,10 +71,12 @@ export interface YfmArgv extends YfmConfig { staticContent: boolean; } -export interface DocPreset { +export type DocPreset = { default: YfmPreset; [varsPreset: string]: YfmPreset; -} +} & { + __metadata: Record[]; +}; export interface YfmToc extends Filter { name: string; diff --git a/src/resolvers/md2html.ts b/src/resolvers/md2html.ts index ed75c2ad0..565fa5cf1 100644 --- a/src/resolvers/md2html.ts +++ b/src/resolvers/md2html.ts @@ -16,7 +16,7 @@ import { transformToc, } from '../utils'; import {Lang, PROCESSING_FINISHED} from '../constants'; -import {getAssetsPublicPath, getUpdatedMetadata} from '../services/metadata'; +import {getAssetsPublicPath, getVCSMetadata} from '../services/metadata'; import {MarkdownItPluginCb} from '@diplodoc/transform/lib/plugins/typings'; export interface FileTransformOptions { @@ -42,6 +42,7 @@ export async function resolveMd2HTML(options: ResolverOptions): Promise readFileSync(resolvedInputPath, 'utf8'), metadata, vars.__system, + vars.__metadata, ); const {result, changelogs} = transformMd2Md(content, { diff --git a/src/services/metadata.ts b/src/services/metadata.ts index 544a7a44b..3f5f4460f 100644 --- a/src/services/metadata.ts +++ b/src/services/metadata.ts @@ -1,7 +1,7 @@ -import {dump} from 'js-yaml'; +import {dump, load} from 'js-yaml'; import {VCSConnector} from '../vcs-connector/connector-models'; -import {MetaDataOptions, Metadata, Resources} from '../models'; +import {MetaDataOptions, Metadata, Resources, VarsMetadata} from '../models'; import { getAuthorDetails, updateAuthorMetadataStringByAuthorLogin, @@ -22,6 +22,7 @@ async function getContentWithUpdatedMetadata( fileContent: string, options?: MetaDataOptions, systemVars?: unknown, + metadataVars?: VarsMetadata, ): Promise { let result; @@ -32,7 +33,9 @@ async function getContentWithUpdatedMetadata( addSourcePath: options?.addSourcePath, resources: options?.resources, systemVars, + metadataVars, }); + result = await getContentWithUpdatedDynamicMetadata(result, options); return result; @@ -45,6 +48,7 @@ function getContentWithUpdatedStaticMetadata({ addSourcePath, resources, systemVars, + metadataVars = [], }: { fileContent: string; sourcePath?: string; @@ -52,10 +56,16 @@ function getContentWithUpdatedStaticMetadata({ addSourcePath?: boolean; resources?: Resources; systemVars?: unknown; + metadataVars?: VarsMetadata; }): string { const newMetadatas: string[] = []; - if ((!addSystemMeta || !systemVars) && !addSourcePath && !resources) { + if ( + (!addSystemMeta || !systemVars) && + !addSourcePath && + !resources && + metadataVars.length === 0 + ) { return fileContent; } @@ -77,7 +87,25 @@ function getContentWithUpdatedStaticMetadata({ if (matches && matches.length > 0) { const [, fileMetadata, , fileMainContent] = matches; - return `${getUpdatedMetadataString(newMetadatas, fileMetadata)}${fileMainContent}`; + if (!metadataVars.length) { + return `${getUpdatedMetadataString(newMetadatas, fileMetadata)}${fileMainContent}`; + } + + const parsed = load(fileMetadata) as Record; + + if (!Array.isArray(parsed.metadata)) { + parsed.metadata = [parsed.metadata]; + } + + parsed.metadata = parsed.metadata.concat(metadataVars); + + const patchedMetada = dump(parsed); + + return `${getUpdatedMetadataString(newMetadatas, patchedMetada)}${fileMainContent}`; + } + + if (metadataVars.length) { + newMetadatas.push(dump({metadata: metadataVars})); } return `${getUpdatedMetadataString(newMetadatas)}${fileContent}`; @@ -239,7 +267,7 @@ function getUpdatedMetadataString(newMetadatas: string[], defaultMetadata = ''): }`; } -async function getUpdatedMetadata( +async function getVCSMetadata( options: MetaDataOptions, fileContent: string, meta?: Metadata, @@ -309,6 +337,6 @@ function getAssetsPublicPath(filePath: string) { export { getContentWithUpdatedMetadata, getContentWithUpdatedStaticMetadata, - getUpdatedMetadata, + getVCSMetadata, getAssetsPublicPath, }; diff --git a/src/services/preset.ts b/src/services/preset.ts index 78d126cad..c24533ba8 100644 --- a/src/services/preset.ts +++ b/src/services/preset.ts @@ -7,10 +7,11 @@ export type PresetStorage = Map; let presetStorage: PresetStorage = new Map(); function add(parsedPreset: DocPreset, path: string, varsPreset: string) { - const combinedValues: YfmPreset = { + const combinedValues = { ...(parsedPreset.default || {}), ...(parsedPreset[varsPreset] || {}), - }; + __metadata: parsedPreset.__metadata, + } as YfmPreset; const key = dirname(normalize(path)); presetStorage.set(key, combinedValues); diff --git a/src/utils/markup.ts b/src/utils/markup.ts index da1781b7b..0be1f518f 100644 --- a/src/utils/markup.ts +++ b/src/utils/markup.ts @@ -1,34 +1,42 @@ import {join} from 'path'; import {platform} from 'process'; -import {CUSTOM_STYLE, Platforms, ResourceType} from '../constants'; -import {Resources, SinglePageResult} from '../models'; +import {CUSTOM_STYLE, Platforms} from '../constants'; +import {LeadingPage, Resources, SinglePageResult, TextItems, VarsMetadata} from '../models'; import {ArgvService, PluginService} from '../services'; import {preprocessPageHtmlForSinglePage} from './singlePage'; import {DocInnerProps, DocPageData, render} from '@diplodoc/client/ssr'; import manifest from '@diplodoc/client/manifest'; +import {escape} from 'html-escaper'; + const dst = (bundlePath: string) => (target: string) => join(bundlePath, target); +export const сarriage = platform === Platforms.WINDOWS ? '\r\n' : '\n'; export interface TitleMeta { title?: string; } -export type Meta = TitleMeta & Resources; + +export type Meta = TitleMeta & + Resources & { + metadata: VarsMetadata; + }; export function generateStaticMarkup( props: DocInnerProps, pathToBundle: string, ): string { - const {title: metaTitle, style, script} = (props.data.meta as Meta) || {}; + const {style, script, metadata, ...restYamlConfigMeta} = (props.data.meta as Meta) || {}; const {title: tocTitle} = props.data.toc; const {title: pageTitle} = props.data; const title = getTitle({ - metaTitle, + metaTitle: props.data.meta.title, tocTitle: tocTitle as string, pageTitle, }); + const resources = getResources({style, script}); const {staticContent} = ArgvService.getConfig(); @@ -40,7 +48,7 @@ export function generateStaticMarkup( - ${getMetadata(props.data.meta as Record)} + ${getMetadata(metadata, restYamlConfigMeta)} ${title} + + + + +
+
+ + + + + + +`; + +exports[`Allow load custom resources md2html with metadata 3`] = ` + + + + + + + + + + Documentation + + + + + + +
+
+ + + + + + +`; + +exports[`Allow load custom resources md2html with metadata 4`] = ` + + + + + + + + + + Documentation + + + + + + +
+
+ + + + + + +`; + +exports[`Allow load custom resources md2md with metadata 1`] = `"["index.yaml","page.md","project/config.md","toc.yaml"]"`; + +exports[`Allow load custom resources md2md with metadata 2`] = ` +"title: Documentation +description: '' +meta: + title: Documentation + noIndex: true +links: + - title: Getting started with Documentation + description: This guide will show you the basics of working with Documentation + href: page.md +" +`; + +exports[`Allow load custom resources md2md with metadata 3`] = ` +"--- +metadata: + - name: test-yfm + content: inline test + - name: yfm-config + content: config test + - name: yfm + content: builder in page +--- + +Lorem" +`; + +exports[`Allow load custom resources md2md with metadata 4`] = ` +"--- +metadata: + - name: test-yfm + content: inline test + - name: yfm-config + content: config test + - name: yfm + value: builder in config +--- + +Lorem" +`; + +exports[`Allow load custom resources md2md with metadata 5`] = ` +"title: Documentation +href: index.yaml +items: + - name: Documentation + href: page.md + - name: Config + href: project/config.md +base: . +" +`; diff --git a/tests/e2e/metadata.spec.ts b/tests/e2e/metadata.spec.ts new file mode 100644 index 000000000..b941ae27e --- /dev/null +++ b/tests/e2e/metadata.spec.ts @@ -0,0 +1,15 @@ +import {getTestPaths, runYfmDocs, compareDirectories} from '../utils'; + +const geretateMapTestTemplate = (testTitle: string, testRootPath: string, {md2md = true, md2html = true}) => { + test(testTitle, () => { + const {inputPath, outputPath} = getTestPaths(testRootPath); + runYfmDocs(inputPath, outputPath, {md2md, md2html}); + compareDirectories(outputPath); + }); +} + +describe('Allow load custom resources', () => { + geretateMapTestTemplate('md2md with metadata', 'mocks/metadata/md2md-with-metadata', {md2html: false}) + + geretateMapTestTemplate('md2html with metadata', 'mocks/metadata/md2html-with-metadata', {md2md: false}) +}); diff --git a/tests/mocks/load-custom-resources/md2html-with-resources/input/page.md b/tests/mocks/load-custom-resources/md2html-with-resources/input/page.md index 0f927d5c4..ab14aea30 100644 --- a/tests/mocks/load-custom-resources/md2html-with-resources/input/page.md +++ b/tests/mocks/load-custom-resources/md2html-with-resources/input/page.md @@ -1,5 +1,7 @@ --- -meta: some meta +metadata: + - name: yfm + content: builder --- -Lorem \ No newline at end of file +Lorem diff --git a/tests/mocks/load-custom-resources/md2md-with-resources/input/page.md b/tests/mocks/load-custom-resources/md2md-with-resources/input/page.md index 0f927d5c4..ab14aea30 100644 --- a/tests/mocks/load-custom-resources/md2md-with-resources/input/page.md +++ b/tests/mocks/load-custom-resources/md2md-with-resources/input/page.md @@ -1,5 +1,7 @@ --- -meta: some meta +metadata: + - name: yfm + content: builder --- -Lorem \ No newline at end of file +Lorem diff --git a/tests/mocks/load-custom-resources/single-page-with-resources/input/page.md b/tests/mocks/load-custom-resources/single-page-with-resources/input/page.md index 0f927d5c4..ab14aea30 100644 --- a/tests/mocks/load-custom-resources/single-page-with-resources/input/page.md +++ b/tests/mocks/load-custom-resources/single-page-with-resources/input/page.md @@ -1,5 +1,7 @@ --- -meta: some meta +metadata: + - name: yfm + content: builder --- -Lorem \ No newline at end of file +Lorem diff --git a/tests/mocks/metadata/md2html-with-metadata/input/index.yaml b/tests/mocks/metadata/md2html-with-metadata/input/index.yaml new file mode 100644 index 000000000..60f350c8d --- /dev/null +++ b/tests/mocks/metadata/md2html-with-metadata/input/index.yaml @@ -0,0 +1,9 @@ +title: Documentation +description: "" +meta: + title: Documentation + noIndex: true +links: + - title: Getting started with Documentation + description: This guide will show you the basics of working with Documentation + href: page.md diff --git a/tests/mocks/metadata/md2html-with-metadata/input/page.md b/tests/mocks/metadata/md2html-with-metadata/input/page.md new file mode 100644 index 000000000..d5a9a94c4 --- /dev/null +++ b/tests/mocks/metadata/md2html-with-metadata/input/page.md @@ -0,0 +1,7 @@ +--- +metadata: + - name: yfm + value: builder in page +--- + +Lorem \ No newline at end of file diff --git a/tests/mocks/metadata/md2html-with-metadata/input/presets.yaml b/tests/mocks/metadata/md2html-with-metadata/input/presets.yaml new file mode 100644 index 000000000..a5dec198e --- /dev/null +++ b/tests/mocks/metadata/md2html-with-metadata/input/presets.yaml @@ -0,0 +1,5 @@ +__metadata: + - name: test-yfm + content: inline test + - name: yfm-config + content: config test diff --git a/tests/mocks/metadata/md2html-with-metadata/input/project/config.md b/tests/mocks/metadata/md2html-with-metadata/input/project/config.md new file mode 100644 index 000000000..2e1519666 --- /dev/null +++ b/tests/mocks/metadata/md2html-with-metadata/input/project/config.md @@ -0,0 +1,7 @@ +--- +metadata: + - name: yfm + value: builder in config +--- + +Lorem \ No newline at end of file diff --git a/tests/mocks/metadata/md2html-with-metadata/input/toc.yaml b/tests/mocks/metadata/md2html-with-metadata/input/toc.yaml new file mode 100644 index 000000000..35f2b4c6c --- /dev/null +++ b/tests/mocks/metadata/md2html-with-metadata/input/toc.yaml @@ -0,0 +1,7 @@ +title: Documentation +href: index.yaml +items: + - name: Documentation + href: page.md + - name: Config + href: project/config.md \ No newline at end of file diff --git a/tests/mocks/metadata/md2md-with-metadata/input/index.yaml b/tests/mocks/metadata/md2md-with-metadata/input/index.yaml new file mode 100644 index 000000000..60f350c8d --- /dev/null +++ b/tests/mocks/metadata/md2md-with-metadata/input/index.yaml @@ -0,0 +1,9 @@ +title: Documentation +description: "" +meta: + title: Documentation + noIndex: true +links: + - title: Getting started with Documentation + description: This guide will show you the basics of working with Documentation + href: page.md diff --git a/tests/mocks/metadata/md2md-with-metadata/input/page.md b/tests/mocks/metadata/md2md-with-metadata/input/page.md new file mode 100644 index 000000000..847a0324d --- /dev/null +++ b/tests/mocks/metadata/md2md-with-metadata/input/page.md @@ -0,0 +1,7 @@ +--- +metadata: + - name: yfm + content: builder in page +--- + +Lorem \ No newline at end of file diff --git a/tests/mocks/metadata/md2md-with-metadata/input/presets.yaml b/tests/mocks/metadata/md2md-with-metadata/input/presets.yaml new file mode 100644 index 000000000..a5dec198e --- /dev/null +++ b/tests/mocks/metadata/md2md-with-metadata/input/presets.yaml @@ -0,0 +1,5 @@ +__metadata: + - name: test-yfm + content: inline test + - name: yfm-config + content: config test diff --git a/tests/mocks/metadata/md2md-with-metadata/input/project/config.md b/tests/mocks/metadata/md2md-with-metadata/input/project/config.md new file mode 100644 index 000000000..2e1519666 --- /dev/null +++ b/tests/mocks/metadata/md2md-with-metadata/input/project/config.md @@ -0,0 +1,7 @@ +--- +metadata: + - name: yfm + value: builder in config +--- + +Lorem \ No newline at end of file diff --git a/tests/mocks/metadata/md2md-with-metadata/input/toc.yaml b/tests/mocks/metadata/md2md-with-metadata/input/toc.yaml new file mode 100644 index 000000000..35f2b4c6c --- /dev/null +++ b/tests/mocks/metadata/md2md-with-metadata/input/toc.yaml @@ -0,0 +1,7 @@ +title: Documentation +href: index.yaml +items: + - name: Documentation + href: page.md + - name: Config + href: project/config.md \ No newline at end of file diff --git a/tests/units/services/metadata.test.ts b/tests/units/services/metadata.test.ts index 36b2464ef..3cefec685 100644 --- a/tests/units/services/metadata.test.ts +++ b/tests/units/services/metadata.test.ts @@ -1,5 +1,5 @@ import {Contributor, Contributors, Metadata, MetaDataOptions} from 'models'; -import {getUpdatedMetadata} from 'services/metadata'; +import {getVCSMetadata} from 'services/metadata'; import {replaceDoubleToSingleQuotes} from 'utils/markup'; import {VCSConnector} from 'vcs-connector/connector-models'; @@ -35,7 +35,7 @@ jest.mock('services/authors', () => ({ })); const defaultVCSConnector: VCSConnector = { - addNestedContributorsForPath: () => { }, + addNestedContributorsForPath: () => {}, getContributorsByPath: () => Promise.resolve(null), getUserByLogin: () => Promise.resolve(null), getExternalAuthorByPath: () => null, @@ -64,7 +64,7 @@ describe('getUpdatedMetadata', () => { }; metaDataOptions.isContributorsEnabled = false; - const newMetadata = await getUpdatedMetadata(metaDataOptions, fileContent); + const newMetadata = await getVCSMetadata(metaDataOptions, fileContent); expect(newMetadata).toEqual(expectedMetadata); }); @@ -76,77 +76,89 @@ describe('getUpdatedMetadata', () => { }; metaDataOptions.vcsConnector = undefined; - const newMetadata = await getUpdatedMetadata(metaDataOptions, fileContent); + const newMetadata = await getVCSMetadata(metaDataOptions, fileContent); expect(newMetadata).toEqual(expectedMetadata); }); - test('returns new metadata with filled contributors ' + - 'when metadata options has "isContributorsEnabled" and "vcsConnector"', async () => { - const fileContent = ''; - const expectedMetadata = { - contributors: contributorsString, - }; - - const newMetadata = await getUpdatedMetadata(metaDataOptions, fileContent); - - expect(newMetadata).toEqual(expectedMetadata); - }); - - test('returns updated metadata with empty contributors when file has default metadata ' + - 'and metadata options has "vcsConnector" and do not have "isContributorsEnabled"', async () => { - const fileContent = ''; - const meta: Metadata = { - title: 'Some title', - }; - const expectedMetadata = { - ...meta, - contributors: '[]', - author: null, - }; - metaDataOptions.isContributorsEnabled = false; - - const newMetadata = await getUpdatedMetadata(metaDataOptions, fileContent, meta); - - expect(newMetadata).toEqual(expectedMetadata); - }); - - test('returns updated metadata with empty contributors when file has metadata with author ' + - 'and metadata options has "vcsConnector" and do not have "isContributorsEnabled"', async () => { - const fileContent = ''; - const meta: Metadata = { - title: 'Some title', - author: 'Some author', - }; - const expectedMetadata = { - ...meta, - contributors: '[]', - author: authorString, - }; - metaDataOptions.isContributorsEnabled = false; - - const newMetadata = await getUpdatedMetadata(metaDataOptions, fileContent, meta); - - expect(newMetadata).toEqual(expectedMetadata); - }); - - test('returns updated metadata with empty contributors when file has metadata with author ' + - 'and metadata options do not have "isContributorsEnabled" and "vcsConnector"', async () => { - const fileContent = ''; - const meta: Metadata = { - title: 'Some title', - author: 'Some author', - }; - const expectedMetadata = { - ...meta, - contributors: '[]', - author: null, - }; - metaDataOptions.isContributorsEnabled = false; - metaDataOptions.vcsConnector = undefined; - - const newMetadata = await getUpdatedMetadata(metaDataOptions, fileContent, meta); - - expect(newMetadata).toEqual(expectedMetadata); - }); + test( + 'returns new metadata with filled contributors ' + + 'when metadata options has "isContributorsEnabled" and "vcsConnector"', + async () => { + const fileContent = ''; + const expectedMetadata = { + contributors: contributorsString, + }; + + const newMetadata = await getVCSMetadata(metaDataOptions, fileContent); + + expect(newMetadata).toEqual(expectedMetadata); + }, + ); + + test( + 'returns updated metadata with empty contributors when file has default metadata ' + + 'and metadata options has "vcsConnector" and do not have "isContributorsEnabled"', + async () => { + const fileContent = ''; + const meta: Metadata = { + title: 'Some title', + }; + const expectedMetadata = { + ...meta, + contributors: '[]', + author: null, + }; + metaDataOptions.isContributorsEnabled = false; + + const newMetadata = await getVCSMetadata(metaDataOptions, fileContent, meta); + + expect(newMetadata).toEqual(expectedMetadata); + }, + ); + + test( + 'returns updated metadata with empty contributors when file has metadata with author ' + + 'and metadata options has "vcsConnector" and do not have "isContributorsEnabled"', + async () => { + const fileContent = ''; + const meta: Metadata = { + title: 'Some title', + author: 'Some author', + }; + const expectedMetadata = { + ...meta, + contributors: '[]', + author: authorString, + }; + metaDataOptions.isContributorsEnabled = false; + + const newMetadata = await getVCSMetadata(metaDataOptions, fileContent, meta); + + expect(newMetadata).toEqual(expectedMetadata); + }, + ); + + test( + 'returns updated metadata with empty contributors when file has metadata with author ' + + 'and metadata options do not have "isContributorsEnabled" and "vcsConnector"', + async () => { + const fileContent = ''; + const meta: Metadata = { + title: 'Some title', + author: 'Some author', + }; + const expectedMetadata = { + ...meta, + contributors: '[]', + author: null, + }; + metaDataOptions.isContributorsEnabled = false; + metaDataOptions.vcsConnector = undefined; + + const newMetadata = await getVCSMetadata(metaDataOptions, fileContent, meta); + + expect(newMetadata).toEqual(expectedMetadata); + }, + ); }); diff --git a/tsconfig.json b/tsconfig.json index 4572080e5..9f6569773 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,10 @@ { "extends": "@diplodoc/tsconfig", "compilerOptions": { + "lib": ["ES2019"], "target": "es6", - "moduleResolution": "NodeNext", - "outDir": "build" + "outDir": "build", + "module": "CommonJS" }, "include": ["src"], "exclude": ["node_modules"],