diff --git a/www/apps/book/app/advanced-development/custom-cli-scripts/seed-data/page.mdx b/www/apps/book/app/advanced-development/custom-cli-scripts/seed-data/page.mdx new file mode 100644 index 0000000000..7e33c10d7d --- /dev/null +++ b/www/apps/book/app/advanced-development/custom-cli-scripts/seed-data/page.mdx @@ -0,0 +1,200 @@ +export const metadata = { + title: `${pageNumber} Seed Data with Custom CLI Script`, +} + +# {metadata.title} + +In this chapter, you'll learn how to seed data using a custom CLI script. + +## How to Seed Data + +To seed dummy data for development or demo purposes, use a custom CLI script. + +In the CLI script, use your custom workflows or Medusa's existing workflows, which you can browse in [this reference](!resources!/medusa-workflows-reference), to seed data. + +### Example: Seed Dummy Products + +In this section, you'll follow an example of creating a custom CLI script that seeds fifty dummy products. + +First, install the [Faker](https://fakerjs.dev/) library to generate random data in your script: + +```bash npm2yarn +npm install --save-dev @faker-js/faker +``` + +Then, create the file `src/scripts/demo-products.ts` with the following content: + +export const highlights = [ + ["16", "salesChannelModuleService", "Resolve the Sales Chanel Module's main service"], + ["19", "logger", "Resolve the logger to log messages in the terminal."], + ["22", "query", "Resolve Query to retrieve data later."], + ["26", "defaultSalesChannel", "Retrieve the default sales channel to associate products with."], + ["31", "sizeOptions", "Declare the size options to be used in the products' variants."], + ["32", "colorOptions", "Declare the color options to be used in the products' variants."], + ["33", "currency_code", "Declare the currency code to use in products' prices."], + ["34", "productsNum", "The number of products to seed."] +] + +```ts title="src/scripts/demo-products.ts" highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports" +import { ExecArgs } from "@medusajs/framework/types" +import { faker } from "@faker-js/faker" +import { + ContainerRegistrationKeys, + Modules, + ProductStatus, +} from "@medusajs/framework/utils" +import { + createInventoryLevelsWorkflow, + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" + +export default async function seedDummyProducts({ + container, +}: ExecArgs) { + const salesChannelModuleService = container.resolve( + Modules.SALES_CHANNEL + ) + const logger = container.resolve( + ContainerRegistrationKeys.LOGGER + ) + const query = container.resolve( + ContainerRegistrationKeys.QUERY + ) + + const defaultSalesChannel = await salesChannelModuleService + .listSalesChannels({ + name: "Default Sales Channel", + }) + + const sizeOptions = ["S", "M", "L", "XL"] + const colorOptions = ["Black", "White"] + const currency_code = "eur" + const productsNum = 50 + + // TODO seed products +} +``` + +So far, in the script, you: + +- Resolve the Sales Channel Module's main service to retrieve the application's default sales channel. This is the sales channel the dummy products will be available in. +- Resolve the Logger to log messages in the terminal, and Query to later retrieve data useful for the seeded products. +- Initialize some default data to use when seeding the products next. + +Next, replace the `TODO` with the following: + +```ts title="src/scripts/demo-products.ts" +const productsData = new Array(productsNum).fill(0).map((_, index) => { + const title = faker.commerce.product() + "_" + index + return { + title, + is_giftcard: true, + description: faker.commerce.productDescription(), + status: ProductStatus.PUBLISHED, + options: [ + { + title: "Size", + values: sizeOptions, + }, + { + title: "Color", + values: colorOptions, + }, + ], + images: [ + { + url: faker.image.urlPlaceholder({ + text: title, + }), + }, + { + url: faker.image.urlPlaceholder({ + text: title, + }), + }, + ], + variants: new Array(10).fill(0).map((_, variantIndex) => ({ + title: `${title} ${variantIndex}`, + sku: `variant-${variantIndex}${index}`, + prices: new Array(10).fill(0).map((_, priceIndex) => ({ + currency_code, + amount: 10 * priceIndex, + })), + options: { + Size: sizeOptions[Math.floor(Math.random() * 3)], + }, + })), + sales_channels: [ + { + id: defaultSalesChannel[0].id, + }, + ], + } +}) + +// TODO seed products +``` + +You generate fifty products using the sales channel and variables you initialized, and using Faker for random data, such as the product's title or images. + +Then, replace the new `TODO` with the following: + +```ts title="src/scripts/demo-products.ts" +const { result: products } = await createProductsWorkflow(container).run({ + input: { + products: productsData, + }, +}) + +logger.info(`Seeded ${products.length} products.`) + +// TODO add inventory levels +``` + +You create the generated products using the `createProductsWorkflow` imported previously from `@medusajs/medusa/core-flows`. It accepts the product data as input, and returns the created products. + +Only thing left is to create inventory levels for the products. So, replace the last `TODO` with the following: + +```ts title="src/scripts/demo-products.ts" +logger.info("Seeding inventory levels.") + +const { data: stockLocations } = await query.graph({ + entity: "stock_location", + fields: ["id"], +}) + +const { data: inventoryItems } = await query.graph({ + entity: "inventory_item", + fields: ["id"], +}) + +const inventoryLevels = inventoryItems.map((inventoryItem) => ({ + location_id: stockLocations[0].id, + stocked_quantity: 1000000, + inventory_item_id: inventoryItem.id, +})) + +await createInventoryLevelsWorkflow(container).run({ + input: { + inventory_levels: inventoryLevels, + }, +}) + +logger.info("Finished seeding inventory levels data.") +``` + +You use Query to retrieve the stock location, to use the first location in the application, and the inventory items. + +Then, you generate inventory levels for each inventory item, associating it with the first stock location. + +Finally, you use the `createInventoryLevelsWorkflow` imported from `@medusajs/medusa/core-flows` to create the inventory levels. + +### Test Script + +To test out the script, run the following command in your project's directory: + +```bash +npx medusa exec ./src/scripts/demo-products.ts +``` + +This seeds the products to your database. If you run your Medusa application and view the products in the dashboard, you'll find fifty new products. diff --git a/www/apps/book/app/advanced-development/module-links/custom-columns/page.mdx b/www/apps/book/app/advanced-development/module-links/custom-columns/page.mdx index 802f20b3a3..fd9a12f5f7 100644 --- a/www/apps/book/app/advanced-development/module-links/custom-columns/page.mdx +++ b/www/apps/book/app/advanced-development/module-links/custom-columns/page.mdx @@ -31,9 +31,9 @@ export default defineLink( extraColumns: { metadata: { type: "json", - } - } - } + }, + }, + }, } ) ``` @@ -94,16 +94,16 @@ Learn more about the remote link, how to resolve it, and its methods in [this ch ```ts await remoteLink.create({ [Modules.PRODUCT]: { - product_id: "123" + product_id: "123", }, HELLO_MODULE: { - my_custom_id: "321" + my_custom_id: "321", }, data: { metadata: { - test: true - } - } + test: true, + }, + }, }) ``` @@ -138,8 +138,8 @@ const { data } = await query.graph({ entity: productHelloLink.entryPoint, fields: ["metadata", "product.*", "my_custom.*"], filters: { - product_id: "prod_123" - } + product_id: "prod_123", + }, }) ``` @@ -160,15 +160,15 @@ For example: ```ts await remoteLink.create({ [Modules.PRODUCT]: { - product_id: "123" + product_id: "123", }, HELLO_MODULE: { - my_custom_id: "321" + my_custom_id: "321", }, data: { metadata: { - test: false - } - } + test: false, + }, + }, }) ``` diff --git a/www/apps/book/app/advanced-development/modules/db-operations/page.mdx b/www/apps/book/app/advanced-development/modules/db-operations/page.mdx index acb89f4d1e..05be717931 100644 --- a/www/apps/book/app/advanced-development/modules/db-operations/page.mdx +++ b/www/apps/book/app/advanced-development/modules/db-operations/page.mdx @@ -36,7 +36,7 @@ export const methodsHighlight = [ // other imports... import { InjectManager, - MedusaContext + MedusaContext, } from "@medusajs/framework/utils" class HelloModuleService { @@ -99,7 +99,7 @@ export const opHighlights = [ import { InjectManager, InjectTransactionManager, - MedusaContext + MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" import { EntityManager } from "@mikro-orm/knex" @@ -118,10 +118,10 @@ class HelloModuleService { await transactionManager.nativeUpdate( "my_custom", { - id: input.id + id: input.id, }, { - name: input.name + name: input.name, } ) @@ -307,7 +307,7 @@ export const repoHighlights = [ import { InjectManager, InjectTransactionManager, - MedusaContext + MedusaContext, } from "@medusajs/framework/utils" import { Context } from "@medusajs/framework/types" import { EntityManager } from "@mikro-orm/knex" @@ -327,10 +327,10 @@ class HelloModuleService { await transactionManager.nativeUpdate( "my_custom", { - id: input.id + id: input.id, }, { - name: input.name + name: input.name, } ) @@ -342,7 +342,7 @@ class HelloModuleService { return updatedRecord }, { - transaction: sharedContext.transactionManager + transaction: sharedContext.transactionManager, } ) } @@ -397,7 +397,7 @@ class HelloModuleService { // ... }, { - transaction: sharedContext.transactionManager + transaction: sharedContext.transactionManager, } ) } @@ -430,7 +430,7 @@ class HelloModuleService { // ... }, { - isolationLevel: IsolationLevel.READ_COMMITTED + isolationLevel: IsolationLevel.READ_COMMITTED, } ) } @@ -456,7 +456,7 @@ class HelloModuleService { // ... }, { - enableNestedTransactions: false + enableNestedTransactions: false, } ) } diff --git a/www/apps/book/app/more-resources/examples/page.mdx b/www/apps/book/app/more-resources/examples/page.mdx index 1adc21afcc..651b03d951 100644 --- a/www/apps/book/app/more-resources/examples/page.mdx +++ b/www/apps/book/app/more-resources/examples/page.mdx @@ -66,6 +66,12 @@ This chapter provides links to example sections on different Medusa topics. --- +## Custom CLI Scripts + +- [Seed Dummy Products](../../advanced-development/custom-cli-scripts/seed-data/page.mdx) + +--- + ## Admin Customizations - [Send a request to custom API routes from widgets or UI routes](../../customization/customize-admin/widget/page.mdx) diff --git a/www/apps/book/generated/edit-dates.mjs b/www/apps/book/generated/edit-dates.mjs index 7cfe9d1cd3..cc6f0fc170 100644 --- a/www/apps/book/generated/edit-dates.mjs +++ b/www/apps/book/generated/edit-dates.mjs @@ -108,9 +108,10 @@ export const generatedEditDates = { "app/customization/next-steps/page.mdx": "2024-09-12T10:50:04.873Z", "app/customization/page.mdx": "2024-09-12T11:16:18.504Z", "app/more-resources/cheatsheet/page.mdx": "2024-07-11T16:11:26.480Z", - "app/more-resources/examples/page.mdx": "2024-09-27T07:17:16.892Z", + "app/more-resources/examples/page.mdx": "2024-10-03T11:12:50.956Z", "app/architecture/architectural-modules/page.mdx": "2024-09-23T12:51:04.520Z", "app/architecture/overview/page.mdx": "2024-09-23T12:55:01.339Z", "app/advanced-development/data-models/infer-type/page.mdx": "2024-09-30T08:43:53.123Z", + "app/advanced-development/custom-cli-scripts/seed-data/page.mdx": "2024-10-03T11:11:07.181Z", "app/basics/modules/page.mdx": "2024-10-03T13:05:49.551Z" } \ No newline at end of file diff --git a/www/apps/book/sidebar.mjs b/www/apps/book/sidebar.mjs index be38646972..814073317a 100644 --- a/www/apps/book/sidebar.mjs +++ b/www/apps/book/sidebar.mjs @@ -472,6 +472,13 @@ export const sidebar = numberSidebarItems( type: "link", path: "/advanced-development/custom-cli-scripts", title: "Custom CLI Scripts", + children: [ + { + type: "link", + path: "/advanced-development/custom-cli-scripts/seed-data", + title: "Seed Data", + }, + ], }, { type: "link",