Просмотр исходного кода

docs: Convert highlight directives to Shiki notation

Migrate all code block highlight directives from the custom
Docusaurus-style syntax to Shiki's native notation across 82
documentation files.

Changes:
- Replace `// highlight-next-line` with `// [!code highlight]` at line end
- Replace `// highlight-start/end` blocks with per-line markers
- Use language-appropriate comment syntax:
  - TypeScript/JavaScript: `// [!code highlight]`
  - GraphQL/Shell/YAML: `# [!code highlight]`
  - HTML/Angular: `<!-- [!code highlight] -->`
David Höck 2 дней назад
Родитель
Сommit
dc6db7af56
82 измененных файлов с 1092 добавлено и 1638 удалено
  1. 6 10
      docs/docs/guides/core-concepts/channels/index.mdx
  2. 1 2
      docs/docs/guides/core-concepts/collections/index.mdx
  3. 6 8
      docs/docs/guides/core-concepts/email/index.mdx
  4. 13 24
      docs/docs/guides/core-concepts/money/index.mdx
  5. 15 21
      docs/docs/guides/core-concepts/orders/index.mdx
  6. 3 6
      docs/docs/guides/core-concepts/promotions/index.mdx
  7. 23 29
      docs/docs/guides/deployment/deploy-to-digital-ocean-app-platform/index.mdx
  8. 4 8
      docs/docs/guides/deployment/deploy-to-railway/index.mdx
  9. 4 8
      docs/docs/guides/deployment/deploy-to-render/index.mdx
  10. 1 2
      docs/docs/guides/deployment/getting-data-into-production.mdx
  11. 7 13
      docs/docs/guides/developer-guide/cache/index.mdx
  12. 8 14
      docs/docs/guides/developer-guide/channel-aware/index.mdx
  13. 2 4
      docs/docs/guides/developer-guide/configuration/index.mdx
  14. 105 162
      docs/docs/guides/developer-guide/custom-fields/index.mdx
  15. 11 20
      docs/docs/guides/developer-guide/custom-permissions/index.mdx
  16. 8 12
      docs/docs/guides/developer-guide/database-entity/index.mdx
  17. 23 30
      docs/docs/guides/developer-guide/error-handling/index.mdx
  18. 28 42
      docs/docs/guides/developer-guide/events/index.mdx
  19. 53 75
      docs/docs/guides/developer-guide/extend-graphql-api/index.mdx
  20. 11 21
      docs/docs/guides/developer-guide/has-custom-fields/index.mdx
  21. 3 6
      docs/docs/guides/developer-guide/migrations/index.mdx
  22. 13 26
      docs/docs/guides/developer-guide/plugins/index.mdx
  23. 2 4
      docs/docs/guides/developer-guide/rest-endpoint/index.mdx
  24. 25 36
      docs/docs/guides/developer-guide/scheduled-tasks/index.mdx
  25. 6 8
      docs/docs/guides/developer-guide/security/index.mdx
  26. 4 8
      docs/docs/guides/developer-guide/stand-alone-scripts/index.mdx
  27. 2 4
      docs/docs/guides/developer-guide/strategies-configurable-operations/index.mdx
  28. 5 9
      docs/docs/guides/developer-guide/testing/index.mdx
  29. 11 22
      docs/docs/guides/developer-guide/the-api-layer/index.mdx
  30. 9 18
      docs/docs/guides/developer-guide/the-service-layer/index.mdx
  31. 13 28
      docs/docs/guides/developer-guide/translatable/index.mdx
  32. 7 14
      docs/docs/guides/developer-guide/translations/index.mdx
  33. 7 9
      docs/docs/guides/developer-guide/uploading-files/index.mdx
  34. 19 29
      docs/docs/guides/developer-guide/worker-job-queue/index.mdx
  35. 16 23
      docs/docs/guides/extending-the-admin-ui/add-actions-to-pages/index.mdx
  36. 34 43
      docs/docs/guides/extending-the-admin-ui/creating-detail-views/index.mdx
  37. 13 20
      docs/docs/guides/extending-the-admin-ui/creating-list-views/index.mdx
  38. 14 18
      docs/docs/guides/extending-the-admin-ui/custom-detail-components/index.mdx
  39. 6 11
      docs/docs/guides/extending-the-admin-ui/custom-form-inputs/index.mdx
  40. 20 26
      docs/docs/guides/extending-the-admin-ui/dashboard-widgets/index.mdx
  41. 49 84
      docs/docs/guides/extending-the-admin-ui/defining-routes/index.mdx
  42. 48 71
      docs/docs/guides/extending-the-admin-ui/getting-started/index.mdx
  43. 3 7
      docs/docs/guides/extending-the-admin-ui/nav-menu/index.mdx
  44. 1 2
      docs/docs/guides/extending-the-admin-ui/page-tabs/index.mdx
  45. 1 2
      docs/docs/guides/extending-the-admin-ui/ui-library/index.mdx
  46. 2 4
      docs/docs/guides/extending-the-dashboard/creating-pages/detail-pages.mdx
  47. 2 4
      docs/docs/guides/extending-the-dashboard/creating-pages/list-pages.mdx
  48. 21 35
      docs/docs/guides/extending-the-dashboard/custom-form-components/form-component-examples.mdx
  49. 16 24
      docs/docs/guides/extending-the-dashboard/custom-form-components/index.mdx
  50. 7 13
      docs/docs/guides/extending-the-dashboard/deployment/index.mdx
  51. 1 2
      docs/docs/guides/extending-the-dashboard/extending-overview/index.mdx
  52. 16 22
      docs/docs/guides/extending-the-dashboard/getting-started/index.mdx
  53. 3 6
      docs/docs/guides/extending-the-dashboard/localization/index.mdx
  54. 1 2
      docs/docs/guides/extending-the-dashboard/navigation/index.mdx
  55. 10 19
      docs/docs/guides/getting-started/graphql-intro/index.mdx
  56. 7 12
      docs/docs/guides/getting-started/installation/index.mdx
  57. 7 11
      docs/docs/guides/getting-started/try-the-api/index.mdx
  58. 34 47
      docs/docs/guides/how-to/codegen/index.mdx
  59. 3 5
      docs/docs/guides/how-to/configurable-products/index.mdx
  60. 42 57
      docs/docs/guides/how-to/digital-products/index.mdx
  61. 33 44
      docs/docs/guides/how-to/paginated-list/index.mdx
  62. 1 2
      docs/docs/guides/how-to/publish-plugin/index.mdx
  63. 29 34
      docs/docs/guides/how-to/s3-asset-storage/index.mdx
  64. 6 12
      docs/docs/guides/how-to/telemetry/index.mdx
  65. 2 4
      docs/docs/guides/storefront/active-order/index.mdx
  66. 22 31
      docs/docs/guides/storefront/codegen/index.mdx
  67. 13 20
      docs/docs/guides/storefront/connect-api/index.mdx
  68. 1 2
      docs/docs/guides/storefront/customer-accounts/index.mdx
  69. 109 120
      docs/docs/guides/storefront/listing-products/index.mdx
  70. 1 2
      docs/docs/guides/storefront/navigation-menu/index.mdx
  71. 7 9
      docs/docs/reference/admin-ui-api/action-bar/action-bar-context.mdx
  72. 2 4
      docs/docs/reference/admin-ui-api/custom-input-components/register-form-input-component.mdx
  73. 5 9
      docs/docs/reference/admin-ui-api/ui-devkit/admin-ui-extension.mdx
  74. 4 6
      docs/docs/reference/core-plugins/sentry-plugin/index.mdx
  75. 3 5
      docs/docs/reference/core-plugins/stellate-plugin/index.mdx
  76. 3 5
      docs/docs/reference/dashboard/extensions-api/form-components.mdx
  77. 3 5
      docs/docs/reference/typescript-api/common/bootstrap.mdx
  78. 3 5
      docs/docs/reference/typescript-api/configuration/product-variant-price-update-strategy.mdx
  79. 5 8
      docs/docs/reference/typescript-api/data-access/entity-hydrator.mdx
  80. 3 5
      docs/docs/reference/typescript-api/orders/order-interceptor.mdx
  81. 1 2
      docs/docs/reference/typescript-api/tax/address-based-tax-zone-strategy.mdx
  82. 1 2
      docs/docs/reference/typescript-api/telemetry/instrument.mdx

+ 6 - 10
docs/docs/guides/core-concepts/channels/index.mdx

@@ -85,11 +85,9 @@ import { DefaultProductVariantPriceUpdateStrategy, VendureConfig } from '@vendur
 
 export const config: VendureConfig = {
     // ...
-    // highlight-start
-    productVariantPriceUpdateStrategy: new DefaultProductVariantPriceUpdateStrategy({
-        syncPricesAcrossChannels: true,
-    }),
-    // highlight-end
+    productVariantPriceUpdateStrategy: new DefaultProductVariantPriceUpdateStrategy({ // [!code highlight]
+        syncPricesAcrossChannels: true, // [!code highlight]
+    }), // [!code highlight]
     // ...
 };
 ```
@@ -138,11 +136,9 @@ Then we can make a GraphQL API call to the UK Channel by setting the `'vendure-t
 ```ts title="GraphQL API call to UK Channel"
 const { loading, error, data } = useQuery(GET_PRODUCT_LIST, {
     context: {
-        // highlight-start
-        headers: {
-            'vendure-token': 'uk-channel',
-        },
-        // highlight-end
+        headers: { // [!code highlight]
+            'vendure-token': 'uk-channel', // [!code highlight]
+        }, // [!code highlight]
     },
 });
 ```

+ 1 - 2
docs/docs/guides/core-concepts/collections/index.mdx

@@ -109,8 +109,7 @@ export const config: VendureConfig = {
     catalogOptions: {
         collectionFilters: [
             ...defaultCollectionFilters,
-            // highlight-next-line
-            skuCollectionFilter
+            skuCollectionFilter // [!code highlight]
         ],
     },
 };

+ 6 - 8
docs/docs/guides/core-concepts/email/index.mdx

@@ -92,14 +92,12 @@ export const config: VendureConfig = {
     plugins: [
         EmailPlugin.init({
             // ...
-            // highlight-start
-            globalTemplateVars: {
-                fromAddress: '"MyShop" <noreply@myshop.com>',
-                verifyEmailAddressUrl: 'https://www.myshop.com/verify',
-                passwordResetUrl: 'https://www.myshop.com/password-reset',
-                changeEmailAddressUrl: 'https://www.myshop.com/verify-email-address-change'
-            },
-            // highlight-end
+            globalTemplateVars: { // [!code highlight]
+                fromAddress: '"MyShop" <noreply@myshop.com>', // [!code highlight]
+                verifyEmailAddressUrl: 'https://www.myshop.com/verify', // [!code highlight]
+                passwordResetUrl: 'https://www.myshop.com/password-reset', // [!code highlight]
+                changeEmailAddressUrl: 'https://www.myshop.com/verify-email-address-change' // [!code highlight]
+            }, // [!code highlight]
         }),
     ],
 };

+ 13 - 24
docs/docs/guides/core-concepts/money/index.mdx

@@ -20,10 +20,8 @@ For example, here's the response from a query for a product's variant prices:
           "id": "74",
           "name": "Bonsai Tree",
           "currencyCode": "USD",
-          // highlight-start
-          "price": 1999,
-          "priceWithTax": 2399,
-          // highlight-end
+          "price": 1999, // [!code highlight]
+          "priceWithTax": 2399, // [!code highlight]
         }
       ]
     }
@@ -113,12 +111,10 @@ Here's how the `Money` scalar is used in the `ShippingLine` type:
 type ShippingLine {
     id: ID!
     shippingMethod: ShippingMethod!
-    // highlight-start
-    price: Money!
-    priceWithTax: Money!
-    discountedPrice: Money!
-    discountedPriceWithTax: Money!
-    // highlight-end
+    price: Money! # [!code highlight]
+    priceWithTax: Money! # [!code highlight]
+    discountedPrice: Money! # [!code highlight]
+    discountedPriceWithTax: Money! # [!code highlight]
     discounts: [Discount!]!
 }
 ```
@@ -154,10 +150,8 @@ class Quote extends VendureEntity {
     @Column()
     text: string;
 
-    // highlight-start
-    @Money()
-    value: number;
-    // highlight-end
+    @Money() // [!code highlight]
+    value: number; // [!code highlight]
 
     // Whenever you store a monetary value, it's a good idea to also
     // explicitly store the currency code too. This makes it possible
@@ -210,8 +204,7 @@ For example, you want to be able to sell a product for `$1.234` per unit. To do
 import { DefaultMoneyStrategy, VendureConfig } from '@vendure/core';
 
 export class ThreeDecimalPlacesMoneyStrategy extends DefaultMoneyStrategy {
-    // highlight-next-line
-    readonly precision = 3;
+    readonly precision = 3; // [!code highlight]
 }
 
 export const config: VendureConfig = {
@@ -227,20 +220,16 @@ export const config: VendureConfig = {
 
 ```ts title="src/utils/format-currency.ts"
 export function formatCurrency(value: number, currencyCode: string, locale?: string) {
-    // highlight-next-line
-    const majorUnits = value / 1000;
+    const majorUnits = value / 1000; // [!code highlight]
     try {
         return new Intl.NumberFormat(locale, {
             style: 'currency',
             currency: currencyCode,
-            // highlight-start
-            minimumFractionDigits: 3,
-            maximumFractionDigits: 3,
-            // highlight-end
+            minimumFractionDigits: 3, // [!code highlight]
+            maximumFractionDigits: 3, // [!code highlight]
         }).format(majorUnits);
     } catch (e: any) {
-         // highlight-next-line
-        return majorUnits.toFixed(3);
+        return majorUnits.toFixed(3); // [!code highlight]
     }
 }
 ```

+ 15 - 21
docs/docs/guides/core-concepts/orders/index.mdx

@@ -31,8 +31,7 @@ You can see the current state of an order via `state` field on the `Order` type:
 query ActiveOrder {
     activeOrder {
         id
-        // highlight-next-line
-        state
+        state # [!code highlight]
     }
 }
 ```
@@ -45,8 +44,7 @@ query ActiveOrder {
   "data": {
     "activeOrder": {
       "id": "4",
-      // highlight-next-line
-      "state": "AddingItems"
+      "state": "AddingItems" // [!code highlight]
     }
   }
 }
@@ -290,14 +288,12 @@ As an example, let's say we want to add a Surcharge to the order. The following
 export const myOrderProcess: OrderProcess<OrderState> = {
     async onTransitionEnd(fromState, toState, data) {
         if (fromState === 'AddingItems' && toState === 'ArrangingPayment') {
-            // highlight-start
-            // WARNING: This will not work!  
-            await orderService.addSurchargeToOrder(ctx, order.id, {
-                description: 'Test',
-                listPrice: 42,
-                listPriceIncludesTax: false,
-            });
-            // highlight-end
+            // WARNING: This will not work! // [!code highlight]
+            await orderService.addSurchargeToOrder(ctx, order.id, { // [!code highlight]
+                description: 'Test', // [!code highlight]
+                listPrice: 42, // [!code highlight]
+                listPriceIncludesTax: false, // [!code highlight]
+            }); // [!code highlight]
         }
     }
 };
@@ -309,15 +305,13 @@ Instead, you need to ensure you **mutate the `order` object**:
 export const myOrderProcess: OrderProcess<OrderState> = {
     async onTransitionEnd(fromState, toState, data) {
         if (fromState === 'AddingItems' && toState === 'ArrangingPayment') {
-            // highlight-start
-            const {surcharges} = await orderService.addSurchargeToOrder(ctx, order.id, {
-                description: 'Test',
-                listPrice: 42,
-                listPriceIncludesTax: false,
-            });
-            // Important: mutate the order object
-            order.surcharges = surcharges;
-            // highlight-end
+            const {surcharges} = await orderService.addSurchargeToOrder(ctx, order.id, { // [!code highlight]
+                description: 'Test', // [!code highlight]
+                listPrice: 42, // [!code highlight]
+                listPriceIncludesTax: false, // [!code highlight]
+            }); // [!code highlight]
+            // Important: mutate the order object // [!code highlight]
+            order.surcharges = surcharges; // [!code highlight]
         }
     },
 }

+ 3 - 6
docs/docs/guides/core-concepts/promotions/index.mdx

@@ -280,11 +280,9 @@ export const buy1Get1FreeAction = new PromotionItemAction({
         value: 'Buy 1, get 1 free',
     }],
     args: {},
