From a9d6bcf968dd69a2e50891538bdfcf64d771dac4 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Thu, 4 Apr 2024 17:58:53 +0200 Subject: [PATCH] wip --- .../__fixtures__/website/docusaurus.config.js | 7 -- .../src/__tests__/frontMatter.test.ts | 90 +++---------------- .../src/index.ts | 73 +++++---------- .../src/markdownLoader.ts | 22 ----- .../src/options.ts | 3 +- .../src/plugin-content-showcase.d.ts | 42 ++++----- .../src/tags.ts | 45 ++++++++++ .../src/{frontMatter.ts => validation.ts} | 10 +-- website/docusaurus.config.ts | 7 +- 9 files changed, 107 insertions(+), 192 deletions(-) delete mode 100644 packages/docusaurus-plugin-content-showcase/src/markdownLoader.ts create mode 100644 packages/docusaurus-plugin-content-showcase/src/tags.ts rename packages/docusaurus-plugin-content-showcase/src/{frontMatter.ts => validation.ts} (74%) diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js index d048d2caf5..ae48be19a4 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js @@ -11,11 +11,4 @@ module.exports = { url: 'https://your-docusaurus-site.example.com', baseUrl: '/', favicon: 'img/favicon.ico', - markdown: { - parseFrontMatter: async (params) => { - const result = await params.defaultParseFrontMatter(params); - result.frontMatter.custom_frontMatter = 'added by parseFrontMatter'; - return result; - }, - }, }; diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts index ab394f129b..1cc8787eef 100644 --- a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts @@ -5,65 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import {escapeRegexp} from '@docusaurus/utils'; -import {validateShowcaseFrontMatter} from '../frontMatter'; -import type {ShowcaseFrontMatter} from '@docusaurus/plugin-content-showcase'; - -function testField(params: { - prefix: string; - validFrontMatters: ShowcaseFrontMatter[]; - convertibleFrontMatter?: [ - ConvertibleFrontMatter: {[key: string]: unknown}, - ConvertedFrontMatter: ShowcaseFrontMatter, - ][]; - invalidFrontMatters?: [ - InvalidFrontMatter: {[key: string]: unknown}, - ErrorMessage: string, - ][]; -}) { - // eslint-disable-next-line jest/require-top-level-describe - test(`[${params.prefix}] accept valid values`, () => { - params.validFrontMatters.forEach((frontMatter) => { - expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter); - }); - }); - - // eslint-disable-next-line jest/require-top-level-describe - test(`[${params.prefix}] convert valid values`, () => { - params.convertibleFrontMatter?.forEach( - ([convertibleFrontMatter, convertedFrontMatter]) => { - expect(validateShowcaseFrontMatter(convertibleFrontMatter)).toEqual( - convertedFrontMatter, - ); - }, - ); - }); - - // eslint-disable-next-line jest/require-top-level-describe - test(`[${params.prefix}] throw error for values`, () => { - params.invalidFrontMatters?.forEach(([frontMatter, message]) => { - try { - validateShowcaseFrontMatter(frontMatter); - throw new Error( - `Showcase front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify( - frontMatter, - null, - 2, - )}`, - ); - } catch (err) { - // eslint-disable-next-line jest/no-conditional-expect - expect((err as Error).message).toMatch( - new RegExp(escapeRegexp(message)), - ); - } - }); - }); -} +import {validateShowcaseItem} from '../validation'; +import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; describe('showcase front matter schema', () => { it('accepts valid frontmatter', () => { - const frontMatter: ShowcaseFrontMatter = { + const frontMatter: ShowcaseItem = { title: 'title', description: 'description', preview: 'preview', @@ -71,37 +18,24 @@ describe('showcase front matter schema', () => { tags: [], website: 'website', }; - expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter); + expect(validateShowcaseItem(frontMatter)).toEqual(frontMatter); }); it('reject invalid frontmatter', () => { const frontMatter = {}; expect(() => - validateShowcaseFrontMatter(frontMatter), + validateShowcaseItem(frontMatter), ).toThrowErrorMatchingInlineSnapshot( `""title" is required. "description" is required. "preview" is required. "website" is required. "source" is required. "tags" is required"`, ); }); -}); -describe('validateShowcaseFrontMatter full', () => { - testField({ - prefix: 'valid full frontmatter', - validFrontMatters: [ - { - title: 'title', - description: 'description', - preview: 'preview', - source: 'source', - tags: [], - website: 'website', - }, - ], - invalidFrontMatters: [ - [ - {}, - '"title" is required. "description" is required. "preview" is required. "website" is required. "source" is required. "tags" is required', - ], - ], + it('reject invalid frontmatter value', () => { + const frontMatter = {title: 42}; + expect(() => + validateShowcaseItem(frontMatter), + ).toThrowErrorMatchingInlineSnapshot( + `""title" must be a string. "description" is required. "preview" is required. "website" is required. "source" is required. "tags" is required"`, + ); }); }); diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts index 7b372479df..193634b747 100644 --- a/packages/docusaurus-plugin-content-showcase/src/index.ts +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -13,18 +13,13 @@ import { Globby, } from '@docusaurus/utils'; import Yaml from 'js-yaml'; - import {Joi} from '@docusaurus/utils-validation'; -import { - validateFrontMatterTags, - validateShowcaseFrontMatter, -} from './frontMatter'; -import {tagSchema} from './options'; +import {validateFrontMatterTags, validateShowcaseItem} from './validation'; +import {getTagsList} from './tags'; import type {LoadContext, Plugin} from '@docusaurus/types'; import type { PluginOptions, - ShowcaseItem, - TagOption, + ShowcaseItems, } from '@docusaurus/plugin-content-showcase'; import type {ShowcaseContentPaths} from './types'; @@ -35,55 +30,24 @@ export function getContentPathList( } function createTagSchema(tags: string[]): Joi.Schema { - return Joi.alternatives().try( - Joi.string().valid(...tags), // Schema for single string - Joi.array().items(Joi.string().valid(...tags)), // Schema for array of strings - ); + return Joi.array().items(Joi.string().valid(...tags)); // Schema for array of strings } export default function pluginContentShowcase( context: LoadContext, options: PluginOptions, -): Plugin { +): Plugin { const {siteDir, localizationDir} = context; const contentPaths: ShowcaseContentPaths = { contentPath: path.resolve(siteDir, options.path), contentPathLocalized: getPluginI18nPath({ localizationDir, - pluginName: 'docusaurus-plugin-content-pages', + pluginName: 'docusaurus-plugin-content-showcase', pluginId: options.id, }), }; - async function getTagsList( - configTags: string | TagOption[], - ): Promise { - if (typeof configTags === 'object') { - return Object.keys(configTags); - } - - const tagsPath = path.resolve(contentPaths.contentPath, configTags); - - try { - const rawYaml = await fs.readFile(tagsPath, 'utf-8'); - const unsafeYaml = Yaml.load(rawYaml); - const safeYaml = tagSchema.validate(unsafeYaml); - - if (safeYaml.error) { - throw new Error( - `There was an error extracting tags: ${safeYaml.error.message}`, - {cause: safeYaml.error}, - ); - } - - const tagLabels = Object.keys(safeYaml.value); - return tagLabels; - } catch (error) { - throw new Error(`Failed to read tags file for showcase`, {cause: error}); - } - } - return { name: 'docusaurus-plugin-content-showcase', @@ -95,19 +59,24 @@ export default function pluginContentShowcase( // ); // }, - async loadContent(): Promise { + async loadContent(): Promise { if (!(await fs.pathExists(contentPaths.contentPath))) { - return null; + throw new Error( + `The showcase content path does not exist: ${contentPaths.contentPath}`, + ); } const {include} = options; const showcaseFiles = await Globby(include, { cwd: contentPaths.contentPath, - ignore: options.exclude, + ignore: [...options.exclude], }); - const tagList = await getTagsList(options.tags); + const tagList = await getTagsList({ + configTags: options.tags, + configPath: contentPaths.contentPath, + }); const createdTagSchema = createTagSchema(tagList); async function processShowcaseSourceFile(relativeSource: string) { @@ -119,14 +88,14 @@ export default function pluginContentShowcase( const sourcePath = path.join(contentPath, relativeSource); - const rawYaml = await fs.readFile(sourcePath, 'utf-8'); + const data = await fs.readFile(sourcePath, 'utf-8'); // todo remove as ... because bad practice ? - const unsafeYaml = Yaml.load(rawYaml) as {[key: string]: unknown}; - const yaml = validateShowcaseFrontMatter(unsafeYaml); + const unsafeData = Yaml.load(data) as {[key: string]: unknown}; + const showcaseItem = validateShowcaseItem(unsafeData); - validateFrontMatterTags(yaml.tags, createdTagSchema); + validateFrontMatterTags(showcaseItem.tags, createdTagSchema); - return yaml; + return showcaseItem; } async function doProcessShowcaseSourceFile(relativeSource: string) { @@ -160,7 +129,7 @@ export default function pluginContentShowcase( ); addRoute({ - path: '/showcaseAll', + path: options.routeBasePath, component: '@theme/Showcase', modules: { content: showcaseAllData, diff --git a/packages/docusaurus-plugin-content-showcase/src/markdownLoader.ts b/packages/docusaurus-plugin-content-showcase/src/markdownLoader.ts deleted file mode 100644 index e5c91b7bf7..0000000000 --- a/packages/docusaurus-plugin-content-showcase/src/markdownLoader.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import type {LoaderContext} from 'webpack'; - -export default function markdownLoader( - this: LoaderContext, - fileString: string, -): void { - const callback = this.async(); - - // const options = this.getOptions(); - - // TODO provide additional md processing here? like interlinking pages? - // fileString = linkify(fileString) - - return callback(null, fileString); -} diff --git a/packages/docusaurus-plugin-content-showcase/src/options.ts b/packages/docusaurus-plugin-content-showcase/src/options.ts index b4b197f0ea..39b347020d 100644 --- a/packages/docusaurus-plugin-content-showcase/src/options.ts +++ b/packages/docusaurus-plugin-content-showcase/src/options.ts @@ -13,8 +13,9 @@ import type {PluginOptions, Options} from '@docusaurus/plugin-content-showcase'; export const DEFAULT_OPTIONS: PluginOptions = { id: 'showcase', path: 'showcase', // Path to data on filesystem, relative to site dir. - routeBasePath: '/', // URL Route. + routeBasePath: '/showcase', // URL Route. include: ['**/*.{yml,yaml}'], + // todo exclude won't work if user pass a custom file name exclude: [...GlobExcludeDefault, 'tags.*'], tags: 'tags.yaml', }; diff --git a/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts index 90d031b10f..1a822bd5b3 100644 --- a/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts +++ b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts @@ -8,15 +8,17 @@ declare module '@docusaurus/plugin-content-showcase' { import type {LoadContext, Plugin} from '@docusaurus/types'; - export type TagOption = { - [key: string]: { - label: string; - description: { - message: string; - id: string; - }; - color: string; + type Tag = { + label: string; + description: { + message: string; + id: string; }; + color: string; + }; + + export type TagsOption = { + [tagName: string]: Tag; }; export type PluginOptions = { @@ -25,32 +27,20 @@ declare module '@docusaurus/plugin-content-showcase' { routeBasePath: string; include: string[]; exclude: string[]; - tags: string | TagOption[]; + tags: string | TagsOption; }; - type TagType = - | 'favorite' - | 'opensource' - | 'product' - | 'design' - | 'i18n' - | 'versioning' - | 'large' - | 'meta' - | 'personal' - | 'rtl'; - - export type ShowcaseFrontMatter = { + export type ShowcaseItem = { readonly title: string; readonly description: string; readonly preview: string | null; // null = use our serverless screenshot service readonly website: string; readonly source: string | null; - readonly tags: TagType[]; + readonly tags: string[]; }; - export type ShowcaseItem = { - items: ShowcaseFrontMatter[]; + export type ShowcaseItems = { + items: ShowcaseItem[]; }; export type Options = Partial; @@ -58,5 +48,5 @@ declare module '@docusaurus/plugin-content-showcase' { export default function pluginContentShowcase( context: LoadContext, options: PluginOptions, - ): Promise>; + ): Promise>; } diff --git a/packages/docusaurus-plugin-content-showcase/src/tags.ts b/packages/docusaurus-plugin-content-showcase/src/tags.ts new file mode 100644 index 0000000000..8fef1caef4 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/tags.ts @@ -0,0 +1,45 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import fs from 'fs-extra'; +import path from 'path'; +import Yaml from 'js-yaml'; +import {tagSchema} from './options'; +import type {TagsOption} from '@docusaurus/plugin-content-showcase'; + +// todo extract in another file +export async function getTagsList({ + configTags, + configPath, +}: { + configTags: string | TagsOption; + configPath: string; +}): Promise { + if (typeof configTags === 'object') { + return Object.keys(configTags); + } + + const tagsPath = path.resolve(configPath, configTags); + + try { + const data = await fs.readFile(tagsPath, 'utf-8'); + const unsafeData = Yaml.load(data); + const tags = tagSchema.validate(unsafeData); + + if (tags.error) { + throw new Error( + `There was an error extracting tags: ${tags.error.message}`, + {cause: tags.error}, + ); + } + + const tagLabels = Object.keys(tags.value); + return tagLabels; + } catch (error) { + throw new Error(`Failed to read tags file for showcase`, {cause: error}); + } +} diff --git a/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts b/packages/docusaurus-plugin-content-showcase/src/validation.ts similarity index 74% rename from packages/docusaurus-plugin-content-showcase/src/frontMatter.ts rename to packages/docusaurus-plugin-content-showcase/src/validation.ts index 3a1b35a8ed..70fd10a697 100644 --- a/packages/docusaurus-plugin-content-showcase/src/frontMatter.ts +++ b/packages/docusaurus-plugin-content-showcase/src/validation.ts @@ -6,9 +6,9 @@ */ import {Joi, validateFrontMatter} from '@docusaurus/utils-validation'; -import type {ShowcaseFrontMatter} from '@docusaurus/plugin-content-showcase'; +import type {ShowcaseItem} from '@docusaurus/plugin-content-showcase'; -const showcaseFrontMatterSchema = Joi.object({ +const showcaseItemSchema = Joi.object({ title: Joi.string().required(), description: Joi.string().required(), preview: Joi.string().required(), @@ -17,10 +17,10 @@ const showcaseFrontMatterSchema = Joi.object({ tags: Joi.array().items(Joi.string()).required(), }); -export function validateShowcaseFrontMatter(frontMatter: { +export function validateShowcaseItem(frontMatter: { [key: string]: unknown; -}): ShowcaseFrontMatter { - return validateFrontMatter(frontMatter, showcaseFrontMatterSchema); +}): ShowcaseItem { + return validateFrontMatter(frontMatter, showcaseItemSchema); } export function validateFrontMatterTags( diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 897b24e705..094cecb3de 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -239,7 +239,12 @@ export default async function createConfigAsync() { ], themes: ['live-codeblock', ...dogfoodingThemeInstances], plugins: [ - ['content-showcase', {}], + [ + 'content-showcase', + { + routeBasePath: '/showcaseAll', + }, + ], [ './src/plugins/changelog/index.js', {