Browse Source

docs: More docs improvements

Michael Bromley 2 years ago
parent
commit
4653aed286

+ 102 - 38
docs/docs/guides/advanced-topics/stand-alone-scripts.md

@@ -1,54 +1,118 @@
 ---
 title: "Stand-alone CLI Scripts"
-showtoc: true
 ---
 
 # Stand-alone CLI Scripts
 
-It is possible to create stand-alone scripts that can be run from the command-line by using the [bootstrapWorker function]({{< relref "bootstrap-worker" >}}). This can be useful for a variety of use-cases such as running cron jobs or importing data.
+It is possible to create stand-alone scripts that can be run from the command-line by using the [bootstrapWorker function](/reference/typescript-api/worker/bootstrap-worker/). This can be useful for a variety of use-cases such as running cron jobs or importing data.
 
-```ts
-// run-sync.ts
+## Minimal example
 
-import { bootstrapWorker, Logger } from '@vendure/core';
+Here's a minimal example of a script which will bootstrap the Vendure Worker and then log the number of products in the database:
+
+```ts title="src/get-product-count.ts"
+import { bootstrapWorker, Logger, ProductService, RequestContextService } from '@vendure/core';
 
 import { config } from './vendure-config';
-import { DataSyncPlugin, DataSyncService } from './plugins/data-sync';
-
-const loggerCtx = 'DataSync script';
-
-runDataSync()
-  .then(() => process.exit(0))
-  .catch(() => process.exit(1));
-
-async function runDataSync() {
-  // This will bootstrap an instance of the Vendure Worker
-  const { app } = await bootstrapWorker({
-    ...config,
-    plugins: [
-      ...config.plugins,
-      DataSyncPlugin,
-    ]
-  });
-  
-  // Using `app.get()` we can grab an instance of _any_ provider defined in the
-  // Vendure core as well as by our plugins.
-  const dataSyncService = app.get(DataSyncService);
-  
-  Logger.info('Syncing data...', loggerCtx);
-  
-  try {
-    const result = await dataSyncService.runSync();
-    Logger.info(`Completed sync: ${result.count} items processed`, loggerCtx);
-  } catch (e) {
-    Logger.error(e.message, loggerCtx, e.stack);
-    throw e;
-  }
+
+if (require.main === module) {
+    getProductCount()
+        .then(() => process.exit(0))
+        .catch(err => {
+            Logger.error(err);
+            process.exit(1);
+        });
 }
-```
+
+async function getProductCount() {
+    // This will bootstrap an instance of the Vendure Worker, providing
+    // us access to all of the services defined in the Vendure core.
+    // (but without the unnecessary overhead of the API layer).
+    const { app } = await bootstrapWorker(config);
+
+    // Using `app.get()` we can grab an instance of _any_ provider defined in the
+    // Vendure core as well as by our plugins.
+    const productService = app.get(ProductService);
+
+    // For most service methods, we'll need to pass a RequestContext object.
+    // We can use the RequestContextService to create one.
+    const ctx = await app.get(RequestContextService).create({
+        apiType: 'admin',
+    });
+
+    // We use the `findAll()` method to get the total count. Since we aren't
+    // interested in the actual product objects, we can set the `take` option to 0.
+    const { totalItems } = await productService.findAll(ctx, {take: 0});
+
+    Logger.info(
+        [
+            '\n-----------------------------',
+            `There are ${totalItems} products`,
+            '------------------------------',
+        ].join('\n'),
+}
+``` 
 
 This script can then be run from the command-line:
 
 ```shell
-yarn ts-node run-sync.ts
+npx ts-node src/get-product-count.ts
+
+# or
+
+yarn ts-node src/get-product-count.ts
+```
+
+resulting in the following output:
+
+```shell
+info 01/08/23, 11:50 - [Vendure Worker] Bootstrapping Vendure Worker (pid: 4428)...
+info 01/08/23, 11:50 - [Vendure Worker] Vendure Worker is ready
+info 01/08/23, 11:50 - [Vendure Worker]
+-----------------------------------------
+There are 56 products in the database
+-----------------------------------------
+```
+
+## The `app` object
+
+The `app` object returned by the `bootstrapWorker()` function is an instance of the [NestJS Application Context](https://docs.nestjs.com/standalone-applications). It has full access to the NestJS dependency injection container, which means that you can use the `app.get()` method to retrieve any of the services defined in the Vendure core or by any plugins.
+
+```ts title="src/import-customer-data.ts"
+import { bootstrapWorker, CustomerService } from '@vendure/core';
+import { config } from './vendure-config';
+
+// ...
+
+async function importCustomerData() {
+    const { app } = await bootstrapWorker(config);
+    
+    // highlight-start
+    const customerService = app.get(CustomerService);
+    // highlight-end
+}
+```
+
+## Creating a RequestContext
+
+Almost all the methods exposed by Vendure's core services take a `RequestContext` object as the first argument. Usually, this object is created in the [API Layer](/guides/developer-guide/the-api-layer/#resolvers) by the `@Ctx()` decorator, and contains information related to the current API request.
+
+When running a stand-alone script, we aren't making any API requests, so we need to create a `RequestContext` object manually. This can be done using the [`RequestContextService`](/reference/typescript-api/request/request-context-service/):
+
+```ts title="src/get-product-count.ts"
+// ...
+import { RequestContextService } from '@vendure/core';
+
+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 { totalItems } = await productService.findAll(ctx, {take: 0});
+}
 ```

+ 279 - 1
docs/docs/guides/developer-guide/the-api-layer/index.mdx

@@ -116,8 +116,286 @@ As you can see, the resolver function is very simple, and simply delegates the w
 responsible for fetching the data from the database.
 
 :::tip
-
 In general, resolver functions should be kept as simple as possible,
 and the bulk of the business logic should be delegated to the service layer.
+:::
+
+## API Decorators
+
+Following the pattern of NestJS, Vendure makes use of decorators to control various aspects of the API. Here are the
+important decorators to be aware of:
+
+### `@Resolver()`
+
+This is exported by the `@nestjs/graphql` package. It marks a class as a resolver, meaning that its methods can be used
+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()
+export class WishlistResolver {
+    // ...
+}
+```
+
+### `@Query()`
+
+This is exported by the `@nestjs/graphql` package. It marks a method as a resolver function for a query. The method name
+should match the name of the query in the GraphQL schema, or if the method name is different, a name can be provided as
+an argument to the decorator.
+
+```ts title="src/plugins/wishlist/api/wishlist.resolver.ts"
+import { Query, Resolver } from '@nestjs/graphql';
+
+@Resolver()
+export class WishlistResolver {
+
+    // highlight-next-line
+    @Query()
+    wishlist() {
+        // ...
+    }
+}
+```
+
+### `@Mutation()`
+
+This is exported by the `@nestjs/graphql` package. It marks a method as a resolver function for a mutation. The method name
+should match the name of the mutation in the GraphQL schema, or if the method name is different, a name can be provided as
+an argument to the decorator.
+
+```ts title="src/plugins/wishlist/api/wishlist.resolver.ts"
+import { Mutation, Resolver } from '@nestjs/graphql';
+
+@Resolver()
+export class WishlistResolver {
+
+    // highlight-next-line
+    @Mutation()
+    addItemToWishlist() {
+        // ...
+    }
+}
+```
+
+### `@Allow()`
+
+The [`Allow` decorator](/reference/typescript-api/request/allow-decorator) is exported by the `@vendure/core` package. It is used to control access to queries and mutations. It takes a list
+of [Permissions](/reference/typescript-api/common/permission/) and if the current user does not have at least one of the
+permissions, then the query or mutation will return an error.
+
+```ts title="src/plugins/wishlist/api/wishlist.resolver.ts"
+import { Mutation, Resolver } from '@nestjs/graphql';
+import { Allow, Permission } from '@vendure/core';
+
+@Resolver()
+export class WishlistResolver {
+
+    @Mutation()
+    // highlight-next-line
+    @Allow(Permission.UpdateCustomer)
+    updateCustomerWishlist() {
+        // ...
+    }
+}
+```
+
+### `@Transaction()`
+
+The [`Transaction` decorator](/reference/typescript-api/request/transaction-decorator/) is exported by the `@vendure/core` package. It is used to wrap a resolver function in a database transaction. It is
+normally used with mutations, since queries typically do not modify data.
+
+```ts title="src/plugins/wishlist/api/wishlist.resolver.ts"
+import { Mutation, Resolver } from '@nestjs/graphql';
+import { Transaction } from '@vendure/core';
+
+@Resolver()
+export class WishlistResolver {
+
+    // highlight-next-line
+    @Transaction()
+    @Mutation()
+    addItemToWishlist() {
+        // if an error is thrown here, the
+        // entire transaction will be rolled back
+    }
+}
+```
+
+:::note
+The `@Transaction()` decorator _only_ works when used with a `RequestContext` object (see the `@Ctx()` decorator below).
+
+This is because the `Transaction` decorator stores the transaction context on the `RequestContext` object, and by passing
+this object to the service layer, the services and thus database calls can access the transaction context.
+:::
+
+### `@Ctx()`
+
+The [`Ctx` decorator](/reference/typescript-api/request/ctx-decorator/) is exported by the `@vendure/core` package. It is used to inject the
+[`RequestContext` object](/reference/typescript-api/request/request-context/) into a resolver function. The `RequestContext` contains information about the
+current request, such as the current user, the active channel, the active language, etc. The `RequestContext` is a key part
+of the Vendure architecture, and is used throughout the application to provide context to the various services and plugins.
+
+```ts title="src/plugins/wishlist/api/wishlist.resolver.ts"
+import { Mutation, Resolver } from '@nestjs/graphql';
+import { Ctx, RequestContext } from '@vendure/core';
+
+@Resolver()
+export class WishlistResolver {
+
+    @Mutation()
+    // highlight-next-line
+    addItemToWishlist(@Ctx() ctx: RequestContext) {
+        // ...
+    }
+}
+```
+
+:::tip
+As a general rule, _always_ use the `@Ctx()` decorator to inject the `RequestContext` into your resolver functions.
+:::
+
+### `@Args()`
+
+This is exported by the `@nestjs/graphql` package. It is used to inject the arguments passed to a query or mutation.
+
+Given the a schema definition like this:
+
+```graphql
+extend type Mutation {
+    addItemToWishlist(variantId: ID!): Wishlist
+}
+```
+
+The resolver function would look like this:
+
+```ts title="src/plugins/wishlist/api/wishlist.resolver.ts"
+import { Mutation, Resolver, Args } from '@nestjs/graphql';
+import { Ctx, RequestContext, ID } from '@vendure/core';
+
+@Resolver()
+export class WishlistResolver {
+
+    @Mutation()
+    addItemToWishlist(
+        @Ctx() ctx: RequestContext,
+        // highlight-next-line
+        @Args() args: { variantId: ID }
+    ) {
+        // ...
+    }
+}
+```
+
+As you can see, the `@Args()` decorator injects the arguments passed to the query, in this case the `variantId` that we provided in our query.
+
+## Field resolvers
+
+So far, we've seen examples of resolvers for queries and mutations. However, there is another type of resolver which is
+used to resolve the fields of a type. For example, given the following schema definition:
+
+```graphql
+type WishlistItem {
+    id: ID!
+    // highlight-next-line
+    product: Product!
+}
+```
+
+The `product` field is a [relation](/docs/graphql-api/relations/) to the `Product` type. The `product` field resolver
+would look like this:
+
+```ts title="src/plugins/wishlist/api/wishlist-item.resolver.ts"
+import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
+import { Ctx, RequestContext } from '@vendure/core';
+
+import { WishlistItem } from '../entities/wishlist-item.entity';
+
+// highlight-next-line
+@Resolver('WishlistItem')
+export class WishlistItemResolver {
+
+    // highlight-next-line
+    @ResolveField()
+    product(
+        @Ctx() ctx: RequestContext,
+        // highlight-next-line
+        @Parent() wishlistItem: WishlistItem
+    ) {
+        // ...
+    }
+}
+```
+
+Note that in this example, the `@Resolver()` decorator has an argument of `'WishlistItem'`. This tells NestJS that
+this resolver is for the `WishlistItem` type, and that when we use the `@ResolveField()` decorator, we are defining
+a resolver for a field of that type.
+
+In this example we're defining a resolver for the `product` field of the `WishlistItem` type. The
+`@ResolveField()` decorator is used to mark a method as a field resolver. The method name should match the name of the
+field in the GraphQL schema, or if the method name is different, a name can be provided as an argument to the decorator.
+
+## REST endpoints
+
+Although Vendure is primarily a GraphQL-based API, it is possible to add REST endpoints to the API. This is useful if
+you need to integrate with a third-party service which only supports REST, or if you need to provide a REST API for
+
+In order to create REST endpoints, you need to create a plugin (see the [plugin developer guide](/guides/developer-guide/plugins/))
+which includes a [NestJS controller](https://docs.nestjs.com/controllers). Here's a simple example:
+
+```ts title="src/plugins/rest/api/products.controller.ts"
+import { Controller, Get } from '@nestjs/common';
+import { Ctx, ProductService, RequestContext } from '@vendure/core';
+
+@Controller('products')
+export class ProductsController {
+    constructor(private productService: ProductService) {}
+
+    @Get()
+    findAll(@Ctx() ctx: RequestContext) {
+        return this.productService.findAll(ctx);
+    }
+}
+```
+
+The `@Controller()` decorator takes a path argument which is the base path for all the endpoints defined in the controller.
+In this case, the path is `products`, so the endpoint will be available at `/products`.
+
+The controller can be registered with a plugin:
+
+```ts title="src/plugins/rest/products.plugin.ts"
+import { PluginCommonModule, VendurePlugin } from '@vendure/core';
+import { ProductsController } from './api/products.controller';
+
+@VendurePlugin({
+    imports: [PluginCommonModule],
+    // highlight-next-line
+    controllers: [ProductsController],
+})
+export class ProductsRestPlugin {}
+```
+
+And finally, the plugin is added to the `VendureConfig`:
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+import { ProductsRestPlugin } from './plugins/rest/products.plugin';
+
+export const config: VendureConfig = {
+    // ...
+    plugins: [
+        // highlight-next-line
+        ProductsRestPlugin,
+    ],
+};
+```
+
+Now when the server is started, the REST endpoint will be available at `http://localhost:3000/products`.
+
+:::tip
+The following Vendure decorators can also be used with NestJS controllers: `@Allow()`, `@Transaction()`, `@Ctx()`.
 
+Additionally, NestJS supports a number of other REST decorators detailed in the [NestJS controllers guide](https://docs.nestjs.com/controllers#request-object)
 :::

+ 35 - 0
docs/docs/guides/developer-guide/the-service-layer/index.mdx

@@ -60,6 +60,41 @@ export class ProductService {
     is used to access the database, and the `TranslatorService` is used to translate the Product entity into the current
     language.
 
+## Using core services
+
+All the internal Vendure services can be used in your own plugins and scripts. They are listed in the [Services API reference](/reference/typescript-api/services/) and
+can be imported from the `@vendure/core` package.
+
+To make use of a core service in your own plugin, you need to ensure your plugin is importing the `PluginCommonModule` and
+then inject the desired service into your own service's constructor:
+
+```ts title="src/my-plugin/my.plugin.ts"
+import { PluginCommonModule, VendurePlugin } from '@vendure/core';
+import { MyService } from './services/my.service';
+
+@VendurePlugin({
+    // highlight-start
+    imports: [PluginCommonModule],
+    providers: [MyService],
+    // highlight-end
+})
+export class MyPlugin {}
+```
+
+```ts title="src/my-plugin/services/my.service.ts"
+import { Injectable } from '@nestjs/common';
+import { ProductService } from '@vendure/core';
+
+@Injectable()
+export class MyService {
+
+    // highlight-next-line
+    constructor(private productService: ProductService) {}
+
+    // you can now use the productService methods
+}
+```
+
 ## Accessing the database
 
 One of the main responsibilities of the service layer is to interact with the database. For this, you will be using

+ 79 - 15
docs/docs/guides/how-to/configurable-products/index.md

@@ -2,34 +2,78 @@
 title: "Configurable Products"
 ---
 
+A "configurable product" is one where aspects can be configured by the customer, and are unrelated to the product's variants. Examples include:
 
-One common use of custom fields is to support configurable products. Imagine we are selling pens which allow some text to be engraved. To do this we would add a **custom field on the OrderLine**:
+- Engraving text on an item
+- A gift message inserted with the packaging
+- An uploaded image to be printed on a t-shirt
 
-```ts
-OrderLine: [
-    {
-        name: 'engravingText',
-        type: 'string',
-        label: [
+In Vendure this is done by defining one or more [custom fields](/guides/developer-guide/custom-fields/) on the [OrderLine](/reference/typescript-api/order/order-line/) entity.
+
+## Defining custom fields
+
+Let's take the example of an engraving service. Some products can be engraved, others cannot. We will record this information in a custom field on the [ProductVariant](/reference/typescript-api/entities/product-variant/) entity:
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+
+export const config: VendureConfig = {
+    // ...
+    customFields: {
+        ProductVariant: [
             {
-                languageCode: LanguageCode.en,
-                value: 'The text to engrave on the product'
+                name: 'engravable',
+                type: 'boolean',
+                defaultValue: false,
+                label: [
+                    { languageCode: LanguageCode.en, value: 'Engravable' },
+                ],
             },
         ],
     },
-]
+};
+```
+
+For those variants that _are_ engravable, we need to be able to record the text to be engraved. This is done by defining a custom field on the [OrderLine](/reference/typescript-api/entities/order-line/) entity:
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+
+export const config: VendureConfig = {
+    // ...
+    customFields: {
+        OrderLine: [
+            {
+                name: 'engravingText',
+                type: 'string',
+                validate: value => {
+                    if (value.length > 100) {
+                        return 'Engraving text must be less than 100 characters';
+                    }
+                },
+                label: [
+                    { languageCode: LanguageCode.en, value: 'Engraving text' },
+                ],
+            },
+        ]
+    },
+};
 ```
 
-Once defined, the [addItemToOrder mutation]({{< relref "/reference/graphql-api/shop/mutations" >}}#additemtoorder) will have a third argument available, which accepts values for the custom field defined above:
+## Setting the custom field value
+
+Once the custom fields are defined, the [addItemToOrder mutation](/reference/graphql-api/shop/mutations/#additemtoorder) will have a third argument available, which accepts values for the custom field defined above:
 
 ```graphql
 mutation {
     addItemToOrder(
         productVariantId: "42"
         quantity: 1
+        // highlight-start
         customFields: {
             engravingText: "Thanks for all the fish!"
         }
+        // highlight-end
     ) {
         ...on Order {
             id
@@ -45,9 +89,15 @@ mutation {
 }
 ```
 
-Furthermore, the values of these OrderLine custom fields can even be used to modify the price. This is done by defining a custom [OrderItemPriceCalculationStrategy]({{< relref "order-item-price-calculation-strategy" >}}):
+Your storefront application will need to provide a `<textarea>` for the customer to enter the engraving text, which would be displayed conditionally depending on the value of the `engravable` custom field on the `ProductVariant`. 
+
+## Modifying the price
+
+The values of these OrderLine custom fields can even be used to modify the price. This is done by defining a custom [OrderItemPriceCalculationStrategy](/reference/typescript-api/orders/order-item-price-calculation-strategy/). 
+
+Let's say that our engraving service costs and extra $10 on top of the regular price of the product variant. Here's a strategy to implement this:
 
-```ts
+```ts title="src/engraving-price-calculation-strategy.ts"
 import {
     RequestContext, PriceCalculationResult,
     ProductVariant, OrderItemPriceCalculationStrategy
@@ -62,8 +112,8 @@ export class EngravingPriceStrategy implements OrderItemPriceCalculationStrategy
     ) {
         let price = productVariant.listPrice;
         if (customFields.engravingText) {
-            // Add $5 for engraving
-            price += 500;
+            // Add $10 for engraving
+            price += 1000;
         }
         return {
             price,
@@ -72,3 +122,17 @@ export class EngravingPriceStrategy implements OrderItemPriceCalculationStrategy
     }
 }
 ```
+
+This is then added to the config:
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig } from '@vendure/core';
+import { EngravingPriceStrategy } from './engraving-price-calculation-strategy';
+
+export const config: VendureConfig = {
+    // ...
+    orderOptions: {
+        orderItemPriceCalculationStrategy: new EngravingPriceStrategy(),
+    },
+};
+```

+ 407 - 0
docs/docs/guides/how-to/importing-data/index.md

@@ -0,0 +1,407 @@
+---
+title: "Importing Data"
+showtoc: true
+---
+
+If you have hundreds, thousands or more products, inputting all the data by hand via the Admin UI can be too inefficient. To solve this, Vendure supports bulk-importing product and other data.
+
+Data import is also useful for setting up test or demo environments, and is also used by the `@vendure/testing` package for end-to-end tests.
+
+## Product Import Format
+
+Vendure uses a flat **.csv** format for importing product data. The format encodes data about:
+
+* products
+* product variants
+* product & variant assets
+* product & variant facets
+* product & variant custom fields
+
+Here's an example which defines 2 products, "Laptop" and "Clacky Keyboard". The laptop has 4 variants, and the keyboard only a single variant. 
+
+```csv
+name            , slug            , description               , assets                      , facets                              , optionGroups    , optionValues , sku         , price   , taxCategory , stockOnHand , trackInventory , variantAssets , variantFacets
+Laptop          , laptop          , "Description of laptop"   , laptop_01.jpg|laptop_02.jpg , category:electronics|brand:Apple    , screen size|RAM , 13 inch|8GB  , L2201308    , 1299.00 , standard    , 100         , false          ,               , 
+                ,                 ,                           ,                             ,                                     ,                 , 15 inch|8GB  , L2201508    , 1399.00 , standard    , 100         , false          ,               , 
+                ,                 ,                           ,                             ,                                     ,                 , 13 inch|16GB , L2201316    , 2199.00 , standard    , 100         , false          ,               , 
+                ,                 ,                           ,                             ,                                     ,                 , 15 inch|16GB , L2201516    , 2299.00 , standard    , 100         , false          ,               , 
+Clacky Keyboard , clacky-keyboard , "Description of keyboard" , keyboard_01.jpg             , category:electronics|brand:Logitech ,                 ,              , A4TKLA45535 , 74.89   , standard    , 100         , false          ,               ,
+```
+
+Here's an explanation of each column:
+
+* `name`: The name of the product. Rows with an empty "name" are interpreted as variants of the preceeding product row.
+* `slug`: The product's slug. Can be omitted, in which case will be generated from the name.
+* `description`: The product description.
+* `assets`: One or more asset file names separated by the pipe (`|`) character. The files can be located on the local file system, in which case the path is interpreted as being relative to the [`importAssetsDir`](/reference/typescript-api/import-export/import-export-options/#importassetsdir) as defined in the VendureConfig. Files can also be urls which will be fetched from a remote http/https url. If you need more control over how assets are imported, you can implement a custom [AssetImportStrategy](/reference/typescript-api/import-export/asset-import-strategy#assetimportstrategy). The first asset will be set as the featuredAsset.
+* `facets`: One or more facets to apply to the product separated by the pipe (`|`) character. A facet has the format `<facet-name>:<facet-value>`.
+* `optionGroups`: OptionGroups define what variants make up the product. Applies only to products with more than one variant. 
+* `optionValues`: For each optionGroup defined, a corresponding value must be specified for each variant. Applies only to products with more than one variant.
+* `sku`: The Stock Keeping Unit (unique product code) for this product variant.
+* `price`: The price can be either with or without taxes, depending on your channel settings (can be set later).
+* `taxCategory`: The name of an existing tax category. Tax categories can be also be imported using the InitialData object.
+* `stockOnHand`: The number of units in stock.
+* `trackInventory`: Whether this variant should have its stock level tracked, i.e. the stock level is automatically decreased for each unit ordered.
+* `variantAssets`: Same as `assets` but applied to the product variant.
+* `variantFacets`: Same as `facets` but applied to the product variant.
+
+### Importing Custom Field Data
+
+If you have [CustomFields]({{< relref "customizing-models" >}}) defined on your Product or ProductVariant entities, this data can also be encoded in the import csv:
+
+* `product:<customFieldName>`: The value of this column will populate `Product.customFields[customFieldName]`. 
+* `variant:<customFieldName>`: The value of this column will populate `ProductVariant.customFields[customFieldName]`. 
+
+:::info 
+For a real example, see the [products.csv file used to populate the Vendure demo data](https://github.com/vendure-ecommerce/vendure/blob/master/packages/core/mock-data/data-sources/products.csv)
+:::
+
+#### Importing `relation` custom fields
+
+To import custom fields with the type `relation`, the value in the CSV must be a stringified object with an `id` property:
+
+```csv
+... ,product:featuredReview
+... ,"{ ""id"": 123 }"
+```
+
+#### Importing `list` custom fields
+
+To import custom fields with `list` set to `true`, the data should be separated with a pipe (`|`) character:
+
+```csv
+... ,product:keywords
+... ,tablet|pad|android
+```
+
+#### Importing data in multiple languages
+
+If a field is translatable (i.e. of `localeString` type), you can use column names with an appended language code (e.g. `name:en`, `name:de`, `product:keywords:en`, `product:keywords:de`) to specify its value in multiple languages.
+
+Use of language codes has to be consistent throughout the file. You don't have to translate every translatable field. If there are no translated columns for a field, the generic column's value will be used for all languages. But when you do translate columns, the set of languages for each of them needs to be the same. As an example, you cannot use `name:en` and `name:de`, but only provide `slug:en` (it's okay to use only a `slug` column though, in which case this slug will be used for both the English and the German version).
+
+## Initial Data
+
+As well as product data, other initialization data can be populated using the [`InitialData` object](/reference/typescript-api/import-export/initial-data/). **This format is intentionally limited**; more advanced requirements (e.g. setting up ShippingMethods that use custom checkers & calculators) should be carried out via [custom populate scripts](#populating-the-server).
+
+```ts
+import { InitialData, LanguageCode } from '@vendure/core';
+
+export const initialData: InitialData = {
+    paymentMethods: [
+        {
+            name: 'Standard Payment',
+            handler: {
+                code: 'dummy-payment-handler',
+                arguments: [{ name: 'automaticSettle', value: 'false' }],
+            },
+        },
+    ],
+    roles: [
+        {
+            code: 'administrator',
+            description: 'Administrator',
+            permissions: [
+                Permission.CreateCatalog,
+                Permission.ReadCatalog,
+                Permission.UpdateCatalog,
+                Permission.DeleteCatalog,
+                Permission.CreateSettings,
+                Permission.ReadSettings,
+                Permission.UpdateSettings,
+                Permission.DeleteSettings,
+                Permission.CreateCustomer,
+                Permission.ReadCustomer,
+                Permission.UpdateCustomer,
+                Permission.DeleteCustomer,
+                Permission.CreateCustomerGroup,
+                Permission.ReadCustomerGroup,
+                Permission.UpdateCustomerGroup,
+                Permission.DeleteCustomerGroup,
+                Permission.CreateOrder,
+                Permission.ReadOrder,
+                Permission.UpdateOrder,
+                Permission.DeleteOrder,
+                Permission.CreateSystem,
+                Permission.ReadSystem,
+                Permission.UpdateSystem,
+                Permission.DeleteSystem,
+            ],
+        },
+    ],
+    defaultLanguage: LanguageCode.en,
+    countries: [
+        { name: 'Austria', code: 'AT', zone: 'Europe' },
+        { name: 'Malaysia', code: 'MY', zone: 'Asia' },
+        { name: 'United Kingdom', code: 'GB', zone: 'Europe' },
+    ],
+    defaultZone: 'Europe',
+    taxRates: [
+        { name: 'Standard Tax', percentage: 20 },
+        { name: 'Reduced Tax', percentage: 10 },
+        { name: 'Zero Tax', percentage: 0 },
+    ],
+    shippingMethods: [{ name: 'Standard Shipping', price: 500 }, { name: 'Express Shipping', price: 1000 }],
+    collections: [
+        {
+            name: 'Electronics',
+            filters: [
+                {
+                    code: 'facet-value-filter',
+                    args: { facetValueNames: ['Electronics'], containsAny: false },
+                },
+            ],
+            assetPaths: ['jakob-owens-274337-unsplash.jpg'],
+        },
+    ],
+};
+```
+
+* `paymentMethods`: Defines which payment methods are available.
+  * `name`: Name of the payment method.
+  * `handler`: Payment plugin handler information.
+* `roles`: Defines which user roles are available.
+  * `code`: Role code name.
+  * `description`: Role description.
+  * `permissions`: List of permissions to apply to the role.
+* `defaultLanguage`: Sets the language that will be used for all translatable entities created by the initial data e.g. Products, ProductVariants, Collections etc. Should correspond to the language used in your product csv file.
+* `countries`: Defines which countries are available.
+  * `name`: The name of the country in the language specified by `defaultLanguage`
+  * `code`: A standardized code for the country, e.g. [ISO 3166-1](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes)
+  * `zone`: A [Zone](/reference/typescript-api/entities/zone) to which this country belongs.
+* `defaultZone`: Sets the default shipping & tax zone for the default Channel. The zone must correspond to a value of `zone` set in the `countries` array. 
+* `taxRates`: For each item, a new [TaxCategory](/reference/typescript-api/entities/tax-category/) is created, and then a [TaxRate](/reference/typescript-api/entities/tax-rate) is created for each unique zone defined in the `countries` array. 
+* `shippingMethods`: Allows simple flat-rate [ShippingMethods](/reference/typescript-api/entities/shipping-method) to be defined.
+* `collections`: Allows Collections to be created. Currently, only collections based on facet values can be created (`code: 'facet-value-filter'`). The `assetPaths` and `facetValueNames` values must correspond to a value specified in the products csv file. The name should match the value specified in the product csv file (or can be a normalized - lower-case & hyphenated - version thereof). If there are FacetValues in multiple Facets with the same name, the facet may be specified with a colon delimiter, e.g. `brand:apple`, `flavour: apple`.
+
+## Populating The Server
+
+### The `populate()` function
+The `@vendure/core` package exposes a [`populate()` function](/reference/typescript-api/import-export/populate/) which can be used along with the data formats described above to populate your Vendure server:
+
+```ts title="src/my-populate-script.ts"
+import { bootstrap, DefaultJobQueuePlugin } from '@vendure/core';
+import { populate } from '@vendure/core/cli';
+
+import { config } from './vendure-config.ts';
+import { initialData } from './my-initial-data.ts';
+
+const productsCsvFile = path.join(__dirname, 'path/to/products.csv')
+
+const populateConfig = {
+    ...config,
+    plugins: (config.plugins || []).filter(
+        // Remove your JobQueuePlugin during populating to avoid
+        // generating lots of unnecessary jobs as the Collections get created.
+        plugin => plugin !== DefaultJobQueuePlugin,
+    ),
+}
+
+populate(
+    () => bootstrap(config),
+    initialData,
+    productsCsvFile,
+    'my-channel-token' // optional - used to assign imported 
+)                      // entities to the specified Channel
+
+    .then(app => {
+        return app.close();
+    })
+    .then(
+        () => process.exit(0),
+        err => {
+            console.log(err);
+            process.exit(1);
+        },
+    );
+```
+
+:::note
+When removing the `DefaultJobQueuePlugin` from the plugins list as in the code snippet above, one should manually rebuild the search index in order for the newly added products to appear.
+In the Admin UI, this can be done by navigating to the product list view and clicking the three icon next to the search input:
+
+![Rebuild search index](./reindex.webp)
+:::
+
+### Populating test data
+
+When installing with @vendure/create, you have the option of populating test data (products, payment methods, countries, zones, tax rates etc).
+
+This guide illustrates how to populate that test data again on an existing Vendure installation, without needing to re-install from scratch.
+
+1. `npm install --save-dev @vendure/create`. This installs the "create" package, which contains the test data we will need.
+2. drop all tables from your database, but leave the actual database there.
+3. create a script that looks like this:
+
+```ts title="src/populate-test-data.ts"
+import { populate } from '@vendure/core/cli';
+import { bootstrap, VendureConfig } from '@vendure/core';
+import { config } from './vendure-config';
+
+populate(
+    () => bootstrap({
+        ...config,
+        importExportOptions: {
+            importAssetsDir: path.join(
+                require.resolve('@vendure/create/assets/products.csv'),
+                '../images'
+            ),
+        },
+        dbConnectionOptions: {...config.dbConnectionOptions, synchronize: true}
+    }),
+    require('@vendure/create/assets/initial-data.json'),
+    require.resolve('@vendure/create/assets/products.csv')
+)
+    .then(app => app.close())
+    .catch(err => {
+        console.log(err);
+        process.exit(1);
+    });
+```
+
+Running this script will populate the database with the test data like when you first installed Vendure.
+
+### Custom populate scripts
+
+If you require more control over how your data is being imported - for example if you also need to import data into custom entities, or import customer or order information - you can create your own CLI script to do this: see [Stand-Alone CLI Scripts](/guides/advanced-topics/stand-alone-scripts).
+
+In addition to all of the services available in the [Service Layer](/reference/typescript-api/service-layer/), the following specialized import services are available:
+
+* [`ImportParser`](/reference/typescript-api/import-export/import-parser): Used to parse the CSV file into an array of objects.
+* [`FastImporterService`](/reference/typescript-api/import-export/fast-importer-service): Used to create new products & variants in bulk, optimized for speed.
+* [`Populator`](/reference/typescript-api/import-export/populator): Used to populate the initial data.
+* [`AssetImporter`](/reference/typescript-api/import-export/asset-importer): Creates new Assets in bulk, using the configured [`AssetImportStrategy`](/reference/typescript-api/import-export/asset-import-strategy).
+* [`Importer`](/reference/typescript-api/import-export/importer/): Uses all of the above services in combination - this is the basis of the `populate()` function described above.
+
+Using these specialized import services is preferable to using the normal service-layer services (`ProductService`, `ProductVariantService` etc.) for bulk imports. This is because these import services are optimized for bulk imports (they omit unnecessary checks, use optimized SQL queries) and also do not publish events when creating new entities.
+
+However, it is still possible to use the normal service-layer services if you prefer. For example, the following code snippet shows how to create a new ProductVariant using the `ProductVariantService`:
+
+## Importing from other platforms
+
+If you are migrating from another platform, you can create a custom import script to import your data into Vendure.
+
+Your existing platform may provide an API which you can use to fetch the data, or it may provide a mechanism for exporting
+the data to a file.
+
+Therefore, you have a couple of options:
+
+1. Export the data to a file, and then transform this into the Vendure CSV format for import as above.
+2. Write a script which import the data via the other platform's API, and then import this data into Vendure using the services described above, or any other of the Vendure core services.
+
+The first option is the simplest, but may not be possible if the other platform does not provide a suitable export format.
+
+The second option is more complex, but allows for more flexibility and can be used to import data from any source, as well as allowing the import of other data such as customer and order information.
+
+As an illustrative example, let's imagine we are migrating away from an imaginary commerce platform, "OldCommerce", and we want to import our data into Vendure.
+
+Luckily, OldCommerce provides a client package which allows us to easily interact with their API.
+
+:::note
+This is a much-simplified example, but it should serve to illustrate the general approach.
+:::
+
+```ts title="src/import-from-other-platform.ts"
+import { INestApplicationContext } from '@nestjs/common';
+import {
+    bootstrapWorker,
+    ConfigService,
+    Importer,
+    LanguageCode,
+    ParsedProductWithVariants,
+    RequestContext, RequestContextService,
+    TransactionalConnection, User
+} from '@vendure/core';
+import { createClient, OldCommerceProduct } from '@old-commerce/client';
+
+import { config } from './vendure-config';
+
+if (require.main === module) {
+    importData().then(
+        () => process.exit(0),
+        err => {
+            console.log(err);
+            process.exit(1);
+        },
+    );
+}
+
+async function importData() {
+    // We use the bootstrapWorker() function instead of bootstrap() because we don't 
+    // need to start the server, we just need access to the services.
+    const {app} = await bootstrapWorker(config);
+
+    // Create an instace of the client we'll be using to interact with the
+    // OldCommerce API
+    const client = createClient({
+        // OldCommerce client config
+    });
+
+    // Let's grab a reference to each of the Vendure services we'll need.
+    const importer = app.get(Importer);
+
+    // Most service methods require a RequestContext, so we'll create one here.
+    const ctx = await getSuperadminContext(app);
+
+    // Fetch all the products to import from the OldCommerce API
+    const productsToImport: OldCommerceProduct[] = await client.getAllProducts();
+
+    // Transform the OldCommerce products into the format expected by the Importer
+    const importRows: ParsedProductWithVariants[] = productsToImport.map(product => ({
+        product: {
+            translations: [
+                {
+                    languageCode: LanguageCode.en,
+                    name: product.name,
+                    slug: product.slug,
+                    description: product.description,
+                    customFields: {},
+                },
+            ],
+            assetPaths: product.images.map(image => image.sourceUrl),
+            facets: [],
+            optionGroups: product.options.map(option => ({
+                translations: [
+                    {
+                        languageCode: LanguageCode.en,
+                        name: option.name,
+                        values: option.values.map(value => value.name),
+                    },
+                ],
+            })),
+        },
+        variants: product.variations.map(variation => {
+            const optionValues = variation.options.map(option => option.value);
+            return {
+                sku: variation.productCode,
+                price: variation.price,
+                stockOnHand: variation.stock,
+                translations: [{languageCode: LanguageCode.en, optionValues}],
+            };
+        }),
+    }));
+
+    // Import the products
+    await importer.importProducts(ctx, importRows, progress => {
+        console.log(`Imported ${progress.imported} of ${importRows.length} products`);
+    });
+
+    // Close the app
+    await app.close();
+}
+
+/**
+ * Creates a RequestContext configured for the default Channel with the activeUser set
+ * as the superadmin user.
+ */
+export async function getSuperadminContext(app: INestApplicationContext): Promise<RequestContext> {
+    const {superadminCredentials} = app.get(ConfigService).authOptions;
+    const superAdminUser = await app.get(TransactionalConnection)
+        .getRepository(User)
+        .findOneOrFail({where: {identifier: superadminCredentials.identifier}});
+    return app.get(RequestContextService).create({
+        apiType: 'admin',
+        user: superAdminUser,
+    });
+}
+```

BIN
docs/docs/guides/how-to/importing-data/reindex.webp


+ 0 - 236
docs/docs/guides/how-to/importing-product-data.md

@@ -1,236 +0,0 @@
----
-title: "Importing Product Data"
-showtoc: true
----
-
-# Importing Product Data
-
-If you have hundreds, thousands or more products, inputting all the data by hand via the Admin UI can be too inefficient. To solve this, Vendure supports bulk-importing product and other data.
-
-Data import is also useful for setting up test or demo environments, and is also used by the `@vendure/testing` package for end-to-end tests.
-
-## Product Import Format
-
-Vendure uses a flat **.csv** format for importing product data. The format encodes data about:
-
-* products
-* product variants
-* product & variant assets
-* product & variant facets
-* product & variant custom fields
-
-Here's an example which defines 2 products, "Laptop" and "Clacky Keyboard". The laptop has 4 variants, and the keyboard only a single variant. 
-
-```csv
-name            , slug            , description               , assets                      , facets                              , optionGroups    , optionValues , sku         , price   , taxCategory , stockOnHand , trackInventory , variantAssets , variantFacets
-Laptop          , laptop          , "Description of laptop"   , laptop_01.jpg|laptop_02.jpg , category:electronics|brand:Apple    , screen size|RAM , 13 inch|8GB  , L2201308    , 1299.00 , standard    , 100         , false          ,               , 
-                ,                 ,                           ,                             ,                                     ,                 , 15 inch|8GB  , L2201508    , 1399.00 , standard    , 100         , false          ,               , 
-                ,                 ,                           ,                             ,                                     ,                 , 13 inch|16GB , L2201316    , 2199.00 , standard    , 100         , false          ,               , 
-                ,                 ,                           ,                             ,                                     ,                 , 15 inch|16GB , L2201516    , 2299.00 , standard    , 100         , false          ,               , 
-Clacky Keyboard , clacky-keyboard , "Description of keyboard" , keyboard_01.jpg             , category:electronics|brand:Logitech ,                 ,              , A4TKLA45535 , 74.89   , standard    , 100         , false          ,               ,
-```
-
-Here's an explanation of each column:
-
-* `name`: The name of the product. Rows with an empty "name" are interpreted as variants of the preceeding product row.
-* `slug`: The product's slug. Can be omitted, in which case will be generated from the name.
-* `description`: The product description.
-* `assets`: One or more asset file names separated by the pipe (`|`) character. The files can be located on the local file system, in which case the path is interpreted as being relative to the [`importAssetsDir`]({{< relref "/reference/typescript-api/import-export/import-export-options" >}}#importassetsdir) as defined in the VendureConfig. Files can also be urls which will be fetched from a remote http/https url. If you need more control over how assets are imported, you can implement a custom [AssetImportStrategy]({{< relref "asset-import-strategy" >}}). The first asset will be set as the featuredAsset.
-* `facets`: One or more facets to apply to the product separated by the pipe (`|`) character. A facet has the format `<facet-name>:<facet-value>`.
-* `optionGroups`: OptionGroups define what variants make up the product. Applies only to products with more than one variant. 
-* `optionValues`: For each optionGroup defined, a corresponding value must be specified for each variant. Applies only to products with more than one variant.
-* `sku`: The Stock Keeping Unit (unique product code) for this product variant.
-* `price`: The price can be either with or without taxes, depending on your channel settings (can be set later).
-* `taxCategory`: The name of an existing tax category. Tax categories can be also be imported using the InitialData object.
-* `stockOnHand`: The number of units in stock.
-* `trackInventory`: Whether this variant should have its stock level tracked, i.e. the stock level is automatically decreased for each unit ordered.
-* `variantAssets`: Same as `assets` but applied to the product variant.
-* `variantFacets`: Same as `facets` but applied to the product variant.
-
-### Importing Custom Field Data
-
-If you have [CustomFields]({{< relref "customizing-models" >}}) defined on your Product or ProductVariant entities, this data can also be encoded in the import csv:
-
-* `product:<customFieldName>`: The value of this column will populate `Product.customFields[customFieldName]`. 
-* `variant:<customFieldName>`: The value of this column will populate `ProductVariant.customFields[customFieldName]`. 
-
-{{< alert "primary" >}}
-  For a real example, see the [products.csv file used to populate the Vendure demo data](https://github.com/vendure-ecommerce/vendure/blob/master/packages/core/mock-data/data-sources/products.csv)
-{{< /alert >}}
-
-#### Importing `relation` custom fields
-
-To import custom fields with the type `relation`, the value in the CSV must be a stringified object with an `id` property:
-
-```csv
-... ,product:featuredReview
-... ,"{ ""id"": 123 }"
-```
-
-#### Importing `list` custom fields
-
-To import custom fields with `list` set to `true`, the data should be separated with a pipe (`|`) character:
-
-```csv
-... ,product:keywords
-... ,tablet|pad|android
-```
-
-#### Importing data in multiple languages
-
-If a field is translatable (i.e. of `localeString` type), you can use column names with an appended language code (e.g. `name:en`, `name:de`, `product:keywords:en`, `product:keywords:de`) to specify its value in multiple languages.
-
-Use of language codes has to be consistent throughout the file. You don't have to translate every translatable field. If there are no translated columns for a field, the generic column's value will be used for all languages. But when you do translate columns, the set of languages for each of them needs to be the same. As an example, you cannot use `name:en` and `name:de`, but only provide `slug:en` (it's okay to use only a `slug` column though, in which case this slug will be used for both the English and the German version).
-
-## Initial Data
-
-As well as product data, other initialization data can be populated using the [`InitialData` object]({{< relref "initial-data" >}}). **This format is intentionally limited**; more advanced requirements (e.g. setting up ShippingMethods that use custom checkers & calculators) should be carried out via scripts which interact with the [Admin GraphQL API]({{< relref "/reference/graphql-api/admin" >}}).
-
-```ts
-import { InitialData, LanguageCode } from '@vendure/core';
-
-export const initialData: InitialData = {
-    paymentMethods: [
-        {
-            name: 'Standard Payment',
-            handler: {
-                code: 'dummy-payment-handler',
-                arguments: [{ name: 'automaticSettle', value: 'false' }],
-            },
-        },
-    ],
-    roles: [
-        {
-            code: 'administrator',
-            description: 'Administrator',
-            permissions: [
-                Permission.CreateCatalog,
-                Permission.ReadCatalog,
-                Permission.UpdateCatalog,
-                Permission.DeleteCatalog,
-                Permission.CreateSettings,
-                Permission.ReadSettings,
-                Permission.UpdateSettings,
-                Permission.DeleteSettings,
-                Permission.CreateCustomer,
-                Permission.ReadCustomer,
-                Permission.UpdateCustomer,
-                Permission.DeleteCustomer,
-                Permission.CreateCustomerGroup,
-                Permission.ReadCustomerGroup,
-                Permission.UpdateCustomerGroup,
-                Permission.DeleteCustomerGroup,
-                Permission.CreateOrder,
-                Permission.ReadOrder,
-                Permission.UpdateOrder,
-                Permission.DeleteOrder,
-                Permission.CreateSystem,
-                Permission.ReadSystem,
-                Permission.UpdateSystem,
-                Permission.DeleteSystem,
-            ],
-        },
-    ],
-    defaultLanguage: LanguageCode.en,
-    countries: [
-        { name: 'Austria', code: 'AT', zone: 'Europe' },
-        { name: 'Malaysia', code: 'MY', zone: 'Asia' },
-        { name: 'United Kingdom', code: 'GB', zone: 'Europe' },
-    ],
-    defaultZone: 'Europe',
-    taxRates: [
-        { name: 'Standard Tax', percentage: 20 },
-        { name: 'Reduced Tax', percentage: 10 },
-        { name: 'Zero Tax', percentage: 0 },
-    ],
-    shippingMethods: [{ name: 'Standard Shipping', price: 500 }, { name: 'Express Shipping', price: 1000 }],
-    collections: [
-        {
-            name: 'Electronics',
-            filters: [
-                {
-                    code: 'facet-value-filter',
-                    args: { facetValueNames: ['Electronics'], containsAny: false },
-                },
-            ],
-            assetPaths: ['jakob-owens-274337-unsplash.jpg'],
-        },
-    ],
-};
-```
-
-* `paymentMethods`: Defines which payment methods are available.
-  * `name`: Name of the payment method.
-  * `handler`: Payment plugin handler information.
-* `roles`: Defines which user roles are available.
-  * `code`: Role code name.
-  * `description`: Role description.
-  * `permissions`: List of permissions to apply to the role.
-* `defaultLanguage`: Sets the language that will be used for all translatable entities created by the initial data e.g. Products, ProductVariants, Collections etc. Should correspond to the language used in your product csv file.
-* `countries`: Defines which countries are available.
-  * `name`: The name of the country in the language specified by `defaultLanguage`
-  * `code`: A standardized code for the country, e.g. [ISO 3166-1](https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes)
-  * `zone`: A [Zone]({{< relref "zone" >}}) to which this country belongs.
-* `defaultZone`: Sets the default shipping & tax zone for the default Channel. The zone must correspond to a value of `zone` set in the `countries` array. 
-* `taxRates`: For each item, a new [TaxCategory]({{< relref "tax-category" >}}) is created, and then a [TaxRate]({{< relref "tax-rate" >}}) is created for each unique zone defined in the `countries` array. 
-* `shippingMethods`: Allows simple flat-rate [ShippingMethods]({{< relref "shipping-method" >}}) to be defined.
-* `collections`: Allows Collections to be created. Currently, only collections based on facet values can be created (`code: 'facet-value-filter'`). The `assetPaths` and `facetValueNames` values must correspond to a value specified in the products csv file. The name should match the value specified in the product csv file (or can be a normalized - lower-case & hyphenated - version thereof). If there are FacetValues in multiple Facets with the same name, the facet may be specified with a colon delimiter, e.g. `brand:apple`, `flavour: apple`.
-
-## Populating The Server
-
-### The `populate()` function
-The `@vendure/core` package exposes a [`populate()` function]({{< relref "populate" >}}) which can be used along with the data formats described above to populate your Vendure server:
-
-```ts
-// populate-server.ts
-import { bootstrap, DefaultJobQueuePlugin } from '@vendure/core';
-import { populate } from '@vendure/core/cli';
-
-import { config } from './vendure-config.ts';
-import { initialData } from './my-initial-data.ts';
-
-const productsCsvFile = path.join(__dirname, 'path/to/products.csv')
-
-const populateConfig = {
-  ...config,
-  plugins: (config.plugins || []).filter(
-    // Remove your JobQueuePlugin during populating to avoid
-    // generating lots of unnecessary jobs as the Collections get created.
-    plugin => plugin !== DefaultJobQueuePlugin,
-  ),
-}
-
-populate(
-  () => bootstrap(config),
-  initialData,
-  productsCsvFile,
-  'my-channel-token' // optional - used to assign imported 
-)                    // entities to the specified Channel
-
-.then(app => {
-  return app.close();
-})
-.then(
-  () => process.exit(0),
-  err => {
-    console.log(err);
-    process.exit(1);
-  },
-);
-```
-**Attention:** When removing the `DefaultJobQueuePlugin` from the plugins list as in the code snippet above, one should manually rebuild the search index in order for the newly added products to appear.
-In the Admin UI, this can be done by navigating to the Products page and clicking the gear icon next to the search input.
-
-### Custom populate scripts
-
-If you require more control over how your data is being imported - for example if you also need to import data into custom entities - you can create your own CLI script to do this: see [Stand-Alone CLI Scripts]({{< relref "stand-alone-scripts" >}}).
-
-In your script you can make use of the internal parse and import services:
-
-* [Importer]({{< relref "importer" >}})
-* [ImportParser]({{< relref "import-parser" >}})
-* [FastImporterService]({{< relref "fast-importer-service" >}})
-* [AssetImporter]({{< relref "asset-importer" >}})
-* [Populator]({{< relref "populator" >}})
-
-Using these specialized import services is preferable to using the normal service-layer services (ProductService, ProductVariantService etc.) for bulk imports. This is because these import services are optimized for bulk imports (they omit unnecessary checks, use optimized SQL queries) and also do not publish events when creating new entities.