-    // highlight-next-line
-    conditions: [buyXGetYFreeCondition],
+    conditions: [buyXGetYFreeCondition], // [!code highlight]
     execute(ctx, orderLine, args, state) {
-        // highlight-next-line
-        const freeItemIds = state.buy_x_get_y_free.freeItemIds;
+        const freeItemIds = state.buy_x_get_y_free.freeItemIds; // [!code highlight]
         if (idsContainsItem(freeItemIds, orderLine)) {
             const unitPrice = ctx.channel.pricesIncludeTax ? orderLine.unitPriceWithTax : orderLine.unitPrice;
             return -unitPrice;
@@ -313,8 +311,7 @@ export const buyXGetYFreeCondition = new PromotionCondition({
         if (freeItemIds.length === 0) {
             return false;
         }
-        // highlight-next-line
-        return {freeItemIds};
+        return {freeItemIds}; // [!code highlight]
     },
 });
 ```

+ 23 - 29
docs/docs/guides/deployment/deploy-to-digital-ocean-app-platform/index.mdx

@@ -79,26 +79,24 @@ export const config: VendureConfig = {
         AssetServerPlugin.init({
             route: 'assets',
             assetUploadDir: process.env.ASSET_UPLOAD_DIR || path.join(__dirname, '../static/assets'),
-            // highlight-start
-            // If the MINIO_ENDPOINT environment variable is set, we'll use
-            // Minio as the asset storage provider. Otherwise, we'll use the
-            // default local provider.
-            storageStrategyFactory: process.env.MINIO_ENDPOINT ?  configureS3AssetStorage({
-                bucket: 'vendure-assets',
-                credentials: {
-                    accessKeyId: process.env.MINIO_ACCESS_KEY,
-                    secretAccessKey: process.env.MINIO_SECRET_KEY,
-                },
-                nativeS3Configuration: {
-                    endpoint: process.env.MINIO_ENDPOINT,
-                    forcePathStyle: true,
-                    signatureVersion: 'v4',
-                    // The `region` is required by the AWS SDK even when using MinIO,
-                    // so we just use a dummy value here.
-                    region: 'eu-west-1',
-                },
-            }) : undefined,
-            // highlight-end
+            // If the MINIO_ENDPOINT environment variable is set, we'll use // [!code highlight]
+            // Minio as the asset storage provider. Otherwise, we'll use the // [!code highlight]
+            // default local provider. // [!code highlight]
+            storageStrategyFactory: process.env.MINIO_ENDPOINT ?  configureS3AssetStorage({ // [!code highlight]
+                bucket: 'vendure-assets', // [!code highlight]
+                credentials: { // [!code highlight]
+                    accessKeyId: process.env.MINIO_ACCESS_KEY, // [!code highlight]
+                    secretAccessKey: process.env.MINIO_SECRET_KEY, // [!code highlight]
+                }, // [!code highlight]
+                nativeS3Configuration: { // [!code highlight]
+                    endpoint: process.env.MINIO_ENDPOINT, // [!code highlight]
+                    forcePathStyle: true, // [!code highlight]
+                    signatureVersion: 'v4', // [!code highlight]
+                    // The `region` is required by the AWS SDK even when using MinIO, // [!code highlight]
+                    // so we just use a dummy value here. // [!code highlight]
+                    region: 'eu-west-1', // [!code highlight]
+                }, // [!code highlight]
+            }) : undefined, // [!code highlight]
         }),
     ],
     // ...
@@ -170,16 +168,12 @@ DB_PASSWORD=${db.PASSWORD}
 DB_HOST=${db.HOSTNAME}
 DB_PORT=${db.PORT}
 DB_CA_CERT=${db.CA_CERT}
-// highlight-next-line
-COOKIE_SECRET=<add some random characters>
+COOKIE_SECRET=<add some random characters> # [!code highlight]
 SUPERADMIN_USERNAME=superadmin
-// highlight-next-line
-SUPERADMIN_PASSWORD=<create some strong password>
-// highlight-start
-MINIO_ACCESS_KEY=<use the key generated earlier>
-MINIO_SECRET_KEY=<use the secret generated earlier>
-MINIO_ENDPOINT=<use the endpoint of your spaces bucket>
-// highlight-end
+SUPERADMIN_PASSWORD=<create some strong password> # [!code highlight]
+MINIO_ACCESS_KEY=<use the key generated earlier> # [!code highlight]
+MINIO_SECRET_KEY=<use the secret generated earlier> # [!code highlight]
+MINIO_ENDPOINT=<use the endpoint of your spaces bucket> # [!code highlight]
 ```
 
 :::note

+ 4 - 8
docs/docs/guides/deployment/deploy-to-railway/index.mdx

@@ -36,8 +36,7 @@ import { VendureConfig } from '@vendure/core';
 
 export const config: VendureConfig = {
     apiOptions: {
-        // highlight-next-line
-        port: +(process.env.PORT || 3000),
+        port: +(process.env.PORT || 3000), // [!code highlight]
         // ...
     },
     // ...
@@ -85,8 +84,7 @@ export const config: VendureConfig = {
     plugins: [
         AssetServerPlugin.init({
             route: 'assets',
-            // highlight-next-line
-            assetUploadDir: process.env.ASSET_UPLOAD_DIR || path.join(__dirname, '../static/assets'),
+            assetUploadDir: process.env.ASSET_UPLOAD_DIR || path.join(__dirname, '../static/assets'), // [!code highlight]
         }),
     ],
     // ...
@@ -140,11 +138,9 @@ DB_PASSWORD=${{Postgres.PGPASSWORD}}
 DB_HOST=${{Postgres.PGHOST}}
 DB_PORT=${{Postgres.PGPORT}}
 ASSET_UPLOAD_DIR=/vendure-assets
-// highlight-next-line
-COOKIE_SECRET=<add some random characters>
+COOKIE_SECRET=<add some random characters> # [!code highlight]
 SUPERADMIN_USERNAME=superadmin
-// highlight-next-line
-SUPERADMIN_PASSWORD=<create some strong password>
+SUPERADMIN_PASSWORD=<create some strong password> # [!code highlight]
 ```
 
 ![Setting env vars](./02-env-vars.webp) 

+ 4 - 8
docs/docs/guides/deployment/deploy-to-render/index.mdx

@@ -34,8 +34,7 @@ import { VendureConfig } from '@vendure/core';
 
 export const config: VendureConfig = {
     apiOptions: {
-        // highlight-next-line
-        port: +(process.env.PORT || 3000),
+        port: +(process.env.PORT || 3000), // [!code highlight]
         // ...
     },
     // ...
@@ -83,8 +82,7 @@ export const config: VendureConfig = {
     plugins: [
         AssetServerPlugin.init({
             route: 'assets',
-            // highlight-next-line
-            assetUploadDir: process.env.ASSET_UPLOAD_DIR || path.join(__dirname, '../static/assets'),
+            assetUploadDir: process.env.ASSET_UPLOAD_DIR || path.join(__dirname, '../static/assets'), // [!code highlight]
         }),
     ],
     // ...
@@ -161,11 +159,9 @@ DB_PASSWORD=<database "Password">
 DB_HOST=<database "Hostname">
 DB_PORT=<database "Port">
 ASSET_UPLOAD_DIR=/vendure-assets
-// highlight-next-line
-COOKIE_SECRET=<add some random characters>
+COOKIE_SECRET=<add some random characters> # [!code highlight]
 SUPERADMIN_USERNAME=superadmin
-// highlight-next-line
-SUPERADMIN_PASSWORD=<create some strong password>
+SUPERADMIN_PASSWORD=<create some strong password> # [!code highlight]
 ```
 Once the correct values have been entered, click "Create Environment Group".
 

+ 1 - 2
docs/docs/guides/deployment/getting-data-into-production.mdx

@@ -25,8 +25,7 @@ export const config: VendureConfig = {
     // ...
     dbConnectionOptions: {
         type: 'postgres',
-        // highlight-next-line
-        synchronize: process.env.DB_SYNCHRONIZE,
+        synchronize: process.env.DB_SYNCHRONIZE, // [!code highlight]
         host: process.env.DB_HOST,
         port: process.env.DB_PORT,
         username: process.env.DB_USER,

+ 7 - 13
docs/docs/guides/developer-guide/cache/index.mdx

@@ -281,9 +281,7 @@ import {
 
 @Injectable()
 export class PublicChannelService {
-// highlight-start
-  private publicChannel: SelfRefreshingCache<Channel, [RequestContext]>;
-// highlight-end
+  private publicChannel: SelfRefreshingCache<Channel, [RequestContext]>; // [!code highlight]
   private readonly logCtx = 'PublicChannelService';
 
   constructor(
@@ -291,20 +289,16 @@ export class PublicChannelService {
     private eventBus: EventBus,
   ) {
     this.eventBus.ofType(InitializerEvent).subscribe(async () => {
-      // highlight-start
-      this.publicChannel = await createSelfRefreshingCache({
-        name: 'PublicChannelService.publicChannel',
-        ttl: 1000 * 60 * 5, // 5min
-        refresh: { fn: ctx => this.findPublicChannel(ctx), defaultArgs: [RequestContext.empty()] },
-      });
-      // highlight-end
+      this.publicChannel = await createSelfRefreshingCache({ // [!code highlight]
+        name: 'PublicChannelService.publicChannel', // [!code highlight]
+        ttl: 1000 * 60 * 5, // 5min // [!code highlight]
+        refresh: { fn: ctx => this.findPublicChannel(ctx), defaultArgs: [RequestContext.empty()] }, // [!code highlight]
+      }); // [!code highlight]
     });
   }
 
   async getPublicChannel(): Promise<Channel> {
-    // highlight-start
-    const publicChannel = await this.publicChannel.value();
-    // highlight-end
+    const publicChannel = await this.publicChannel.value(); // [!code highlight]
     if (!publicChannel) {
       throw new InternalServerError(`error.public-channel-not-found`);
     }

+ 8 - 14
docs/docs/guides/developer-guide/channel-aware/index.mdx

@@ -30,11 +30,9 @@ class ProductRequest extends VendureEntity implements ChannelAware {
     @Column()
     text: string;
     
-    // highlight-start
-    @ManyToMany(() => Channel)
-    @JoinTable()
-    channels: Channel[];
-    // highlight-end
+    @ManyToMany(() => Channel) // [!code highlight]
+    @JoinTable() // [!code highlight]
+    channels: Channel[]; // [!code highlight]
 }
 ```
 
@@ -57,8 +55,7 @@ export class RequestService {
     async create(ctx: RequestContext, input: CreateRequestInput): Promise<ProductRequest> {
         const request = new ProductRequest(input);
         // Now we need to assign the request to the current channel (+ default channel)
-// highlight-next-line
-        await this.channelService.assignToCurrentChannel(input, ctx);
+        await this.channelService.assignToCurrentChannel(input, ctx); // [!code highlight]
         
         return await this.connection.getRepository(ProductRequest).save(request);
     }
@@ -86,11 +83,9 @@ export class RequestService {
     findOne(ctx: RequestContext,
             requestId: ID,
             relations?: RelationPaths<ProductRequest>) {
-// highlight-start
-        return this.connection.findOneInChannel(ctx, ProductRequest, requestId, ctx.channelId, {
-            relations: unique(effectiveRelations)
-        });
-// highlight-end
+        return this.connection.findOneInChannel(ctx, ProductRequest, requestId, ctx.channelId, { // [!code highlight]
+            relations: unique(effectiveRelations) // [!code highlight]
+        }); // [!code highlight]
     }
 
     findAll(
@@ -102,8 +97,7 @@ export class RequestService {
             .build(ProductRequest, options, {
                 ctx,
                 relations,
-// highlight-next-line
-                channelId: ctx.channelId,
+                channelId: ctx.channelId, // [!code highlight]
             })
             .getManyAndCount()
             .then(([items, totalItems]) => {

+ 2 - 4
docs/docs/guides/developer-guide/configuration/index.mdx

@@ -137,8 +137,7 @@ APP_ENV=dev
 COOKIE_SECRET=toh8soqdlj
 SUPERADMIN_USERNAME=superadmin
 SUPERADMIN_PASSWORD=superadmin
-// highlight-next-line
-MY_API_KEY=12345
+MY_API_KEY=12345 # [!code highlight]
 ```
 
 In order to tell TypeScript about the existence of this new variable, you can add it to the `src/environment.d.ts` file:
@@ -155,8 +154,7 @@ declare global {
             COOKIE_SECRET: string;
             SUPERADMIN_USERNAME: string;
             SUPERADMIN_PASSWORD: string;
-            // highlight-next-line
-            MY_API_KEY: string;
+            MY_API_KEY: string; // [!code highlight]
         }
     }
 }

+ 105 - 162
docs/docs/guides/developer-guide/custom-fields/index.mdx

@@ -55,26 +55,21 @@ The values of the custom fields can then be set and queried via the GraphQL APIs
 mutation {
     updateProduct(input: {
         id: 1
-        // highlight-start
-        customFields: {
-        infoUrl: "https://some-url.com",
-        downloadable: true,
-        }
-        // highlight-end
+        customFields: { # [!code highlight]
+        infoUrl: "https://some-url.com", # [!code highlight]
+        downloadable: true, # [!code highlight]
+        } # [!code highlight]
         translations: [
-        // highlight-next-line
-        { languageCode: en, customFields: { shortName: "foo" } }
+        { languageCode: en, customFields: { shortName: "foo" } } # [!code highlight]
         ]
         }) {
         id
         name
-        // highlight-start
-        customFields {
-            infoUrl
-            downloadable
-            shortName
-        }
-        // highlight-end
+        customFields { # [!code highlight]
+            infoUrl # [!code highlight]
+            downloadable # [!code highlight]
+            shortName # [!code highlight]
+        } # [!code highlight]
     }
 }
 ```
@@ -106,26 +101,22 @@ The custom fields will also extend the filter and sort options available to the
 ```graphql
 query {
     products(options: {
-        // highlight-start
-    filter: {
-        infoUrl: { contains: "new" },
-        downloadable: { eq: true }
-        },
-        sort: {
-            infoUrl: ASC
-        }
-        // highlight-end
+    filter: { # [!code highlight]
+        infoUrl: { contains: "new" }, # [!code highlight]
+        downloadable: { eq: true } # [!code highlight]
+        }, # [!code highlight]
+        sort: { # [!code highlight]
+            infoUrl: ASC # [!code highlight]
+        } # [!code highlight]
         }) {
         items {
             id
             name
-            // highlight-start
-            customFields {
-                infoUrl
-                downloadable
-                shortName
-            }
-            // highlight-end
+            customFields { # [!code highlight]
+                infoUrl # [!code highlight]
+                downloadable # [!code highlight]
+                shortName # [!code highlight]
+            } # [!code highlight]
         }
     }
 }
@@ -161,10 +152,8 @@ const config = {
         Customer: [
             {
                 name: 'avatar',
-                // highlight-start
-                type: 'relation',
-                entity: Asset,
-                // highlight-end
+                type: 'relation', // [!code highlight]
+                entity: Asset, // [!code highlight]
             },
         ],
     },
@@ -288,8 +277,7 @@ const config = {
     customFields: {
         Product: [
             {
-                // highlight-next-line
-                name: 'infoUrl',
+                name: 'infoUrl', // [!code highlight]
                 type: 'string'
             },
         ]
@@ -317,8 +305,7 @@ const config = {
             {
                 name: 'infoUrls',
                 type: 'string',
-                // highlight-next-line
-                list: true,
+                list: true, // [!code highlight]
             },
         ]
     }
@@ -347,13 +334,11 @@ const config = {
             {
                 name: 'infoUrl',
                 type: 'string',
-                // highlight-start
-                label: [
-                    { languageCode: LanguageCode.en, value: 'Info URL' },
-                    { languageCode: LanguageCode.de, value: 'Info-URL' },
-                    { languageCode: LanguageCode.es, value: 'URL de información' },
-                ],
-                // highlight-end
+                label: [ // [!code highlight]
+                    { languageCode: LanguageCode.en, value: 'Info URL' }, // [!code highlight]
+                    { languageCode: LanguageCode.de, value: 'Info-URL' }, // [!code highlight]
+                    { languageCode: LanguageCode.es, value: 'URL de información' }, // [!code highlight]
+                ], // [!code highlight]
             },
         ]
     }
@@ -376,13 +361,11 @@ const config = {
             {
                 name: 'infoUrl',
                 type: 'string',
-                // highlight-start
-                description: [
-                    { languageCode: LanguageCode.en, value: 'A URL to more information about the product' },
-                    { languageCode: LanguageCode.de, value: 'Eine URL zu weiteren Informationen über das Produkt' },
-                    { languageCode: LanguageCode.es, value: 'Una URL con más información sobre el producto' },
-                ],
-                // highlight-end
+                description: [ // [!code highlight]
+                    { languageCode: LanguageCode.en, value: 'A URL to more information about the product' }, // [!code highlight]
+                    { languageCode: LanguageCode.de, value: 'Eine URL zu weiteren Informationen über das Produkt' }, // [!code highlight]
+                    { languageCode: LanguageCode.es, value: 'Una URL con más información sobre el producto' }, // [!code highlight]
+                ], // [!code highlight]
             },
         ]
     }
@@ -403,8 +386,7 @@ const config = {
             {
                 name: 'profitMargin',
                 type: 'int',
-                // highlight-next-line
-                public: false,
+                public: false, // [!code highlight]
             },
         ]
     }
@@ -426,8 +408,7 @@ const config = {
             {
                 name: 'profitMargin',
                 type: 'int',
-                // highlight-next-line
-                readonly: true,
+                readonly: true, // [!code highlight]
             },
         ]
     }
@@ -450,8 +431,7 @@ const config = {
             {
                 name: 'referralId',
                 type: 'string',
-                // highlight-next-line
-                internal: true,
+                internal: true, // [!code highlight]
             },
         ]
     }
@@ -473,8 +453,7 @@ const config = {
             {
                 name: 'reviewRating',
                 type: 'float',
-                // highlight-next-line
-                defaultValue: 0,
+                defaultValue: 0, // [!code highlight]
             },
         ]
     }
@@ -495,10 +474,8 @@ const config = {
             {
                 name: 'reviewRating',
                 type: 'float',
-                // highlight-start
-                nullable: false,
-                defaultValue: 0,
-                // highlight-end
+                nullable: false, // [!code highlight]
+                defaultValue: 0, // [!code highlight]
             },
         ]
     }
@@ -520,8 +497,7 @@ const config = {
             {
                 name: 'externalId',
                 type: 'string',
-                // highlight-next-line
-                unique: true,
+                unique: true, // [!code highlight]
             },
         ]
     }
@@ -546,21 +522,19 @@ const config = {
             {
                 name: 'infoUrl',
                 type: 'string',
-                // highlight-start
-                validate: (value: any) => {
-                    if (!value.startsWith('http')) {
-                        // If a localized error message is not required, a simple string can be returned.
-                        // return 'The URL must start with "http"';
-
-                        // If a localized error message is required, return an array of LocalizedString objects.
-                        return [
-                            { languageCode: LanguageCode.en, value: 'The URL must start with "http"' },
-                            { languageCode: LanguageCode.de, value: 'Die URL muss mit "http" beginnen' },
-                            { languageCode: LanguageCode.es, value: 'La URL debe comenzar con "http"' },
-                        ];
-                    }
-                },
-                // highlight-end
+                validate: (value: any) => { // [!code highlight]
+                    if (!value.startsWith('http')) { // [!code highlight]
+                        // If a localized error message is not required, a simple string can be returned. // [!code highlight]
+                        // return 'The URL must start with "http"'; // [!code highlight]
+ // [!code highlight]
+                        // If a localized error message is required, return an array of LocalizedString objects. // [!code highlight]
+                        return [ // [!code highlight]
+                            { languageCode: LanguageCode.en, value: 'The URL must start with "http"' }, // [!code highlight]
+                            { languageCode: LanguageCode.de, value: 'Die URL muss mit "http" beginnen' }, // [!code highlight]
+                            { languageCode: LanguageCode.es, value: 'La URL debe comenzar con "http"' }, // [!code highlight]
+                        ]; // [!code highlight]
+                    } // [!code highlight]
+                }, // [!code highlight]
             },
         ]
     }
@@ -577,15 +551,13 @@ const config = {
             {
                 name: 'partCode',
                 type: 'string',
-                // highlight-start
-                validate: async (value, injector, ctx) => {
-                    const partCodeService = injector.get(PartCodeService);
-                    const isValid = await partCodeService.validateCode(value);
-                    if (!isValid) {
-                        return `Part code ${value} is not valid`;
-                    }
-                },
-                // highlight-end
+                validate: async (value, injector, ctx) => { // [!code highlight]
+                    const partCodeService = injector.get(PartCodeService); // [!code highlight]
+                    const isValid = await partCodeService.validateCode(value); // [!code highlight]
+                    if (!isValid) { // [!code highlight]
+                        return `Part code ${value} is not valid`; // [!code highlight]
+                    } // [!code highlight]
+                }, // [!code highlight]
             },
         ]
     }
@@ -616,22 +588,18 @@ const config = {
             {
                 name: 'internalNotes',
                 type: 'text',
-                // highlight-start
-                requiresPermission: Permission.SuperAdmin,
-                // highlight-end
+                requiresPermission: Permission.SuperAdmin, // [!code highlight]
             },
             {
                 name: 'shippingType',
                 type: 'string',
-                // highlight-start
-                // You can also use an array of permissions, 
-                // and the user must have at least one of the permissions
-                // to access the field.
-                requiresPermission: [
-                    Permission.SuperAdmin,
-                    Permission.ReadShippingMethod,
-                ],
-                // highlight-end
+                // You can also use an array of permissions,  // [!code highlight]
+                // and the user must have at least one of the permissions // [!code highlight]
+                // to access the field. // [!code highlight]
+                requiresPermission: [ // [!code highlight]
+                    Permission.SuperAdmin, // [!code highlight]
+                    Permission.ReadShippingMethod, // [!code highlight]
+                ], // [!code highlight]
             },
         ]
     }
@@ -664,14 +632,12 @@ const config = {
             {
                 name: 'oldField',
                 type: 'string',
-                // highlight-next-line
-                deprecated: true,
+                deprecated: true, // [!code highlight]
             },
             {
                 name: 'legacyUrl',
                 type: 'string',
-                // highlight-next-line
-                deprecated: 'Use the new infoUrl field instead',
+                deprecated: 'Use the new infoUrl field instead', // [!code highlight]
             },
         ]
     }
@@ -710,8 +676,7 @@ const config = {
             {
                 name: 'gtin',
                 type: 'string',
-                // highlight-next-line
-                pattern: '^\d{8}(?:\d{4,6})?$',
+                pattern: '^\d{8}(?:\d{4,6})?$', // [!code highlight]
             },
         ]
     }
@@ -734,12 +699,10 @@ const config = {
             {
                 name: 'condition',
                 type: 'string',
-                // highlight-start
-                options: [
-                    { value: 'new', label: [{ languageCode: LanguageCode.en, value: 'New' }] },
-                    { value: 'used', label: [{ languageCode: LanguageCode.en, value: 'Used' }] },
-                ],
-                // highlight-end
+                options: [ // [!code highlight]
+                    { value: 'new', label: [{ languageCode: LanguageCode.en, value: 'New' }] }, // [!code highlight]
+                    { value: 'used', label: [{ languageCode: LanguageCode.en, value: 'Used' }] }, // [!code highlight]
+                ], // [!code highlight]
             },
         ]
     }
@@ -762,8 +725,7 @@ const config = {
             {
                 name: 'partCode',
                 type: 'string',
-                // highlight-next-line
-                length: 20,
+                length: 20, // [!code highlight]
             },
         ]
     }
@@ -811,8 +773,7 @@ const config = {
             {
                 name: 'reviewRating',
                 type: 'int',
-                // highlight-next-line
-                min: 0,
+                min: 0, // [!code highlight]
             },
         ]
     }
@@ -833,8 +794,7 @@ const config = {
             {
                 name: 'reviewRating',
                 type: 'int',
-                // highlight-next-line
-                max: 5,
+                max: 5, // [!code highlight]
             },
         ]
     }
@@ -855,8 +815,7 @@ const config = {
             {
                 name: 'reviewRating',
                 type: 'int',
-                // highlight-next-line
-                step: 0.5,
+                step: 0.5, // [!code highlight]
             },
         ]
     }
@@ -887,8 +846,7 @@ const config = {
             {
                 name: 'releaseDate',
                 type: 'datetime',
-                // highlight-next-line
-                min: '2019-01-01T00:00:00.000Z',
+                min: '2019-01-01T00:00:00.000Z', // [!code highlight]
             },
         ]
     }
@@ -909,8 +867,7 @@ const config = {
             {
                 name: 'releaseDate',
                 type: 'datetime',
-                // highlight-next-line
-                max: '2019-12-31T23:59:59.999Z',
+                max: '2019-12-31T23:59:59.999Z', // [!code highlight]
             },
         ]
     }
@@ -947,13 +904,11 @@ const config = {
             {
                 name: 'dimensions',
                 type: 'struct',
-                // highlight-start
-                fields: [
-                    { name: 'length', type: 'int' },
-                    { name: 'width', type: 'int' },
-                    { name: 'height', type: 'int' },
-                ],
-                // highlight-end
+                fields: [ // [!code highlight]
+                    { name: 'length', type: 'int' }, // [!code highlight]
+                    { name: 'width', type: 'int' }, // [!code highlight]
+                    { name: 'height', type: 'int' }, // [!code highlight]
+                ], // [!code highlight]
             },
         ]
     }
@@ -997,29 +952,24 @@ const config = {
                     {
                         name: 'color',
                         type: 'string',
-                        // highlight-start
-                        options: [
-                            { value: 'red', label: [{ languageCode: LanguageCode.en, value: 'Red' }] },
-                            { value: 'blue', label: [{ languageCode: LanguageCode.en, value: 'Blue' }] },
-                        ],
-                        // highlight-end
+                        options: [ // [!code highlight]
+                            { value: 'red', label: [{ languageCode: LanguageCode.en, value: 'Red' }] }, // [!code highlight]
+                            { value: 'blue', label: [{ languageCode: LanguageCode.en, value: 'Blue' }] }, // [!code highlight]
+                        ], // [!code highlight]
                     },
                     {
                         name: 'engraving',
                         type: 'string',
-                        // highlight-start
-                        validate: (value: any) => {
-                            if (value.length > 20) {
-                                return 'Engraving text must be 20 characters or fewer';
-                            }
-                        },
+                        validate: (value: any) => { // [!code highlight]
+                            if (value.length > 20) { // [!code highlight]
+                                return 'Engraving text must be 20 characters or fewer'; // [!code highlight]
+                            } // [!code highlight]
+                        }, // [!code highlight]
                     },
                     {
                         name: 'notifyEmailAddresses',
                         type: 'string',
-                        // highlight-start
-                        list: true,
-                        // highlight-end
+                        list: true, // [!code highlight]
                     }
                 ],
             },
@@ -1054,10 +1004,8 @@ const config = {
             {
                 name: 'relatedProducts',
                 list: true,
-                // highlight-next-line
-                type: 'relation',
-                // highlight-next-line
-                entity: Product,
+                type: 'relation', // [!code highlight]
+                entity: Product, // [!code highlight]
             },
         ]
     }
@@ -1082,8 +1030,7 @@ const config = {
                 list: true,
                 type: 'relation',
                 entity: Product,
-                // highlight-next-line
-                eager: true,
+                eager: true, // [!code highlight]
             },
         ]
     }
@@ -1108,8 +1055,7 @@ const config = {
                 list: true,
                 type: 'relation',
                 entity: CmsArticle,
-                // highlight-next-line
-                graphQLType: 'BlogPost',
+                graphQLType: 'BlogPost', // [!code highlight]
             },
         ]
     }
@@ -1138,9 +1084,7 @@ const config = {
                 list: true,
                 type: 'relation',
                 entity: ProductReview,
-                // highlight-start
-                inverseSide: (review: ProductReview) => review.product,
-                // highlight-end
+                inverseSide: (review: ProductReview) => review.product, // [!code highlight]
             },
         ]
     }
@@ -1210,8 +1154,7 @@ const config = {
             {
                 name: 'rrp',
                 type: 'int',
-                // highlight-next-line
-                ui: { component: 'currency-form-input' },
+                ui: { component: 'currency-form-input' }, // [!code highlight]
             },
         ]
     }

+ 11 - 20
docs/docs/guides/developer-guide/custom-permissions/index.mdx

@@ -30,8 +30,7 @@ import { sync } from '../constants';
 @Resolver()
 export class InventorySyncResolver {
 
-    // highlight-next-line
-    @Allow(sync.Permission)
+    @Allow(sync.Permission) // [!code highlight]
     @Mutation()
     syncInventory(/* ... */) {
         // ...
@@ -62,8 +61,7 @@ import { sync } from './constants';
         resolvers: [InventorySyncResolver]
     },
     configuration: config => {
-        // highlight-next-line
-        config.authOptions.customPermissions.push(sync);
+        config.authOptions.customPermissions.push(sync); // [!code highlight]
         return config;
     },
 })
@@ -94,31 +92,27 @@ import { productReviewPermission } from '../constants';
 @Resolver()
 export class ProductReviewResolver {
 
-    // highlight-next-line
-    @Allow(productReviewPermission.Read)
+    @Allow(productReviewPermission.Read) // [!code highlight]
     @Query()
     productReviews(/* ... */) {
         // ...
     }
     
-    // highlight-next-line
-    @Allow(productReviewPermission.Create)
+    @Allow(productReviewPermission.Create) // [!code highlight]
     @Mutation()
     @Transaction()
     createProductReview(/* ... */) {
         // ...
     }
     
-    // highlight-next-line
-    @Allow(productReviewPermission.Update)
+    @Allow(productReviewPermission.Update) // [!code highlight]
     @Mutation()
     @Transaction()
     updateProductReview(/* ... */) {
         // ...
     }
     
-    // highlight-next-line
-    @Allow(productReviewPermission.Delete)
+    @Allow(productReviewPermission.Delete) // [!code highlight]
     @Mutation()
     @Transaction()
     deleteProductReview(/* ... */) {
@@ -144,8 +138,7 @@ import { productReviewPermission } from './constants';
         resolvers: [ProductReviewResolver]
     },
     configuration: config => {
-        // highlight-next-line
-        config.authOptions.customPermissions.push(productReviewPermission);
+        config.authOptions.customPermissions.push(productReviewPermission); // [!code highlight]
         return config;
     },
 })
@@ -172,12 +165,10 @@ import { productReviewPermission } from './constants';
         config.customFields.Product.push({
             name: 'rating',
             type: 'int',
-            // highlight-start
-            requiresPermission: [
-                productReviewPermission.Read, 
-                productReviewPermission.Update,
-            ],
-            // highlight-end
+            requiresPermission: [ // [!code highlight]
+                productReviewPermission.Read, // [!code highlight]
+                productReviewPermission.Update, // [!code highlight]
+            ], // [!code highlight]
         });
         return config;
     },

+ 8 - 12
docs/docs/guides/developer-guide/database-entity/index.mdx

@@ -48,8 +48,7 @@ Without this setting, ES2022 class field semantics cause entity fields to be ove
 {
     "compilerOptions": {
         "target": "ES2022",
-        // highlight-next-line
-        "useDefineForClassFields": false
+        "useDefineForClassFields": false // [!code highlight]
     }
 }
 ```
@@ -80,8 +79,7 @@ import { VendurePlugin } from '@vendure/core';
 import { ProductReview } from './entities/product-review.entity';
 
 @VendurePlugin({
-    // highlight-next-line
-    entities: [ProductReview],
+    entities: [ProductReview], // [!code highlight]
 })
 export class ReviewsPlugin {}
 ```
