docs: add line highlight validation (#14380)

* docs: add line highlight validation

* add to all projects

* fixes

* fixes

* fix

* fixes
This commit is contained in:
Shahed Nasser
2025-12-22 14:10:39 +02:00
committed by GitHub
parent 6380c1fdf4
commit cb33388202
59 changed files with 514 additions and 243 deletions

View File

@@ -5,6 +5,7 @@ import rehypeSlug from "rehype-slug"
import {
brokenLinkCheckerPlugin,
crossProjectLinksPlugin,
validateHighlightsPlugin,
} from "remark-rehype-plugins"
import path from "path"
import { catchBadRedirects } from "build-scripts"
@@ -93,6 +94,7 @@ const withMDX = createMDX({
tagName: "code",
},
],
[validateHighlightsPlugin, { verbose: false }],
[rehypeSlug],
],
development: process.env.NODE_ENV === "development",

View File

@@ -197,10 +197,10 @@ So, to add the UI route at the `localhost:9000/app/brands` path, create the file
![Directory structure of the Medusa application after adding the UI route.](https://res.cloudinary.com/dza7lstvk/image/upload/v1733472011/Medusa%20Book/brands-admin-dir-overview-3_syytld.jpg)
export const uiRouteHighlights = [
["8", "BrandsPage", "The UI route that displays a new page."],
["23", "defineRouteConfig", "Export config to add a link for the UI route in the sidebar."],
["24", "label", "The sidebar item's label."],
["25", "icon", "The sidebar item's icon."]
["10", "BrandsPage", "The UI route that displays a new page."],
["20", "defineRouteConfig", "Export config to add a link for the UI route in the sidebar."],
["21", "label", "The sidebar item's label."],
["22", "icon", "The sidebar item's icon."]
]
```tsx title="src/admin/routes/brands/page.tsx" highlights={uiRouteHighlights}

View File

@@ -56,7 +56,7 @@ Learn how to create a middleware in the [Middlewares](../middlewares/page.mdx) c
</Note>
export const highlights = [
["10", "req.allowed", "Modify the allowed fields and relations to be retrieved"],
["9", "req.allowed", "Modify the allowed fields and relations to be retrieved"],
]
```ts title="src/api/middlewares.ts" highlights={highlights}

View File

@@ -49,8 +49,8 @@ When you create a record of a data model that has one of another, pass the ID of
For example, assuming you have the [User and Email data models from the previous chapter](../relationships/page.mdx#one-to-one-relationship), set a user's email ID as follows:
export const hasOneHighlights = [
["4", "email_id", "The ID of the email that the user has."],
["11", "email_id", "The ID of the email that the user has."]
["4", "email", "The ID of the email that the user has."],
["11", "email", "The ID of the email that the user has."]
]
```ts highlights={hasOneHighlights}

View File

@@ -576,7 +576,7 @@ You can also create a `listAndCount` method to retrieve the posts with paginatio
Now, you can use Query to retrieve a product and its linked post from the CMS:
export const queryHighlights = [
["3", `"cms_post"`, "The `alias` of the virtual data model in the link configuration."]
["3", `"cms_post.*"`, "The `alias` of the virtual data model in the link configuration."]
]
```ts highlights={queryHighlights}

View File

@@ -81,7 +81,7 @@ You define a service in a `service.ts` or `service.js` file at the root of your
export const highlights = [
["4", "MedusaService", "The service factory function."],
["5", "MyCustom", "The data models to generate data-management methods for."]
["5", "Post", "The data models to generate data-management methods for."]
]
```ts title="src/modules/blog/service.ts" highlights={highlights}

View File

@@ -9,6 +9,7 @@ import {
crossProjectLinksPlugin,
recmaInjectMdxDataPlugin,
remarkAttachFrontmatterDataPlugin,
validateHighlightsPlugin,
} from "remark-rehype-plugins"
import path from "path"
import redirects from "./utils/redirects.mjs"
@@ -78,6 +79,7 @@ const withMDX = mdx({
tagName: "code",
},
],
[validateHighlightsPlugin, { verbose: false }],
[rehypeSlug],
[
cloudinaryImgRehypePlugin,

View File

@@ -13,6 +13,7 @@ import {
prerequisitesLinkFixerPlugin,
remarkAttachFrontmatterDataPlugin,
recmaInjectMdxDataPlugin,
validateHighlightsPlugin,
} from "remark-rehype-plugins"
import bundleAnalyzer from "@next/bundle-analyzer"
import withExtractedTableOfContents from "@stefanprobst/rehype-extract-toc"
@@ -79,6 +80,7 @@ const withMDX = mdx({
tagName: "code",
},
],
[validateHighlightsPlugin, { verbose: false }],
[rehypeSlug],
[
cloudinaryImgRehypePlugin,

View File

@@ -229,7 +229,7 @@ Using [workflows](!docs!/learn/fundamentals/workflows), you can implement this b
export const bundledHighlights1 = [
["11", "products", "Create the products part of the bundle."],
["28", "manage_inventory", "Enabling this without specifying inventory items creates a default inventory item."]
["29", "manage_inventory", "Enabling this without specifying inventory items creates a default inventory item."]
]
```ts highlights={bundledHighlights1}
@@ -390,7 +390,7 @@ Finally, create the bundled product:
export const bundledProductHighlights3 = [
["5", "bundledProduct", "Create the bundled product."],
["22", "inventory_items", "Pass the inventory items of the products part of the bundle."]
["23", "inventory_items", "Pass the inventory items of the products part of the bundle."]
]
```ts highlights={bundledProductHighlights3}

View File

@@ -2884,9 +2884,9 @@ Create the file `src/workflows/merchant-send-quote.ts` with the following conten
![Directory structure after adding the merchant send quote workflow file](https://res.cloudinary.com/dza7lstvk/image/upload/v1741162342/Medusa%20Resources/quote-38_n4ksr0.jpg)
export const sendQuoteHighlights = [
["15", "useQueryGraphStep", "Retrieve the quote's details."],
["24", "validateQuoteNotAccepted", "Validate that the quote isn't already accepted by the customer."],
["29", "updateQuotesStep", "Update the quote's status to `pending_customer`."]
["14", "useQueryGraphStep", "Retrieve the quote's details."],
["23", "validateQuoteNotAccepted", "Validate that the quote isn't already accepted by the customer."],
["28", "updateQuotesStep", "Update the quote's status to `pending_customer`."]
]
```ts title="src/workflows/merchant-send-quote.ts" highlights={sendQuoteHighlights}

View File

@@ -1415,8 +1415,8 @@ To create the workflow, create the file `src/workflows/apply-loyalty-on-cart.ts`
export const applyLoyaltyOnCartWorkflowHighlights = [
["46", "useQueryGraphStep", "Retrieve the cart's details."],
["57", "validateCustomerExistsStep", "Validate that the customer is registered."],
["59", "getCartLoyaltyPromoStep", "Retrieve the cart's loyalty promotion."],
["61", "acquireLockStep", "Acquire a lock on the cart to prevent concurrent modifications."],
["61", "getCartLoyaltyPromoStep", "Retrieve the cart's loyalty promotion."],
["66", "acquireLockStep", "Acquire a lock on the cart to prevent concurrent modifications."],
["72", "getCartLoyaltyPromoAmountStep", "Get the amount to be discounted based on the loyalty points."],
]

View File

@@ -917,11 +917,11 @@ A Notification Module Provider has a service that contains the sending logic. Th
So, create the file `src/modules/twilio-sms/service.ts` with the following content:
export const twilioSmsServiceHighlights = [
["8", "accountSid", "Twilio account SID."],
["9", "authToken", "Twilio auth token."],
["10", "from", "Twilio phone number to send the SMS from."],
["14", "identifier", "Unique identifier for the module."],
["15", "client", "Twilio client to send the SMS."],
["9", "accountSid", "Twilio account SID."],
["10", "authToken", "Twilio auth token."],
["11", "from", "Twilio phone number to send the SMS from."],
["15", "identifier", "Unique identifier for the module."],
["16", "client", "Twilio client to send the SMS."],
]
```ts title="src/modules/twilio-sms/service.ts" highlights={twilioSmsServiceHighlights}

View File

@@ -538,12 +538,12 @@ If you get a type error on resolving the Preorder Module, run the Medusa applica
</Note>
export const updatePreorderVariantStepHighlights = [
["16", "preorderModuleService", "Resolve the Preorder Module's service from the Medusa container."],
["20", "oldData", "Retrieve existing record to undo updates if an error occurs."],
["24", "preorderVariant", "Update the pre-order variant."],
["28", "preorderVariant", "Return the updated pre-order variant."],
["28", "oldData", "Pass the old data to the compensation function."],
["39", "updatePreorderVariants", "Undo updates if an error occurs during the workflow execution."]
["17", "preorderModuleService", "Resolve the Preorder Module's service from the Medusa container."],
["21", "oldData", "Retrieve existing record to undo updates if an error occurs."],
["25", "preorderVariant", "Update the pre-order variant."],
["29", "preorderVariant", "Return the updated pre-order variant."],
["29", "oldData", "Pass the old data to the compensation function."],
["40", "updatePreorderVariants", "Undo updates if an error occurs during the workflow execution."]
]
```ts title="src/workflows/steps/update-preorder-variant.ts" highlights={updatePreorderVariantStepHighlights}
@@ -623,10 +623,10 @@ The `createPreorderVariantStep` creates a pre-order variant record.
To create the step, create the file `src/workflows/steps/create-preorder-variant.ts` with the following content:
export const createPreorderVariantStepHighlights = [
["15", "preorderVariant", "Create the pre-order variant."],
["19", "preorderVariant", "Return the created pre-order variant."],
["19", "id", "Pass the ID to the compensation function."],
["30", "deletePreorderVariants", "Delete the pre-order variant if an error occurs during the workflow execution."]
["16", "preorderVariant", "Create the pre-order variant."],
["20", "preorderVariant", "Return the created pre-order variant."],
["20", "id", "Pass the ID to the compensation function."],
["31", "deletePreorderVariants", "Delete the pre-order variant if an error occurs during the workflow execution."]
]
```ts title="src/workflows/steps/create-preorder-variant.ts" highlights={createPreorderVariantStepHighlights}
@@ -811,7 +811,7 @@ export const upsertPreorderVariantRouteHighlights = [
["10", "UpsertPreorderVariantSchema", "The schema to validate the request body."],
["18", "POST", "Expose a POST API route."],
["24", "upsertProductVariantPreorderWorkflow", "Execute the workflow to create or update pre-order variant configurations."],
["32", "preorder_variant", "Return the created or updated pre-order variant in the response."],
["33", "preorder_variant", "Return the created or updated pre-order variant in the response."],
]
```ts title="src/api/admin/variants/[id]/preorders/route.ts" highlights={upsertPreorderVariantRouteHighlights}
@@ -944,12 +944,12 @@ The `disablePreorderVariantStep` changes the status of a pre-order variant recor
Create the file `src/workflows/steps/disable-preorder-variant.ts` with the following content:
export const disablePreorderVariantStepHighlights = [
["15", "oldData", "Retrieve existing record to undo updates if an error occurs."],
["17", "preorderVariant", "Update the pre-order variant."],
["19", "DISABLED", "Set the pre-order variant's status to `disabled`."],
["22", "preorderVariant", "Return the updated pre-order variant."],
["22", "oldData", "Pass the old data to the compensation function."],
["31", "updatePreorderVariants", "Undo updates if an error occurs during the workflow execution."]
["16", "oldData", "Retrieve existing record to undo updates if an error occurs."],
["18", "preorderVariant", "Update the pre-order variant."],
["20", "DISABLED", "Set the pre-order variant's status to `disabled`."],
["23", "preorderVariant", "Return the updated pre-order variant."],
["23", "oldData", "Pass the old data to the compensation function."],
["32", "updatePreorderVariants", "Undo updates if an error occurs during the workflow execution."]
]
```ts title="src/workflows/steps/disable-preorder-variant.ts" highlights={disablePreorderVariantStepHighlights}
@@ -1688,9 +1688,9 @@ The `createPreordersStep` creates a `Preorder` record for each pre-order variant
Create the file `src/workflows/steps/create-preorders.ts` with the following content:
export const createPreordersStepHighlights = [
["16", "preorders", "Create pre-orders."],
["23", "preorders", "Return the pre-orders."],
["32", "deletePreorders", "Delete pre-orders if an error occurs during the workflow execution."],
["17", "preorders", "Create pre-orders."],
["24", "preorders", "Return the pre-orders."],
["33", "deletePreorders", "Delete pre-orders if an error occurs during the workflow execution."],
]
```ts title="src/workflows/steps/create-preorders.ts" highlights={createPreordersStepHighlights}
@@ -1927,7 +1927,7 @@ export const validateCartHookHighlights = [
["32", "variantsToAdd", "Retrieve the pre-order variants of the new items being added to the cart."],
["42", "cartHasPreorderVariants", "Check if the cart has pre-order variants."],
["48", "newItemsHavePreorderVariants", "Check if the new items being added have pre-order variants."],
["55", "throw MedusaError", "Throw an error if the cart has mixed pre-order and available items."],
["55", "MedusaError", "Throw an error if the cart has mixed pre-order and available items."],
]
```ts title="src/workflows/hooks/validate-cart.ts" highlights={validateCartHookHighlights}
@@ -2759,12 +2759,12 @@ The `updatePreordersStep` updates the details of pre-order records.
Create the file `src/workflows/steps/update-preorders.ts` with the following content:
export const updatePreordersHighlights = [
["13", "preorders", "The pre-orders to update."],
["16", "oldPreorders", "The pre-orders before the update."],
["20", "updatedPreorders", "Update the pre-orders."],
["24", "updatedPreorders", "Return the updated pre-orders."],
["24", "oldPreorders", "Pass the old pre-orders to the compensation function."],
["33", "updatePreorders", "Revert the pre-order updates if an error occurs during the workflow's execution."]
["14", "preorders", "The pre-orders to update."],
["17", "oldPreorders", "The pre-orders before the update."],
["21", "updatedPreorders", "Update the pre-orders."],
["25", "updatedPreorders", "Return the updated pre-orders."],
["25", "oldPreorders", "Pass the old pre-orders to the compensation function."],
["34", "updatePreorders", "Revert the pre-order updates if an error occurs during the workflow's execution."]
]
```ts title="src/workflows/steps/update-preorders.ts" highlights={updatePreordersHighlights}
@@ -2964,14 +2964,7 @@ export const retrieveItemsToFulfillStep = createStep(
Then, update the workflow to the following:
export const fulfillPreorderWorkflowHighlights2 = [
["22"], ["23"], ["24"], ["25"], ["37"], ["55"], ["56"], ["57"], ["58"],
["59"], ["60"], ["61"], ["62"], ["63"], ["64"], ["65"], ["66"], ["67"],
["68"], ["69"], ["70"], ["71"], ["72"], ["73"], ["74"], ["75"], ["76"],
["77"], ["78"], ["79"], ["80"],
]
```ts title="src/workflows/fulfill-preorder.ts" highlights={fulfillPreorderWorkflowHighlights2}
```ts title="src/workflows/fulfill-preorder.ts"
import { InferTypeOf } from "@medusajs/framework/types"
import { PreorderVariant } from "../modules/preorder/models/preorder-variant"
import { createWorkflow, transform, when, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
@@ -3135,7 +3128,7 @@ export const fulfillPreordersJobHighlights2 = [
["24", "available_date", "Retrieve only the pre-order variants available today."],
["43", "unfulfilledPreorders", "The unfulfilled pre-orders retrieved."],
["44", "preorderMetadata", "The pagination details of the pre-order query."],
["66", "fulfillPreorderWorkflow.run", "Fulfill a pre-order of the variant."],
["66", "fulfillPreorderWorkflow", "Fulfill a pre-order of the variant."],
]
```ts title="src/jobs/fulfill-preorders.ts" highlights={fulfillPreordersJobHighlights2}
@@ -3498,7 +3491,7 @@ export const orderCanceledHighlights = [
["14", "workflowInput", "Prepare the input to pass to the workflow."],
["24", "preorders", "Retrieve the pre-orders of the order."],
["41", "push", "Add the pre-orders to the workflow input."],
["46", "cancelPreordersWorkflow.run", "Execute the cancel pre-orders workflow."],
["46", "cancelPreordersWorkflow", "Execute the cancel pre-orders workflow."],
]
```ts title="src/subscribers/order-canceled.ts" highlights={orderCanceledHighlights}
@@ -3642,10 +3635,7 @@ Create the file `src/workflows/steps/retrieve-preorder-updates.ts` with the foll
export const retrievePreorderUpdatesStepHighlights = [
["20", "preordersToCancel", "The pre-orders to cancel."],
["24", "preordersToCreate", "The pre-orders to create."],
["20", "preorder_variant", "The pre-order variant associated with the item."],
["32", "", "Find the new pre-order items."],
["44", "push", "Add the pre-order variant to the pre-orders to create."],
["50", "", "Find the pre-order items that need to be canceled."],
["55", "push", "Add the pre-order to the pre-orders to cancel."],
]

View File

@@ -5540,8 +5540,6 @@ You also need to update the components that use the `Item` component to pass the
In `src/modules/cart/templates/items.tsx`, replace the `return` statement with the following:
export const itemsTemplateHighlights = [
["13", "className"],
["17", "className"],
["34", "cartItems", "Pass new prop"]
]
@@ -5669,12 +5667,7 @@ You calculate the total items by summing the quantities of the `filteredItems`,
Finally, in the `return` statement, replace all usages of `cartState.items` with `filteredItems`, and remove the children element of the `DeleteButton` for better styling:
export const cartDropdownHighlights = [
["6", "filteredItems"], ["9", "filteredItems"],
["21"], ["22"], ["23"], ["24"], ["25"]
]
```tsx title="src/modules/layout/components/cart-dropdown/index.tsx" badgeLabel="Storefront" badgeColor="blue" highlights={cartDropdownHighlights}
```tsx title="src/modules/layout/components/cart-dropdown/index.tsx" badgeLabel="Storefront" badgeColor="blue" highlights={[["6"], ["9"], ["21"], ["22"], ["23"], ["24"], ["25"]]}
return (
<div
// ...
@@ -5789,7 +5782,7 @@ export const removeProductBuilderFromCartWorkflowHighlights = [
["40", "itemsToRemove", "Identify items to remove."],
["51", "relatedItems", "Identify addon items to remove."],
["68", "deleteLineItemsWorkflow", "Delete line items from cart."],
["75", "updatedCart", "Retrieve updated cart."],
["73", "updatedCart", "Retrieve updated cart."],
["84", "releaseLockStep", "Release lock on cart."],
]
@@ -5992,7 +5985,6 @@ Then, pass an `isBuilderConfigItem` prop to the `DeleteButton` component, and up
export const deleteButtonHighlights = [
["3", "isBuilderConfigItem", "New prop to determine if item belongs to a product with builder configurations."],
["6"],
["12", "removeBuilderLineItem", "Use when the line item belongs to a product with builder configurations."],
]

View File

@@ -28,7 +28,7 @@ The Redis Cache Module is deprecated starting from [Medusa v2.11.0](https://gith
Add the module into the `modules` property of the exported object in `medusa-config.ts`:
export const highlights = [
["11", "redisUrl", "The Redis connection URL."]
["7", "redisUrl", "The Redis connection URL."]
]
```ts title="medusa-config.ts" highlights={highlights}

View File

@@ -28,14 +28,10 @@ Our Cloud offering automatically provisions a Redis instance and configures the
Add the module into the `modules` property of the exported object in `medusa-config.ts`:
export const highlights = [
["12", "url", "The Redis connection URL."]
["8", "redisUrl", "The Redis connection URL."]
]
```ts title="medusa-config.ts" highlights={highlights}
import { Modules } from "@medusajs/framework/utils"
// ...
module.exports = defineConfig({
// ...
modules: [

View File

@@ -971,9 +971,9 @@ In `src/modules/avalara/service.ts`, add the following methods to the `AvalaraTa
export const avalaraItemMethodsHighlights = [
["3", "createItems", "Create multiple items in Avalara using the Create Items API."],
["36", "getItem", "Retrieve an item from Avalara using the Get Item API."],
["53", "updateItem", "Update an item in Avalara using the Update Item API."],
["82", "deleteItem", "Delete an item in Avalara using the Delete Item API."]
["35", "getItem", "Retrieve an item from Avalara using the Get Item API."],
["51", "updateItem", "Update an item in Avalara using the Update Item API."],
["79", "deleteItem", "Delete an item in Avalara using the Delete Item API."]
]
```ts title="src/modules/avalara/service.ts" highlights={avalaraItemMethodsHighlights}

View File

@@ -2380,7 +2380,7 @@ So, create the file `src/api/store/products/[id]/[locale]/route.ts` with the fol
export const getProductLocaleDetailsRouteHighlights = [
["11", "locale", "Retrieve the locale from the request's path parameters."],
["12", "id", "Retrieve the product's ID from the request's path parameters."],
["11", "id", "Retrieve the product's ID from the request's path parameters."],
["13", "query", "Resolve Query from the Medusa container."],
["15", "data", "Retrieve the product's details from Contentful."],
["19", `"contentful_product.*"`, "Retrieve the product's details from Contentful."],

View File

@@ -31,7 +31,7 @@ This guide was built with Payload v3.54.0. If you're using a different version a
## Summary
By following this tutorial, you'll learn how to:
By following this tutorial, you'll learn how t"o:
- Install and set up Medusa.
- Set up Payload in the Next.js Starter Storefront.
@@ -357,13 +357,13 @@ Finally, you'll add the `Product` collection, which will be synced with Medusa's
Create the file `src/collections/Products.ts` with the following content:
export const productCollectionHighlights = [
["21", "update", "Only allow updating from Medusa"],
["20", "update", "Only allow updating from Medusa"],
["144", "update", "Only allow updating from Medusa"],
["173", "update", "Only allow updating from Medusa"],
["190", "update", "Only allow updating from Medusa"],
["203", "update", "Only allow updating from Medusa"],
["224", "create", "Only allow creating from Medusa"],
["225", "delete", "Only allow deleting from Medusa"]
["172", "update", "Only allow updating from Medusa"],
["189", "update", "Only allow updating from Medusa"],
["202", "update", "Only allow updating from Medusa"],
["223", "create", "Only allow creating from Medusa"],
["224", "delete", "Only allow deleting from Medusa"]
]
```ts title="src/collections/Products.ts" badgeLabel="Storefront" badgeColor="blue" highlights={productCollectionHighlights}

View File

@@ -681,9 +681,9 @@ If you get a type error on resolving the Sanity Module, run the Medusa applicati
</Note>
export const syncStepHighlights = [
["13", "createStep", "Create a step."],
["15", "container", "The Medusa container to resolve resources."],
["36", "graph", "Retrieve a paginated list of products with their sanity document."]
["14", "createStep", "Create a step."],
["16", "container", "The Medusa container to resolve resources."],
["37", "graph", "Retrieve a paginated list of products with their sanity document."]
]
```ts title="src/workflows/sanity-sync-products/steps/sync.ts" highlights={syncStepHighlights}
@@ -1205,7 +1205,7 @@ Now that you're managing a product's content in Sanity, you want to show that co
A product's details are retrieved in the file `src/app/[countryCode]/(main)/products/[handle]/page.tsx`. So, replace the `ProductPage` function with the following:
export const sanityContentHighlights = [
["19", "sanity", "Get the product's content from Sanity."]
["24", "sanity", "Get the product's content from Sanity."]
]
```tsx title="src/app/[countryCode]/(main)/products/[handle]/page.tsx" badgeLabel="Storefront" badgeColor="blue" highlights={sanityContentHighlights}

View File

@@ -800,10 +800,10 @@ Finally, add the `calculatePrice` method to `ShipStationProviderService`:
export const serviceHighlights5 = [
["14", "shipment_id", "Retrieve the shipping method's shipment ID."],
["20", "createShipment", "If a shipment isn't already created, create the shipment and get its rates."],
["23", "getShipmentRates", "If a shipment is already created, retrieve its rates."],
["27", "calculatedPrice", "Calculate the price from the specified rate."],
["34", "is_calculated_price_tax_inclusive", "Return whether the price includes taxes."]
["24", "createShipment", "If a shipment isn't already created, create the shipment and get its rates."],
["37", "getShipmentRates", "If a shipment is already created, retrieve its rates."],
["41", "calculatedPrice", "Calculate the price from the specified rate."],
["47", "is_calculated_price_tax_inclusive", "Return whether the price includes taxes."]
]
```ts title="src/modules/shipstation/service.ts" highlights={serviceHighlights5}
@@ -892,8 +892,8 @@ Add the `validateFulfillmentData` method to `ShipStationProviderService`:
export const serviceHighlights4 = [
["8", "shipment_id", "Get the ID of an already created shipment, if available."],
["14", "createShipment", "Create a shipment if it doesn't already exist."],
["20", "shipment_id", "Store the shipment ID in the shipping method's `data`."]
["17", "createShipment", "Create a shipment if it doesn't already exist."],
["38", "shipment_id", "Store the shipment ID in the shipping method's `data`."]
]
```ts title="src/modules/shipstation/service.ts" highlights={serviceHighlights4}

View File

@@ -657,13 +657,13 @@ The component will display the available shipping options for returns, along wit
To create the component, create the file `src/modules/account/components/return-shipping-selector/index.tsx` with the following content:
export const returnShippingSelectorHighlights = [
["13", "shippingOptions", "The available shipping options for returns."],
["14", "selectedOption", "The currently selected shipping option ID."],
["15", "onOptionSelect", "Callback function when a shipping option is selected."],
["16", "cartId", "The ID of the cart associated with the order being returned."],
["17", "currencyCode", "The currency code for displaying prices."],
["32", "useEffect", "Fetches calculated prices for shipping options with price type 'calculated'."],
["58", "", "Renders a message if no shipping options are available."],
["16", "shippingOptions", "The available shipping options for returns."],
["17", "selectedOption", "The currently selected shipping option ID."],
["18", "onOptionSelect", "Callback function when a shipping option is selected."],
["19", "cartId", "The ID of the cart associated with the order being returned."],
["20", "currencyCode", "The currency code for displaying prices."],
["35", "useEffect", "Fetches calculated prices for shipping options with price type 'calculated'."],
["61", "", "Renders a message if no shipping options are available."],
]
```tsx title="src/modules/account/components/return-shipping-selector/index.tsx" highlights={returnShippingSelectorHighlights} collapsibleLines="1-11" expandButtonLabel="Show Imports"

View File

@@ -614,7 +614,7 @@ export const createWishlistStepHighlights = [
["16", "createWishlists", "Create the wishlist."],
["18", "wishlist", "Return the wishlist"],
["18", "wishlist.id", "Pass the wishlist's ID to the compensation function."],
["24", "deleteWishlists", "Delete the wishlist if an error occurs in the workflow."]
["27", "deleteWishlists", "Delete the wishlist if an error occurs in the workflow."]
]
```ts title="src/workflows/steps/create-wishlist.ts" highlights={createWishlistStepHighlights}
@@ -1140,7 +1140,7 @@ Create the file `src/workflows/steps/create-wishlist-item.ts` with the following
export const createWishlistItemStepHighlights = [
["16", "createWishlistItems", "Create the wishlist item."],
["24", "deleteWishlistItems", "Delete the wishlist item if an error occurs in the workflow."]
["27", "deleteWishlistItems", "Delete the wishlist item if an error occurs in the workflow."]
]
```ts title="src/workflows/steps/create-wishlist-item.ts" highlights={createWishlistItemStepHighlights}

View File

@@ -1297,8 +1297,8 @@ export const createBundledProductComponentHighlights2 = [
["3", "currentProductPage", "The current page of products retrieved from the server."],
["4", "productsCount", "The total number of products."],
["5", "hasNextPage", "Whether there are more products to load."],
["9", "useQuery", "Retrieve the products from the API route."],
["23", "fetchMoreProducts", "Fetch more products when the user scrolls to the end of the list."],
["10", "useQuery", "Retrieve the products from the API route."],
["24", "fetchMoreProducts", "Fetch more products when the user scrolls to the end of the list."],
]
```tsx title="src/admin/components/create-bundled-product.tsx" highlights={createBundledProductComponentHighlights2}
@@ -1799,7 +1799,7 @@ export const prepareBundleCartDataStepHighlights = [
["12", "bundle", "The bundle to add to the cart."],
["15", "quantity", "The quantity of the bundle to add to the cart."],
["16", "items", "The selected variants for each item in the bundle."],
["15", "bundleItems", "Prepare the items to be added to the cart."],
["25", "bundleItems", "Prepare the items to be added to the cart."],
["45", "variant_id", "The ID of the selected variant to add to the cart."],
["46", "quantity", "The quantity of the variant to add to the cart."],
["47", "metadata", "The metadata for the line item in the cart."],

View File

@@ -1887,9 +1887,9 @@ Create the file `src/workflows/create-digital-product-order/steps/create-digital
export const createDpoHighlights = [
["18", "InferTypeOf", "Infer the type of the `DigitalProduct` data model since it's a variable."],
["33", "createDigitalProductOrders", "Create the digital product order."],
["41", "digital_product_order", "Pass the created digital product order to the compensation function."],
["51", "deleteDigitalProductOrders", "Delete the digital product order if an error occurs in the workflow."]
["32", "createDigitalProductOrders", "Create the digital product order."],
["40", "digital_product_order", "Pass the created digital product order to the compensation function."],
["50", "deleteDigitalProductOrders", "Delete the digital product order if an error occurs in the workflow."]
]
```ts title="src/workflows/create-digital-product-order/steps/create-digital-product-order.ts" highlights={createDpoHighlights} collapsibleLines="1-14" expandMoreLabel="Show Imports"
@@ -2653,10 +2653,10 @@ Create the file `src/api/store/customers/me/digital-products/[mediaId]/download/
export const downloadUrlHighlights = [
["20", "query.graph", "Get the customer's orders and linked digital orders."],
["36", "query.graph", "Get the digital product orders of the customer and associated products and media."],
["56", "foundMedia", "Set `foundMedia` if the media's ID is equal to the ID passed as a route parameter."],
["65", "!foundMedia", "If `foundMedia` isn't set, throw an error."],
["72", "retrieveFile", "Retrieve the details of the media's file."]
["34", "query.graph", "Get the digital product orders of the customer and associated products and media."],
["55", "foundMedia", "Set `foundMedia` if the media's ID is equal to the ID passed as a route parameter."],
["61", "!foundMedia", "If `foundMedia` isn't set, throw an error."],
["68", "retrieveFile", "Retrieve the details of the media's file."]
]
```ts title="src/api/store/customers/me/digital-products/[mediaId]/download/route.ts" highlights={downloadUrlHighlights} collapsibleLines="1-10" expandMoreLabel="Show Imports"

View File

@@ -1751,7 +1751,7 @@ Create the file `src/workflows/delivery/steps/notify-restaurant.ts` with the fol
export const notifyRestaurantStepHighlights = [
["11", "async", "Set the step as async."],
["18", "graph", "Retrieve the delivery and its restaurant."],
["30", "emit", "Emit a custom event that can be used to notify the restaurant that a new delivery is created."]
["28", "emit", "Emit a custom event that can be used to notify the restaurant that a new delivery is created."]
]
```ts title="src/workflows/delivery/steps/notify-restaurant.ts" highlights={notifyRestaurantStepHighlights} collapsibleLines="1-6" expandButtonLabel="Show Imports"
@@ -1835,7 +1835,7 @@ This step is async and its only purpose is to wait until its marked as succes
Create the file `src/workflows/delivery/steps/create-order.ts` with the following content:
export const createOrderStepHighlights1 = [
["16", "deliveryQuery", "Retrieve the delivery with its linked cart's details."]
["15", "delivery", "Retrieve the delivery with its linked cart's details."]
]
```ts title="src/workflows/delivery/steps/create-order.ts" highlights={createOrderStepHighlights1} collapsibleLines="1-9" expandButtonLabel="Show Imports"

View File

@@ -585,11 +585,11 @@ You can now create the workflow that creates a vendor and its admin.
Create the file `src/workflows/marketplace/create-vendor/index.ts` with the following content:
export const vendorWorkflowHighlights = [
["27", "createVendorStep", "Create the vendor."],
["33", "transform", "Prepare the vendor admin's data."],
["43", "createVendorAdminStep", "Create the vendor admin."],
["47", "setAuthAppMetadataStep", "Create the `vendor` actor type."],
["53", "useQueryGraphStep", "Retrieve the created vendor with its admins."],
["28", "createVendorStep", "Create the vendor."],
["34", "transform", "Prepare the vendor admin's data."],
["44", "createVendorAdminStep", "Create the vendor admin."],
["48", "setAuthAppMetadataStep", "Create the `vendor` actor type."],
["54", "useQueryGraphStep", "Retrieve the created vendor with its admins."],
]
```ts title="src/workflows/marketplace/create-vendor/index.ts" highlights={vendorWorkflowHighlights}
@@ -1542,7 +1542,6 @@ export const vendorOrder3Highlights = [
["3", "map", "Loop over the vendor IDs and create a child order with only their items."],
["11", "prepareOrderData", "Format the order data and pass it as the workflow's input."],
["18", "push", "Add a link between the vendor and the order to be created later."],
["30", "cancelOrderWorkflow", "Cancel all created workflows if an error occurs while creating any of them."]
]
```ts title="src/workflows/marketplace/create-vendor-orders/steps/create-vendor-orders.ts" highlights={vendorOrder3Highlights}

View File

@@ -722,10 +722,14 @@ The compensation function receives the subscription as a parameter. It cancels t
Create the file `src/workflows/create-subscription/index.ts` with the following content:
export const createSubscriptionWorkflowHighlights = [
["26", "completeCartWorkflow", "Complete the cart and create the order."],
["32", "useQueryGraphStep", "Retrieve the order's details."],
["87", "createSubscriptionStep", "Create the subscription."],
["94", "createRemoteLinkStep", "Create the links returned by the previous step."]
["29", "acquireLockStep", "Acquire a lock on the cart to prevent race conditions."],
["34", "completeCartWorkflow", "Complete the cart and create the order."],
["40", "useQueryGraphStep", "Retrieve the order's details."],
["95", "useQueryGraphStep", "Retrieve any existing subscription links for the order."],
["101", "when", "Check if a subscription already exists for the order."],
["108", "createSubscriptionStep", "Create the subscription."],
["115", "createRemoteLinkStep", "Create the links returned by the previous step."],
["120", "releaseLockStep", "Release the lock on the cart."]
]
```ts title="src/workflows/create-subscription/index.ts" highlights={createSubscriptionWorkflowHighlights} collapsibleLines="1-13" expandMoreLabel="Show Imports"

View File

@@ -1212,13 +1212,7 @@ This displays the selected seat number and show date for ticket products instead
You should also remove the quantity selector for ticket products since you can't purchase multiple tickets for the same seat. Remove the following highlighted lines from the `return` statement of the `Item` component:
export const removeHighlights = [
["5"], ["6"], ["7"], ["8"], ["9"], ["10"], ["11"], ["12"], ["13"], ["14"],
["15"], ["16"], ["17"], ["18"], ["19"], ["20"], ["21"], ["22"], ["23"], ["24"],
["25"], ["26"], ["27"], ["29"]
]
```tsx title="src/modules/cart/components/item/index.tsx" highlights={removeHighlights}
```tsx title="src/modules/cart/components/item/index.tsx"
{type === "full" && (
<Table.Cell>
<div className="flex gap-2 items-center w-28">

View File

@@ -26,7 +26,6 @@ For example:
export const highlights = [
["17", "cartId", "Retrieve the cart ID from `localStorage`."],
["19", "TODO", "You can create the cart and set it here as explained in the Create Cart guide."],
["23"], ["24"], ["25"], ["26"],
["29", "formatPrice", "This function was previously created to format product prices. You can re-use the same function."],
["32", "currency", "If you reuse the `formatPrice` function, pass the currency code as a parameter."],
]

View File

@@ -50,8 +50,6 @@ For example:
export const highlights = [
["4", "useCart", "The `useCart` hook was defined in the Cart React Context documentation."],
["30", "address", "Assemble the address object to be used for both shipping and billing addresses."],
["42"], ["43"], ["44"], ["45"], ["46"], ["47"], ["48"],
["49"], ["50"],
["96", "", "The address's country can only be within the cart's region."]
]

View File

@@ -39,7 +39,6 @@ For example:
export const highlights = [
["4", "useCart", "The `useCart` hook was defined in the Cart React Context documentation."],
["14", "TODO", "Cart must have at least one item. If not, redirect to another page."],
["28"], ["29"], ["30"], ["31"], ["32"], ["33"], ["34"],
]
```tsx highlights={highlights}

View File

@@ -200,12 +200,12 @@ export const highlights = [
export const fetchHighlights = [
["5", "retrieveShippingOptions", "This function retrieves the shipping options of the customer's cart."],
["21", "calculateShippingOptionPrices", "This function retrieves the prices of shipping options of type `calculated`."],
["34", "data", "Pass in this property any data relevant to the fulfillment provider."],
["56", "formatPrice", "This function formats a price based on the cart's currency."],
["65", "getShippingOptionPrice", "This function gets the price of a shipping option based on its type."],
["77", "setShippingMethod", "This function sets the shipping method of the cart using the selected shipping option."],
["91", "data", "Pass in this property any data relevant to the fulfillment provider."],
["13", "calculateShippingOptionPrices", "This function retrieves the prices of shipping options of type `calculated`."],
["19", "data", "Pass in this property any data relevant to the fulfillment provider."],
["39", "formatPrice", "This function formats a price based on the cart's currency."],
["48", "getShippingOptionPrice", "This function gets the price of a shipping option based on its type."],
["60", "setShippingMethod", "This function sets the shipping method of the cart using the selected shipping option."],
["65", "data", "Pass in this property any data relevant to the fulfillment provider."],
]
```ts highlights={fetchHighlights}

View File

@@ -161,8 +161,6 @@ To add a new address for the customer, send a request to the [Add Customer Addre
export const addHighlights = [
["4", "useRegion", "Use the hook defined in the Region Context guide."],
["5", "useCustomer", "Use the hook defined in the Customer Context guide."],
["29"], ["30"], ["31"], ["32"], ["33"], ["34"], ["35"], ["36"], ["37"],
["38"], ["39"], ["40"], ["41"], ["42"], ["43"],
]
```tsx highlights={addHighlights} collapsibleLines="46-117" expandButtonLabel="Show form"
@@ -334,8 +332,6 @@ export const editHighlights = [
["4", "useRegion", "Use the hook defined in the Region Context guide."],
["5", "useCustomer", "Use the hook defined in the Customer Context guide."],
["18", "address", "Retrieve the address from the customer's `addresses` property."],
["59"], ["60"], ["61"], ["62"], ["63"], ["64"], ["65"], ["66"], ["67"],
["68"], ["69"], ["70"], ["71"], ["72"], ["73"],
]
```tsx highlights={editHighlights} collapsibleLines="76-147" expandButtonLabel="Show form"

View File

@@ -292,7 +292,7 @@ curl '{backend_url}/store/products' \
</CodeTab>
<CodeTab label="Fetch" value="fetch">
```ts highlights={["2", "", "Passes the cookie session ID in your request."]}
```ts highlights={[["2", "credentials", "Passes the cookie session ID in your request."]]}
fetch(`<BACKEND_URL>/store/products`, {
credentials: "include",
})

View File

@@ -33,7 +33,6 @@ For example:
export const highlights = [
["4", "useCustomer", "Use the hook defined in the Customer Context guide."],
["35"], ["36"], ["37"], ["38"], ["39"], ["40"], ["41"], ["42"], ["43"], ["44"],
]
```tsx highlights={highlights} collapsibleLines="47-85" expandButtonLabel="Show form"

View File

@@ -1350,7 +1350,7 @@ Then, replace the `TODO` in the `Product` component with the following:
export const productHighlights4 = [
["1", "price", "The formatted price to show to the customer."],
["2", "selectedVariantPrice", "Determine the variant to show its price, which is either the selected variant or the variant with the smallest price."],
["29", "formatPrice", "Return the formatted price using the utility you created."]
["19", "formatPrice", "Return the formatted price using the utility you created."]
]
```tsx title="components/Product/index.tsx" highlights={productHighlights4}
@@ -2049,7 +2049,7 @@ export const shippingHighlights3 = [
["7", "filter", "Retrieve shipping options with calculated prices only."],
["9", "fetch", "Retrieve the calculated price from the Medusa application."],
["24", "Promise.allSettled", "Wait for all calculated prices to be retrieved."],
["13", "setCalculatedPrices", "Set the calculated prices in the state variable."],
["33", "setCalculatedPrices", "Set the calculated prices in the state variable."],
]
```tsx title="components/Shipping/index.tsx" highlights={shippingHighlights3}

View File

@@ -50,11 +50,7 @@ Learn how to install and configure the JS SDK in the [JS SDK documentation](../.
<CodeTabs group="store-request">
<CodeTab label="React" value="react">
export const highlights = [
["18"], ["19"], ["20"], ["21"], ["22"]
]
```tsx highlights={highlights}
```tsx highlights={[["18"], ["19"], ["20"], ["21"], ["22"]]}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"

View File

@@ -36,12 +36,7 @@ Learn how to install and configure the JS SDK in the [JS SDK documentation](../.
<CodeTabs group="store-request">
<CodeTab label="React" value="react">
export const highlights = [
["18"], ["19"], ["20"],
["21"], ["22"],
]
```tsx highlights={highlights}
```tsx highlights={[["18"], ["19"], ["20"], ["21"], ["22"]]}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"

View File

@@ -48,9 +48,8 @@ Learn how to install and configure the JS SDK in the [JS SDK documentation](../.
<CodeTab label="React" value="react">
export const highlights = [
["22"], ["23", `fields`, "Select the fields of category children"],
["23", `fields`, "Select the fields of category children"],
["24", "include_descendants_tree", "Indicate that all nested categories should be retrieved."],
["25"], ["26"], ["27"], ["28"], ["29"],
["39", "category_children", "Show the nested categories."],
]
@@ -156,11 +155,9 @@ For example:
<CodeTab label="React" value="react">
export const categoriesHighlights = [
["22"], ["23"], ["24", `fields`, "Select the fields of category children"],
["24", `fields`, "Select the fields of category children"],
["25", "include_descendants_tree", "Indicate that all nested categories should be retrieved."],
["26", "parent_category_id", "Since each category will have its children, you only retrieve categories that don't have a parent."],
["27"], ["28"],
["29"], ["30"], ["31"],
["45", "category_children", "Show the nested categories."],
]

View File

@@ -31,10 +31,7 @@ Learn how to install and configure the JS SDK in the [JS SDK documentation](../.
<CodeTab label="React" value="react">
export const highlights = [
["29"], ["30"], ["31"],
["32", "category_id", "Pass the category ID as a query parameter."], ["33"],
["34"], ["35"], ["36"],
["37"], ["38"], ["39"], ["40"], ["41"], ["42"], ["43"], ["44"]
["32", "category_id", "Pass the category ID as a query parameter."],
]
```tsx highlights={highlights}

View File

@@ -39,12 +39,7 @@ Learn how to install and configure the JS SDK in the [JS SDK documentation](../.
<CodeTabs group="store-request">
<CodeTab label="React" value="react">
export const highlights = [
["22"], ["23"], ["24"],
["25"], ["26"],
]
```tsx highlights={highlights}
```tsx highlights={[["22"], ["23"], ["24"], ["25"], ["26"]]}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"
@@ -118,13 +113,7 @@ To retrieve a product by its handle, send a request to the [List Product Categor
<CodeTabs group="store-request">
<CodeTab label="React" value="react">
export const handleHighlights = [
["22"], ["23"], ["24"],
["25"], ["26"], ["27"], ["28"],
["29"], ["30"],
]
```tsx highlights={handleHighlights}
```tsx highlights={[["22"], ["23"], ["24"], ["25"], ["26"], ["27"], ["28"], ["29"], ["30"]]}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"

View File

@@ -36,12 +36,7 @@ Learn how to install and configure the JS SDK in the [JS SDK documentation](../.
<CodeTabs group="store-request">
<CodeTab label="React" value="react">
export const highlights = [
["18"], ["19"], ["20"],
["21"], ["22"],
]
```tsx highlights={highlights}
```tsx highlights={[["18"], ["19"], ["20"], ["21"], ["22"]]}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"

View File

@@ -31,10 +31,7 @@ Learn how to install and configure the JS SDK in the [JS SDK documentation](../.
<CodeTab label="React" value="react">
export const highlights = [
["29"], ["30"], ["31"],
["32", "collection_id", "Pass the collection ID as a query parameter."], ["33"],
["34"], ["35"], ["36"],
["37"], ["38"], ["39"], ["40"], ["41"], ["42"], ["43"], ["44"], ["45"], ["46"], ["47"]
["32", "collection_id", "Pass the collection ID as a query parameter."],
]
```tsx highlights={highlights}

View File

@@ -39,12 +39,7 @@ Learn how to install and configure the JS SDK in the [JS SDK documentation](../.
<CodeTabs group="store-request">
<CodeTab label="React" value="react">
export const highlights = [
["22"], ["23"], ["24"],
["25"], ["26"],
]
```tsx highlights={highlights}
```tsx highlights={[["22"], ["23"], ["24"], ["25"], ["26"]]}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"
@@ -117,12 +112,7 @@ To retrieve a product by its handle, send a request to the [List Product Collect
<CodeTabs group="store-request">
<CodeTab label="React" value="react">
export const handleHighlights = [
["24"], ["25"], ["26"],
["27"], ["28"], ["29"], ["30"], ["31"], ["32"],
]
```tsx highlights={handleHighlights}
```tsx highlights={[["24"], ["25"], ["26"], ["27"], ["28"], ["29"], ["30"], ["31"], ["32"]]}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"

View File

@@ -29,11 +29,7 @@ Learn how to install and configure the JS SDK in the [JS SDK documentation](../.
<CodeTabs group="store-request">
<CodeTab label="React" value="react">
export const highlights = [
["18"], ["19"], ["20"], ["21"], ["22"]
]
```tsx highlights={highlights}
```tsx highlights={[["18"], ["19"], ["20"], ["21"], ["22"]]}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"

View File

@@ -38,12 +38,7 @@ Learn how to install and configure the JS SDK in the [JS SDK documentation](../.
<CodeTabs group="store-request">
<CodeTab label="React" value="react">
export const highlights = [
["22"], ["23"],
["24"], ["25"], ["26"], ["27"],
]
```tsx highlights={highlights}
```tsx highlights={[["22"], ["23"], ["24"], ["25"], ["26"], ["27"]]}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"
@@ -137,11 +132,7 @@ To retrieve a product by its handle, send a request to the [List Products API ro
<CodeTabs group="store-request">
<CodeTab label="React" value="react">
export const handleHighlights = [
["24"], ["25"], ["26"], ["27"], ["28"], ["29"], ["30"], ["31"], ["32"]
]
```tsx highlights={handleHighlights}
```tsx highlights={[["24"], ["25"], ["26"], ["27"], ["28"], ["29"], ["30"], ["31"], ["32"]]}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"

View File

@@ -48,7 +48,7 @@ export const highlights = [
["38", "regionId", "Retrieve the selected region from the `localStorage`."],
["41", "list", "If no region is selected, retrieve the list of regions from the Medusa application and select the first one."],
["47", "retrieve", "If a region is selected, retrieve it from the Medusa application."],
["6", "useRegion", "The hook that child components of the provider use to access the region."]
["64", "useRegion", "The hook that child components of the provider use to access the region."]
]
```tsx highlights={highlights}

View File

@@ -33,11 +33,7 @@ Learn how to install and configure the JS SDK in the [JS SDK documentation](../.
<CodeTabs group="store-request">
<CodeTab label="React" value="react">
export const highlights = [
["18"], ["19"], ["20"], ["21"], ["22"]
]
```tsx highlights={highlights}
```tsx highlights={[["18"], ["19"], ["20"], ["21"], ["22"]]}
"use client" // include with Next.js 13+
import { useEffect, useState } from "react"

View File

@@ -4,6 +4,7 @@ import {
prerequisitesLinkFixerPlugin,
recmaInjectMdxDataPlugin,
typeListLinkFixerPlugin,
validateHighlightsPlugin,
workflowDiagramLinkFixerPlugin,
} from "remark-rehype-plugins"
@@ -48,6 +49,7 @@ const withMDX = mdx({
},
],
...mdxPluginOptions.options.rehypePlugins,
[validateHighlightsPlugin, { verbose: false }],
[localLinksRehypePlugin],
[typeListLinkFixerPlugin],
[

View File

@@ -13,6 +13,7 @@ import {
prerequisitesLinkFixerPlugin,
remarkAttachFrontmatterDataPlugin,
recmaInjectMdxDataPlugin,
validateHighlightsPlugin,
uiRehypePlugin,
} from "remark-rehype-plugins"
import bundleAnalyzer from "@next/bundle-analyzer"
@@ -81,6 +82,7 @@ const withMDX = mdx({
tagName: "code",
},
],
[validateHighlightsPlugin, { verbose: false }],
[rehypeSlug],
[
cloudinaryImgRehypePlugin,

View File

@@ -13,6 +13,7 @@ import {
prerequisitesLinkFixerPlugin,
remarkAttachFrontmatterDataPlugin,
recmaInjectMdxDataPlugin,
validateHighlightsPlugin,
} from "remark-rehype-plugins"
import bundleAnalyzer from "@next/bundle-analyzer"
import withExtractedTableOfContents from "@stefanprobst/rehype-extract-toc"
@@ -79,6 +80,7 @@ const withMDX = mdx({
tagName: "code",
},
],
[validateHighlightsPlugin, { verbose: false }],
[rehypeSlug],
[
cloudinaryImgRehypePlugin,

View File

@@ -9,24 +9,39 @@ import {
JSXTextExpression,
LiteralExpression,
ObjectExpression,
TemplateLiteralExpression,
VariableDeclaration,
} from "types"
const ALLOWED_BODY_TYPES = ["ExpressionStatement", "ExportNamedDeclaration"]
export function estreeToJs(estree: Estree) {
// TODO improve on this utility. Currently it's implemented to work
// for specific use cases as we don't have a lot of info on other
// use cases.
if (
!estree.body?.length ||
estree.body[0].type !== "ExpressionStatement" ||
!estree.body[0].expression
!estree.body[0].type ||
!ALLOWED_BODY_TYPES.includes(estree.body[0].type)
) {
return
}
switch (estree.body[0].type) {
case "ExpressionStatement":
if (!estree.body[0].expression) {
return
}
return expressionToJs(estree.body[0].expression)
case "ExportNamedDeclaration":
if (!estree.body[0].declaration) {
return
}
return declarationToJs(estree.body[0].declaration)
}
}
function expressionToJs(
export function expressionToJs(
expression: Expression
): ExpressionJsVar | ExpressionJsVar[] | undefined {
switch (expression.type) {
@@ -84,5 +99,36 @@ function expressionToJs(
},
data: text,
}
case "TemplateLiteral":
const templateExpression = expression as TemplateLiteralExpression
let value = ""
let raw = ""
templateExpression.quasis.forEach((quasi) => {
value += quasi.value.cooked
raw += quasi.value.raw
})
return {
original: {
type: "Literal",
value,
raw,
},
data: value,
}
}
}
function declarationToJs(declaration: VariableDeclaration): {
name: string
value: ExpressionJsVar | ExpressionJsVar[] | undefined
} {
if (!declaration.declarations.length) {
throw new Error("No declarations found")
}
const name = declaration.declarations[0].id.name
const value = expressionToJs(declaration.declarations[0].init)
return {
name,
value,
}
}

View File

@@ -175,7 +175,7 @@ function componentChecker({
const itemJsVar = estreeToJs(attribute.value.data.estree)
if (!itemJsVar) {
if (!itemJsVar || "name" in itemJsVar) {
return
}
@@ -217,7 +217,7 @@ function checkLink({
currentPageFilePath: string
options: BrokenLinkCheckerOptions
}) {
if (!link || typeof link !== "string") {
if (!link || typeof link !== "string" || link === "/" || link === "#") {
return
}
// try to remove hash

View File

@@ -88,7 +88,7 @@ function componentFixer(
const itemJsVar = estreeToJs(attribute.value.data.estree)
if (!itemJsVar) {
if (!itemJsVar || "name" in itemJsVar) {
return
}

View File

@@ -11,6 +11,7 @@ export * from "./prerequisites-link-fixer.js"
export * from "./resolve-admonitions.js"
export * from "./type-list-link-fixer.js"
export * from "./ui-rehype-plugin.js"
export * from "./validate-highlights.js"
export * from "./workflow-diagram-link-fixer.js"
export * from "./utils/fix-link.js"

View File

@@ -105,7 +105,7 @@ export function componentLinkFixer(
const itemJsVar = estreeToJs(attribute.value.data.estree)
if (!itemJsVar) {
if (!itemJsVar || "name" in itemJsVar) {
return
}

View File

@@ -0,0 +1,253 @@
import {
ArrayExpression,
Estree,
ExpressionJsVar,
Identifier,
JSXElementExpression,
JSXExpressionContainer,
LiteralExpression,
UnistNode,
UnistTree,
} from "types"
import type { Transformer } from "unified"
import { estreeToJs, expressionToJs } from "docs-utils"
type Options = {
verbose?: boolean
}
export function validateHighlightsPlugin(options?: Options): Transformer {
const { verbose } = options || {}
return async (tree) => {
const { visit } = await import("unist-util-visit")
const highlightConsts = new Map<string, string[][]>()
visit(tree as UnistTree, ["mdxjsEsm", "element"], (node: UnistNode) => {
if (node.type === "mdxjsEsm") {
if (node.data && "estree" in node.data && node.data.estree) {
const highlightsJsVar = estreeToJs(node.data.estree)
if (
!highlightsJsVar ||
!("name" in highlightsJsVar) ||
typeof highlightsJsVar.name !== "string" ||
!Array.isArray(highlightsJsVar.value)
) {
return
}
const highlights = getHighlightsFromExpressionJsVar(
highlightsJsVar.value
)
if (highlights) {
highlightConsts.set(highlightsJsVar.name, highlights)
}
}
return
}
if (
node.tagName !== "pre" ||
!node.children?.length ||
!node.children[0].data ||
!("estree" in node.children[0].data) ||
!node.children[0].data.estree
) {
return
}
const body = (node.children[0].data.estree as Estree).body?.[0]
if (
!body ||
body.type !== "ExpressionStatement" ||
!body.expression ||
body.expression.type !== "JSXElement"
) {
return
}
const bodyExpression = body.expression as JSXElementExpression
if (
!bodyExpression.children.length ||
bodyExpression.children[0].type !== "JSXExpressionContainer"
) {
return
}
const codeExpression = (
bodyExpression.children[0] as JSXExpressionContainer
).expression
if (codeExpression.type !== "Literal") {
return
}
const highlightsAttr = bodyExpression.openingElement?.attributes.find(
(attr) => attr.name.name === "highlights"
)
if (!highlightsAttr) {
return
}
let highlights: string[][] | undefined
if (
highlightsAttr.value.expression.type !== "Identifier" &&
highlightsAttr.value.expression.type === "ArrayExpression"
) {
const highlightsJs = getExpressionJsVarFromArrExpression(
highlightsAttr.value.expression as ArrayExpression
)
if (!highlightsJs || !Array.isArray(highlightsJs)) {
return
}
highlights = getHighlightsFromExpressionJsVar(highlightsJs)
if (!highlights) {
return
}
} else {
if (highlightsAttr.value.expression.type !== "Identifier") {
return
}
const name = (highlightsAttr.value.expression as Identifier).name
highlights = highlightConsts.get(name)
if (!highlights) {
return
}
}
const code = (codeExpression as LiteralExpression).value as string
validateCodeHighlights({
highlights,
code,
verbose,
})
})
}
}
const getExpressionJsVarFromArrExpression = (
expression: ArrayExpression
): ExpressionJsVar[][] => {
if (!Array.isArray(expression.elements)) {
return []
}
return expression.elements.map((elm) => {
const elmJsVar = expressionToJs(elm)
if (!elmJsVar || !Array.isArray(elmJsVar)) {
return []
}
return elmJsVar
})
}
const getHighlightsFromExpressionJsVar = (
expression: ExpressionJsVar[] | ExpressionJsVar[][]
): string[][] | undefined => {
const highlights: string[][] = []
const shouldExtractHighlights = !Array.isArray(expression[0])
// the expression is an array where each three items are a highlight
// so we need to split the expression into groups of three items first
const highlightExpressions = shouldExtractHighlights
? (expression as ExpressionJsVar[]).reduce((acc, item, index) => {
if (index % 3 === 0) {
acc.push([])
}
acc[acc.length - 1].push(item)
return acc
}, [] as ExpressionJsVar[][])
: (expression as ExpressionJsVar[][])
if (!highlightExpressions.length) {
return undefined
}
for (const highlightExpression of highlightExpressions) {
if (highlightExpression.length !== 3) {
continue
}
const highlight = []
for (const item of highlightExpression) {
if (typeof item.data !== "string") {
continue
}
highlight.push(item.data)
}
highlights.push(highlight)
}
return !highlights.length ? undefined : highlights
}
/**
* Validates that the highlights of each code block are correct. It validates:
*
* 1. The highlight's first parameter is a string number of a valid line number in the code block.
* 2. The highlight's second parameter is a string that is a valid text in the line of the code block.
*/
const validateCodeHighlights = ({
highlights,
code,
verbose,
}: {
highlights: string[][]
code: string
verbose?: boolean
}): void => {
try {
const lines = code.trim().split("\n")
for (const highlight of highlights) {
if (highlight.length < 2) {
throw new Error(`Highlight array must have at least 2 elements`)
}
const lineNumber = highlight[0]
const text = highlight[1]
if (typeof lineNumber !== "string" || typeof text !== "string") {
throw new Error(`Highlight array must have at least 2 elements`)
}
if (isNaN(Number(lineNumber))) {
throw new Error(`Highlight line number must be a number`)
}
const line = lines[Number(lineNumber) - 1]?.trim()
if (line === undefined) {
throw new Error(`Highlight line number ${lineNumber} not found in code`)
}
if (text.length && !line.includes(text)) {
throw new Error(
`Highlight text ${text} not found in line ${lineNumber}`
)
}
}
} catch (err) {
throw new Error(
formatError({
message: (err as Error).message,
code,
verbose,
})
)
}
}
const formatError = ({
message,
code,
verbose,
}: {
message: string
code: string
verbose?: boolean
}): string => {
return `${message}${verbose ? `:\n${code}` : ""}`
}

View File

@@ -41,6 +41,10 @@ export type LiteralExpression = {
export type JSXElementExpression = {
type: "JSXElement" | "JSXFragment"
children: Expression[]
openingElement: {
type: "JSXOpeningElement"
attributes: UnistJSXAttributeNode[]
}
}
export type JSXTextExpression = {
@@ -49,6 +53,33 @@ export type JSXTextExpression = {
raw: string
}
export type VariableDeclaration = {
type: "VariableDeclaration"
declarations: VariableDeclarator[]
}
export type VariableDeclarator = {
type: "VariableDeclarator"
id: Identifier
init: Expression
}
export type Identifier = {
type: "Identifier"
name: string
}
export type TemplateLiteralExpression = {
type: "TemplateLiteral"
expressions: Expression[]
quasis: {
type: "TemplateElement"
value: {
raw: string
cooked: string
}
}[]
}
export type Expression =
| {
type: string
@@ -60,10 +91,28 @@ export type Expression =
| JSXTextExpression
export interface Estree {
body?: {
type?: string
body?: (
| {
type?: "ExpressionStatement"
expression?: Expression
}[]
}
| {
type?: "ExportNamedDeclaration"
declaration?: VariableDeclaration
}
)[]
}
export interface UnistJSXAttributeNode {
type: "JSXAttribute"
name: {
type: "JSXIdentifier"
name: string
}
value: {
type: "JSXExpressionContainer"
expression: Identifier | Expression
}
}
export interface UnistNodeWithData extends UnistNode {
@@ -244,3 +293,8 @@ export type ExpressionJsVarObj = {
}
export type ExpressionJsVar = ExpressionJsVarObj | ExpressionJsVarLiteral
export type JSXExpressionContainer = {
type: "JSXExpressionContainer"
expression: Expression
}