diff --git a/astro.config.ts b/astro.config.ts index d7099a81d7f5ff..3e59ae1d5edd96 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -8,14 +8,15 @@ import starlightLinksValidator from "starlight-links-validator"; import icon from "astro-icon"; import sitemap from "@astrojs/sitemap"; import react from "@astrojs/react"; + import { readdir } from "fs/promises"; +import { fileURLToPath } from "url"; import rehypeTitleFigure from "rehype-title-figure"; import rehypeMermaid from "./src/plugins/rehype/mermaid.ts"; import rehypeAutolinkHeadings from "./src/plugins/rehype/autolink-headings.ts"; import rehypeExternalLinks from "./src/plugins/rehype/external-links.ts"; import rehypeHeadingSlugs from "./src/plugins/rehype/heading-slugs.ts"; -import { fileURLToPath } from "url"; async function autogenSections() { const sections = ( diff --git a/src/components/Render.astro b/src/components/Render.astro index fd854e3aee7ab2..8583134d2de073 100644 --- a/src/components/Render.astro +++ b/src/components/Render.astro @@ -13,29 +13,49 @@ const props = z.object({ let { file, product, params } = props.parse(Astro.props); if (!product) { - product = Astro.params.slug?.split("/")[0]; -} + const fromSlug = Astro.params.slug?.split("/")[0]; -if (!product) { - throw new Error( - `[Render] Unable to infer which folder ${file} is in, please provide a "product" input with your "file" input.`, - ); + if (!fromSlug) { + throw new Error( + `[Render] Unable to infer which folder ${file} is in, please provide a "product" input with your "file" input.`, + ); + } + + product = fromSlug; } -const partial = await getEntry("partials", `${product}/${file}`); +const id = `${product}/${file}`; +const partial = await getEntry("partials", id); if (!partial) { throw new Error( - `[Render] Couldn't find partial: ${file}. Included on ${Astro.params.slug}`, + `[Render] Couldn't find "${id}" included on "${Astro.url.pathname}"`, ); } +// We currently only enforce parameters if `params` is set in the frontmatter, +// until we can migrate existing `inputParameters` frontmatter to `params`. if (partial.data.params) { - const expected = partial.data.params; - if (!params) + const expected = partial.data.params.sort(); + const optional = expected.filter((p) => p.endsWith("?")); + const received = Object.keys(params ?? {}).sort(); + + const maximum = expected.length; + const minimum = maximum - optional.length; + + if ( + received.length < minimum || + received.length > maximum || + expected.some((p: string) => { + if (p.endsWith("?")) return false; + + return !received.includes(p); + }) + ) { throw new Error( - `${file} included on ${Astro.params.slug} expected parameters: ${expected}, got none`, + `[Render] Expected parameters ${JSON.stringify(expected)} but received parameters ${JSON.stringify(received)} for "${file}" included on "${Astro.url.pathname}"`, ); + } } const { Content } = await render(partial); diff --git a/src/content.config.ts b/src/content.config.ts index 5f8f1f01d0f4bb..3a7e99ec2fcd66 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -1,4 +1,4 @@ -import { z, defineCollection } from "astro:content"; +import { defineCollection } from "astro:content"; import { docsLoader, i18nLoader } from "@astrojs/starlight/loaders"; import { docsSchema, i18nSchema } from "@astrojs/starlight/schema"; @@ -20,6 +20,7 @@ import { warpReleasesSchema, changelogsNextSchema, fieldsSchema, + partialsSchema, } from "~/schemas"; function contentLoader(name: string) { @@ -36,10 +37,6 @@ function dataLoader(name: string) { }); } -const partialSchema = z.object({ - params: z.string().array().optional(), -}); - export const collections = { docs: defineCollection({ loader: docsLoader(), @@ -61,7 +58,7 @@ export const collections = { }), partials: defineCollection({ loader: contentLoader("partials"), - schema: partialSchema, + schema: partialsSchema, }), glossary: defineCollection({ loader: dataLoader("glossary"), diff --git a/src/content/docs/cloudflare-one/identity/idp-integration/entra-id.mdx b/src/content/docs/cloudflare-one/identity/idp-integration/entra-id.mdx index 51d66ac873662d..e22cde97e6ad46 100644 --- a/src/content/docs/cloudflare-one/identity/idp-integration/entra-id.mdx +++ b/src/content/docs/cloudflare-one/identity/idp-integration/entra-id.mdx @@ -120,7 +120,7 @@ The Microsoft Entra ID integration allows you to synchronize IdP groups and auto ### 2. Configure SCIM in Entra ID diff --git a/src/content/docs/style-guide/components/markdown.mdx b/src/content/docs/style-guide/components/markdown.mdx index 38a51a2bfc435c..a40df4d0024c33 100644 --- a/src/content/docs/style-guide/components/markdown.mdx +++ b/src/content/docs/style-guide/components/markdown.mdx @@ -2,6 +2,12 @@ title: Markdown --- +:::caution +This component does not use Astro to render the Markdown, as a result it cannot use components, `~/assets/` images or code blocks. + +Where feasible, use [JSX](/style-guide/components/render/#properties-in-markdown-syntax) instead +::: + This component uses `marked` to turn the `text` prop into HTML. This is useful with, for example partial files that have variables you need to format. ```mdx live diff --git a/src/content/docs/style-guide/components/render.mdx b/src/content/docs/style-guide/components/render.mdx index 23069da3b383d6..57060bbe4d84a5 100644 --- a/src/content/docs/style-guide/components/render.mdx +++ b/src/content/docs/style-guide/components/render.mdx @@ -2,90 +2,174 @@ title: Render --- -import { Steps } from "~/components"; +import { Code, Details, Type, MetaInfo } from "~/components"; The `Render` component allows us to include a "partial", a reusable Markdown snippet, onto a page. -It also accepts parameters that can be used as variables within the partial, so that even content -which needs slight differences between usages can be turned into a partial. +It also accepts parameters that can be used as variables within the partial, so that even content which needs slight differences between usages can be turned into a partial. ## Component ```mdx live import { Render } from "~/components"; - -{/* -Hello, {props.name}! +``` -Hello `{props.name}` +### Inputs -Hello {props.name} +- `file` -[link]({props.link}) + This should be the name of the partial, without the containing directory or file extension. For example, `/partials/style-guide/hello.mdx` would be `file="hello"`. -link -*/} -``` +- `product` -### Inputs + By default, it will look for partials in the same product folder as the current page. You can use this to specify a different product. + + :::caution + + When using the `Render` component inside partials, the original `product` is lost. + + For example, if there are three files: + + 1. `docs/fundamentals/index.mdx` + 2. `partials/dns/thing.mdx` + 3. `partials/dns/thing2.mdx` -**file** `string`: This should be the name of the partial, without the containing directory or file extension. -For example, `/partials/style-guide/hello.mdx` would be `file="hello"`. + `docs/fundamentals/index.mdx` uses `` -**product** `string` (optional): By default, it will look for partials in the same product folder as the current page. -You can use this to specify a different product. + `partials/dns/thing.mdx` must use `` as `product` cannot be inferred. -**params** `object` (optional): If you wish to substitute values inside your partial, you can use pass params which can be -referenced in your partial. Refer to [params](#params). + ::: -## Partials +- `params` -Partials only have one optional frontmatter property, which is `params`. This takes an array of strings, -which should be the expected parameters. When this is defined, but those parameters are not passed, an error -will be thrown. + If you wish to substitute values inside your partial, you can use pass params which can be referenced in your partial. Refer to [properties](#properties). -```mdx title="/src/content/partials/style-guide/hello.mdx" +## Properties + +### Defining expected properties in frontmatter + +Anything defined in the `params` property of the `Render` component is available inside the partial, using [JavaScript expressions](https://mdxjs.com/docs/using-mdx/). + +To protect against required properties being missed, any partial that relies on `params` should also define `params` in the partial's frontmatter. This should be an array of strings, matching the property names you expect. If a property is optional, such as for [conditional content](#properties-to-render-content-conditionally), add a `?` to the end of the name. + +```mdx --- params: - - name - - foo - - bar + - product + - deprecated? --- +``` + +For each of the below examples, you can open the dropdown to view the partial's content. + +### Properties as a plain string + +The below example would render `Hello, world!`. + +import simpleRaw from "~/content/partials/style-guide/simple-props.mdx?raw"; + +
+ +
+ +```mdx live +import { Render } from "~/components"; + + +``` + +### Properties in Markdown syntax + +When using JavaScript expressions, you are now "inside JSX" and cannot use traditional Markdown syntax. Similarly, you cannot use a JavaScript expression inside Markdown syntax. + +Ideally, you should not use Markdown syntax, such as `**strong**` or `[text](link)`, with properties. If using JSX is not feasible, there is a [`Markdown`](/style-guide/components/markdown/) component that will take a `text` property. + +The [MDX documentation](https://mdxjs.com/table-of-components/#components) includes a mapping of common Markdown syntax to their equivalent JSX elements. + +#### Strong + +import strongRaw from "~/content/partials/style-guide/strong-in-props.mdx?raw"; -Hello, {props.name}! +
+ +
-Hello, {props.foo}! +```mdx live +import { Render } from "~/components"; -Hello, {props.bar}! + ``` -### Params +#### Links + +import linkRaw from "~/content/partials/style-guide/link-in-props.mdx?raw"; + +
+ +
-In the above example, you will notice there is: +```mdx live +import { Render } from "~/components"; - + +``` -1. A `params` input on the `Render` component. -2. A `params` property in the frontmatter. -3. A reference to `props.name`. +#### Images + +import imageRaw from "~/content/partials/style-guide/image-in-props.mdx?raw"; + +
+ +
+ +```mdx live +import { Render } from "~/components"; + + +``` -
+#### Code blocks -#### Input +import codeRaw from "~/content/partials/style-guide/code-in-props.mdx?raw"; + +
+ +
+ +```mdx live +import { Render } from "~/components"; + + +``` + +### Properties to render content conditionally + +Anything that you can represent in a JavaScript expression can be used in your conditional logic. + +This may be the [and (`&&`) operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND) or [ternary (`? ... : ... `) operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_operator), the below example uses both. + +import optionalRaw from "~/content/partials/style-guide/optional-props.mdx?raw"; + +
+ +
+ +```mdx live +import { Render } from "~/components"; -When using the `params` input on the `Render` component, you can write a [JavaScript object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_objects) -which is later available inside the partial. + -#### Frontmatter +
-The `params` frontmatter property on a partial expects an array of strings, containing the "expected parameters". This is so that if -your partial requires parameters to be passed, and none are passed, we can warn the user. + -#### Props +
-The way that you can access these parameters is with the `props` object, surrounded in curly braces `{}`. -This is a [JavaScript expression within MDX](https://mdxjs.com/docs/using-mdx/#props). + +``` \ No newline at end of file diff --git a/src/content/partials/cloudflare-one/access/enable-scim-on-dashboard.mdx b/src/content/partials/cloudflare-one/access/enable-scim-on-dashboard.mdx index 3878ec8c43e369..ccd70df213801f 100644 --- a/src/content/partials/cloudflare-one/access/enable-scim-on-dashboard.mdx +++ b/src/content/partials/cloudflare-one/access/enable-scim-on-dashboard.mdx @@ -1,8 +1,7 @@ --- params: - idp - - and - - supportgroups + - supportgroups? --- import { Markdown } from "~/components" @@ -11,7 +10,7 @@ import { Markdown } from "~/components" 2. Find the {props.idp} integration and select **Edit**. -3. Turn on **Enable SCIM**{props.and}**{props.supportgroups}**. +3. Turn on **Enable SCIM** {props.supportgroups && and props.supportgroups.} 4. (Optional) Configure the following settings: diff --git a/src/content/partials/style-guide/code-in-props.mdx b/src/content/partials/style-guide/code-in-props.mdx new file mode 100644 index 00000000000000..12bd2c918db135 --- /dev/null +++ b/src/content/partials/style-guide/code-in-props.mdx @@ -0,0 +1,30 @@ +--- +params: + - code +--- + +import { Code } from "~/components"; + +#### Inline + +**Don't do this!** + +`{props.code}` + +**Do this!** + +

{props.code}

+ +
+ +#### Codeblocks + +**Don't do this!** + +```js +{props.code} +``` + +**Do this!** + + \ No newline at end of file diff --git a/src/content/partials/style-guide/hello.mdx b/src/content/partials/style-guide/hello.mdx deleted file mode 100644 index 2fefdcfb8641b7..00000000000000 --- a/src/content/partials/style-guide/hello.mdx +++ /dev/null @@ -1,14 +0,0 @@ ---- -params: - - name ---- - -Hello, {props.name}! - -Hello `{props.name}` - -Hello {props.name} - -[link]({props.link}) (href="%7Bprops.link%7D") - -link (href="/style-guide/components/render/") \ No newline at end of file diff --git a/src/content/partials/style-guide/image-in-props.mdx b/src/content/partials/style-guide/image-in-props.mdx new file mode 100644 index 00000000000000..cca4506f76e98c --- /dev/null +++ b/src/content/partials/style-guide/image-in-props.mdx @@ -0,0 +1,12 @@ +--- +params: + - image +--- + +**Don't do this!** + +`![Alt text]({props.image})` + +**Do this!** + +Alt text \ No newline at end of file diff --git a/src/content/partials/style-guide/link-in-props.mdx b/src/content/partials/style-guide/link-in-props.mdx new file mode 100644 index 00000000000000..75e80a9ba4bab7 --- /dev/null +++ b/src/content/partials/style-guide/link-in-props.mdx @@ -0,0 +1,16 @@ +--- +params: + - link +--- + +**Don't do this!** + +This will link to `/style-guide/components/%7Bprops.link%7D`. + +[Markdown link]({props.link}) + +**Do this!** + +This will link to `style-guide/components/render/#links`. + +

JSX link

\ No newline at end of file diff --git a/src/content/partials/style-guide/optional-props.mdx b/src/content/partials/style-guide/optional-props.mdx new file mode 100644 index 00000000000000..6c6256808676ea --- /dev/null +++ b/src/content/partials/style-guide/optional-props.mdx @@ -0,0 +1,23 @@ +--- +params: + - product + - deprecated? +--- + +{ + props.deprecated && ( +

+ + {props.product} is deprecated, please use alternative products. + +

+ ) +} + +{ + props.product === "Thing Three" ? ( +

Welcome to our Thing Three launch countdown!

+ ) : ( +

Welcome to the {props.product} landing page.

+ ) +} diff --git a/src/content/partials/style-guide/simple-props.mdx b/src/content/partials/style-guide/simple-props.mdx new file mode 100644 index 00000000000000..89a65604442455 --- /dev/null +++ b/src/content/partials/style-guide/simple-props.mdx @@ -0,0 +1,6 @@ +--- +params: + - name +--- + +Hello, {props.name}! \ No newline at end of file diff --git a/src/content/partials/style-guide/strong-in-props.mdx b/src/content/partials/style-guide/strong-in-props.mdx new file mode 100644 index 00000000000000..70fdd5cc28422c --- /dev/null +++ b/src/content/partials/style-guide/strong-in-props.mdx @@ -0,0 +1,13 @@ +--- +params: + - dont + - do +--- + +**Don't do this!** + +{props.dont} + +**Do this!** + +{props.do} \ No newline at end of file diff --git a/src/schemas/partials.ts b/src/schemas/partials.ts index 0acc176a4fb068..58e1c607553521 100644 --- a/src/schemas/partials.ts +++ b/src/schemas/partials.ts @@ -1,5 +1,26 @@ import { z } from "astro:schema"; -export const partialSchema = z.object({ - params: z.string().array().optional(), -}); +const paramsDocs = + "https://developers.cloudflare.com/style-guide/components/render/#defining-expected-properties-in-frontmatter"; + +export const partialsSchema = z + .object({ + slug: z + .string() + .optional() + .describe( + "Used to define custom IDs: https://docs.astro.build/en/guides/content-collections/#defining-custom-ids", + ), + params: z + .string() + .array() + .optional() + .describe(`Used to define expected properties: ${paramsDocs}`), + inputParameters: z + .string() + .optional() + .describe( + `Deprecated - this field has no functionality, please migrate to ${paramsDocs}`, + ), + }) + .strict();