@@ -106,14 +104,12 @@ export class ReviewService {
 
     async createReview(ctx: RequestContext, productId: string, rating: number, text: string) {
         const product = await this.connection.getEntityOrThrow(ctx, Product, productId);
-        // highlight-start
-        const review = new ProductReview({
-            product,
-            rating,
-            text,
-        });
-        return this.connection.getRepository(ctx, ProductReview).save(review);
-        // highlight-end
+        const review = new ProductReview({ // [!code highlight]
+            product, // [!code highlight]
+            rating, // [!code highlight]
+            text, // [!code highlight]
+        }); // [!code highlight]
+        return this.connection.getRepository(ctx, ProductReview).save(review); // [!code highlight]
     }
 }
 ```

+ 23 - 30
docs/docs/guides/developer-guide/error-handling/index.mdx

@@ -88,24 +88,22 @@ export function query(document: string, variables: Record<string, any> = {}) {
             window.alert(err.message);
         })
         .then((result) => {
-            // highlight-start
-            // We check for any GraphQL errors which would be in the
-            // `errors` array of the response body:
-            if (Array.isArray(result.errors)) {
-                // It looks like we have an unexpected error.
-                // At this point you could take actions like:
-                // - logging the error to a remote service
-                // - displaying an error popup to the user
-                // - inspecting the `error.extensions.code` to determine the
-                //   type of error and take appropriate action. E.g. a
-                //   in response to a FORBIDDEN_ERROR you can redirect the
-                //   user to a login page.
-
-                // In this example we just display an alert:
-                const errorMessage = result.errors.map((e) => e.message).join('\n');
-                window.alert(`Unexpected error caught:\n\n${errorMessage}`);
-            }
-            // highlight-end
+            // We check for any GraphQL errors which would be in the // [!code highlight]
+            // `errors` array of the response body: // [!code highlight]
+            if (Array.isArray(result.errors)) { // [!code highlight]
+                // It looks like we have an unexpected error. // [!code highlight]
+                // At this point you could take actions like: // [!code highlight]
+                // - logging the error to a remote service // [!code highlight]
+                // - displaying an error popup to the user // [!code highlight]
+                // - inspecting the `error.extensions.code` to determine the // [!code highlight]
+                //   type of error and take appropriate action. E.g. a // [!code highlight]
+                //   in response to a FORBIDDEN_ERROR you can redirect the // [!code highlight]
+                //   user to a login page. // [!code highlight]
+
+                // In this example we just display an alert: // [!code highlight]
+                const errorMessage = result.errors.map((e) => e.message).join('\n'); // [!code highlight]
+                window.alert(`Unexpected error caught:\n\n${errorMessage}`); // [!code highlight]
+            } // [!code highlight]
             return result;
         });
 }
@@ -196,8 +194,7 @@ Here's how a response would look in both the success and error result cases:
 {
   "data": {
     "applyCouponCode": {
-      // highlight-next-line
-      "__typename": "Order",
+      "__typename": "Order", // [!code highlight]
       "id": "123",
       "couponCodes": ["VALID-CODE"],
       "totalWithTax": 12599,
@@ -213,12 +210,10 @@ Here's how a response would look in both the success and error result cases:
 {
   "data": {
     "applyCouponCode": {
-      // highlight-next-line
-      "__typename": "CouponCodeLimitError",
+      "__typename": "CouponCodeLimitError", // [!code highlight]
       "errorCode": "COUPON_CODE_LIMIT_ERROR",
       "message": "Coupon code cannot be used more than once per customer",
-      // highlight-next-line
-      "limit": 1
+      "limit": 1 // [!code highlight]
     }
   }
 }
@@ -304,12 +299,10 @@ switch (result.applyCouponCode.__typename) {
         // handle limit error
         break;
     default:
-        // highlight-start
-        // this line will cause a TypeScript error if there are any
-        // ErrorResults which we have not handled in the switch cases
-        // above.
-        const _exhaustiveCheck: never = result.applyCouponCode;
-        // highlight-end
+        // this line will cause a TypeScript error if there are any // [!code highlight]
+        // ErrorResults which we have not handled in the switch cases // [!code highlight]
+        // above. // [!code highlight]
+        const _exhaustiveCheck: never = result.applyCouponCode; // [!code highlight]
 }
 ```
 

+ 28 - 42
docs/docs/guides/developer-guide/events/index.mdx

@@ -101,17 +101,14 @@ import { StorefrontBuildService } from './services/storefront-build.service';
 })
 export class StorefrontBuildPlugin implements OnModuleInit {
     constructor(
-        // highlight-next-line
-        private eventBus: EventBus,
+        private eventBus: EventBus, // [!code highlight]
         private storefrontBuildService: StorefrontBuildService,
     ) {}
 
     onModuleInit() {
-        // highlight-start
-        this.eventBus.ofType(ProductEvent).subscribe(event => {
-            this.storefrontBuildService.triggerBuild();
-        });
-        // highlight-end
+        this.eventBus.ofType(ProductEvent).subscribe(event => { // [!code highlight]
+            this.storefrontBuildService.triggerBuild(); // [!code highlight]
+        }); // [!code highlight]
     }
 }
 ```
@@ -123,15 +120,13 @@ This means that you can use any of the [RxJS operators](https://rxjs-dev.firebas
 For example, to debounce the stream of events, you could do this:
 
 ```ts
-// highlight-next-line
-import { debounceTime } from 'rxjs/operators';
+import { debounceTime } from 'rxjs/operators'; // [!code highlight]
 
 // ...
 
 this.eventBus
     .ofType(ProductEvent)
-    // highlight-next-line
-    .pipe(debounceTime(1000))
+    .pipe(debounceTime(1000)) // [!code highlight]
     .subscribe(event => {
         this.storefrontBuildService.triggerBuild();
     });
