populate.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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. console.log(
  86. `${importResult.errors.length} errors encountered when importing product data. See: ${errorFile}`,
  87. );
  88. await fs.writeFile(errorFile, importResult.errors.join('\n'));
  89. }
  90. Logger.info(`Imported ${importResult.imported} products`, loggerCtx);
  91. await populateCollections(app, initialData);
  92. }
  93. Logger.info('Done!', loggerCtx);
  94. return app;
  95. }
  96. export async function populateInitialData(
  97. app: INestApplicationContext,
  98. initialData: import('@vendure/core').InitialData,
  99. channel?: import('@vendure/core').Channel,
  100. ) {
  101. const { Populator, Logger } = await import('@vendure/core');
  102. const populator = app.get(Populator);
  103. try {
  104. await populator.populateInitialData(initialData, channel);
  105. Logger.info(`Populated initial data`, loggerCtx);
  106. } catch (err) {
  107. Logger.error(err.message, loggerCtx);
  108. }
  109. }
  110. export async function populateCollections(
  111. app: INestApplicationContext,
  112. initialData: import('@vendure/core').InitialData,
  113. channel?: import('@vendure/core').Channel,
  114. ) {
  115. const { Populator, Logger } = await import('@vendure/core');
  116. const populator = app.get(Populator);
  117. try {
  118. if (initialData.collections.length) {
  119. await populator.populateCollections(initialData, channel);
  120. Logger.info(`Created ${initialData.collections.length} Collections`, loggerCtx);
  121. }
  122. } catch (err) {
  123. Logger.info(err.message, loggerCtx);
  124. }
  125. }
  126. export async function importProductsFromCsv(
  127. app: INestApplicationContext,
  128. productsCsvPath: string,
  129. languageCode: import('@vendure/core').LanguageCode,
  130. channel?: import('@vendure/core').Channel,
  131. ): Promise<import('@vendure/core').ImportProgress> {
  132. const { Importer, RequestContextService } = await import('@vendure/core');
  133. const importer = app.get(Importer);
  134. const requestContextService = app.get(RequestContextService);
  135. const productData = await fs.readFile(productsCsvPath, 'utf-8');
  136. const ctx = await requestContextService.create({
  137. apiType: 'admin',
  138. languageCode,
  139. channelOrToken: channel,
  140. });
  141. return importer.parseAndImport(productData, ctx, true).toPromise();
  142. }