populate.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { INestApplicationContext } from '@nestjs/common';
  2. import fs from 'fs-extra';
  3. import path from 'path';
  4. const loggerCtx = 'Populate';
  5. // tslint:disable:no-console
  6. /**
  7. * @description
  8. * Populates the Vendure server with some initial data and (optionally) product data from
  9. * a supplied CSV file. The format of the CSV file is described in the section
  10. * [Importing Product Data](/docs/developer-guide/importing-product-data).
  11. *
  12. * If the `channelOrToken` argument is provided, all ChannelAware entities (Products, ProductVariants,
  13. * Assets, ShippingMethods, PaymentMethods etc.) will be assigned to the specified Channel.
  14. * The argument can be either a Channel object or a valid channel `token`.
  15. *
  16. * Internally the `populate()` function does the following:
  17. *
  18. * 1. Uses the {@link Populator} to populate the {@link InitialData}.
  19. * 2. If `productsCsvPath` is provided, uses {@link Importer} to populate Product data.
  20. * 3. Uses {@Populator} to populate collections specified in the {@link InitialData}.
  21. *
  22. * @example
  23. * ```TypeScript
  24. * import { bootstrap } from '\@vendure/core';
  25. * import { populate } from '\@vendure/core/cli';
  26. * import { config } from './vendure-config.ts'
  27. * import { initialData } from './my-initial-data.ts';
  28. *
  29. * const productsCsvFile = path.join(__dirname, 'path/to/products.csv')
  30. *
  31. * populate(
  32. * () => bootstrap(config),
  33. * initialData,
  34. * productsCsvFile,
  35. * )
  36. * .then(app => app.close())
  37. * .then(
  38. * () => process.exit(0),
  39. * err => {
  40. * console.log(err);
  41. * process.exit(1);
  42. * },
  43. * );
  44. * ```
  45. *
  46. * @docsCategory import-export
  47. */
  48. export async function populate<T extends INestApplicationContext>(
  49. bootstrapFn: () => Promise<T | undefined>,
  50. initialDataPathOrObject: string | object,
  51. productsCsvPath?: string,
  52. channelOrToken?: string | import('@vendure/core').Channel,
  53. ): Promise<T> {
  54. const app = await bootstrapFn();
  55. if (!app) {
  56. throw new Error('Could not bootstrap the Vendure app');
  57. }
  58. let channel: import('@vendure/core').Channel | undefined;
  59. const { ChannelService, Channel, Logger } = await import('@vendure/core');
  60. if (typeof channelOrToken === 'string') {
  61. channel = await app.get(ChannelService).getChannelFromToken(channelOrToken);
  62. if (!channel) {
  63. Logger.warn(
  64. `Warning: channel with token "${channelOrToken}" was not found. Using default Channel instead.`,
  65. loggerCtx,
  66. );
  67. }
  68. } else if (channelOrToken instanceof Channel) {
  69. channel = channelOrToken;
  70. }
  71. const initialData: import('@vendure/core').InitialData =
  72. typeof initialDataPathOrObject === 'string'
  73. ? require(initialDataPathOrObject)
  74. : initialDataPathOrObject;
  75. await populateInitialData(app, initialData, channel);
  76. if (productsCsvPath) {
  77. const importResult = await importProductsFromCsv(
  78. app,
  79. productsCsvPath,
  80. initialData.defaultLanguage,
  81. channel,
  82. );
  83. if (importResult.errors && importResult.errors.length) {
  84. const errorFile = path.join(process.cwd(), 'vendure-import-error.log');
  85. Logger.error(
  86. `${importResult.errors.length} errors encountered when importing product data. See: ${errorFile}`,
  87. loggerCtx,
  88. );
  89. await fs.writeFile(errorFile, importResult.errors.join('\n'));
  90. }
  91. Logger.info(`Imported ${importResult.imported} products`, loggerCtx);
  92. await populateCollections(app, initialData, channel);
  93. }
  94. Logger.info('Done!', loggerCtx);
  95. return app;
  96. }
  97. export async function populateInitialData(
  98. app: INestApplicationContext,
  99. initialData: import('@vendure/core').InitialData,
  100. channel?: import('@vendure/core').Channel,
  101. ) {
  102. const { Populator, Logger } = await import('@vendure/core');
  103. const populator = app.get(Populator);
  104. try {
  105. await populator.populateInitialData(initialData, channel);
  106. Logger.info(`Populated initial data`, loggerCtx);
  107. } catch (err) {
  108. Logger.error(err.message, loggerCtx);
  109. }
  110. }
  111. export async function populateCollections(
  112. app: INestApplicationContext,
  113. initialData: import('@vendure/core').InitialData,
  114. channel?: import('@vendure/core').Channel,
  115. ) {
  116. const { Populator, Logger } = await import('@vendure/core');
  117. const populator = app.get(Populator);
  118. try {
  119. if (initialData.collections.length) {
  120. await populator.populateCollections(initialData, channel);
  121. Logger.info(`Created ${initialData.collections.length} Collections`, loggerCtx);
  122. }
  123. } catch (err) {
  124. Logger.info(err.message, loggerCtx);
  125. }
  126. }
  127. export async function importProductsFromCsv(
  128. app: INestApplicationContext,
  129. productsCsvPath: string,
  130. languageCode: import('@vendure/core').LanguageCode,
  131. channel?: import('@vendure/core').Channel,
  132. ): Promise<import('@vendure/core').ImportProgress> {
  133. const { Importer, RequestContextService } = await import('@vendure/core');
  134. const importer = app.get(Importer);
  135. const requestContextService = app.get(RequestContextService);
  136. const productData = await fs.readFile(productsCsvPath, 'utf-8');
  137. const ctx = await requestContextService.create({
  138. apiType: 'admin',
  139. languageCode,
  140. channelOrToken: channel,
  141. });
  142. return importer.parseAndImport(productData, ctx, true).toPromise();
  143. }