@@ -161,9 +156,7 @@ export class MyPluginPlugin implements OnModuleInit {
 
     onModuleInit() {
         this.eventBus
-            // highlight-start
-            .filter(event => event instanceof ProductEvent || event instanceof ProductVariantEvent)
-            // highlight-end
+            .filter(event => event instanceof ProductEvent || event instanceof ProductVariantEvent) // [!code highlight]
             .subscribe(event => {
                 // the event will be a ProductEvent or ProductVariantEvent
             });
@@ -187,8 +180,7 @@ export class MyPluginService {
 
     async doSomethingWithProduct(ctx: RequestContext, product: Product) {
         // ... do something
-        // highlight-next-line
-        await this.eventBus.publish(new ProductEvent(ctx, product, 'updated'));
+        await this.eventBus.publish(new ProductEvent(ctx, product, 'updated')); // [!code highlight]
     }
 }
 ```
@@ -232,8 +224,7 @@ export class ProductReviewService {
     ) {}
 
     async submitReview(ctx: RequestContext, input: ProductReviewInput) {
-        // highlight-next-line
-        this.eventBus.publish(new ReviewSubmittedEvent(ctx, input));
+        this.eventBus.publish(new ReviewSubmittedEvent(ctx, input)); // [!code highlight]
         // handle creation of the new review
         // ...
     }
@@ -285,14 +276,12 @@ export class BlogPlugin implements OnModuleInit {
 
     onModuleInit() {
         this.eventBus
-            // highlight-start
-            .ofType(BlogPostEvent)
-            .pipe(filter(event => event.type === 'created'))
-            .subscribe(event => {
-                const blogPost = event.entity;
-                // do something with the newly created BlogPost
-            });
-        // highlight-end
+            .ofType(BlogPostEvent) // [!code highlight]
+            .pipe(filter(event => event.type === 'created')) // [!code highlight]
+            .subscribe(event => { // [!code highlight]
+                const blogPost = event.entity; // [!code highlight]
+                // do something with the newly created BlogPost // [!code highlight]
+            }); // [!code highlight]
     }
 }
 ```
@@ -339,20 +328,18 @@ export class MyPluginPlugin implements OnModuleInit {
     ) {}
 
     onModuleInit() {
-        // highlight-start
-        this.eventBus.registerBlockingEventHandler({
-            event: CustomerEvent,
-            id: 'sync-customer-details-handler',
-            handler: async event => {
-                // This hypothetical service method would do nothing
-                // more than adding a new job to the job queue. This gives us
-                // the guarantee that the job is added before the publishing
-                // code is able to continue, while minimizing the time spent
-                // in the event handler.
-                await this.customerSyncService.triggerCustomerSyncJob(event);
-            },
-        });
-        // highlight-end
+        this.eventBus.registerBlockingEventHandler({ // [!code highlight]
+            event: CustomerEvent, // [!code highlight]
+            id: 'sync-customer-details-handler', // [!code highlight]
+            handler: async event => { // [!code highlight]
+                // This hypothetical service method would do nothing // [!code highlight]
+                // more than adding a new job to the job queue. This gives us // [!code highlight]
+                // the guarantee that the job is added before the publishing // [!code highlight]
+                // code is able to continue, while minimizing the time spent // [!code highlight]
+                // in the event handler. // [!code highlight]
+                await this.customerSyncService.triggerCustomerSyncJob(event); // [!code highlight]
+            }, // [!code highlight]
+        }); // [!code highlight]
     }
 }
 ```
@@ -400,7 +387,6 @@ this.eventBus.registerBlockingEventHandler({
     handler: async event => {
         // ...
     },
-    // highlight-next-line
-    before: 'sync-customer-details-handler',
+    before: 'sync-customer-details-handler', // [!code highlight]
 });
 ```

+ 53 - 75
docs/docs/guides/developer-guide/extend-graphql-api/index.mdx

@@ -23,21 +23,19 @@ const schemaExtension = gql`
 
 @VendurePlugin({
     imports: [PluginCommonModule],
-    // highlight-start
-    // We pass our schema extension and any related resolvers
-    // to our plugin metadata  
-    shopApiExtensions: {
-        schema: schemaExtension,
-        resolvers: [TopProductsResolver],
-    },
-    // Likewise, if you want to extend the Admin API,
-    // you would use `adminApiExtensions` in exactly the
-    // same way.  
-    // adminApiExtensions: {
-    //     schema: someSchemaExtension
-    //     resolvers: [SomeResolver],
-    // },
-    // highlight-end
+    // We pass our schema extension and any related resolvers // [!code highlight]
+    // to our plugin metadata   // [!code highlight]
+    shopApiExtensions: { // [!code highlight]
+        schema: schemaExtension, // [!code highlight]
+        resolvers: [TopProductsResolver], // [!code highlight]
+    }, // [!code highlight]
+    // Likewise, if you want to extend the Admin API, // [!code highlight]
+    // you would use `adminApiExtensions` in exactly the // [!code highlight]
+    // same way.   // [!code highlight]
+    // adminApiExtensions: { // [!code highlight]
+    //     schema: someSchemaExtension // [!code highlight]
+    //     resolvers: [SomeResolver], // [!code highlight]
+    // }, // [!code highlight]
 })
 export class TopProductsPlugin {
 }
@@ -56,8 +54,7 @@ import gql from 'graphql-tag';
 
 export const shopApiExtensions = gql`
   extend type Query {
-    // highlight-next-line
-    activeBanner(locationId: String!): String
+    activeBanner(locationId: String!): String # [!code highlight]
   }
 `;
 ```
@@ -81,12 +78,10 @@ import { BannerService } from '../services/banner.service.ts';
 class BannerShopResolver {
     constructor(private bannerService: BannerService) {}
 
-    // highlight-start
-    @Query()
-    activeBanner(@Ctx() ctx: RequestContext, @Args() args: { locationId: string; }) {
-        return this.bannerService.getBanner(ctx, args.locationId);
-    }
-    // highlight-end
+    @Query() // [!code highlight]
+    activeBanner(@Ctx() ctx: RequestContext, @Args() args: { locationId: string; }) { // [!code highlight]
+        return this.bannerService.getBanner(ctx, args.locationId); // [!code highlight]
+    } // [!code highlight]
 }
 ```
 
@@ -102,12 +97,10 @@ import { shopApiExtensions } from './api/api-extensions';
 
 @VendurePlugin({
     imports: [PluginCommonModule],
-    // highlight-start
-    shopApiExtensions: {
-        schema: shopApiExtensions,
-        resolvers: [BannerShopResolver],
-    },
-    // highlight-end
+    shopApiExtensions: { // [!code highlight]
+        schema: shopApiExtensions, // [!code highlight]
+        resolvers: [BannerShopResolver], // [!code highlight]
+    }, // [!code highlight]
     providers: [BannerService],
 })
 export class BannerPlugin {}
@@ -124,8 +117,7 @@ import gql from 'graphql-tag';
 
 export const adminApiExtensions = gql`
   extend type Mutation {
-    // highlight-next-line
-    setBannerText(locationId: String!, text: String!): String!
+    setBannerText(locationId: String!, text: String!): String! # [!code highlight]
   }
 `;
 ```
@@ -143,14 +135,12 @@ import { BannerService } from '../services/banner.service.ts';
 class BannerAdminResolver {
     constructor(private bannerService: BannerService) {}
 
-    // highlight-start
-    @Allow(Permission.UpdateSettings)
-    @Transaction()
-    @Mutation()
-    setBannerText(@Ctx() ctx: RequestContext, @Args() args: { locationId: string; text: string; }) {
-        return this.bannerService.setBannerText(ctx, args.locationId, args.text);
-    }
-    // highlight-end
+    @Allow(Permission.UpdateSettings) // [!code highlight]
+    @Transaction() // [!code highlight]
+    @Mutation() // [!code highlight]
+    setBannerText(@Ctx() ctx: RequestContext, @Args() args: { locationId: string; text: string; }) { // [!code highlight]
+        return this.bannerService.setBannerText(ctx, args.locationId, args.text); // [!code highlight]
+    } // [!code highlight]
 }
 ```
 
@@ -175,12 +165,10 @@ import { shopApiExtensions, adminApiExtensions } from './api/api-extensions';
         schema: shopApiExtensions,
         resolvers: [BannerShopResolver],
     },
-    // highlight-start
-    adminApiExtensions: {
-        schema: adminApiExtensions,
-        resolvers: [BannerAdminResolver],
-    },
-    // highlight-end
+    adminApiExtensions: { // [!code highlight]
+        schema: adminApiExtensions, // [!code highlight]
+        resolvers: [BannerAdminResolver], // [!code highlight]
+    }, // [!code highlight]
     providers: [BannerService],
 })
 export class BannerPlugin {}
@@ -225,17 +213,15 @@ Let's define a new GraphQL type which corresponds to this entity:
 import gql from 'graphql-tag';
 
 export const apiExtensions = gql`
-  // highlight-start
-  type ProductReview implements Node {
-    id: ID!
-    createdAt: DateTime!
-    updatedAt: DateTime!
-    product: Product!
-    productId: ID!
-    text: String!
-    rating: Float!
-  }
-  // highlight-end
+  type ProductReview implements Node { # [!code highlight]
+    id: ID! # [!code highlight]
+    createdAt: DateTime! # [!code highlight]
+    updatedAt: DateTime! # [!code highlight]
+    product: Product! # [!code highlight]
+    productId: ID! # [!code highlight]
+    text: String! # [!code highlight]
+    rating: Float! # [!code highlight]
+  } # [!code highlight]
 `;
 ```
 
@@ -257,8 +243,7 @@ import { ProductReview } from './entities/product-review.entity';
 @VendurePlugin({
     imports: [PluginCommonModule],
     shopApiExtensions: {
-        // highlight-next-line
-        schema: apiExtensions,
+        schema: apiExtensions, // [!code highlight]
     },
     entities: [ProductReview],
 })
@@ -280,11 +265,9 @@ export const shopApiExtensions = gql`
     to: Int!
   }
 
-  // highlight-start
-  extend type ProductVariant {
-    delivery: DeliveryEstimate!
-  }
-  // highlight-end
+  extend type ProductVariant { # [!code highlight]
+    delivery: DeliveryEstimate! # [!code highlight]
+  } # [!code highlight]
 }`;
 ```
 
@@ -298,17 +281,14 @@ import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
 import { Ctx, RequestContext, ProductVariant } from '@vendure/core';
 import { DeliveryEstimateService } from '../services/delivery-estimate.service';
 
-// highlight-next-line
-@Resolver('ProductVariant')
+@Resolver('ProductVariant') // [!code highlight]
 export class ProductVariantEntityResolver {
     constructor(private deliveryEstimateService: DeliveryEstimateService) { }
 
-    // highlight-start
-    @ResolveField()
-    delivery(@Ctx() ctx: RequestContext, @Parent() variant: ProductVariant) {
-        return this.deliveryEstimateService.getEstimate(ctx, variant.id);
-    }
-    // highlight-end
+    @ResolveField() // [!code highlight]
+    delivery(@Ctx() ctx: RequestContext, @Parent() variant: ProductVariant) { // [!code highlight]
+        return this.deliveryEstimateService.getEstimate(ctx, variant.id); // [!code highlight]
+    } // [!code highlight]
 }
 ```
 
@@ -323,10 +303,8 @@ import { shopApiExtensions } from './api/api-extensions';
 @VendurePlugin({
     imports: [PluginCommonModule],
     shopApiExtensions: {
-        // highlight-start
-        schema: shopApiExtensions,
-        resolvers: [ProductVariantEntityResolver]
-        // highlight-end
+        schema: shopApiExtensions, // [!code highlight]
+        resolvers: [ProductVariantEntityResolver] // [!code highlight]
     }
 })
 export class DeliveryTimePlugin {}

+ 11 - 21
docs/docs/guides/developer-guide/has-custom-fields/index.mdx

@@ -21,20 +21,16 @@ import {
 } from '@vendure/core';
 import { Column, Entity, ManyToOne } from 'typeorm';
 
-// highlight-next-line
-export class CustomProductReviewFields {}
+export class CustomProductReviewFields {} // [!code highlight]
 
 @Entity()
-// highlight-next-line
-export class ProductReview extends VendureEntity implements HasCustomFields {
+export class ProductReview extends VendureEntity implements HasCustomFields { // [!code highlight]
     constructor(input?: DeepPartial<ProductReview>) {
         super(input);
     }
 
-    // highlight-start
-    @Column(() => CustomProductReviewFields)
-    customFields: CustomProductReviewFields;
-    // highlight-end
+    @Column(() => CustomProductReviewFields) // [!code highlight]
+    customFields: CustomProductReviewFields; // [!code highlight]
     
     @ManyToOne(() => Product)
     product: Product;
@@ -100,20 +96,17 @@ Following this caveat, codegen will now produce correct types including `customF
 
 ```ts
 export type ProductReview = Node & {
-  // highlight-next-line
-  customFields?: Maybe<Scalars['JSON']['output']>;
+  customFields?: Maybe<Scalars['JSON']['output']>; // [!code highlight]
   // Note: Other fields omitted for brevity
 }
 
 export type CreateProductReviewInput = {
-  // highlight-next-line
-  customFields?: InputMaybe<Scalars['JSON']['input']>;
+  customFields?: InputMaybe<Scalars['JSON']['input']>; // [!code highlight]
   // Note: Other fields omitted for brevity
 }
 
 export type UpdateProductReviewInput = {
-  // highlight-next-line
-  customFields?: InputMaybe<Scalars['JSON']['input']>;
+  customFields?: InputMaybe<Scalars['JSON']['input']>; // [!code highlight]
   // Note: Other fields omitted for brevity
 }
 ```
@@ -132,19 +125,16 @@ import { ProductReview } from '../entities/product-review.entity';
 export class ReviewService {
     constructor(
       private connection: TransactionalConnection,
-      // highlight-next-line
-      private customFieldRelationService: CustomFieldRelationService,
+      private customFieldRelationService: CustomFieldRelationService, // [!code highlight]
     ) {}
 
     async create(ctx: RequestContext, input: CreateProductReviewInput) {
         const product = await this.connection.getEntityOrThrow(ctx, Product, input.productId);
         // You'll probably want to do more validation/logic here in a real world scenario
         
-        // highlight-start
-        const review = new ProductReview({ ...input, product });
-        const savedEntity = await this.connection.getRepository(ctx, ProductReview).save(review);
-        await this.customFieldRelationService.updateRelations(ctx, ProductReview, input, savedEntity);
-        // highlight-end
+        const review = new ProductReview({ ...input, product }); // [!code highlight]
+        const savedEntity = await this.connection.getRepository(ctx, ProductReview).save(review); // [!code highlight]
+        await this.customFieldRelationService.updateRelations(ctx, ProductReview, input, savedEntity); // [!code highlight]
 
         return savedEntity;
     }

+ 3 - 6
docs/docs/guides/developer-guide/migrations/index.mdx

@@ -21,8 +21,7 @@ export const config: VendureConfig = {
     // ...
     dbConnectionOptions: {
         // ...
-        // highlight-next-line
-        synchronize: false,
+        synchronize: false, // [!code highlight]
     }
 };
 ```
@@ -81,8 +80,7 @@ be run automatically. This is because the `runMigrations` function is called in
 import { bootstrap, runMigrations } from '@vendure/core';
 import { config } from './vendure-config';
 
-// highlight-next-line
-runMigrations(config)
+runMigrations(config) // [!code highlight]
     .then(() => bootstrap(config))
     .catch(err => {
         console.log(err);
@@ -169,8 +167,7 @@ export const config: VendureConfig = {
     // ...
     dbConnectionOptions: {
         // ...
-        // highlight-next-line
-        migrations: [path.join(__dirname, './migrations/*.+(js|ts)')],
+        migrations: [path.join(__dirname, './migrations/*.+(js|ts)')], // [!code highlight]
     }
 };
 ```

+ 13 - 26
docs/docs/guides/developer-guide/plugins/index.mdx

@@ -60,8 +60,7 @@ import { AvatarPlugin } from './plugins/avatar-plugin/avatar.plugin';
 
 export const config: VendureConfig = {
     // ...
-    // highlight-next-line
-    plugins: [AvatarPlugin],
+    plugins: [AvatarPlugin], // [!code highlight]
 };
 ```
 
@@ -178,10 +177,8 @@ We'll start by creating a new directory to house our plugin, add create the main
     ├── index.ts
     ├── vendure-config.ts
     ├── plugins
-        // highlight-next-line
-        ├── wishlist-plugin
-            // highlight-next-line
-            ├── wishlist.plugin.ts
+        ├── wishlist-plugin // [!code highlight]
+            ├── wishlist.plugin.ts // [!code highlight]
 ```
 
 ```ts title="src/plugins/wishlist-plugin/wishlist.plugin.ts"
@@ -205,8 +202,7 @@ First let's create the file to house the entity:
 ├── wishlist-plugin
     ├── wishlist.plugin.ts
     ├── entities
-        // highlight-next-line
-        ├── wishlist-item.entity.ts
+        ├── wishlist-item.entity.ts // [!code highlight]
 ```
 
 By convention, we'll store the entity definitions in the `entities` directory of the plugin. Again, this is not a requirement, but it is a good way to keep your plugin organized.
@@ -284,8 +280,7 @@ In order to make use of this custom field in a type-safe way, we can tell TypeSc
 ```txt
 ├── wishlist-plugin
     ├── wishlist.plugin.ts
-    // highlight-next-line
-    ├── types.ts
+    ├── types.ts // [!code highlight]
 ```
 
 ```ts title="src/plugins/wishlist-plugin/types.ts"
@@ -301,8 +296,7 @@ declare module '@vendure/core/dist/entity/custom-entity-fields' {
 We can then import this types file in our plugin's main file:
 
 ```ts title="src/plugins/wishlist-plugin/wishlist.plugin.ts"
-// highlight-next-line
-import './types';
+import './types'; // [!code highlight]
 ```
 
 :::note
@@ -319,8 +313,7 @@ Let's create a service to handle the wishlist functionality:
 ├── wishlist-plugin
     ├── wishlist.plugin.ts
     ├── services
-        // highlight-next-line
-        ├── wishlist.service.ts
+        ├── wishlist.service.ts // [!code highlight]
 ```
 
 ```ts title="src/plugins/wishlist-plugin/services/wishlist.service.ts"
@@ -432,8 +425,7 @@ import { WishlistService } from './services/wishlist.service';
 
 @VendurePlugin({
     imports: [PluginCommonModule],
-    // highlight-next-line
-    providers: [WishlistService],
+    providers: [WishlistService], // [!code highlight]
     entities: [WishlistItem],
     configuration: config => {
         // ...
@@ -452,8 +444,7 @@ First we will create a new file to hold the GraphQL schema extensions:
 ├── wishlist-plugin
     ├── wishlist.plugin.ts
     ├── api
-        // highlight-next-line
-        ├── api-extensions.ts
+        ├── api-extensions.ts // [!code highlight]
 ```
 
 ```ts title="src/plugins/wishlist-plugin/api/api-extensions.ts"
@@ -512,8 +503,7 @@ Now that we have defined the GraphQL schema extensions, we need to create a reso
     ├── wishlist.plugin.ts
     ├── api
         ├── api-extensions.ts
-        // highlight-next-line
-        ├── wishlist.resolver.ts
+        ├── wishlist.resolver.ts // [!code highlight]
 ```
 
 ```ts title="src/plugins/wishlist-plugin/api/wishlist.resolver.ts"
@@ -570,8 +560,7 @@ import { WishlistShopResolver } from './api/wishlist.resolver';
     imports: [PluginCommonModule],
     shopApiExtensions: {
         schema: shopApiExtensions,
-        // highlight-next-line
-        resolvers: [WishlistShopResolver],
+        resolvers: [WishlistShopResolver], // [!code highlight]
     },
     configuration: config => {
         // ...
@@ -595,8 +584,7 @@ The compatibility is specified via the `compatibility` property in the plugin me
 ```ts title="src/plugins/wishlist-plugin/wishlist.plugin.ts"
 @VendurePlugin({
     // ...
-    // highlight-next-line
-    compatibility: '^2.0.0',
+    compatibility: '^2.0.0', // [!code highlight]
 })
 export class WishlistPlugin {}
 ```
@@ -615,8 +603,7 @@ export const config: VendureConfig = {
     // ...
     plugins: [
         // ...
-        // highlight-next-line
-        WishlistPlugin,
+        WishlistPlugin, // [!code highlight]
     ],
 };
 ```

+ 2 - 4
docs/docs/guides/developer-guide/rest-endpoint/index.mdx

@@ -65,8 +65,7 @@ export const config: VendureConfig = {
     // ...
     plugins: [
         // ...
-        // highlight-next-line
-        RestPlugin,
+        RestPlugin, // [!code highlight]
     ],
 };
 ```
@@ -83,8 +82,7 @@ import { Allow, Permission, Ctx, ProductService, RequestContext } from '@vendure
 export class ProductsController {
     constructor(private productService: ProductService) {}
 
-    // highlight-next-line
-    @Allow(Permission.ReadProduct)
+    @Allow(Permission.ReadProduct) // [!code highlight]
     @Get()
     findAll(@Ctx() ctx: RequestContext) {
         return this.productService.findAll(ctx);

+ 25 - 36
docs/docs/guides/developer-guide/scheduled-tasks/index.mdx

@@ -132,28 +132,24 @@ This can be done directly in your Vendure config file:
 ```ts title="vendure-config.ts"
 import { cleanSessionsTask, DefaultSchedulerPlugin, VendureConfig } from '@vendure/core';
 
-// highlight-next-line
-import { SitemapPlugin, generateSitemapTask } from './plugins/sitemap';
+import { SitemapPlugin, generateSitemapTask } from './plugins/sitemap'; // [!code highlight]
 
 export const config: VendureConfig = {
     // ...
     schedulerOptions: {
         tasks: [
             cleanSessionsTask,
-            // highlight-start
-            // Here's an example of overriding the
-            // default params using the `configure()` method.
-            generateSitemapTask.configure({
-                params: {
-                    shopBaseUrl: 'https://www.shoes.com'
-                }
-            }),
-            // highlight-end
+            // Here's an example of overriding the // [!code highlight]
+            // default params using the `configure()` method. // [!code highlight]
+            generateSitemapTask.configure({ // [!code highlight]
+                params: { // [!code highlight]
+                    shopBaseUrl: 'https://www.shoes.com' // [!code highlight]
+                } // [!code highlight]
+            }), // [!code highlight]
         ],
     },
     plugins: [
-        // highlight-next-line
-        SitemapPlugin,
+        SitemapPlugin, // [!code highlight]
         DefaultSchedulerPlugin.init()
     ],
 };
@@ -179,16 +175,14 @@ import { generateSitemapTask } from './config/generate-sitemap-task';
     imports: [PluginCommonModule],
     providers: [SitemapService],
     configuration: (config: VendureConfig) => {
-        // highlight-start
-        // Add the task to the schedulerOptions.tasks array
-        config.schedulerOptions.tasks.push(
-            generateSitemapTask.configure({
-                params: {
-                    shopBaseUrl: SitemapPlugin.options.shopBaseUrl,
-                }
-            })
-        );
-        // highlight-end
+        // Add the task to the schedulerOptions.tasks array // [!code highlight]
+        config.schedulerOptions.tasks.push( // [!code highlight]
+            generateSitemapTask.configure({ // [!code highlight]
+                params: { // [!code highlight]
+                    shopBaseUrl: SitemapPlugin.options.shopBaseUrl, // [!code highlight]
+                } // [!code highlight]
+            }) // [!code highlight]
+        ); // [!code highlight]
         return config;
     },
 })
@@ -209,17 +203,14 @@ This plugin can now be consumed like this:
 ```ts title="vendure-config.ts"
 import { DefaultSchedulerPlugin, VendureConfig } from '@vendure/core';
 
-// highlight-next-line
-import { SitemapPlugin } from './plugins/sitemap';
+import { SitemapPlugin } from './plugins/sitemap'; // [!code highlight]
 
 export const config: VendureConfig = {
     // ...
     plugins: [
-        // highlight-start
-        SitemapPlugin.init({
-            shopBaseUrl: 'https://www.shoes.com'
-        }),
-        // highlight-end
+        SitemapPlugin.init({ // [!code highlight]
+            shopBaseUrl: 'https://www.shoes.com' // [!code highlight]
+        }), // [!code highlight]
         DefaultSchedulerPlugin.init()
     ],
 };
@@ -283,12 +274,10 @@ export class SitemapService {
 
     @Cron('0 0 * * *')
     async generateSitemap() {
-        // highlight-start
-        if (this.processContext.isWorker) {
-            // Only run on the worker
-            await this.triggerGenerate();
-        }
-        // highlight-end
+        if (this.processContext.isWorker) { // [!code highlight]
+            // Only run on the worker // [!code highlight]
+            await this.triggerGenerate(); // [!code highlight]
+        } // [!code highlight]
     }
 }
 ```

+ 6 - 8
docs/docs/guides/developer-guide/security/index.mdx

@@ -89,14 +89,12 @@ export const config: VendureConfig = {
   plugins: [
     AssetServerPlugin.init({
       // ...
-      // highlight-start  
-      imageTransformStrategy: new PresetOnlyStrategy({
-        defaultPreset: 'large',
-        permittedQuality: [0, 50, 75, 85, 95],
-        permittedFormats: ['jpg', 'webp', 'avif'],
-        allowFocalPoint: false,
-      }),
-      // highlight-end
+      imageTransformStrategy: new PresetOnlyStrategy({ // [!code highlight]
+        defaultPreset: 'large', // [!code highlight]
+        permittedQuality: [0, 50, 75, 85, 95], // [!code highlight]
+        permittedFormats: ['jpg', 'webp', 'avif'], // [!code highlight]
+        allowFocalPoint: false, // [!code highlight]
+      }), // [!code highlight]
     }),
   ]
 };

+ 4 - 8
docs/docs/guides/developer-guide/stand-alone-scripts/index.mdx

@@ -86,9 +86,7 @@ import { config } from './vendure-config';
 async function importCustomerData() {
     const { app } = await bootstrapWorker(config);
     
-    // highlight-start
-    const customerService = app.get(CustomerService);
-    // highlight-end
+    const customerService = app.get(CustomerService); // [!code highlight]
 }
 ```
 
@@ -106,11 +104,9 @@ async function getProductCount() {
     const { app } = await bootstrapWorker(config);
     const productService = app.get(ProductService);
     
-    // highlight-start
-    const ctx = await app.get(RequestContextService).create({
-        apiType: 'admin',
-    });
-    // highlight-end
+    const ctx = await app.get(RequestContextService).create({ // [!code highlight]
+        apiType: 'admin', // [!code highlight]
+    }); // [!code highlight]
     
     const { totalItems } = await productService.findAll(ctx, {take: 0});
 }

+ 2 - 4
docs/docs/guides/developer-guide/strategies-configurable-operations/index.mdx

@@ -64,8 +64,7 @@ import { MyOrderCodeStrategy } from '../config/my-order-code-strategy';
 export const config: VendureConfig = {
     // ...
     orderOptions: {
-        // highlight-next-line
-        orderCodeStrategy: new MyOrderCodeStrategy(),
+        orderCodeStrategy: new MyOrderCodeStrategy(), // [!code highlight]
     },
 }
 ```
@@ -94,8 +93,7 @@ import { MyStockLocationStrategy } from '../config/my-stock-location-strategy';
 export const config: VendureConfig = {
     // ...
     catalogOptions: {
-        // highlight-next-line
-        stockLocationStrategy: new MyStockLocationStrategy({ maxDistance: 100 }),
+        stockLocationStrategy: new MyStockLocationStrategy({ maxDistance: 100 }), // [!code highlight]
     },
 }
 ```

+ 5 - 9
docs/docs/guides/developer-guide/testing/index.mdx

@@ -204,11 +204,9 @@ import { MyPlugin } from '../my-plugin.ts';
 describe('my plugin', () => {
 
     const {server, adminClient, shopClient} = createTestEnvironment(mergeConfig(testConfig, {
-        // highlight-start
-        apiOptions: {
-            port: 3051,
-        },
-        // highlight-end
+        apiOptions: { // [!code highlight]
+            port: 3051, // [!code highlight]
+        }, // [!code highlight]
         plugins: [MyPlugin],
     }));
 
@@ -234,8 +232,7 @@ describe('my plugin', () => {
         plugins: [MyPlugin],
     });
     
-    // highlight-next-line
-    let productService: ProductService;
+    let productService: ProductService; // [!code highlight]
     
     beforeAll(async () => {
         await server.init({
@@ -244,8 +241,7 @@ describe('my plugin', () => {
             customerCount: 2,
         });
         await adminClient.asSuperAdmin();
-        // highlight-next-line
-        productService = server.app.get(ProductService);
+        productService = server.app.get(ProductService); // [!code highlight]
     }, 60000);
 
 });

+ 11 - 22
docs/docs/guides/developer-guide/the-api-layer/index.mdx

@@ -258,8 +258,7 @@ to resolve the fields of a GraphQL query or mutation.
 ```ts title="src/plugins/wishlist/api/wishlist.resolver.ts"
 import { Resolver } from '@nestjs/graphql';
 
-// highlight-next-line
-@Resolver()
+@Resolver() // [!code highlight]
 export class WishlistResolver {
     // ...
 }
@@ -277,8 +276,7 @@ import { Query, Resolver } from '@nestjs/graphql';
 @Resolver()
 export class WishlistResolver {
 
-    // highlight-next-line
-    @Query()
+    @Query() // [!code highlight]
     wishlist() {
         // ...
     }
@@ -297,8 +295,7 @@ import { Mutation, Resolver } from '@nestjs/graphql';
 @Resolver()
 export class WishlistResolver {
 
-    // highlight-next-line
-    @Mutation()
+    @Mutation() // [!code highlight]
     addItemToWishlist() {
         // ...
     }
@@ -319,8 +316,7 @@ import { Allow, Permission } from '@vendure/core';
 export class WishlistResolver {
 
     @Mutation()
-    // highlight-next-line
-    @Allow(Permission.UpdateCustomer)
+    @Allow(Permission.UpdateCustomer) // [!code highlight]
     updateCustomerWishlist() {
         // ...
     }
@@ -339,8 +335,7 @@ import { Transaction } from '@vendure/core';
 @Resolver()
 export class WishlistResolver {
 
-    // highlight-next-line
-    @Transaction()
+    @Transaction() // [!code highlight]
     @Mutation()
     addItemToWishlist() {
         // if an error is thrown here, the
@@ -371,8 +366,7 @@ import { Ctx, RequestContext } from '@vendure/core';
 export class WishlistResolver {
 
     @Mutation()
-    // highlight-next-line
-    addItemToWishlist(@Ctx() ctx: RequestContext) {
+    addItemToWishlist(@Ctx() ctx: RequestContext) { // [!code highlight]
         // ...
     }
 }
@@ -406,8 +400,7 @@ export class WishlistResolver {
     @Mutation()
     addItemToWishlist(
         @Ctx() ctx: RequestContext,
-        // highlight-next-line
-        @Args() args: { variantId: ID }
+        @Args() args: { variantId: ID } // [!code highlight]
     ) {
         // ...
     }
@@ -424,8 +417,7 @@ used to resolve the fields of a type. For example, given the following schema de
 ```graphql
 type WishlistItem {
     id: ID!
-    // highlight-next-line
-    product: Product!
+    product: Product! # [!code highlight]
 }
 ```
 
@@ -438,16 +430,13 @@ import { Ctx, RequestContext } from '@vendure/core';
 
 import { WishlistItem } from '../entities/wishlist-item.entity';
 
-// highlight-next-line
-@Resolver('WishlistItem')
+@Resolver('WishlistItem') // [!code highlight]
 export class WishlistItemResolver {
 
-    // highlight-next-line
-    @ResolveField()
+    @ResolveField() // [!code highlight]
     product(
         @Ctx() ctx: RequestContext,
-        // highlight-next-line
-        @Parent() wishlistItem: WishlistItem
+        @Parent() wishlistItem: WishlistItem // [!code highlight]
     ) {
         // ...
     }

+ 9 - 18
docs/docs/guides/developer-guide/the-service-layer/index.mdx

@@ -73,10 +73,8 @@ import { PluginCommonModule, VendurePlugin } from '@vendure/core';
 import { MyService } from './services/my.service';
 
 @VendurePlugin({
-    // highlight-start
-    imports: [PluginCommonModule],
-    providers: [MyService],
-    // highlight-end
+    imports: [PluginCommonModule], // [!code highlight]
+    providers: [MyService], // [!code highlight]
 })
 export class MyPlugin {}
 ```
@@ -88,8 +86,7 @@ import { ProductService } from '@vendure/core';
 @Injectable()
 export class MyService {
 
-    // highlight-next-line
-    constructor(private productService: ProductService) {}
+    constructor(private productService: ProductService) {} // [!code highlight]
 
     // you can now use the productService methods
 }
@@ -240,10 +237,8 @@ const product = await this.connection.getRepository(ctx, Product).findOne({
     where: { id: productId },
 });
 if (product) {
-    // highlight-start
-    console.log(product.featuredAsset.preview);
-    // ^ Error: Cannot read property 'preview' of undefined
-    // highlight-end
+    console.log(product.featuredAsset.preview); // [!code highlight]
+    // ^ Error: Cannot read property 'preview' of undefined // [!code highlight]
 }
 ```
 
@@ -253,16 +248,14 @@ the `relations` option:
 ```ts
 const product = await this.connection.getRepository(ctx, Product).findOne({
     where: { id: productId },
-    // highlight-next-line
-    relations: { featuredAsset: true },
+    relations: { featuredAsset: true }, // [!code highlight]
 });
 ```
 or in the case of the QueryBuilder API, we can use the `leftJoinAndSelect()` method:
 
 ```ts
 const product = await this.connection.getRepository(ctx, Product).createQueryBuilder('product')
-    // highlight-next-line
-    .leftJoinAndSelect('product.featuredAsset', 'featuredAsset')
+    .leftJoinAndSelect('product.featuredAsset', 'featuredAsset') // [!code highlight]
     .where('product.id = :id', { id: productId })
     .getOne();
 ```
@@ -284,10 +277,8 @@ const myShippingCalculator = new ShippingCalculator({
         entityHydrator = injector.get(EntityHydrator);
     },
     calculate: (ctx, order, args) => {
-      // highlight-start
-      // ensure that the customer and customer.groups relations are joined
-      await entityHydrator.hydrate(ctx, order, { relations: ['customer.groups' ]});
-      // highlight-end
+      // ensure that the customer and customer.groups relations are joined // [!code highlight]
+      await entityHydrator.hydrate(ctx, order, { relations: ['customer.groups' ]}); // [!code highlight]
 
       if (order.customer?.groups?.some(g => g.name === 'VIP')) {
         // ... do something special for VIP customers

+ 13 - 28
docs/docs/guides/developer-guide/translatable/index.mdx

@@ -20,9 +20,7 @@ class ProductRequest extends VendureEntity implements Translatable {
     constructor(input?: DeepPartial<ProductRequest>) {
         super(input);
     }
-// highlight-start
-    text: LocaleString;
-// highlight-end
+    text: LocaleString; // [!code highlight]
     
     @ManyToOne(type => Product)
     product: Product;
@@ -31,10 +29,8 @@ class ProductRequest extends VendureEntity implements Translatable {
     productId: ID;
 
 
-// highlight-start
-    @OneToMany(() => ProductRequestTranslation, translation => translation.base, { eager: true })
-    translations: Array<Translation<ProductRequest>>;
-// highlight-end
+    @OneToMany(() => ProductRequestTranslation, translation => translation.base, { eager: true }) // [!code highlight]
+    translations: Array<Translation<ProductRequest>>; // [!code highlight]
 }
 ```
 
@@ -61,9 +57,7 @@ export class ProductRequestTranslation
     languageCode: LanguageCode;
 
     @Column('varchar')
-// highlight-start
-    text: string; // same name as the translatable field in the base entity
-// highlight-end
+    text: string; // same name as the translatable field in the base entity // [!code highlight]
     @Index()
     @ManyToOne(() => ProductRequest, base => base.translations, { onDelete: 'CASCADE' })
     base: ProductRequest;
@@ -80,10 +74,8 @@ type ProductRequestTranslation {
     id: ID!
     createdAt: DateTime!
     updatedAt: DateTime!
-// highlight-start
-    languageCode: LanguageCode!
-    text: String!
-// highlight-end
+    languageCode: LanguageCode! # [!code highlight]
+    text: String! # [!code highlight]
 }
 
 type ProductRequest implements Node {
@@ -92,8 +84,7 @@ type ProductRequest implements Node {
     updatedAt: DateTime!
     # Will be filled with the translation for the current language
     text: String!
-// highlight-next-line
-    translations: [ProductRequestTranslation!]!
+    translations: [ProductRequestTranslation!]! # [!code highlight]
 }
 
 ```
@@ -129,16 +120,13 @@ by defining the types like `CreateRequestInput` inside the GraphQL schema:
 input ProductRequestTranslationInput {
     # Only defined for update mutations
     id: ID
-// highlight-start
-    languageCode: LanguageCode!
-    text: String!
-// highlight-end
+    languageCode: LanguageCode! # [!code highlight]
+    text: String! # [!code highlight]
 }
 
 input CreateProductRequestInput {
     text: String!
-// highlight-next-line
-    translations: [ProductRequestTranslationInput!]!
+    translations: [ProductRequestTranslationInput!]! # [!code highlight]
 }
 ```
 
@@ -172,8 +160,7 @@ Once again it's important to provide the `translations` array in the input objec
 
 input UpdateProductRequestInput {
     text: String
-// highlight-next-line
-    translations: [ProductRequestTranslationInput!]
+    translations: [ProductRequestTranslationInput!] # [!code highlight]
 }
 ```
 
@@ -199,8 +186,7 @@ export class RequestService {
             .getManyAndCount()
             .then(([items, totalItems]) => {
                 return {
-// highlight-next-line
-                    items: items.map(item => this.translator.translate(item, ctx)),
+                    items: items.map(item => this.translator.translate(item, ctx)), // [!code highlight]
                     totalItems,
                 };
             });
@@ -217,8 +203,7 @@ export class RequestService {
                 where: { id },
                 relations,
             })
-// highlight-next-line
-            .then(entity => entity && this.translator.translate(entity, ctx));
+            .then(entity => entity && this.translator.translate(entity, ctx)); // [!code highlight]
     }
 }
 ```

+ 7 - 14
docs/docs/guides/developer-guide/translations/index.mdx

@@ -33,16 +33,13 @@ To understand how translatable entities are implemented, let's take a look at a
 @Entity()
 export class Facet extends VendureEntity implements Translatable {
     
-    // highlight-next-line
-    name: LocaleString;
+    name: LocaleString; // [!code highlight]
 
     @Column({ unique: true })
     code: string;
 
-    // highlight-next-line
-    @OneToMany(type => FacetTranslation, translation => translation.base, { eager: true })
-    // highlight-next-line
-    translations: Array<Translation<Facet>>;
+    @OneToMany(type => FacetTranslation, translation => translation.base, { eager: true }) // [!code highlight]
+    translations: Array<Translation<Facet>>; // [!code highlight]
 }
 ```
 
@@ -54,8 +51,7 @@ export class FacetTranslation extends VendureEntity implements Translation<Facet
 
     @Column('varchar') languageCode: LanguageCode;
 
-    // highlight-next-line
-    @Column() name: string;
+    @Column() name: string; // [!code highlight]
 
     @Index()
     @ManyToOne(type => Facet, base => base.translations, { onDelete: 'CASCADE' })
@@ -83,16 +79,13 @@ export class MyService {
     async getFacet(ctx: RequestContext, id: ID): Promise<Facet | undefined> {
         const facet = await this.connection.getRepository(ctx, Facet).findOne(id);
         if (facet) {
-            // highlight-next-line
-            return this.translatorService.translate(facet, ctx);
+            return this.translatorService.translate(facet, ctx); // [!code highlight]
         }
     }
     
     async getFacets(ctx: RequestContext): Promise<Facet[]> {
-        // highlight-next-line
-        const facets = await this.connection.getRepository(ctx, Facet).find();
-        // highlight-next-line
-        return Promise.all(facets.map(facet => this.translatorService.translate(facet, ctx)));
+        const facets = await this.connection.getRepository(ctx, Facet).find(); // [!code highlight]
+        return Promise.all(facets.map(facet => this.translatorService.translate(facet, ctx))); // [!code highlight]
     }
 }
 ```

+ 7 - 9
docs/docs/guides/developer-guide/uploading-files/index.mdx

@@ -67,15 +67,13 @@ import { Asset, LanguageCode, PluginCommonModule, VendurePlugin } from '@vendure
 @VendurePlugin({
     imports: [PluginCommonModule],
     configure: config => {
-        // highlight-start
-        config.customFields.Customer.push({
-            name: 'avatar',
-            type: 'relation',
-            label: [{languageCode: LanguageCode.en, value: 'Customer avatar'}],
-            entity: Asset,
-            nullable: true,
-        });
-        // highlight-end
+        config.customFields.Customer.push({ // [!code highlight]
+            name: 'avatar', // [!code highlight]
+            type: 'relation', // [!code highlight]
+            label: [{languageCode: LanguageCode.en, value: 'Customer avatar'}], // [!code highlight]
+            entity: Asset, // [!code highlight]
+            nullable: true, // [!code highlight]
+        }); // [!code highlight]
         return config;
     },
 })

+ 19 - 29
docs/docs/guides/developer-guide/worker-job-queue/index.mdx

@@ -338,8 +338,7 @@ import { JobQueue, JobQueueService, Product, TransactionalConnection,
 @Injectable()
 class ProductExportService implements OnModuleInit {
 
-    // highlight-next-line
-    private jobQueue: JobQueue<{ ctx: SerializedRequestContext; }>;
+    private jobQueue: JobQueue<{ ctx: SerializedRequestContext; }>; // [!code highlight]
 
     constructor(private jobQueueService: JobQueueService,
                 private connection: TransactionalConnection) {
@@ -349,8 +348,7 @@ class ProductExportService implements OnModuleInit {
         this.jobQueue = await this.jobQueueService.createQueue({
             name: 'export-products',
             process: async job => {
-                // highlight-next-line
-                const ctx = RequestContext.deserialize(job.data.ctx);
+                const ctx = RequestContext.deserialize(job.data.ctx); // [!code highlight]
                 const allProducts = await this.connection.getRepository(ctx, Product).find();
                 // ... logic to export the product omitted for brevity
             },
@@ -358,8 +356,7 @@ class ProductExportService implements OnModuleInit {
     }
 
     exportAllProducts(ctx: RequestContext) {
-        // highlight-next-line
-        return this.jobQueue.add({ ctx: ctx.serialize() });
+        return this.jobQueue.add({ ctx: ctx.serialize() }); // [!code highlight]
     }
 }
 ```
@@ -382,8 +379,7 @@ import { JobQueue, JobQueueService,
 @Injectable()
 class ProductExportService implements OnModuleInit {
 
-    // highlight-next-line
-    private jobQueue: JobQueue<{ channelToken: string; languageCode: LanguageCode; }>;
+    private jobQueue: JobQueue<{ channelToken: string; languageCode: LanguageCode; }>; // [!code highlight]
 
     constructor(private jobQueueService: JobQueueService,
                 private requestContextService: RequestContextService) {
@@ -393,26 +389,22 @@ class ProductExportService implements OnModuleInit {
         this.jobQueue = await this.jobQueueService.createQueue({
             name: 'export-products',
             process: async job => {
-                // highlight-start
-                // Reconstruct the RequestContext from the passed data
-                const ctx = await this.requestContextService.create({
-                    channelOrToken: job.data.channelToken,
-                    languageCode: job.data.languageCode,
-                })
-                // highlight-end
+                // Reconstruct the RequestContext from the passed data // [!code highlight]
+                const ctx = await this.requestContextService.create({ // [!code highlight]
+                    channelOrToken: job.data.channelToken, // [!code highlight]
+                    languageCode: job.data.languageCode, // [!code highlight]
+                }) // [!code highlight]
                 // ... logic to export the product omitted for brevity
             },
         });
     }
 
     exportAllProducts(ctx: RequestContext) {
-        // highlight-start
-        // Pass only the necessary data
-        return this.jobQueue.add({
-            channelId: ctx.channel.token,
-            languageCode: ctx.languageCode
-        });
-        // highlight-end
+        // Pass only the necessary data // [!code highlight]
+        return this.jobQueue.add({ // [!code highlight]
+            channelId: ctx.channel.token, // [!code highlight]
+            languageCode: ctx.languageCode // [!code highlight]
+        }); // [!code highlight]
     }
 }
 ```
@@ -453,13 +445,11 @@ class ProductExportService implements OnModuleInit {
                 });
                 let successfulExportCount = 0;
                 for (const product of allProducts) {
-                    // highlight-start
-                    if (job.state === JobState.CANCELLED) {
-                        // If the job has been cancelled, stop processing
-                        // to prevent unnecessary work.
-                        throw new Error('Job was cancelled');
-                    }
-                    // highlight-end
+                    if (job.state === JobState.CANCELLED) { // [!code highlight]
+                        // If the job has been cancelled, stop processing // [!code highlight]
+                        // to prevent unnecessary work. // [!code highlight]
+                        throw new Error('Job was cancelled'); // [!code highlight]
+                    } // [!code highlight]
 
                     // ... logic to export the product omitted for brevity
                     successfulExportCount++;

+ 16 - 23
docs/docs/guides/extending-the-admin-ui/add-actions-to-pages/index.mdx

@@ -90,10 +90,8 @@ export default [
         id: 'print-invoice',
         label: 'Print invoice',
         locationId: 'order-detail',
-        // highlight-start
-        // The route can be a constant value...
-        routerLink: ['./extensions/order-invoices'],
-        // highlight-end
+        // The route can be a constant value... // [!code highlight]
+        routerLink: ['./extensions/order-invoices'], // [!code highlight]
     }),
 ];
 ```
@@ -109,13 +107,11 @@ export default [
         id: 'print-invoice',
         label: 'Print invoice',
         locationId: 'order-detail',
-        // highlight-start
-        // The route can be a function
-        routerLink: route => {
-            const id = route.snapshot.params.id;
-            return ['./extensions/order-invoices', id];
-        },
-        // highlight-end
+        // The route can be a function // [!code highlight]
+        routerLink: route => { // [!code highlight]
+            const id = route.snapshot.params.id; // [!code highlight]
+            return ['./extensions/order-invoices', id]; // [!code highlight]
+        }, // [!code highlight]
     }),
 ];
 ```
@@ -147,16 +143,14 @@ export default [
         id: 'myButtonId',
         label: 'My Button Label',
         locationId: 'order-detail',
-        // highlight-start
-        onClick: async (event, context) => {
-            try {
-                const orderId = context.route.snapshot.params.id;
-                await firstValueFrom(context.dataService.mutate(mutation, { orderId }));
-            } catch (error) {
-                context.notificationService.error('Error executing mutation: ' + error.message);
-            }
-        },
-        // highlight-end
+        onClick: async (event, context) => { // [!code highlight]
+            try { // [!code highlight]
+                const orderId = context.route.snapshot.params.id; // [!code highlight]
+                await firstValueFrom(context.dataService.mutate(mutation, { orderId })); // [!code highlight]
+            } catch (error) { // [!code highlight]
+                context.notificationService.error('Error executing mutation: ' + error.message); // [!code highlight]
+            } // [!code highlight]
+        }, // [!code highlight]
     }),
 ];
 ```
@@ -223,8 +217,7 @@ export default [
         label: 'Print invoice',
         locationId: 'order-detail',
         routerLink: ['./extensions/order-invoices'],
-        // highlight-next-line
-        requiresPermission: 'CreateInvoice',
+        requiresPermission: 'CreateInvoice', // [!code highlight]
     }),
 ];
 ```

+ 34 - 43
docs/docs/guides/extending-the-admin-ui/creating-detail-views/index.mdx

@@ -212,25 +212,23 @@ export default [
         component: ReviewListComponent,
         breadcrumb: 'Product reviews',
     }),
-    // highlight-start
-    // Detail view
-    registerRouteComponent({
-        path: ':id',
-        component: ReviewDetailComponent,
-        query: getReviewDetailDocument,
-        entityKey: 'productReview',
-        getBreadcrumbs: entity => [
-            {
-                label: 'Product reviews',
-                link: ['/extensions', 'product-reviews'],
-            },
-            {
-                label: `#${entity?.id} (${entity?.product.name})`,
-                link: [],
-            },
-        ],
-    }),
-    // highlight-end
+    // Detail view // [!code highlight]
+    registerRouteComponent({ // [!code highlight]
+        path: ':id', // [!code highlight]
+        component: ReviewDetailComponent, // [!code highlight]
+        query: getReviewDetailDocument, // [!code highlight]
+        entityKey: 'productReview', // [!code highlight]
+        getBreadcrumbs: entity => [ // [!code highlight]
+            { // [!code highlight]
+                label: 'Product reviews', // [!code highlight]
+                link: ['/extensions', 'product-reviews'], // [!code highlight]
+            }, // [!code highlight]
+            { // [!code highlight]
+                label: `#${entity?.id} (${entity?.product.name})`, // [!code highlight]
+                link: [], // [!code highlight]
+            }, // [!code highlight]
+        ], // [!code highlight]
+    }), // [!code highlight]
 ]
 ```
 
@@ -242,8 +240,7 @@ If you have set up your entity to support custom fields, and you want custom fie
 you need to add the following to your detail component:
 
 ```ts title="src/plugins/reviews/ui/components/review-detail/review-detail.component.ts"
-// highlight-next-line
-import { getCustomFieldsDefaults } from '@vendure/admin-ui/core';
+import { getCustomFieldsDefaults } from '@vendure/admin-ui/core'; // [!code highlight]
 
 @Component({
     selector: 'review-detail',
@@ -255,15 +252,13 @@ import { getCustomFieldsDefaults } from '@vendure/admin-ui/core';
 })
 export class ReviewDetailComponent extends TypedBaseDetailComponent<typeof getReviewDetailDocument, 'review'> implements OnInit, OnDestroy {
 
-    // highlight-next-line
-    customFields = this.getCustomFieldConfig('ProductReview');
+    customFields = this.getCustomFieldConfig('ProductReview'); // [!code highlight]
 
     detailForm = this.formBuilder.group({
         title: [''],
         rating: [1],
         authorName: [''],
-        // highlight-next-line
-        customFields: this.formBuilder.group(getCustomFieldsDefaults(this.customFields)),
+        customFields: this.formBuilder.group(getCustomFieldsDefaults(this.customFields)), // [!code highlight]
     });
 
     protected setFormValues(entity: NonNullable<ResultOf<typeof getReviewDetailDocument>['review']>, languageCode: LanguageCode): void {
@@ -273,11 +268,9 @@ export class ReviewDetailComponent extends TypedBaseDetailComponent<typeof getRe
             authorName: entity.authorName,
             productId: entity.productId,
         });
-        // highlight-start
-        if (this.customFields.length) {
-            this.setCustomFieldFormValues(this.customFields, this.detailForm.get('customFields'), entity);
-        }
-        // highlight-end
+        if (this.customFields.length) { // [!code highlight]
+            this.setCustomFieldFormValues(this.customFields, this.detailForm.get('customFields'), entity); // [!code highlight]
+        } // [!code highlight]
     }
 }
 ```
@@ -311,19 +304,17 @@ Then add a card for your custom fields to the template:
                     <!-- etc -->
                 </div>
             </vdr-card>
-            // highlight-start
-            <vdr-card
-                    formGroupName="customFields"
-                    *ngIf="customFields.length"
-                    [title]="'common.custom-fields' | translate"
-            >
-                <vdr-tabbed-custom-fields
-                        entityName="ProductReview"
-                        [customFields]="customFields"
-                        [customFieldsFormGroup]="detailForm.get('customFields')"
-                ></vdr-tabbed-custom-fields>
-            </vdr-card>
-            // highlight-end
+            <vdr-card <!-- [!code highlight] -->
+                    formGroupName="customFields" <!-- [!code highlight] -->
+                    *ngIf="customFields.length" <!-- [!code highlight] -->
+                    [title]="'common.custom-fields' | translate" <!-- [!code highlight] -->
+            > <!-- [!code highlight] -->
+                <vdr-tabbed-custom-fields <!-- [!code highlight] -->
+                        entityName="ProductReview" <!-- [!code highlight] -->
+                        [customFields]="customFields" <!-- [!code highlight] -->
+                        [customFieldsFormGroup]="detailForm.get('customFields')" <!-- [!code highlight] -->
+                ></vdr-tabbed-custom-fields> <!-- [!code highlight] -->
+            </vdr-card> <!-- [!code highlight] -->
         </vdr-page-block>
     </vdr-page-detail-layout>
 </form>

+ 13 - 20
docs/docs/guides/extending-the-admin-ui/creating-list-views/index.mdx

@@ -235,13 +235,11 @@ import { registerRouteComponent } from '@vendure/admin-ui/core';
 import { ReviewListComponent } from './components/review-list/review-list.component';
 
 export default [
-    // highlight-start
-    registerRouteComponent({
-        path: '',
-        component: ReviewListComponent,
-        breadcrumb: 'Product reviews',
-    }),
-    // highlight-end
+    registerRouteComponent({ // [!code highlight]
+        path: '', // [!code highlight]
+        component: ReviewListComponent, // [!code highlight]
+        breadcrumb: 'Product reviews', // [!code highlight]
+    }), // [!code highlight]
 ]
 ```
 
@@ -263,8 +261,7 @@ you need to add the following to your list component:
 })
 export class ReviewListComponent extends TypedBaseListComponent<typeof getReviewListDocument, 'reviews'> {
 
-    // highlight-next-line
-    customFields = this.getCustomFieldConfig('ProductReview');
+    customFields = this.getCustomFieldConfig('ProductReview'); // [!code highlight]
 
     readonly filters = this.createFilterCollection()
         .addIdFilter()
@@ -287,8 +284,7 @@ export class ReviewListComponent extends TypedBaseListComponent<typeof getReview
             label: 'Author',
             filterField: 'authorName',
         })
-        // highlight-next-line
-        .addCustomFieldFilters(this.customFields)
+        .addCustomFieldFilters(this.customFields) // [!code highlight]
         .connectToRoute(this.route);
 
     readonly sorts = this.createSortCollection()
@@ -298,8 +294,7 @@ export class ReviewListComponent extends TypedBaseListComponent<typeof getReview
         .addSort({name: 'title'})
         .addSort({name: 'rating'})
         .addSort({name: 'authorName'})
-        // highlight-next-line
-        .addCustomFieldSorts(this.customFields)
+        .addCustomFieldSorts(this.customFields) // [!code highlight]
         .connectToRoute(this.route);
     
     // rest of class omitted for brevity
@@ -320,12 +315,10 @@ and then add the `vdr-dt2-custom-field-column` component to your data table:
     (itemsPerPageChange)="setItemsPerPage($event)"
 >
     <!-- rest of data table omitted for brevity -->
-    // highlight-start
-    <vdr-dt2-custom-field-column
-            *ngFor="let customField of customFields"
-            [customField]="customField"
-            [sorts]="sorts"
-    />
-    // highlight-end
+    <vdr-dt2-custom-field-column <!-- [!code highlight] -->
+            *ngFor="let customField of customFields" <!-- [!code highlight] -->
+            [customField]="customField" <!-- [!code highlight] -->
+            [sorts]="sorts" <!-- [!code highlight] -->
+    /> <!-- [!code highlight] -->
 </vdr-data-table-2>
 ```

+ 14 - 18
docs/docs/guides/extending-the-admin-ui/custom-detail-components/index.mdx

@@ -153,15 +153,13 @@ export class ProductInfoComponent implements CustomDetailComponent, OnInit {
     entity$: Observable<any>
     detailForm: FormGroup;
     
-    // highlight-start
-    updateDescription() {
-        const descriptionControl = this.detailForm.get('description');
-        if (descriptionControl) {
-            descriptionControl.setValue('New description');
-            descriptionControl.markAsDirty();
-        }        
-    }
-    // highlight-end
+    updateDescription() { // [!code highlight]
+        const descriptionControl = this.detailForm.get('description'); // [!code highlight]
+        if (descriptionControl) { // [!code highlight]
+            descriptionControl.setValue('New description'); // [!code highlight]
+            descriptionControl.markAsDirty(); // [!code highlight]
+        } // [!code highlight]
+    } // [!code highlight]
 }
 ```
 
@@ -175,15 +173,13 @@ import { Card, useDetailComponentData } from '@vendure/admin-ui/react';
 export function ProductInfo() {
     const { detailForm } = useDetailComponentData();
 
-    // highlight-start
-    const updateDescription = () => {
-        const descriptionControl = detailForm.get('description');
-        if (descriptionControl) {
-            descriptionControl.setValue('New description');
-            descriptionControl.markAsDirty();
-        }
-    };
-    // highlight-end
+    const updateDescription = () => { // [!code highlight]
+        const descriptionControl = detailForm.get('description'); // [!code highlight]
+        if (descriptionControl) { // [!code highlight]
+            descriptionControl.setValue('New description'); // [!code highlight]
+            descriptionControl.markAsDirty(); // [!code highlight]
+        } // [!code highlight]
+    }; // [!code highlight]
 
     return (
         <button className="button secondary" onClick={updateDescription}>Update description</button>

+ 6 - 11
docs/docs/guides/extending-the-admin-ui/custom-form-inputs/index.mdx

@@ -145,10 +145,8 @@ export const config: VendureConfig = {
                 outputPath: path.join(__dirname, '../admin-ui'),
                 extensions: [{
                     id: 'common',
-                    // highlight-start
-                    extensionPath: path.join(__dirname, 'plugins/common/ui'),
-                    providers: ['providers.ts'],
-                    // highlight-end
+                    extensionPath: path.join(__dirname, 'plugins/common/ui'), // [!code highlight]
+                    providers: ['providers.ts'], // [!code highlight]
                 }],
             }),
         }),
@@ -165,8 +163,7 @@ customFields: {
     Product: [
         {
             name: 'intensity', type: 'int', min: 0, max: 100, defaultValue: 0,
-            // highlight-next-line
-            ui: {component: 'slider-form-input'}
+            ui: {component: 'slider-form-input'} // [!code highlight]
         },
     ],
 }
@@ -265,11 +262,9 @@ export const orderFixedDiscount = new PromotionOrderAction({
     args: {
         discount: {
             type: 'int',
-            // highlight-start
-            ui: {
-                component: 'currency-form-input',
-            },
-            // highlight-end
+            ui: { // [!code highlight]
+                component: 'currency-form-input', // [!code highlight]
+            }, // [!code highlight]
         },
     },
     execute(ctx, order, args) {

+ 20 - 26
docs/docs/guides/extending-the-admin-ui/dashboard-widgets/index.mdx

@@ -80,17 +80,15 @@ Our widget now needs to be registered in our [providers file](/extending-the-adm
 import { registerDashboardWidget } from '@vendure/admin-ui/core';
 
 export default [
-    // highlight-start
-    registerDashboardWidget('reviews', {
-        title: 'Latest reviews',
-        supportedWidths: [4, 6, 8, 12],
-        requiresPermissions: ['ReadReview'],
-        loadComponent: () =>
-            import('./reviews-widget/reviews-widget.component').then(
-                m => m.ReviewsWidgetComponent,
-            ),
-    }),
-    // highlight-end
+    registerDashboardWidget('reviews', { // [!code highlight]
+        title: 'Latest reviews', // [!code highlight]
+        supportedWidths: [4, 6, 8, 12], // [!code highlight]
+        requiresPermissions: ['ReadReview'], // [!code highlight]
+        loadComponent: () => // [!code highlight]
+            import('./reviews-widget/reviews-widget.component').then( // [!code highlight]
+                m => m.ReviewsWidgetComponent, // [!code highlight]
+            ), // [!code highlight]
+    }), // [!code highlight]
 ];
 ```
 
@@ -112,14 +110,12 @@ export default [
     registerDashboardWidget('reviews', {
         // omitted for brevity
     }),
-    // highlight-start
-    setDashboardWidgetLayout([
-        { id: 'welcome', width: 12 },
-        { id: 'orderSummary', width: 4 },
-        { id: 'latestOrders', width: 8 },
-        { id: 'reviews', width: 6 },
-    ]),
-    // highlight-end
+    setDashboardWidgetLayout([ // [!code highlight]
+        { id: 'welcome', width: 12 }, // [!code highlight]
+        { id: 'orderSummary', width: 4 }, // [!code highlight]
+        { id: 'latestOrders', width: 8 }, // [!code highlight]
+        { id: 'reviews', width: 6 }, // [!code highlight]
+    ]), // [!code highlight]
 ];
 ```
 
@@ -138,12 +134,10 @@ import { registerDashboardWidget } from '@vendure/admin-ui/core';
 import { OrderSummaryWidgetComponent } from '@vendure/admin-ui/dashboard';
 
 export default [
-    // highlight-start
-    registerDashboardWidget('orderSummary', {
-        title: 'dashboard.orders-summary',
-        loadComponent: () => OrderSummaryWidgetComponent,
-        requiresPermissions: ['SuperAdmin'],
-    }),
-    // highlight-end
+    registerDashboardWidget('orderSummary', { // [!code highlight]
+        title: 'dashboard.orders-summary', // [!code highlight]
+        loadComponent: () => OrderSummaryWidgetComponent, // [!code highlight]
+        requiresPermissions: ['SuperAdmin'], // [!code highlight]
+    }), // [!code highlight]
 ];
 ```

+ 49 - 84
docs/docs/guides/extending-the-admin-ui/defining-routes/index.mdx

@@ -29,15 +29,13 @@ AdminUiPlugin.init({
     adminUiConfig: {
         apiPort: serverPort,
     },
-    // highlight-start
-    app: compileUiExtensions({
-        outputPath: path.join(__dirname, '../admin-ui'),
-        extensions: [
-            GreeterPlugin.ui,
-        ],
-        devMode: true,
-    }),
-    // highlight-end
+    app: compileUiExtensions({ // [!code highlight]
+        outputPath: path.join(__dirname, '../admin-ui'), // [!code highlight]
+        extensions: [ // [!code highlight]
+            GreeterPlugin.ui, // [!code highlight]
+        ], // [!code highlight]
+        devMode: true, // [!code highlight]
+    }), // [!code highlight]
 }),
 ```
 
@@ -153,14 +151,12 @@ export class GreeterPlugin {
         return GreeterPlugin;
     }
 
-    // highlight-start
-    static ui: AdminUiExtension = {
-        id: 'greeter-ui',
-        extensionPath: path.join(__dirname, 'ui'),
-        routes: [{ route: 'greeter', filePath: 'routes.ts' }],
-        providers: ['providers.ts'],
-    };
-    // highlight-end
+    static ui: AdminUiExtension = { // [!code highlight]
+        id: 'greeter-ui', // [!code highlight]
+        extensionPath: path.join(__dirname, 'ui'), // [!code highlight]
+        routes: [{ route: 'greeter', filePath: 'routes.ts' }], // [!code highlight]
+        providers: ['providers.ts'], // [!code highlight]
+    }; // [!code highlight]
 }
 
 ```
@@ -233,8 +229,7 @@ import { TestComponent } from './components/test/test.component';
 export default [
     registerRouteComponent({
         component: TestComponent,
-        // highlight-next-line
-        path: ':id',
+        path: ':id', // [!code highlight]
         title: 'Test',
         breadcrumb: 'Test',
     }),
@@ -251,8 +246,7 @@ import { Test } from './components/Test';
 export default [
     registerReactRouteComponent({
         component: Test,
-        // highlight-next-line
-        path: ':id',
+        path: ':id', // [!code highlight]
         title: 'Test',
         breadcrumb: 'Test',
     }),
@@ -276,8 +270,7 @@ import { ActivatedRoute } from '@angular/router';
     selector: 'test',
     template: `
         <vdr-page-block>
-            // highlight-next-line
-            <p>id: {{ id }}</p>
+            <p>id: {{ id }}</p> {/* [!code highlight] */}
         </vdr-page-block>`,
     standalone: true,
     imports: [SharedModule],
@@ -286,8 +279,7 @@ export class TestComponent {
     id: string;
 
     constructor(private route: ActivatedRoute) {
-        // highlight-next-line
-        this.id = this.route.snapshot.paramMap.get('id');
+        this.id = this.route.snapshot.paramMap.get('id'); // [!code highlight]
     }
 }
 ```
@@ -300,12 +292,10 @@ import React from 'react';
 import { useRouteParams } from '@vendure/admin-ui/react';
 
 export function Test() {
-    // highlight-next-line
-    const { params } = useRouteParams();
+    const { params } = useRouteParams(); // [!code highlight]
     return (
         <div className="page-block">
-            // highlight-next-line
-            <p>id: {params.id}</p>
+            <p>id: {params.id}</p> {/* [!code highlight] */}
         </div>
     );
 }
@@ -342,12 +332,10 @@ import { Component } from '@angular/core';
     imports: [SharedModule],
 })
 export class TestComponent {
-    // highlight-next-line
-    constructor(private notificationService: NotificationService) {}
+    constructor(private notificationService: NotificationService) {} // [!code highlight]
     
     showNotification() {
-        // highlight-next-line
-        this.notificationService.success('Hello!');
+        this.notificationService.success('Hello!'); // [!code highlight]
     }
 }
 ```
@@ -359,17 +347,14 @@ In React, we use the [`useInjector()`](/reference/admin-ui-api/react-hooks/use-i
 
 ```tsx title="src/plugins/my-plugin/ui/components/Test.tsx"
 import { NotificationService } from '@vendure/admin-ui/core';
-// highlight-next-line
-import { useInjector } from '@vendure/admin-ui/react';
+import { useInjector } from '@vendure/admin-ui/react'; // [!code highlight]
 import React from 'react';
 
 export function Test() {
-    // highlight-next-line
-    const notificationService = useInjector(NotificationService);
+    const notificationService = useInjector(NotificationService); // [!code highlight]
     
     function showNotification() {
-        // highlight-next-line
-        notificationService.success('Hello!');
+        notificationService.success('Hello!'); // [!code highlight]
     }
     return (
         <div className="page-block">
@@ -400,8 +385,7 @@ import { TestComponent } from './components/test/test.component';
 export default [
     registerRouteComponent({
         component: TestComponent,
-        // highlight-next-line
-        title: 'Test',
+        title: 'Test', // [!code highlight]
         breadcrumb: 'Test',
     }),
 ];
@@ -417,8 +401,7 @@ import { Test } from './components/Test';
 export default [
     registerReactRouteComponent({
         component: Test,
-        // highlight-next-line
-        title: 'Test',
+        title: 'Test', // [!code highlight]
         breadcrumb: 'Test',
     }),
 ];
@@ -443,20 +426,17 @@ import { Component } from '@angular/core';
     template: `
         <vdr-page-block>
             <vdr-card>
-                // highlight-next-line
-                <button class="button primary" (click)="handleClick()">Update title</button>
+                <button class="button primary" (click)="handleClick()">Update title</button> {/* [!code highlight] */}
             </vdr-card>
         </vdr-page-block>`,
     standalone: true,
     imports: [SharedModule],
 })
 export class TestComponent {
-    // highlight-next-line
-    constructor(private pageMetadataService: PageMetadataService) {}
+    constructor(private pageMetadataService: PageMetadataService) {} // [!code highlight]
 
     handleClick() {
-        // highlight-next-line
-        pageMetadataService.setTitle('New title');
+        pageMetadataService.setTitle('New title'); // [!code highlight]
     }
 }
 ```
@@ -469,12 +449,10 @@ import { Card, usePageMetadata } from '@vendure/admin-ui/react';
 import React from 'react';
 
 export function Test() {
-    // highlight-next-line
-    const { setTitle } = usePageMetadata();
+    const { setTitle } = usePageMetadata(); // [!code highlight]
 
     function handleClick() {
-        // highlight-next-line
-        setTitle('New title');
+        setTitle('New title'); // [!code highlight]
     }
     return (
         <div className="page-block">
@@ -510,8 +488,7 @@ export default [
         component: TestComponent,
         title: 'Test',
         locationId: 'my-location-id'
-        // highlight-next-line
-        breadcrumb: 'Test',
+        breadcrumb: 'Test', // [!code highlight]
     }),
 ];
 ```
@@ -527,8 +504,7 @@ export default [
     registerReactRouteComponent({
         component: Test,
         title: 'Test',
-        // highlight-next-line
-        breadcrumb: 'Test',
+        breadcrumb: 'Test', // [!code highlight]
     }),
 ];
 ```
@@ -547,20 +523,16 @@ export default [
         component: TestComponent,
         path: 'test-1',
         title: 'Test 1',
-        // highlight-start
-        breadcrumb: { label: 'Test', link: '/extensions/test' },
-        // highlight-end
+        breadcrumb: { label: 'Test', link: '/extensions/test' }, // [!code highlight]
     }),
     registerRouteComponent({
         component: TestComponent,
         path: 'test-2',
         title: 'Test 2',
-        // highlight-start
-        breadcrumb: [
-            { label: 'Parent', link: '/extensions/test' },
-            { label: 'Child', link: '/extensions/test/test-2' },
-        ],
-        // highlight-end
+        breadcrumb: [ // [!code highlight]
+            { label: 'Parent', link: '/extensions/test' }, // [!code highlight]
+            { label: 'Child', link: '/extensions/test/test-2' }, // [!code highlight]
+        ], // [!code highlight]
     }),
 ];
 ```
@@ -583,20 +555,17 @@ import { Component } from '@angular/core';
     template: `
         <vdr-page-block>
             <vdr-card>
-                // highlight-next-line
-                <button class="button primary" (click)="handleClick()">Update breadcrumb</button>
+                <button class="button primary" (click)="handleClick()">Update breadcrumb</button> {/* [!code highlight] */}
             </vdr-card>
         </vdr-page-block>`,
     standalone: true,
     imports: [SharedModule],
 })
 export class TestComponent {
-    // highlight-next-line
-    constructor(private pageMetadataService: PageMetadataService) {}
+    constructor(private pageMetadataService: PageMetadataService) {} // [!code highlight]
 
     handleClick() {
-        // highlight-next-line
-        pageMetadataService.setBreadcrumb('New breadcrumb');
+        pageMetadataService.setBreadcrumb('New breadcrumb'); // [!code highlight]
     }
 }
 ```
@@ -609,12 +578,10 @@ import { Card, usePageMetadata } from '@vendure/admin-ui/react';
 import React from 'react';
 
 export function Test() {
-    // highlight-next-line
-    const { setBreadcrumb } = usePageMetadata();
+    const { setBreadcrumb } = usePageMetadata(); // [!code highlight]
 
     function handleClick() {
-        // highlight-next-line
-        setBreadcrumb('New breadcrumb');
+        setBreadcrumb('New breadcrumb'); // [!code highlight]
     }
     return (
         <div className="page-block">
@@ -754,14 +721,12 @@ export default [
         path: ':id',
         title: 'Test',
         breadcrumb: 'Test',
-        // highlight-start
-        routeConfig: {
-            pathMatch: 'full',
-            canActivate: [(route: ActivatedRouteSnapshot) => {
-                return inject(PermissionsService).canActivate(route.params.id);
-            }],
-        },
-        // highlight-end
+        routeConfig: { // [!code highlight]
+            pathMatch: 'full', // [!code highlight]
+            canActivate: [(route: ActivatedRouteSnapshot) => { // [!code highlight]
+                return inject(PermissionsService).canActivate(route.params.id); // [!code highlight]
+            }], // [!code highlight]
+        }, // [!code highlight]
     }),
 ];
 ```

+ 48 - 71
docs/docs/guides/extending-the-admin-ui/getting-started/index.mdx

@@ -97,22 +97,19 @@ static property on your plugin class:
 ```ts title="src/plugins/my-plugin/my.plugin.ts"
 import * as path from 'path';
 import { PluginCommonModule, Type, VendurePlugin } from '@vendure/core';
-// highlight-next-line
-import { AdminUiExtension } from '@vendure/ui-devkit/compiler';
+import { AdminUiExtension } from '@vendure/ui-devkit/compiler'; // [!code highlight]
 
 @VendurePlugin({
     imports: [PluginCommonModule],
     compatibility: '^3.0.0',
 })
 export class MyPlugin {
-    // highlight-start
-    static ui: AdminUiExtension = {
-        id: 'my-plugin-ui',
-        extensionPath: path.join(__dirname, 'ui'),
-        routes: [{ route: 'my-plugin', filePath: 'routes.ts' }],
-        providers: ['providers.ts'],
-    };
-    // highlight-end
+    static ui: AdminUiExtension = { // [!code highlight]
+        id: 'my-plugin-ui', // [!code highlight]
+        extensionPath: path.join(__dirname, 'ui'), // [!code highlight]
+        routes: [{ route: 'my-plugin', filePath: 'routes.ts' }], // [!code highlight]
+        providers: ['providers.ts'], // [!code highlight]
+    }; // [!code highlight]
 }
 ```
 
@@ -121,8 +118,7 @@ You can then use the [`compileUiExtensions` function](/reference/admin-ui-api/ui
 ```ts title="src/vendure-config.ts"
 import { VendureConfig } from '@vendure/core';
 import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
-// highlight-next-line
-import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+import { compileUiExtensions } from '@vendure/ui-devkit/compiler'; // [!code highlight]
 import { MyPlugin } from './plugins/greeter/my.plugin';
 
 export const config: VendureConfig = {
@@ -130,13 +126,11 @@ export const config: VendureConfig = {
     plugins: [
         AdminUiPlugin.init({
             port: 3002,
-            // highlight-start
-            app: compileUiExtensions({
-                outputPath: path.join(__dirname, '../admin-ui'),
-                extensions: [MyPlugin.ui],
-                devMode: true,
-            }),
-            // highlight-end
+            app: compileUiExtensions({ // [!code highlight]
+                outputPath: path.join(__dirname, '../admin-ui'), // [!code highlight]
+                extensions: [MyPlugin.ui], // [!code highlight]
+                devMode: true, // [!code highlight]
+            }), // [!code highlight]
             adminUiConfig: {
                 apiPort: 3000,
             },
@@ -161,10 +155,8 @@ You can exclude them in your main `tsconfig.json` by adding a line to the "exclu
     "exclude": [
         "node_modules",
         "migration.ts",
-        // highlight-start
-        "src/plugins/**/ui/*",
-        "admin-ui"
-        // highlight-end
+        "src/plugins/**/ui/*", // [!code highlight]
+        "admin-ui" // [!code highlight]
     ]
 }
 ```
@@ -226,8 +218,7 @@ compileUiExtensions({
         {
             id: 'test-extension',
             extensionPath: path.join(__dirname, 'plugins/my-plugin/ui'),
-            // highlight-next-line
-            providers: ['providers.ts'],
+            providers: ['providers.ts'], // [!code highlight]
         },
     ],
     devMode: true,
@@ -248,14 +239,12 @@ In addition to the specialized UI extension providers listed above, the provider
 import { Injectable } from '@angular/core';
 import { addActionBarItem } from '@vendure/admin-ui/core';
 
-// highlight-start
-@Injectable()
-class MyService {
-    greet() {
-        return 'Hello!';
-    }
-}
-// highlight-end
+@Injectable() // [!code highlight]
+class MyService { // [!code highlight]
+    greet() { // [!code highlight]
+        return 'Hello!'; // [!code highlight]
+    } // [!code highlight]
+} // [!code highlight]
 
 export default [
     MyService,
@@ -264,11 +253,9 @@ export default [
         label: 'Print invoice',
         locationId: 'order-detail',
         onClick: (event, context) => {
-            // highlight-start
-            const myService = context.injector.get(MyService);
-            console.log(myService.greet());
-            // logs "Hello!"
-            // highlight-end
+            const myService = context.injector.get(MyService); // [!code highlight]
+            console.log(myService.greet()); // [!code highlight]
+            // logs "Hello!" // [!code highlight]
         },
     }),
 ];
@@ -534,16 +521,14 @@ export const config: VendureConfig = {
                 extensions: [
                     {
                         extensionPath: path.join(__dirname, 'plugins/greeter/ui'),
-                        // highlight-start
-                        ngModules: [
-                            {
-                                type: 'lazy',
-                                route: 'greet',
-                                ngModuleFileName: 'greeter.module.ts',
-                                ngModuleName: 'GreeterModule',
-                            },
-                        ],
-                        // highlight-end
+                        ngModules: [ // [!code highlight]
+                            { // [!code highlight]
+                                type: 'lazy', // [!code highlight]
+                                route: 'greet', // [!code highlight]
+                                ngModuleFileName: 'greeter.module.ts', // [!code highlight]
+                                ngModuleName: 'GreeterModule', // [!code highlight]
+                            }, // [!code highlight]
+                        ], // [!code highlight]
                     },
                 ],
             }),
@@ -594,15 +579,13 @@ export const config: VendureConfig = {
                 extensions: [
                     {
                         extensionPath: path.join(__dirname, 'plugins/invoices/ui'),
-                        // highlight-start
-                        ngModules: [
-                            {
-                                type: 'shared',
-                                ngModuleFileName: 'invoice-shared.module.ts',
-                                ngModuleName: 'InvoiceSharedModule',
-                            },
-                        ],
-                        // highlight-end
+                        ngModules: [ // [!code highlight]
+                            { // [!code highlight]
+                                type: 'shared', // [!code highlight]
+                                ngModuleFileName: 'invoice-shared.module.ts', // [!code highlight]
+                                ngModuleName: 'InvoiceSharedModule', // [!code highlight]
+                            }, // [!code highlight]
+                        ], // [!code highlight]
                     },
                 ],
             }),
@@ -619,18 +602,15 @@ If you have existing UI extensions written using the legacy API, you can migrate
 
 ```ts
 import { Component } from '@angular/core';
-// highlight-next-line
-import { SharedModule } from '@vendure/admin-ui/core';
+import { SharedModule } from '@vendure/admin-ui/core'; // [!code highlight]
 
 @Component({
     selector: 'greeter',
     template: `<vdr-page-block
         ><h1>{{ greeting }}</h1></vdr-page-block
     >`,
-    // highlight-start
-    standalone: true,
-    imports: [SharedModule],
-    // highlight-end
+    standalone: true, // [!code highlight]
+    imports: [SharedModule], // [!code highlight]
 })
 export class GreeterComponent {
     greeting = 'Hello!';
@@ -640,14 +620,11 @@ export class GreeterComponent {
 2. In templates for page components, remove the `<vdr-page-header>` and `<vdr-page-body>` components, as they are included by default now when using
    the `registeRouteComponent()` function:
     ```html
-    // highlight-start
-    <vdr-page-header>
-        <vdr-page-title></vdr-page-title>
-    </vdr-page-header>
-    <vdr-page-body>
-        // highlight-end
+    <vdr-page-header> <!-- [!code highlight] -->
+        <vdr-page-title></vdr-page-title> <!-- [!code highlight] -->
+    </vdr-page-header> <!-- [!code highlight] -->
+    <vdr-page-body> <!-- [!code highlight] -->
         <vdr-page-block>This content should remain</vdr-page-block>
-        // highlight-next-line
-    </vdr-page-body>
+    </vdr-page-body> <!-- [!code highlight] -->
     ```
 3. Remove any `NgModule` files, and replace lazy modules with `routes.ts`, and shared modules with `providers.ts` (see above).

+ 3 - 7
docs/docs/guides/extending-the-admin-ui/nav-menu/index.mdx

@@ -52,9 +52,7 @@ export const config: VendureConfig = {
                         id: 'greeter',
                         extensionPath: path.join(__dirname, 'plugins/greeter/ui'),
                         routes: [{ route: 'greet', filePath: 'routes.ts' }],
-                        // highlight-start
-                        providers: ['providers.ts']
-                        // highlight-end
+                        providers: ['providers.ts'] // [!code highlight]
                     },
                 ],
             }),
@@ -87,10 +85,8 @@ export default [
         id: 'collections',  // <-- we will override the "collections" menu item
         label: 'Collections',
         routerLink: ['/catalog', 'collections'],
-        // highlight-start
-        // we use an invalid permission which ensures it is hidden from all users
-        requiresPermission: '__disable__'
-        // highlight-end
+        // we use an invalid permission which ensures it is hidden from all users // [!code highlight]
+        requiresPermission: '__disable__' // [!code highlight]
     },
     'catalog'),
 ];

+ 1 - 2
docs/docs/guides/extending-the-admin-ui/page-tabs/index.mdx

@@ -33,8 +33,7 @@ export default [
     registerRouteComponent({
         component: TestComponent,
         title: 'Test',
-        // highlight-next-line
-        locationId: 'my-location-id'
+        locationId: 'my-location-id' // [!code highlight]
     }),
 ];
 ```

+ 1 - 2
docs/docs/guides/extending-the-admin-ui/ui-library/index.mdx

@@ -231,8 +231,7 @@ If you want to force a particular field to always take up the full width (i.e. t
 
 ```html
 <div class="form-grid">
-    // highlight-next-line
-    <vdr-form-field label="Page title" class="form-grid-span">
+    <vdr-form-field label="Page title" class="form-grid-span"> <!-- [!code highlight] -->
         <input type="text" />
     </vdr-form-field>
 </div>

+ 2 - 4
docs/docs/guides/extending-the-dashboard/creating-pages/detail-pages.mdx

@@ -119,14 +119,12 @@ Now we can register this route in our `index.tsx` file:
 import { defineDashboardExtension } from '@vendure/dashboard';
 
 import { articleList } from './article-list';
-// highlight-next-line
-import { articleDetail } from './article-detail';
+import { articleDetail } from './article-detail'; // [!code highlight]
 
 defineDashboardExtension({
     routes: [
         articleList,
-        // highlight-next-line
-        articleDetail,
+        articleDetail, // [!code highlight]
     ],
 });
 ```

+ 2 - 4
docs/docs/guides/extending-the-dashboard/creating-pages/list-pages.mdx

@@ -116,13 +116,11 @@ Let's register this route (and we can also remove the test page) in our `index.t
 ```tsx title="src/plugins/cms/dashboard/index.tsx"
 import { defineDashboardExtension } from '@vendure/dashboard';
 
-// highlight-next-line
-import { articleList } from './article-list';
+import { articleList } from './article-list'; // [!code highlight]
 
 defineDashboardExtension({
     routes: [
-        // highlight-next-line
-        articleList,
+        articleList, // [!code highlight]
     ],
 });
 ```

+ 21 - 35
docs/docs/guides/extending-the-dashboard/custom-form-components/form-component-examples.mdx

@@ -18,23 +18,19 @@ the validity of the email address, as defined by the custom field "pattern" opti
     import {useFormContext} from 'react-hook-form';
 
     export const EmailInputComponent: DashboardFormComponent = ({name, value, onChange, disabled}) => {
-        // highlight-start
-        const {getFieldState} = useFormContext();
-        const isValid = getFieldState(name).invalid === false;
-        // highlight-end
+        const {getFieldState} = useFormContext(); // [!code highlight]
+        const isValid = getFieldState(name).invalid === false; // [!code highlight]
 
         return (
             <AffixedInput
                 prefix={<Mail className="h-4 w-4 text-muted-foreground" />}
                 suffix={
                     value &&
-                    // highlight-start
-                    (isValid ? (
-                        <Check className="h-4 w-4 text-green-500" />
-                    ) : (
-                        <X className="h-4 w-4 text-red-500" />
-                    ))
-                    // highlight-end
+                    (isValid ? ( // [!code highlight]
+                        <Check className="h-4 w-4 text-green-500" /> // [!code highlight]
+                    ) : ( // [!code highlight]
+                        <X className="h-4 w-4 text-red-500" /> // [!code highlight]
+                    )) // [!code highlight]
                 }
                 value={value || ''}
                 onChange={e => onChange(e.target.value)}
@@ -57,10 +53,8 @@ the validity of the email address, as defined by the custom field "pattern" opti
         customFormComponents: {
             customFields: [
                 {
-                    // highlight-start
-                    id: 'custom-email',
-                    component: EmailInputComponent,
-                    // highlight-end
+                    id: 'custom-email', // [!code highlight]
+                    component: EmailInputComponent, // [!code highlight]
                 },
             ],
         }
@@ -75,10 +69,8 @@ the validity of the email address, as defined by the custom field "pattern" opti
                 name: 'supplierEmail',
                 type: 'string',
                 label: [{ languageCode: LanguageCode.en, value: 'Supplier Email' }],
-                // highlight-start
-                pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$',
-                ui: { component: 'custom-email' },
-                // highlight-end
+                pattern: '^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$', // [!code highlight]
+                ui: { component: 'custom-email' }, // [!code highlight]
             });
             return config;
         }
@@ -190,9 +182,7 @@ internal logic.
                 name: 'rrp',
                 type: 'int',
                 label: [{ languageCode: LanguageCode.en, value: 'RRP' }],
-                // highlight-start
-                ui: { component: 'custom-price' },
-                // highlight-end
+                ui: { component: 'custom-price' }, // [!code highlight]
             });
             return config;
         }
@@ -307,9 +297,7 @@ This component brings better UX to a simple comma-separated tags custom field.
                 name: 'tags',
                 type: 'string',
                 label: [{ languageCode: LanguageCode.en, value: 'Tags' }],
-                // highlight-start
-                ui: { component: 'custom-tags' },
-                // highlight-end
+                ui: { component: 'custom-tags' }, // [!code highlight]
             });
             return config;
         }
@@ -415,16 +403,14 @@ react to changes in that field.
     defineDashboardExtension({
         detailForms: [
             {
-                // highlight-start
-                pageId: 'product-detail',
-                inputs: [
-                    {
-                        blockId: 'main-form',
-                        field: 'slug',
-                        component: SlugInputComponent,
-                    },
-                ],
-                // highlight-end
+                pageId: 'product-detail', // [!code highlight]
+                inputs: [ // [!code highlight]
+                    { // [!code highlight]
+                        blockId: 'main-form', // [!code highlight]
+                        field: 'slug', // [!code highlight]
+                        component: SlugInputComponent, // [!code highlight]
+                    }, // [!code highlight]
+                ], // [!code highlight]
             },
         ]
     });

+ 16 - 24
docs/docs/guides/extending-the-dashboard/custom-form-components/index.mdx

@@ -97,12 +97,10 @@ defineDashboardExtension({
         // Custom field components for custom fields
         customFields: [
             {
-                // highlight-start
-                // The "id" is a global identifier for this custom component. We will
-                // reference it in the next step.
-                id: 'color-picker',
-                component: ColorPickerComponent,
-                // highlight-end
+                // The "id" is a global identifier for this custom component. We will // [!code highlight]
+                // reference it in the next step. // [!code highlight]
+                id: 'color-picker', // [!code highlight]
+                component: ColorPickerComponent, // [!code highlight]
             },
         ],
     },
@@ -123,11 +121,9 @@ Now that we've registered it as a custom field component, we can use it in our c
             label: [{ languageCode: LanguageCode.en, value: 'Color' }],
             description: [{ languageCode: LanguageCode.en, value: 'Main color for this product' }],
             ui: {
-                // highlight-start
-                // This is the ID of the custom field
-                // component we registered above.
-                component: 'color-picker',
-                // highlight-end
+                // This is the ID of the custom field // [!code highlight]
+                // component we registered above. // [!code highlight]
+                component: 'color-picker', // [!code highlight]
             },
         });
         return config;
@@ -152,9 +148,7 @@ const customShippingCalculator = new ShippingCalculator({
             description: [
                 { languageCode: LanguageCode.en, value: 'Color code for this shipping calculator' },
             ],
-            // highlight-start
-            ui: { component: 'color-picker' },
-            // highlight-end
+            ui: { component: 'color-picker' }, // [!code highlight]
         },
     },
     calculate: (ctx, order, args) => {
@@ -198,16 +192,14 @@ import { MarkdownEditorComponent } from './components/markdown-editor';
 defineDashboardExtension({
     detailForms: [
         {
-            // highlight-start
-            pageId: 'product-detail',
-            inputs: [
-                {
-                    blockId: 'main-form',
-                    field: 'description',
-                    component: MarkdownEditorComponent,
-                },
-            ],
-            // highlight-end
+            pageId: 'product-detail', // [!code highlight]
+            inputs: [ // [!code highlight]
+                { // [!code highlight]
+                    blockId: 'main-form', // [!code highlight]
+                    field: 'description', // [!code highlight]
+                    component: MarkdownEditorComponent, // [!code highlight]
+                }, // [!code highlight]
+            ], // [!code highlight]
         },
     ],
 });

+ 7 - 13
docs/docs/guides/extending-the-dashboard/deployment/index.mdx

@@ -33,9 +33,7 @@ import { pathToFileURL } from 'url';
 import { defineConfig } from 'vite';
 
 export default defineConfig({
-    // highlight-start
-    base: '/dashboard/',
-    // highlight-end
+    base: '/dashboard/', // [!code highlight]
     plugins: [
         vendureDashboardPlugin({
             vendureConfigPath: pathToFileURL('./src/vendure-config.ts'),
@@ -66,10 +64,8 @@ export const config: VendureConfig = {
     plugins: [
         // ... other plugins
         DashboardPlugin.init({
-            // highlight-start
-            // Important: This must match the base path from vite.config.mts (without slashes)
-            route: 'dashboard',
-            // highlight-end
+            // Important: This must match the base path from vite.config.mts (without slashes) // [!code highlight]
+            route: 'dashboard', // [!code highlight]
             // Path to the Vite build output directory
             appDir: path.join(__dirname, './dist'),
         }),
@@ -139,12 +135,10 @@ export default defineConfig({
     plugins: [
         vendureDashboardPlugin({
             vendureConfigPath: pathToFileURL('./src/vendure-config.ts'),
-            // highlight-start
-            api: {
-                host: process.env.VENDURE_API_HOST || 'https://api.mystore.com',
-                port: parseInt(process.env.VENDURE_API_PORT || '443'),
-            },
-            // highlight-end
+            api: { // [!code highlight]
+                host: process.env.VENDURE_API_HOST || 'https://api.mystore.com', // [!code highlight]
+                port: parseInt(process.env.VENDURE_API_PORT || '443'), // [!code highlight]
+            }, // [!code highlight]
             gqlOutputPath: path.resolve(__dirname, './src/gql/'),
         }),
     ],

+ 1 - 2
docs/docs/guides/extending-the-dashboard/extending-overview/index.mdx

@@ -110,8 +110,7 @@ declare ui extensions anywhere except on the plugin itself.
         schema: adminApiExtensions,
         resolvers: [ArticleAdminResolver],
     },
-    // highlight-next-line
-    dashboard: './dashboard/index.tsx',
+    dashboard: './dashboard/index.tsx', // [!code highlight]
 })
 export class CmsPlugin {
     // ...

+ 16 - 22
docs/docs/guides/extending-the-dashboard/getting-started/index.mdx

@@ -72,19 +72,15 @@ from your build, and reference a new `tsconfig.dashboard.json` that will have co
         "migration.ts",
         "src/plugins/**/ui/*",
         "admin-ui",
-        // highlight-start
-        "src/plugins/**/dashboard/*",
-        "src/gql/*",
-        "vite.*.*ts"
-        // highlight-end
+        "src/plugins/**/dashboard/*", // [!code highlight]
+        "src/gql/*", // [!code highlight]
+        "vite.*.*ts" // [!code highlight]
     ],
-    // highlight-start
-    "references": [
-        {
-            "path": "./tsconfig.dashboard.json"
-        }
-    ]
-    // highlight-end
+    "references": [ // [!code highlight]
+        { // [!code highlight]
+            "path": "./tsconfig.dashboard.json" // [!code highlight]
+        } // [!code highlight]
+    ] // [!code highlight]
 }
 ```
 
@@ -147,16 +143,14 @@ import { DashboardPlugin } from '@vendure/dashboard/plugin';
 const config: VendureConfig = {
     plugins: [
         // ... existing plugins
-        // highlight-start  
-        DashboardPlugin.init({
-            // The route should correspond to the `base` setting
-            // in the vite.config.mts file
-            route: 'dashboard',
-            // This appDir should correspond to the `build.outDir`
-            // setting in the vite.config.mts file
-            appDir: './dist/dashboard',
-        }),
-        // highlight-end  
+        DashboardPlugin.init({ // [!code highlight]
+            // The route should correspond to the `base` setting // [!code highlight]
+            // in the vite.config.mts file // [!code highlight]
+            route: 'dashboard', // [!code highlight]
+            // This appDir should correspond to the `build.outDir` // [!code highlight]
+            // setting in the vite.config.mts file // [!code highlight]
+            appDir: './dist/dashboard', // [!code highlight]
+        }), // [!code highlight]  
     ],
 };
 ```

+ 3 - 6
docs/docs/guides/extending-the-dashboard/localization/index.mdx

@@ -27,11 +27,9 @@ function MyComponent() {
     return (
         <div>
             <h1>
-                // highlight-next-line
-                <Trans>Welcome to Dashboard</Trans>
+                <Trans>Welcome to Dashboard</Trans> {/* [!code highlight] */}
             </h1>
-            // highlight-next-line
-            <p>{t`Click here to continue`}</p>
+            <p>{t`Click here to continue`}</p> {/* [!code highlight] */}
         </div>
     );
 }
@@ -89,6 +87,5 @@ open up the `de.po` file and add German translations for each of the strings, by
 ```text title="de.po"
 #: test-plugins/reviews/dashboard/review-list.tsx:51
 msgid "Welcome to Dashboard"
-// highlight-next-line
-msgstr "Willkommen zum Dashboard"
+msgstr "Willkommen zum Dashboard" # [!code highlight]
 ```

+ 1 - 2
docs/docs/guides/extending-the-dashboard/navigation/index.mdx

@@ -182,8 +182,7 @@ defineDashboardExtension({
             component: () => (
                 <div className="flex h-screen items-center justify-center text-2xl">This is a public page!</div>
             ),
-            // highlight-next-line
-            authenticated: false
+            authenticated: false // [!code highlight]
         },
     ]
 });

+ 10 - 19
docs/docs/guides/getting-started/graphql-intro/index.mdx

@@ -265,8 +265,7 @@ mutation {
     "updateCustomerEmail": {
       "id": "1",
       "name": "John Smith",
-      // highlight-next-line
-      "email": "john.smith@email.com"
+      "email": "john.smith@email.com" // [!code highlight]
     }
   }
 }
@@ -281,8 +280,7 @@ Operations can also have a **name**, which, while not required, is recommended f
 Here's the above query with a name:
 
 ```graphql
-// highlight-next-line
-query GetCustomers {
+query GetCustomers { # [!code highlight]
   customers {
     id
     name
@@ -303,10 +301,8 @@ Here's how we can re-write the above mutation operation to use variables:
 <TabItem value="Mutation" label="Mutation" default>
 
 ```graphql
-// highlight-next-line
-mutation UpdateCustomerEmail($input: UpdateCustomerEmailInput!) {
-  // highlight-next-line
-  updateCustomerEmail(input: $input) {
+mutation UpdateCustomerEmail($input: UpdateCustomerEmailInput!) { # [!code highlight]
+  updateCustomerEmail(input: $input) { # [!code highlight]
     id
     name
     email
@@ -335,8 +331,7 @@ mutation UpdateCustomerEmail($input: UpdateCustomerEmailInput!) {
     "updateCustomerEmail": {
       "id": "1",
       "name": "John Smith",
-      // highlight-next-line
-      "email": "john.smith@email.com"
+      "email": "john.smith@email.com" // [!code highlight]
     }
   }
 }
@@ -388,8 +383,7 @@ type Mutation {
   updateCustomerEmail(input: UpdateCustomerEmailInput!): UpdateCustomerEmailResult!
 }
 
-// highlight-next-line
-union UpdateCustomerEmailResult = Customer | EmailAddressInUseError
+union UpdateCustomerEmailResult = Customer | EmailAddressInUseError # [!code highlight]
 
 type EmailAddressInUseError {
   errorCode: String!
@@ -465,8 +459,7 @@ The above operation could also be written to use the `CustomerFields` fragment w
 ```graphql
 mutation UpdateCustomerEmail($input: UpdateCustomerEmailInput!) {
   updateCustomerEmail(input: $input) {
-    // highlight-next-line
-    ...CustomerFields
+    ...CustomerFields # [!code highlight]
     ... on EmailAddressInUseError {
       errorCode
       message
@@ -521,11 +514,9 @@ which fields of that object type we want to fetch. For example:
       name
       slug
       variants {
-        // highlight-start
-        # Sub-fields are required for object types
-        sku
-        priceWithTax
-        // highlight-end
+        # Sub-fields are required for object types # [!code highlight]
+        sku # [!code highlight]
+        priceWithTax # [!code highlight]
       }
     }
   }

+ 7 - 12
docs/docs/guides/getting-started/installation/index.mdx

@@ -31,8 +31,7 @@ If you have Docker Desktop installed, it will create and configure a Postgres da
 ┌  Let's create a Vendure App ✨
 ◆  How should we proceed?
-// highlight-next-line
-│  ● Quick Start (Get up and running in a single step)
+│  ● Quick Start (Get up and running in a single step) // [!code highlight]
 │  ○ Manual Configuration
 ```
@@ -50,12 +49,10 @@ quickly see a working storefront connected to your Vendure server.
 ◇  Docker is running
-// highlight-start
-◆  Would you like to include the Next.js storefront?
-│  ○ No
-│  ● Yes
-└
-// highlight-end
+◆  Would you like to include the Next.js storefront? // [!code highlight]
+│  ○ No // [!code highlight]
+│  ● Yes // [!code highlight]
+└ // [!code highlight]
 ```
 
 And that's it! After a minute or two, you'll have a **fully-functional Vendure server** installed locally.
@@ -90,8 +87,7 @@ Vendure supports a number of different databases. The `@vendure/create` tool wil
 │  ○ MySQL
 │  ○ MariaDB
 │  ○ Postgres
-// highlight-next-line
-│  ● SQLite
+│  ● SQLite // [!code highlight]
 ```
 
@@ -123,8 +119,7 @@ The final prompt will ask whether to populate your new Vendure server with some
 // ...
 ◆  Populate with some sample product data?
-// highlight-next-line
-│  ● yes
+│  ● yes // [!code highlight]
 │  ○ no
 ```

+ 7 - 11
docs/docs/guides/getting-started/try-the-api/index.mdx

@@ -203,25 +203,21 @@ The Admin API exposes a lot more information about products than you can get fro
 ```graphql title="Admin API"
 query GetProduct {
   product(id: 42) {
-    // highlight-next-line
-    enabled
+    enabled # [!code highlight]
     name
     variants {
       id
       name
-      // highlight-next-line
-      enabled
+      enabled # [!code highlight]
       prices {
         currencyCode
         price
       }
-      // highlight-start
-      stockLevels {
-        stockLocationId
-        stockOnHand
-        stockAllocated
-      }
-      // highlight-end
+      stockLevels { # [!code highlight]
+        stockLocationId # [!code highlight]
+        stockOnHand # [!code highlight]
+        stockAllocated # [!code highlight]
+      } # [!code highlight]
     }
   }
 }

+ 34 - 47
docs/docs/guides/how-to/codegen/index.mdx

@@ -94,8 +94,7 @@ import { Allow, Ctx, PaginatedList, RequestContext, Transaction } from '@vendure
 import { organizationPermission } from '../constants';
 import { Organization } from '../entities/organization.entity';
 import { OrganizationService } from '../services/organization.service';
-// highlight-next-line
-import { QueryOrganizationArgs, MutationCreateOrganizationArgs } from '../gql/generated';
+import { QueryOrganizationArgs, MutationCreateOrganizationArgs } from '../gql/generated'; // [!code highlight]
 
 @Resolver()
 export class AdminResolver {
@@ -103,18 +102,16 @@ export class AdminResolver {
 
     @Query()
     @Allow(organizationPermission.Read)
-    // highlight-next-line
-    organization(@Ctx() ctx: RequestContext, @Args() args: QueryOrganizationArgs): Promise<Organization> {
+    organization(@Ctx() ctx: RequestContext, @Args() args: QueryOrganizationArgs): Promise<Organization> { // [!code highlight]
         return this.organizationService.findOne(ctx, args.id);
     }
-    
+
     @Transaction()
     @Mutation()
     @Allow(organizationPermission.Create)
     createOrganization(
         @Ctx() ctx: RequestContext,
-        // highlight-next-line
-        @Args() args: MutationCreateOrganizationArgs,
+        @Args() args: MutationCreateOrganizationArgs, // [!code highlight]
     ): Promise<Organization> {
         return this.organizationService.create(ctx, args.input);
     }
@@ -130,20 +127,17 @@ import { Injectable } from '@nestjs/common';
 import { RequestContext, TransactionalConnection } from '@vendure/core';
 
 import { Organization } from '../entities/organization.entity';
-// highlight-next-line
-import { CreateOrganizationInput, UpdateOrganizationInput } from "../gql/generated";
+import { CreateOrganizationInput, UpdateOrganizationInput } from "../gql/generated"; // [!code highlight]
 
 @Injectable()
 export class OrganizationService {
     constructor(private connection: TransactionalConnection) {}
 
-    // highlight-next-line
-    async create(ctx: RequestContext, input: CreateOrganizationInput): Promise<Organization> {
+    async create(ctx: RequestContext, input: CreateOrganizationInput): Promise<Organization> { // [!code highlight]
         return this.connection.getRepository(ctx, Organization).save(new Organization(input));
     }
 
-    // highlight-next-line
-    async update(ctx: RequestContext, input: UpdateOrganizationInput): Promise<Organization> {
+    async update(ctx: RequestContext, input: UpdateOrganizationInput): Promise<Organization> { // [!code highlight]
         const example = await this.connection.getEntityOrThrow(ctx, Organization, input.id);
         const updated = {...example, ...input};
         return this.connection.getRepository(ctx, Organization).save(updated);
@@ -179,18 +173,16 @@ const config: CodegenConfig = {
         namingConvention: { enumValues: 'keep' },
     },
     generates: {
-        // highlight-start
-        'apps/marketplace/src/plugins/marketplace/ui/gql/': {
-            preset: 'client',
-            documents: 'apps/marketplace/src/plugins/marketplace/ui/**/*.ts',
-            // This disables the "fragment masking" feature. Fragment masking
-            // can improve component isolation but introduces some additional
-            // complexity that we will avoid for now.
-            presetConfig: {
-                fragmentMasking: false,
-            },
-        },
-        // highlight-end
+        'apps/marketplace/src/plugins/marketplace/ui/gql/': { // [!code highlight]
+            preset: 'client', // [!code highlight]
+            documents: 'apps/marketplace/src/plugins/marketplace/ui/**/*.ts', // [!code highlight]
+            // This disables the "fragment masking" feature. Fragment masking // [!code highlight]
+            // can improve component isolation but introduces some additional // [!code highlight]
+            // complexity that we will avoid for now. // [!code highlight]
+            presetConfig: { // [!code highlight]
+                fragmentMasking: false, // [!code highlight]
+            }, // [!code highlight]
+        }, // [!code highlight]
         'apps/marketplace/src/plugins/marketplace/gql/generated.ts': {
             plugins: ['typescript'],
         },
@@ -212,22 +204,20 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
 import { SharedModule, TypedBaseListComponent } from '@vendure/admin-ui/core';
 import { graphql } from '../../gql';
 
-// highlight-start
-const getOrganizationListDocument = graphql(`
-    query GetOrganizationList($options: OrganizationListOptions) {
-        organizations(options: $options) {
-            items {
-                id
-                createdAt
-                updatedAt
-                name
-                invoiceEmailAddresses
-            }
-            totalItems
-        }
-    }
-`);
-// highlight-end
+const getOrganizationListDocument = graphql(` // [!code highlight]
+    query GetOrganizationList($options: OrganizationListOptions) { // [!code highlight]
+        organizations(options: $options) { // [!code highlight]
+            items { // [!code highlight]
+                id // [!code highlight]
+                createdAt // [!code highlight]
+                updatedAt // [!code highlight]
+                name // [!code highlight]
+                invoiceEmailAddresses // [!code highlight]
+            } // [!code highlight]
+            totalItems // [!code highlight]
+        } // [!code highlight]
+    } // [!code highlight]
+`); // [!code highlight]
 
 @Component({
     selector: 'organization-list',
@@ -238,10 +228,8 @@ const getOrganizationListDocument = graphql(`
     imports: [SharedModule],
 })
 export class OrganizationListComponent extends TypedBaseListComponent<
-    // highlight-start
-    typeof getOrganizationListDocument,
-    'organizations'
-    // highlight-end
+    typeof getOrganizationListDocument, // [!code highlight]
+    'organizations' // [!code highlight]
 > {
     
     // Sort & filter definitions omitted for brevity.
@@ -251,8 +239,7 @@ export class OrganizationListComponent extends TypedBaseListComponent<
     constructor() {
         super();
         super.configure({
-            // highlight-next-line
-            document: getOrganizationListDocument,
+            document: getOrganizationListDocument, // [!code highlight]
             getItems: (data) => data.organizations,
             setVariables: (skip, take) => ({
                 options: {

+ 3 - 5
docs/docs/guides/how-to/configurable-products/index.mdx

@@ -69,11 +69,9 @@ mutation {
     addItemToOrder(
         productVariantId: "42"
         quantity: 1
-        // highlight-start
-        customFields: {
-            engravingText: "Thanks for all the fish!"
-        }
-        // highlight-end
+        customFields: { # [!code highlight]
+            engravingText: "Thanks for all the fish!" # [!code highlight]
+        } # [!code highlight]
     ) {
         ...on Order {
             id

+ 42 - 57
docs/docs/guides/how-to/digital-products/index.mdx

@@ -23,15 +23,13 @@ import { LanguageCode, PluginCommonModule, VendurePlugin } from '@vendure/core';
 @VendurePlugin({
     imports: [PluginCommonModule],
     configuration: config => {
-        // highlight-start
-        config.customFields.ProductVariant.push({
-            type: 'boolean',
-            name: 'isDigital',
-            defaultValue: false,
-            label: [{ languageCode: LanguageCode.en, value: 'This product is digital' }],
-            public: true,
-        });
-        // highlight-end
+        config.customFields.ProductVariant.push({ // [!code highlight]
+            type: 'boolean', // [!code highlight]
+            name: 'isDigital', // [!code highlight]
+            defaultValue: false, // [!code highlight]
+            label: [{ languageCode: LanguageCode.en, value: 'This product is digital' }], // [!code highlight]
+            public: true, // [!code highlight]
+        }); // [!code highlight]
         return config;
     },
 })
@@ -52,15 +50,13 @@ import { LanguageCode, PluginCommonModule, VendurePlugin } from '@vendure/core';
     imports: [PluginCommonModule],
     configuration: config => {
         // config.customFields.ProductVariant.push({ ... omitted
-        // highlight-start
-        config.customFields.ShippingMethod.push({
-            type: 'boolean',
-            name: 'digitalFulfilmentOnly',
-            defaultValue: false,
-            label: [{ languageCode: LanguageCode.en, value: 'Digital fulfilment only' }],
-            public: true,
-        });
-        // highlight-end
+        config.customFields.ShippingMethod.push({ // [!code highlight]
+            type: 'boolean', // [!code highlight]
+            name: 'digitalFulfilmentOnly', // [!code highlight]
+            defaultValue: false, // [!code highlight]
+            label: [{ languageCode: LanguageCode.en, value: 'Digital fulfilment only' }], // [!code highlight]
+            public: true, // [!code highlight]
+        }); // [!code highlight]
         return config;
     },
 })
@@ -77,16 +73,14 @@ import { LanguageCode, PluginCommonModule, VendurePlugin } from '@vendure/core';
     configuration: config => {
         // config.customFields.ProductVariant.push({ ... omitted
         // config.customFields.ShippingMethod.push({ ... omitted
-        // highlight-start
-        config.customFields.Fulfillment.push({
-            type: 'string',
-            name: 'downloadUrls',
-            nullable: true,
-            list: true,
-            label: [{ languageCode: LanguageCode.en, value: 'Urls of any digital purchases' }],
-            public: true,
-        });
-        // highlight-end
+        config.customFields.Fulfillment.push({ // [!code highlight]
+            type: 'string', // [!code highlight]
+            name: 'downloadUrls', // [!code highlight]
+            nullable: true, // [!code highlight]
+            list: true, // [!code highlight]
+            label: [{ languageCode: LanguageCode.en, value: 'Urls of any digital purchases' }], // [!code highlight]
+            public: true, // [!code highlight]
+        }); // [!code highlight]
         return config;
     },
 })
@@ -172,9 +166,7 @@ import { digitalFulfillmentHandler } from './config/digital-fulfillment-handler'
         // config.customFields.ProductVariant.push({ ... omitted
         // config.customFields.ShippingMethod.push({ ... omitted
         // config.customFields.Fulfillment.push({ ... omitted
-        // highlight-start
-        config.shippingOptions.fulfillmentHandlers.push(digitalFulfillmentHandler);
-        // highlight-end
+        config.shippingOptions.fulfillmentHandlers.push(digitalFulfillmentHandler); // [!code highlight]
         return config;
     },
 })
@@ -291,8 +283,7 @@ const config: VendureConfig = {
     // ... other config omitted
     plugins: [
         // ... other plugins omitted
-        // highlight-next-line
-        DigitalProductsPlugin,
+        DigitalProductsPlugin, // [!code highlight]
     ],
 };
 ```
@@ -340,17 +331,15 @@ query {
 {
   "data": {
     "eligibleShippingMethods": [
-      // highlight-start
-      {
-        "id": "3",
-        "name": "Digital Download",
-        "price": 0,
-        "priceWithTax": 0,
-        "customFields": {
-          "isDigital": true
-        }
-      },
-      // highlight-end
+      { // [!code highlight]
+        "id": "3", // [!code highlight]
+        "name": "Digital Download", // [!code highlight]
+        "price": 0, // [!code highlight]
+        "priceWithTax": 0, // [!code highlight]
+        "customFields": { // [!code highlight]
+          "isDigital": true // [!code highlight]
+        } // [!code highlight]
+      }, // [!code highlight]
       {
         "id": "1",
         "name": "Standard Shipping",
@@ -386,8 +375,7 @@ required by any physical products in the order.
 ```graphql
 mutation SetShippingMethod {
   setOrderShippingMethod(
-      // highlight-next-line
-      shippingMethodId: ["3", "1"]
+      shippingMethodId: ["3", "1"] # [!code highlight]
     ) {
     ... on Order {
       id
@@ -436,8 +424,7 @@ mutation SetShippingMethod {
             "name": "Jeff Buckley Grace mp3 download",
             "sku": "1231241241231",
             "customFields": {
-              // highlight-next-line
-              "isDigital": true
+              "isDigital": true // [!code highlight]
             }
           }
         },
@@ -455,15 +442,13 @@ mutation SetShippingMethod {
         }
       ],
       "shippingLines": [
-        // highlight-start
-        {
-          "id": "13",
-          "shippingMethod": {
-            "name": "Digital Download"
-          },
-          "priceWithTax": 0
-        },
-        // highlight-end
+        { // [!code highlight]
+          "id": "13", // [!code highlight]
+          "shippingMethod": { // [!code highlight]
+            "name": "Digital Download" // [!code highlight]
+          }, // [!code highlight]
+          "priceWithTax": 0 // [!code highlight]
+        }, // [!code highlight]
         {
           "id": "14",
           "shippingMethod": {

+ 33 - 44
docs/docs/guides/how-to/paginated-list/index.mdx

@@ -113,18 +113,16 @@ Given the above parts of the plugin, we can now query the list of reviews in the
 query {
   productReviews(
     options: {
-      // highlight-start
-      skip: 0
-      take: 10
-      sort: {
-        createdAt: DESC
-      }
-      filter: {
-        rating: {
-          between: { start: 3, end: 5 }
-        }
-      }
-      // highlight-end
+      skip: 0 # [!code highlight]
+      take: 10 # [!code highlight]
+      sort: { # [!code highlight]
+        createdAt: DESC # [!code highlight]
+      } # [!code highlight]
+      filter: { # [!code highlight]
+        rating: { # [!code highlight]
+          between: { start: 3, end: 5 } # [!code highlight]
+        } # [!code highlight]
+      } # [!code highlight]
     }) {
     totalItems
     items {
@@ -202,19 +200,17 @@ query {
     options: {
     skip: 0
     take: 10
-    // highlight-start
-    filter: {
-      _and: [
-        { text: { startsWith: "phone" } },
-        {
-          _or: [
-            { rating: { gte: 4 } },
-            { rating: { eq: 0 } }
-          ]
-        }
-      ]
-    }
-    // highlight-end
+    filter: { # [!code highlight]
+      _and: [ # [!code highlight]
+        { text: { startsWith: "phone" } }, # [!code highlight]
+        { # [!code highlight]
+          _or: [ # [!code highlight]
+            { rating: { gte: 4 } }, # [!code highlight]
+            { rating: { eq: 0 } } # [!code highlight]
+          ] # [!code highlight]
+        } # [!code highlight]
+      ] # [!code highlight]
+    } # [!code highlight]
     }) {
     totalItems
     items {
@@ -284,11 +280,9 @@ import gql from 'graphql-tag';
 export const adminApiExtensions = gql`
 # ... existing definitions from earlier example omitted
 
-// highlight-start
-input ProductReviewFilterParameter {
-  productName: StringOperators
-}
-// highlight-end
+input ProductReviewFilterParameter { # [!code highlight]
+  productName: StringOperators # [!code highlight]
+} # [!code highlight]
 `;
 ```
 
@@ -311,14 +305,11 @@ export class ProductReviewService {
     findAll(ctx: RequestContext, options?: ListQueryOptions<ProductReview>): Promise<PaginatedList<ProductReview>> {
         return this.listQueryBuilder
             .build(ProductReview, options, {
-                // highlight-next-line
-                relations: ['product'],
+                relations: ['product'], // [!code highlight]
                 ctx,
-                // highlight-start
-                customPropertyMap: {
-                    productName: 'product.name',
-                }
-                // highlight-end
+                customPropertyMap: { // [!code highlight]
+                    productName: 'product.name', // [!code highlight]
+                } // [!code highlight]
             })
             .getManyAndCount()
             .then(([items, totalItems]) => ({ items, totalItems }));
@@ -334,13 +325,11 @@ query {
     options: {
       skip: 0
       take: 10
-      // highlight-start
-      filter: {
-        productName: {
-          contains: "phone"
-        }
-      }
-      // highlight-end
+      filter: { # [!code highlight]
+        productName: { # [!code highlight]
+          contains: "phone" # [!code highlight]
+        } # [!code highlight]
+      } # [!code highlight]
   }) {
     totalItems
     items {

+ 1 - 2
docs/docs/guides/how-to/publish-plugin/index.mdx

@@ -90,8 +90,7 @@ package by specifying it in the `files` field of your `package.json` file.
  "files": [
     "dist",
     "README.md",
-    // highlight-next-line
-    "CHANGELOG.md"
+    "CHANGELOG.md" // [!code highlight]
   ]
 }
 ```

+ 29 - 34
docs/docs/guides/how-to/s3-asset-storage/index.mdx

@@ -379,12 +379,10 @@ Configure your Vendure application to use S3-compatible asset storage by modifyi
 
 ```ts title="src/vendure-config.ts"
 import { VendureConfig } from '@vendure/core';
-// highlight-start
-import {
-  AssetServerPlugin,
-  configureS3AssetStorage
-} from '@vendure/asset-server-plugin';
-// highlight-end
+import { // [!code highlight]
+  AssetServerPlugin, // [!code highlight]
+  configureS3AssetStorage // [!code highlight]
+} from '@vendure/asset-server-plugin'; // [!code highlight]
 import 'dotenv/config';
 import path from 'path';
 
@@ -394,32 +392,30 @@ export const config: VendureConfig = {
   // ... other configuration options
 
   plugins: [
-    // highlight-start
-    AssetServerPlugin.init({
-      route: 'assets',
-      assetUploadDir: path.join(__dirname, '../static/assets'),
-      assetUrlPrefix: IS_DEV ? undefined : 'https://www.my-shop.com/assets/',
-
-      // S3-Compatible Storage Configuration
-      // Dynamically switches between local storage and S3 based on environment
-      storageStrategyFactory: process.env.S3_BUCKET
-        ? configureS3AssetStorage({
-            bucket: process.env.S3_BUCKET,
-            credentials: {
-              accessKeyId: process.env.S3_ACCESS_KEY_ID!,
-              secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
-            },
-            nativeS3Configuration: {
-              // Platform-specific endpoint configuration
-              endpoint: process.env.S3_ENDPOINT,
-              region: process.env.S3_REGION,
-              forcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true',
-              signatureVersion: 'v4',
-            },
-          })
-        : undefined, // Fallback to local storage when S3 not configured
-    }),
-    // highlight-end
+    AssetServerPlugin.init({ // [!code highlight]
+      route: 'assets', // [!code highlight]
+      assetUploadDir: path.join(__dirname, '../static/assets'), // [!code highlight]
+      assetUrlPrefix: IS_DEV ? undefined : 'https://www.my-shop.com/assets/', // [!code highlight]
+ // [!code highlight]
+      // S3-Compatible Storage Configuration // [!code highlight]
+      // Dynamically switches between local storage and S3 based on environment // [!code highlight]
+      storageStrategyFactory: process.env.S3_BUCKET // [!code highlight]
+        ? configureS3AssetStorage({ // [!code highlight]
+            bucket: process.env.S3_BUCKET, // [!code highlight]
+            credentials: { // [!code highlight]
+              accessKeyId: process.env.S3_ACCESS_KEY_ID!, // [!code highlight]
+              secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!, // [!code highlight]
+            }, // [!code highlight]
+            nativeS3Configuration: { // [!code highlight]
+              // Platform-specific endpoint configuration // [!code highlight]
+              endpoint: process.env.S3_ENDPOINT, // [!code highlight]
+              region: process.env.S3_REGION, // [!code highlight]
+              forcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true', // [!code highlight]
+              signatureVersion: 'v4', // [!code highlight]
+            }, // [!code highlight]
+          }) // [!code highlight]
+        : undefined, // Fallback to local storage when S3 not configured // [!code highlight]
+    }), // [!code highlight]
 
     // ... other plugins
   ],
@@ -487,8 +483,7 @@ For production deployments with CDN or custom domains:
 ```ts title="src/vendure-config.ts"
 AssetServerPlugin.init({
   route: 'assets',
-  // highlight-next-line
-  assetUrlPrefix: 'https://cdn.yourdomain.com/assets/',
+  assetUrlPrefix: 'https://cdn.yourdomain.com/assets/', // [!code highlight]
   storageStrategyFactory: process.env.S3_BUCKET
     ? configureS3AssetStorage({
         // ... S3 configuration

+ 6 - 12
docs/docs/guides/how-to/telemetry/index.mdx

@@ -176,16 +176,12 @@ You can do this by adding the following script to your `package.json`:
 ```json
 {
     "scripts": {
-        // highlight-start
-        "dev:server": "OTEL_RESOURCE_ATTRIBUTES=service.name=vendure-server ts-node --require ./src/preload.ts ./src/index.ts",
-        "dev:worker": "OTEL_RESOURCE_ATTRIBUTES=service.name=vendure-worker ts-node --require ./src/preload.ts ./src/index-worker.ts",
-        // highlight-end
+        "dev:server": "OTEL_RESOURCE_ATTRIBUTES=service.name=vendure-server ts-node --require ./src/preload.ts ./src/index.ts", // [!code highlight]
+        "dev:worker": "OTEL_RESOURCE_ATTRIBUTES=service.name=vendure-worker ts-node --require ./src/preload.ts ./src/index-worker.ts", // [!code highlight]
         "dev": "concurrently npm:dev:*",
         "build": "tsc",
-        // highlight-start
-        "start:server": "OTEL_RESOURCE_ATTRIBUTES=service.name=vendure-server node --require ./dist/preload.js ./dist/index.js",
-        "start:worker": "OTEL_RESOURCE_ATTRIBUTES=service.name=vendure-worker node --require ./dist/preload.js ./dist/index-worker.js",
-        // highlight-end
+        "start:server": "OTEL_RESOURCE_ATTRIBUTES=service.name=vendure-server node --require ./dist/preload.js ./dist/index.js", // [!code highlight]
+        "start:worker": "OTEL_RESOURCE_ATTRIBUTES=service.name=vendure-worker node --require ./dist/preload.js ./dist/index-worker.js", // [!code highlight]
         "start": "concurrently npm:start:*"
     },
 }
@@ -237,11 +233,9 @@ add the [Instrument decorator](/reference/typescript-api/telemetry/instrument) t
 
 ```ts
 import { Injectable } from '@nestjs/common';
-// highlight-next-line
-import { Instrument } from '@vendure/core';
+import { Instrument } from '@vendure/core'; // [!code highlight]
 
-// highlight-next-line
-@Instrument()
+@Instrument() // [!code highlight]
 @Injectable()
 export class MyService {
 

+ 2 - 4
docs/docs/guides/storefront/active-order/index.mdx

@@ -75,12 +75,10 @@ import { ACTIVE_ORDER_FRAGMENT } from './fragments';
 export const GET_ACTIVE_ORDER = /*GraphQL*/`
   query GetActiveOrder {
     activeOrder {
-      // highlight-next-line
-      ...ActiveOrder
+      ...ActiveOrder # [!code highlight]
     }
   }
-  // highlight-next-line
-  ${ACTIVE_ORDER_FRAGMENT}
+  ${ACTIVE_ORDER_FRAGMENT} # [!code highlight]
 `;
 ```
 

+ 22 - 31
docs/docs/guides/storefront/codegen/index.mdx

@@ -49,18 +49,16 @@ const config: CodegenConfig = {
     'src/gql/': {
       preset: 'client',
       plugins: [],
-      // highlight-start
-      config: {
-        scalars: {
-            // This tells codegen that the `Money` scalar is a number
-            Money: 'number',
-        },
-        namingConvention: {
-            // This ensures generated enums do not conflict with the built-in types.
-            enumValues: 'keep',
-        },
-      }
-      // highlight-end
+      config: { // [!code highlight]
+        scalars: { // [!code highlight]
+            // This tells codegen that the `Money` scalar is a number // [!code highlight]
+            Money: 'number', // [!code highlight]
+        }, // [!code highlight]
+        namingConvention: { // [!code highlight]
+            // This ensures generated enums do not conflict with the built-in types. // [!code highlight]
+            enumValues: 'keep', // [!code highlight]
+        }, // [!code highlight]
+      } // [!code highlight]
     },
   }
 };
@@ -92,14 +90,11 @@ replace it with the `graphql()` function.
 ```ts title="src/App.tsx"
 import { useQuery } from '@tanstack/react-query';
 import request from 'graphql-request'
-// highlight-next-line
-import { graphql } from './gql';
-
-// highlight-start
-// GET_PRODUCTS will be a `TypedDocumentNode` type,
-// which encodes the types of the query variables and the response data.
-const GET_PRODUCTS = graphql(`
-// highlight-end
+import { graphql } from './gql'; // [!code highlight]
+
+// GET_PRODUCTS will be a `TypedDocumentNode` type, // [!code highlight]
+// which encodes the types of the query variables and the response data. // [!code highlight]
+const GET_PRODUCTS = graphql(` // [!code highlight]
     query GetProducts($options: ProductListOptions) {
         products(options: $options) {
             items {
@@ -115,21 +110,17 @@ const GET_PRODUCTS = graphql(`
 `);
 
 export default function App() {
-  // highlight-start
-  // `data` will now be correctly typed
-  const { isLoading, data } = useQuery({
-  // highlight-end
+  // `data` will now be correctly typed // [!code highlight]
+  const { isLoading, data } = useQuery({ // [!code highlight]
     queryKey: ['products'],
     queryFn: async () =>
       request(
         'http://localhost:3000/shop-api',
-        // highlight-start
-        GET_PRODUCTS,
-        {
-        // The variables will also be correctly typed
-        options: { take: 3 },
-        }
-        // highlight-end
+        GET_PRODUCTS, // [!code highlight]
+        { // [!code highlight]
+        // The variables will also be correctly typed // [!code highlight]
+        options: { take: 3 }, // [!code highlight]
+        } // [!code highlight]
       ),
   });
 

+ 13 - 20
docs/docs/guides/storefront/connect-api/index.mdx

@@ -32,8 +32,7 @@ import { VendureConfig } from '@vendure/core';
 export const config: VendureConfig = {
     // ...
     authOptions: {
-        // highlight-next-line
-        tokenMethod: ['bearer', 'cookie'],
+        tokenMethod: ['bearer', 'cookie'], // [!code highlight]
     },
 };
 ```
@@ -55,11 +54,9 @@ Using cookies is the simpler approach for browser-based applications, since the
         // ...
         authOptions: {
             tokenMethod: ['bearer', 'cookie'],
-            // highlight-start
-            cookieOptions: {
-                secret: process.env.COOKIE_SESSION_SECRET
-            }
-            // highlight-end
+            cookieOptions: { // [!code highlight]
+                secret: process.env.COOKIE_SESSION_SECRET // [!code highlight]
+            } // [!code highlight]
         }
     }
     ```
@@ -125,9 +122,7 @@ export function query(document: string, variables: Record<string, any> = {}) {
         method: 'POST',
         headers: {
             'content-type': 'application/json',
-            // highlight-start
-            'vendure-token': 'uk-channel',
-            // highlight-end
+            'vendure-token': 'uk-channel', // [!code highlight]
         },
         credentials: 'include',
         body: JSON.stringify({
@@ -213,16 +208,14 @@ export function query(document: string, variables: Record<string, any> = {}) {
     if (languageCode) {
         endpoint += `?languageCode=${languageCode}`;
     }
-    // highlight-start
-    return fetch(endpoint, {
-        method: 'POST',
-        headers,
-        credentials: 'include',
-        body: JSON.stringify({
-            query: document,
-            variables,
-        }),
-    // highlight-end
+    return fetch(endpoint, { // [!code highlight]
+        method: 'POST', // [!code highlight]
+        headers, // [!code highlight]
+        credentials: 'include', // [!code highlight]
+        body: JSON.stringify({ // [!code highlight]
+            query: document, // [!code highlight]
+            variables, // [!code highlight]
+        }), // [!code highlight]
     }).then((res) => {
         if (!res.ok) {
             throw new Error(`An error ocurred, HTTP status: ${res.status}`);

+ 1 - 2
docs/docs/guides/storefront/customer-accounts/index.mdx

@@ -228,8 +228,7 @@ EmailPlugin.init({
     outputPath: path.join(__dirname, '../static/email/output'),
     globalTemplateVars: {
         fromAddress: '"Vendure Demo Store" <noreply@vendure.io>',
-        // highlight-next-line
-        verifyEmailAddressUrl: 'https://demo.vendure.io/storefront/account/verify',
+        verifyEmailAddressUrl: 'https://demo.vendure.io/storefront/account/verify', // [!code highlight]
         passwordResetUrl: 'https://demo.vendure.io/storefront/account/reset-password',
         changeEmailAddressUrl: 'https://demo.vendure.io/storefront/account/change-email-address'
     },

+ 109 - 120
docs/docs/guides/storefront/listing-products/index.mdx

@@ -81,8 +81,7 @@ Next, we can use the `search` query to fetch the products in the collection:
 query GetCollectionProducts($slug: String!, $skip: Int, $take: Int) {
   search(
     input: {
-      // highlight-next-line
-      collectionSlug: $slug,
+      collectionSlug: $slug, # [!code highlight]
       groupByProduct: true,
       skip: $skip,
       take: $take }
@@ -189,8 +188,7 @@ The `search` query can also be used to perform a full-text search of the product
 query SearchProducts($term: String!, $skip: Int, $take: Int) {
   search(
     input: {
-      // highlight-next-line
-      term: $term,
+      term: $term, # [!code highlight]
       groupByProduct: true,
       skip: $skip,
       take: $take }
@@ -302,19 +300,17 @@ query SearchProducts($term: String!, $skip: Int, $take: Int) {
       take: $take }
   ) {
     totalItems
-    // highlight-start
-    facetValues {
-      count
-      facetValue {
-        id
-        name
-        facet {
-          id
-          name
-        }
-      }
-    }
-    // highlight-end
+    facetValues { # [!code highlight]
+      count # [!code highlight]
+      facetValue { # [!code highlight]
+        id # [!code highlight]
+        name # [!code highlight]
+        facet { # [!code highlight]
+          id # [!code highlight]
+          name # [!code highlight]
+        } # [!code highlight]
+      } # [!code highlight]
+    } # [!code highlight]
     items {
       productName
       slug
@@ -358,98 +354,96 @@ query SearchProducts($term: String!, $skip: Int, $take: Int) {
   "data": {
     "search": {
       "totalItems": 8,
-      // highlight-start
-      "facetValues": [
-        {
-          "facetValue": {
-            "id": "1",
-            "name": "Electronics",
-            "facet": {
-              "id": "1",
-              "name": "category"
-            }
-          },
-          "count": 8
-        },
-        {
-          "facetValue": {
-            "id": "9",
-            "name": "Photo",
-            "facet": {
-              "id": "1",
-              "name": "category"
-            }
-          },
-          "count": 8
-        },
-        {
-          "facetValue": {
-            "id": "10",
-            "name": "Polaroid",
-            "facet": {
-              "id": "2",
-              "name": "brand"
-            }
-          },
-          "count": 1
-        },
-        {
-          "facetValue": {
-            "id": "11",
-            "name": "Nikkon",
-            "facet": {
-              "id": "2",
-              "name": "brand"
-            }
-          },
-          "count": 2
-        },
-        {
-          "facetValue": {
-            "id": "12",
-            "name": "Agfa",
-            "facet": {
-              "id": "2",
-              "name": "brand"
-            }
-          },
-          "count": 1
-        },
-        {
-          "facetValue": {
-            "id": "14",
-            "name": "Kodak",
-            "facet": {
-              "id": "2",
-              "name": "brand"
-            }
-          },
-          "count": 1
-        },
-        {
-          "facetValue": {
-            "id": "15",
-            "name": "Sony",
-            "facet": {
-              "id": "2",
-              "name": "brand"
-            }
-          },
-          "count": 1
-        },
-        {
-          "facetValue": {
-            "id": "16",
-            "name": "Rolleiflex",
-            "facet": {
-              "id": "2",
-              "name": "brand"
-            }
-          },
-          "count": 1
-        }
-      ],
-      // highlight-end
+      "facetValues": [ // [!code highlight]
+        { // [!code highlight]
+          "facetValue": { // [!code highlight]
+            "id": "1", // [!code highlight]
+            "name": "Electronics", // [!code highlight]
+            "facet": { // [!code highlight]
+              "id": "1", // [!code highlight]
+              "name": "category" // [!code highlight]
+            } // [!code highlight]
+          }, // [!code highlight]
+          "count": 8 // [!code highlight]
+        }, // [!code highlight]
+        { // [!code highlight]
+          "facetValue": { // [!code highlight]
+            "id": "9", // [!code highlight]
+            "name": "Photo", // [!code highlight]
+            "facet": { // [!code highlight]
+              "id": "1", // [!code highlight]
+              "name": "category" // [!code highlight]
+            } // [!code highlight]
+          }, // [!code highlight]
+          "count": 8 // [!code highlight]
+        }, // [!code highlight]
+        { // [!code highlight]
+          "facetValue": { // [!code highlight]
+            "id": "10", // [!code highlight]
+            "name": "Polaroid", // [!code highlight]
+            "facet": { // [!code highlight]
+              "id": "2", // [!code highlight]
+              "name": "brand" // [!code highlight]
+            } // [!code highlight]
+          }, // [!code highlight]
+          "count": 1 // [!code highlight]
+        }, // [!code highlight]
+        { // [!code highlight]
+          "facetValue": { // [!code highlight]
+            "id": "11", // [!code highlight]
+            "name": "Nikkon", // [!code highlight]
+            "facet": { // [!code highlight]
+              "id": "2", // [!code highlight]
+              "name": "brand" // [!code highlight]
+            } // [!code highlight]
+          }, // [!code highlight]
+          "count": 2 // [!code highlight]
+        }, // [!code highlight]
+        { // [!code highlight]
+          "facetValue": { // [!code highlight]
+            "id": "12", // [!code highlight]
+            "name": "Agfa", // [!code highlight]
+            "facet": { // [!code highlight]
+              "id": "2", // [!code highlight]
+              "name": "brand" // [!code highlight]
+            } // [!code highlight]
+          }, // [!code highlight]
+          "count": 1 // [!code highlight]
+        }, // [!code highlight]
+        { // [!code highlight]
+          "facetValue": { // [!code highlight]
+            "id": "14", // [!code highlight]
+            "name": "Kodak", // [!code highlight]
+            "facet": { // [!code highlight]
+              "id": "2", // [!code highlight]
+              "name": "brand" // [!code highlight]
+            } // [!code highlight]
+          }, // [!code highlight]
+          "count": 1 // [!code highlight]
+        }, // [!code highlight]
+        { // [!code highlight]
+          "facetValue": { // [!code highlight]
+            "id": "15", // [!code highlight]
+            "name": "Sony", // [!code highlight]
+            "facet": { // [!code highlight]
+              "id": "2", // [!code highlight]
+              "name": "brand" // [!code highlight]
+            } // [!code highlight]
+          }, // [!code highlight]
+          "count": 1 // [!code highlight]
+        }, // [!code highlight]
+        { // [!code highlight]
+          "facetValue": { // [!code highlight]
+            "id": "16", // [!code highlight]
+            "name": "Rolleiflex", // [!code highlight]
+            "facet": { // [!code highlight]
+              "id": "2", // [!code highlight]
+              "name": "brand" // [!code highlight]
+            } // [!code highlight]
+          }, // [!code highlight]
+          "count": 1 // [!code highlight]
+        } // [!code highlight]
+      ], // [!code highlight]
       "items": [
         {
           "productName": "Instant Camera",
@@ -507,16 +501,14 @@ request we know that there should be 2 such products, and that the `facetValue.i
 ```json
 {
   "facetValue": {
-    // highlight-next-line
-    "id": "11",
+    "id": "11", // [!code highlight]
     "name": "Nikkon",
     "facet": {
       "id": "2",
       "name": "brand"
     }
   },
-  // highlight-next-line
-  "count": 2
+  "count": 2 // [!code highlight]
 }
 ```
 
@@ -535,8 +527,7 @@ change the query itself.
 
 ```graphql
 query SearchProducts($input: SearchInput!) {
-  // highlight-next-line
-  search(input: $input) {
+  search(input: $input) { # [!code highlight]
     totalItems
     facetValues {
       count
@@ -581,11 +572,9 @@ query SearchProducts($input: SearchInput!) {
     "skip": 0,
     "take": 10,
     "groupByProduct": true,
-    // highlight-start
-    "facetValueFilters": [
-      { "and": "11" }
-    ]
-    // highlight-end
+    "facetValueFilters": [ // [!code highlight]
+      { "and": "11" } // [!code highlight]
+    ] // [!code highlight]
   }
 }
 ```

+ 1 - 2
docs/docs/guides/storefront/navigation-menu/index.mdx

@@ -85,8 +85,7 @@ query GetAllCollections {
       id
       slug
       name
-      // highlight-next-line
-      parentId
+      parentId # [!code highlight]
       featuredAsset {
         id
         preview

+ 7 - 9
docs/docs/reference/admin-ui-api/action-bar/action-bar-context.mdx

@@ -59,15 +59,13 @@ addActionBarDropdownMenuItem({
     label: 'Print Invoice',
     icon: 'printer',
     buttonState: context => {
-        // highlight-start
-        return context.entity$.pipe(
-            map((order) => {
-                return order?.state === 'PaymentSettled'
-                    ? { disabled: false, visible: true }
-                    : { disabled: true, visible: true };
-            }),
-        );
-        // highlight-end
+        return context.entity$.pipe( // [!code highlight]
+            map((order) => { // [!code highlight]
+                return order?.state === 'PaymentSettled' // [!code highlight]
+                    ? { disabled: false, visible: true } // [!code highlight]
+                    : { disabled: true, visible: true }; // [!code highlight]
+            }), // [!code highlight]
+        ); // [!code highlight]
     },
     requiresPermission: ['UpdateOrder'],
 }),

+ 2 - 4
docs/docs/reference/admin-ui-api/custom-input-components/register-form-input-component.mdx

@@ -14,8 +14,7 @@ a custom field.
 import { registerFormInputComponent } from '@vendure/admin-ui/core';
 
 export default [
-    // highlight-next-line
-    registerFormInputComponent('my-custom-input', MyCustomFieldControl),
+    registerFormInputComponent('my-custom-input', MyCustomFieldControl), // [!code highlight]
 ];
 ```
 
@@ -33,8 +32,7 @@ const config: VendureConfig = {
       {
         name: 'rrp',
         type: 'int',
-        // highlight-next-line
-        ui: { component: 'my-custom-input' },
+        ui: { component: 'my-custom-input' }, // [!code highlight]
       },
     ]
   }

+ 5 - 9
docs/docs/reference/admin-ui-api/ui-devkit/admin-ui-extension.mdx

@@ -96,8 +96,7 @@ import path from 'path';
 import { AdminUiExtension } from '@vendure/ui-devkit/compiler';
 
 export const uiExtensions: AdminUiExtension = {
-  // highlight-next-line
-  pathAlias: '@common-ui-module',     // this is the important part
+  pathAlias: '@common-ui-module',     // this is the important part // [!code highlight]
   extensionPath: path.join(__dirname, 'ui'),
   ngModules: [
     {
@@ -114,8 +113,7 @@ export const uiExtensions: AdminUiExtension = {
   "compilerOptions": {
     "baseUrl": ".",
     "paths": {
-      // highlight-next-line
-      "@common-ui-module/*": ["packages/common-ui-module/src/ui/*"]
+      "@common-ui-module/*": ["packages/common-ui-module/src/ui/*"] // [!code highlight]
     }
   }
 }
@@ -124,11 +122,9 @@ export const uiExtensions: AdminUiExtension = {
 ```ts title="packages/sample-plugin/src/ui/ui-extension.module.ts"
 import { NgModule } from '@angular/core';
 import { SharedModule } from '@vendure/admin-ui/core';
-// highlight-start
-// the import below works both in the context of the custom Admin UI app as well as the main project
-// '@common-ui-module' is the value of "pathAlias" and 'ui-shared.module' is the file we want to reference inside "extensionPath"
-import { CommonSharedUiModule, CommonUiComponent } from '@common-ui-module/ui-shared.module';
-// highlight-end
+// the import below works both in the context of the custom Admin UI app as well as the main project // [!code highlight]
+// '@common-ui-module' is the value of "pathAlias" and 'ui-shared.module' is the file we want to reference inside "extensionPath" // [!code highlight]
+import { CommonSharedUiModule, CommonUiComponent } from '@common-ui-module/ui-shared.module'; // [!code highlight]
 
 @NgModule({
   imports: [

+ 4 - 6
docs/docs/reference/core-plugins/sentry-plugin/index.mdx

@@ -73,12 +73,10 @@ export const config: VendureConfig = {
     // ...
     plugins: [
         // ...
-        // highlight-start
-        SentryPlugin.init({
-            // Optional configuration
-            includeErrorTestMutation: true,
-        }),
-        // highlight-end
+        SentryPlugin.init({ // [!code highlight]
+            // Optional configuration // [!code highlight]
+            includeErrorTestMutation: true, // [!code highlight]
+        }), // [!code highlight]
     ],
 };
 ```

+ 3 - 5
docs/docs/reference/core-plugins/stellate-plugin/index.mdx

@@ -131,11 +131,9 @@ import { graphql } from '../generated/gql';
 export const searchProductsDocument = graphql(`
     query SearchProducts($input: SearchInput!) {
         search(input: $input) {
-            // highlight-start
-            cacheIdentifier {
-                collectionSlug
-            }
-            // highlight-end
+            cacheIdentifier { # [!code highlight]
+                collectionSlug # [!code highlight]
+            } # [!code highlight]
             items {
                # ...
             }

+ 3 - 5
docs/docs/reference/dashboard/extensions-api/form-components.mdx

@@ -105,11 +105,9 @@ export const MyCustomInput: DashboardFormComponent = props => {
   // implementation omitted
 }
 
-// highlight-start
-MyCustomInput.metadata = {
-  isListInput: true
-}
-// highlight-end
+MyCustomInput.metadata = { // [!code highlight]
+  isListInput: true // [!code highlight]
+} // [!code highlight]
 ```
 
 ```ts title="Signature"

+ 3 - 5
docs/docs/reference/typescript-api/common/bootstrap.mdx

@@ -29,11 +29,9 @@ import { bootstrap } from '@vendure/core';
 import { config } from './vendure-config';
 
 bootstrap(config, {
-  // highlight-start
-  nestApplicationOptions: {
-    snapshot: true,
-  }
-  // highlight-end
+  nestApplicationOptions: { // [!code highlight]
+    snapshot: true, // [!code highlight]
+  } // [!code highlight]
 }).catch(err => {
   console.log(err);
   process.exit(1);

+ 3 - 5
docs/docs/reference/typescript-api/configuration/product-variant-price-update-strategy.mdx

@@ -123,11 +123,9 @@ import { DefaultProductVariantPriceUpdateStrategy, VendureConfig } from '@vendur
 export const config: VendureConfig = {
   // ...
   catalogOptions: {
-    // highlight-start
-    productVariantPriceUpdateStrategy: new DefaultProductVariantPriceUpdateStrategy({
-      syncPricesAcrossChannels: true,
-    }),
-    // highlight-end
+    productVariantPriceUpdateStrategy: new DefaultProductVariantPriceUpdateStrategy({ // [!code highlight]
+      syncPricesAcrossChannels: true, // [!code highlight]
+    }), // [!code highlight]
   },
   // ...
 };

+ 5 - 8
docs/docs/reference/typescript-api/data-access/entity-hydrator.mdx

@@ -18,8 +18,7 @@ import { ID, RequestContext, EntityHydrator, ProductVariantService } from '@vend
 export class MyService {
 
   constructor(
-     // highlight-next-line
-     private entityHydrator: EntityHydrator,
+     private entityHydrator: EntityHydrator, // [!code highlight]
      private productVariantService: ProductVariantService,
   ) {}
 
@@ -30,12 +29,10 @@ export class MyService {
     // at this stage, we don't know which of the Product relations
     // will be joined at runtime.
 
-    // highlight-start
-    await this.entityHydrator
-      .hydrate(ctx, product, { relations: ['facetValues.facet' ]});
-
-    // You can be sure now that the `facetValues` & `facetValues.facet` relations are populated
-    // highlight-end
+    await this.entityHydrator // [!code highlight]
+      .hydrate(ctx, product, { relations: ['facetValues.facet' ]}); // [!code highlight]
+ // [!code highlight]
+    // You can be sure now that the `facetValues` & `facetValues.facet` relations are populated // [!code highlight]
   }
 }
 ```

+ 3 - 5
docs/docs/reference/typescript-api/orders/order-interceptor.mdx

@@ -61,11 +61,9 @@ mutation AddItemToOrder($productVariantId: ID!, $quantity: Int!) {
       errorCode
       message
     }
-    // highlight-start
-    ... on OrderInterceptorError {
-      interceptorError
-    }
-    // highlight-end
+    ... on OrderInterceptorError { # [!code highlight]
+      interceptorError # [!code highlight]
+    } # [!code highlight]
   }
 }
 ```

+ 1 - 2
docs/docs/reference/typescript-api/tax/address-based-tax-zone-strategy.mdx

@@ -25,8 +25,7 @@ import { VendureConfig, AddressBasedTaxZoneStrategy } from '@vendure/core';
 export const config: VendureConfig = {
   // other options...
   taxOptions: {
-    // highlight-next-line
-    taxZoneStrategy: new AddressBasedTaxZoneStrategy(),
+    taxZoneStrategy: new AddressBasedTaxZoneStrategy(), // [!code highlight]
   },
 };
 ```

+ 1 - 2
docs/docs/reference/typescript-api/telemetry/instrument.mdx

@@ -26,8 +26,7 @@ import { Instrument } from '@vendure/core';
 import { Injectable } from '@nestjs/common';
 
 @Injectable()
-// highlight-next-line
-@Instrument()
+@Instrument() // [!code highlight]
 export class MyService {
 
   // Calls to this method will be instrumented