@haus-tech/product-import-export-plugin 3.6.12 → 3.6.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +110 -10
  3. package/package.json +1 -1
  4. package/src/constants.d.ts +1 -0
  5. package/src/constants.js +2 -1
  6. package/src/constants.js.map +1 -1
  7. package/src/dashboard/export-configuration-panel.tsx +1 -0
  8. package/src/dashboard/utils.ts +1 -0
  9. package/src/helpers/find-text-column.d.ts +8 -0
  10. package/src/helpers/find-text-column.js +32 -0
  11. package/src/helpers/find-text-column.js.map +1 -0
  12. package/src/index.d.ts +1 -0
  13. package/src/index.js +1 -0
  14. package/src/index.js.map +1 -1
  15. package/src/product-import-export.plugin.d.ts +11 -1
  16. package/src/product-import-export.plugin.js +35 -3
  17. package/src/product-import-export.plugin.js.map +1 -1
  18. package/src/providers/import-providers/import-parser.d.ts +9 -0
  19. package/src/providers/import-providers/import-parser.js +26 -8
  20. package/src/providers/import-providers/import-parser.js.map +1 -1
  21. package/src/providers/import-providers/product-importer.d.ts +58 -10
  22. package/src/providers/import-providers/product-importer.js +163 -94
  23. package/src/providers/import-providers/product-importer.js.map +1 -1
  24. package/src/services/channel-column-resolver.d.ts +24 -0
  25. package/src/services/channel-column-resolver.js +79 -0
  26. package/src/services/channel-column-resolver.js.map +1 -0
  27. package/src/services/export-relation-lookups.d.ts +26 -0
  28. package/src/services/export-relation-lookups.js +56 -0
  29. package/src/services/export-relation-lookups.js.map +1 -0
  30. package/src/services/extended-fast-importer.service.d.ts +16 -0
  31. package/src/services/extended-fast-importer.service.js +46 -21
  32. package/src/services/extended-fast-importer.service.js.map +1 -1
  33. package/src/services/import-row-groups.d.ts +17 -0
  34. package/src/services/import-row-groups.js +69 -0
  35. package/src/services/import-row-groups.js.map +1 -0
  36. package/src/services/product-export.service.d.ts +9 -3
  37. package/src/services/product-export.service.js +57 -10
  38. package/src/services/product-export.service.js.map +1 -1
  39. package/src/services/product-import.service.d.ts +1 -3
  40. package/src/services/product-import.service.js +10 -9
  41. package/src/services/product-import.service.js.map +1 -1
  42. package/src/services/relation-lookup-resolver.d.ts +52 -0
  43. package/src/services/relation-lookup-resolver.js +245 -0
  44. package/src/services/relation-lookup-resolver.js.map +1 -0
  45. package/src/services/self-relation-custom-fields.d.ts +7 -0
  46. package/src/services/self-relation-custom-fields.js +24 -0
  47. package/src/services/self-relation-custom-fields.js.map +1 -1
  48. package/src/strategies/import-row-validation-strategy.d.ts +71 -0
  49. package/src/strategies/import-row-validation-strategy.js +16 -0
  50. package/src/strategies/import-row-validation-strategy.js.map +1 -0
  51. package/src/types.d.ts +21 -1
  52. package/src/ui/components/export-dialog.component.ts +2 -0
  53. package/src/ui/components/product-import.component.ts +1 -0
  54. package/src/services/sku-relation-resolver.d.ts +0 -33
  55. package/src/services/sku-relation-resolver.js +0 -177
  56. package/src/services/sku-relation-resolver.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 3.6.14
2
+
3
+ ### 🚀 Features
4
+
5
+ - **product-import-export-plugin:** export relation custom fields by a lookup column ([#62](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/pull/62))
6
+ - **product-import-export-plugin:** assign imported products to channels with a channels column ([#64](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/pull/64), [#63](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/issues/63))
7
+
8
+ ### 🩹 Fixes
9
+
10
+ - **product-import-export-plugin:** import the asset sources the export writes ([#65](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/pull/65))
11
+
12
+ ## 3.6.13
13
+
14
+ ### 🚀 Features
15
+
16
+ - **product-import-export-plugin:** link relations by any column and validate import rows ([#59](https://github.com/WeAreHausTech/haus-tech-vendure-plugins-public/pull/59))
17
+
1
18
  ## 3.6.12
2
19
 
3
20
  ### 🩹 Fixes
package/README.md CHANGED
@@ -72,6 +72,7 @@ export const config = {
72
72
  | `storageStrategy` | `ImportJobStorageStrategy` | local disk | Strategy instance for queued import file storage and retrieval. |
73
73
  | `storageStrategyFactory` | `(injector: Injector) => ImportJobStorageStrategy` | `undefined` | Factory for DI-aware strategy creation (recommended for S3/remote storage). |
74
74
  | `importJobStorage` | `'local' \| 's3'` | `'local'` | Deprecated compatibility flag. Prefer `storageStrategy` / `storageStrategyFactory`. |
75
+ | `rowValidationStrategy` | `ImportRowValidationStrategy` | accepts all | Checks the rows of a file before anything is written. See [Row validation](#row-validation). |
75
76
 
76
77
  Available import storage strategies:
77
78
 
@@ -88,6 +89,7 @@ Available import storage strategies:
88
89
  | `defaultExportFields` | `string[]` | See above | Fields that will be pre-selected in the export UI |
89
90
  | `requiredExportFields` | `string[]` | `['name', 'sku']` | Fields that must always be included |
90
91
  | `customExportColumns` | `CustomExportColumn[]`| `undefined` | Extra CSV columns resolved per variant row. See [Custom export columns](#custom-export-columns) below. |
92
+ | `relationLookups` | `Record<string, string>` | `undefined` | Relation custom fields written by a column of the related row instead of its id. See [Exporting relations by a column](#exporting-relations-by-a-column) below. |
91
93
  | `storageStrategy` | `'s3' \| 'disk'` | `'disk'` | Where exported files are stored |
92
94
  | `s3Options` | `object` | `undefined` | S3 configuration (required when `storageStrategy` is `'s3'`) |
93
95
 
@@ -98,6 +100,60 @@ The plugin supports configurable storage for exported files:
98
100
  - **S3** – Stores export files in an S3-compatible bucket
99
101
  - **Disk (default)** – Saves files locally on the server
100
102
 
103
+ ### Row validation
104
+
105
+ `importOptions.rowValidationStrategy` lets you reject rows by your own rules. It runs on the parsed file twice:
106
+
107
+ 1. At upload, before the file is stored. If it returns errors, the upload answers `400` with `{ "errors": [...] }` and nothing is queued.
108
+ 2. When the import job starts, before anything is written, since data can change while the job waits. If it returns errors, the job fails with the same list and nothing is imported.
109
+
110
+ Each error is shown as `Row <line>: <message>`, in one list with the [relation lookup](#relations-by-sku-or-another-column) errors, ordered by row. Line 1 is the header; blank and comma-only lines are not counted. Numbers count CSV records, so a quoted cell that spans several lines is still one row.
111
+
112
+ A job that fails this way ends in the `FAILED` state with the list as its error, instead of `COMPLETED` with the errors in its result. The same holds when a relation lookup no longer matches when the job runs.
113
+
114
+ ```typescript
115
+ import {
116
+ ImportRowError,
117
+ ImportRowGroup,
118
+ ImportRowValidationStrategy,
119
+ } from '@haus-tech/product-import-export-plugin'
120
+ import { Injector, RequestContext, TransactionalConnection } from '@vendure/core'
121
+
122
+ class NoLabelOnSampleStrategy implements ImportRowValidationStrategy {
123
+ private connection!: TransactionalConnection
124
+
125
+ init(injector: Injector) {
126
+ this.connection = injector.get(TransactionalConnection)
127
+ }
128
+
129
+ async validate(ctx: RequestContext, rows: ImportRowGroup[]): Promise<ImportRowError[]> {
130
+ return rows
131
+ .filter((row) => row.product.customFields.labelId !== undefined)
132
+ .filter((row) => row.variants.some(({ variant }) => variant.sku.startsWith('SAMPLE-')))
133
+ .map((row) => ({ line: row.line, message: 'a sample product cannot have a label' }))
134
+ }
135
+ }
136
+
137
+ ProductImportExportPlugin.init({
138
+ importOptions: { rowValidationStrategy: new NoLabelOnSampleStrategy() },
139
+ exportOptions: {},
140
+ })
141
+ ```
142
+
143
+ Each `ImportRowGroup` is one product: its first row (`line`, `product`) and every variant row under it (`variants[].line`, `variants[].variant`).
144
+
145
+ - Relation custom fields are keyed by their Admin API input name (`labelId`, `labelsIds`) and hold id strings, also when the file names the related rows by a lookup column such as `:sku` or `:code`. An empty cell, and a value a lookup could not resolve, is left out.
146
+ - An `Asset` relation keeps its cell text under the field name, since assets are created only when the row is written. Other custom fields keep their cell text under the field name.
147
+ - `product.productId` is set when the product's first row has a product id in the first column, that is when the row updates an existing product by id. Without it, the importer matches existing products by variant SKU.
148
+ - `product.customFields` holds the custom fields of the product's first translation, which include every non-localised field; each entry of `product.translations` has its own.
149
+ - The groups are copies: changing them does not change what is imported.
150
+
151
+ `init(injector)` and `destroy()` are called when the application starts and stops. The default strategy accepts every row.
152
+
153
+ If `validate` throws, the upload answers `500` and nothing is queued, and a running job fails with the thrown error. Return errors instead to show them by row.
154
+
155
+ Lookup columns must be plain text columns of the related entity, so a translated field (for example a collection's `name`, which lives on its translation entity) cannot be one. The strategy does not run when a lookup header is wrong, since no row is read then.
156
+
101
157
  ### Custom export columns
102
158
 
103
159
  Add columns to the exported CSV whose value is computed per variant row, for example a permalink built from the product slug and variant SKU.
@@ -143,7 +199,8 @@ export const config = {
143
199
  | Column | Format | Example |
144
200
  | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
145
201
  | `name`, `slug`, `description` | Text, or `name:en`, `name:sv` for translations | `My Product` |
146
- | `assets` | Pipe-separated URLs, or JSON `[{"url":"...","name":"..."}]` | `https://example.com/1.jpg\|https://example.com/2.jpg` |
202
+ | `assets` | Pipe-separated URLs or existing asset sources, or JSON `[{"id":1,"url":"...","name":"..."}]` | `https://example.com/1.jpg\|source/b6/2.jpg` |
203
+ | `channels` | Channel tokens, pipe-separated (see [Assigning products to channels](#assigning-products-to-channels)) | `club-a\|club-b` |
147
204
  | `facets` | `facet:value` pairs, pipe-separated | `Brand:Acme\|Category:Electronics` |
148
205
  | `optionGroups` | Pipe-separated group names. Use `name:code` to share a group across products (see [Sharing option groups](#sharing-option-groups)) | `Size\|Color` or `size:shoe-size` |
149
206
  | `optionValues` | Pipe-separated values (order matches option groups) | `Large\|Blue` |
@@ -162,21 +219,63 @@ export const config = {
162
219
  - If a column exists in the header but the cell is empty, that field is explicitly cleared.
163
220
  - For products with more than one variant, `optionGroups` and `optionValues` are required.
164
221
  - The header is checked before the upload is queued. A custom field column the shop does not have, a missing required column or mixed translated and untranslated columns make the upload answer `400` with `{ "errors": ["Invalid custom field: product:clubName"] }`, the Dashboard lists the errors, and nothing is imported.
165
- - Product data (`slug`, `description`, `assets`, `facets`, `optionGroups` and `product:` custom fields) is read from a product's first row, the row with a `name`. If a later variant row of the product fills one of these columns, that product is not imported and the import reports the column and line. Other products in the file still import. `id` may repeat on every row.
222
+ - Product data (`slug`, `description`, `assets`, `channels`, `facets`, `optionGroups` and `product:` custom fields) is read from a product's first row, the row with a `name`. If a later variant row of the product fills one of these columns, that product is not imported and the import reports the column and line. Other products in the file still import. `id` may repeat on every row.
223
+ - An asset value that is not a URL names an existing asset the way the export writes it: by the JSON `id`, otherwise by its `source`. It is linked without being uploaded again, so an unchanged export imports with every image kept, also with **replace**. A value that matches no asset is reported as an error. A URL is downloaded unless an asset with the same file name exists.
224
+
225
+ #### Assigning products to channels
226
+
227
+ A product is always assigned to the default channel and to the channel the import runs in. The `channels` column assigns it to more channels, named by token, so one file run from the default channel can fill several channels:
166
228
 
167
- #### Relations to variants by SKU
229
+ ```csv
230
+ name,sku,price,channels
231
+ Club Tee,CLUB-TEE-M,299,club-a
232
+ Shared Cap,CAP-1,199,club-a|club-b
233
+ ```
234
+
235
+ - The product and its variants are assigned, each variant with a price row in each channel's currency, and so are the option groups and facets the row writes. A shared option group (`name:code`) keeps the channels it has; Vendure reads a product's option groups whatever their channels. A variant added to the product later in the file gets the same channels.
236
+ - The column only adds. An empty cell or a file without the column leaves a product in the channels it is in; remove a product from a channel in the Admin UI or Dashboard.
237
+ - The upload is checked before it is queued, and the job checks again before its first write. Any problem answers `400` with the rows and nothing is imported:
238
+ - `Row 3: channels: no channel with token 'club-x'`
239
+ - `Row 2: channels: no right to update products in channel 'club-b'`: the uploader needs `UpdateCatalog` or `UpdateProduct` in every channel the file names.
240
+ - `Row 2: channels: an import run in channel 'club-a' can only name 'club-a', not 'club-b'`: outside the default channel, a file can only name the channel it runs in.
241
+ - Server code that calls `ProductImporter` with a context without a user is trusted, so it is not checked for rights; the token and channel rules still apply.
242
+ - The export writes the column (select `channels`): from the default channel the product's other channels, by channel id; from another channel only that channel's token, so a club's file never shows which other channels sell the product. An exported file imports again.
168
243
 
169
- A relation custom field to `ProductVariant` can name the related variant by SKU instead of id. Add `:sku` to the column header after the type:
244
+ #### Relations by SKU or another column
245
+
246
+ A relation custom field can name the related row by one of its columns instead of by id. Add the column as a fourth segment to the header, after the type (the related entity's class name in lower case):
170
247
 
171
248
  ```csv
172
- name,sku,price,variant:baseVariant:productvariant:sku
173
- Club Tee,CLUB-TEE-M,299,BASE-TEE-M
249
+ name,sku,price,variant:baseVariant:productvariant:sku,product:label:productlabel:code
250
+ Club Tee,CLUB-TEE-M,299,BASE-TEE-M,ECO
251
+ ```
252
+
253
+ - The column must be a text column of the related entity (not a relation or custom field). Otherwise the upload answers `400` with `{ "errors": ["Invalid lookup column \"title\" on ProductLabel"] }`.
254
+ - Each value must match exactly one row of the related entity, in any channel. Rows with a `deletedAt` are skipped. Matching is exact, so case and spacing count. A list field takes `VALUE|VALUE`.
255
+ - The upload is checked before it is queued. If a value matches no row or more than one, the upload answers `400` with the rows, for example `Row 2: no ProductLabel with code ECO` or `Row 3: code ECO matches 2 ProductLabel rows`. `:sku` on a relation to `ProductVariant` keeps its own wording: `Row 2: no variant with SKU BASE-TEE-M` and `Row 3: SKU BASE-TEE-M matches 2 variants`. The Dashboard lists the rows and nothing is imported. The import job checks again when it runs; if a value no longer matches exactly one row, the job fails with the same list and nothing is imported.
256
+ - The related rows must exist before the upload, so import them in an earlier file.
257
+ - The third segment must be the related entity's class name (`productvariant`, `productlabel`; case does not matter), and a field takes one lookup column. Otherwise the upload answers `400` and names the column. `:sku` on a relation to `ProductVariant` accepts any third segment, as it always has.
258
+ - A lookup works only on relation custom fields. Columns without a fourth segment still take ids.
259
+
260
+ #### Exporting relations by a column
261
+
262
+ The export writes relation custom fields as ids unless `exportOptions.relationLookups` names a column for the field. Keys are `<owner>:<field>`, values the column of the related entity:
263
+
264
+ ```ts
265
+ ProductImportExportPlugin.init({
266
+ importOptions: {},
267
+ exportOptions: {
268
+ relationLookups: {
269
+ 'variant:baseVariant': 'sku',
270
+ 'product:label': 'code',
271
+ },
272
+ },
273
+ })
174
274
  ```
175
275
 
176
- - Each SKU must match exactly one variant that is not deleted, in any channel. A list field takes `SKU|SKU`.
177
- - The upload is checked before it is queued. If a SKU matches no variant or more than one, the upload answers `400` with `{ "errors": ["Row 2: no variant with SKU BASE-TEE-M"] }`, the Dashboard lists the rows, and nothing is imported. The import job checks again when it runs.
178
- - The related variants must exist before the upload, so import them in an earlier file.
179
- - `:sku` is the only lookup, and it works only on relation fields to `ProductVariant`. Columns without it still take ids.
276
+ - The export then writes the field under the lookup header (`variant:baseVariant:productvariant:sku`, `product:label:productlabel:code`) with the related rows' values, `VALUE|VALUE` for a list field. The import reads that header as described above, so the file imports again, also into another database where the ids differ.
277
+ - The column must be a plain text column of the related entity, and the field must be a relation custom field that does not relate to `Asset`. Otherwise the application does not start and the error names the key.
278
+ - A related row that is soft-deleted, or has no value in the column, is left out of the cell, since the import matches only live rows by a value. Values must be unique among live rows: a value two live rows share makes the import of the file fail with the lookup error above.
180
279
 
181
280
  #### Sharing option groups
182
281
 
@@ -248,6 +347,7 @@ The Admin UI offers basic import and export. Some features such as the export-al
248
347
  1. In the Admin UI or Dashboard, select products to export (or use bulk export).
249
348
  2. Configure export fields (assets, facets, custom fields, etc.).
250
349
  - For products with more than one variant, `optionGroups` and `optionValues` must be included.
350
+ - `channels` is available as a selectable export field (see [Assigning products to channels](#assigning-products-to-channels)).
251
351
  - `id` (the Vendure product id) is available as a selectable export field. It is written after the translated `name`/`slug`/`description` columns, so it is never CSV column 0, since import treats column 0 positionally as a product id for by-id matching.
252
352
  - If `customExportColumns` is configured, only the columns whose `name` is selected are written to the CSV.
253
353
  - The job reports progress per page; the Dashboard job queue shows it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haus-tech/product-import-export-plugin",
3
- "version": "3.6.12",
3
+ "version": "3.6.14",
4
4
  "description": "A Vendure plugin for importing and exporting products using a CSV file",
5
5
  "author": "Haus Tech",
6
6
  "repository": {
@@ -2,3 +2,4 @@ export declare const PRODUCT_IMPORT_EXPORT_PLUGIN_OPTIONS: unique symbol;
2
2
  export declare const EXPORT_STORAGE_STRATEGY: unique symbol;
3
3
  export declare const IMPORT_JOB_STORAGE_STRATEGY: unique symbol;
4
4
  export declare const loggerCtx = "ProductImportExportPlugin";
5
+ export declare const IMPORT_ROW_VALIDATION_STRATEGY: unique symbol;
package/src/constants.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loggerCtx = exports.IMPORT_JOB_STORAGE_STRATEGY = exports.EXPORT_STORAGE_STRATEGY = exports.PRODUCT_IMPORT_EXPORT_PLUGIN_OPTIONS = void 0;
3
+ exports.IMPORT_ROW_VALIDATION_STRATEGY = exports.loggerCtx = exports.IMPORT_JOB_STORAGE_STRATEGY = exports.EXPORT_STORAGE_STRATEGY = exports.PRODUCT_IMPORT_EXPORT_PLUGIN_OPTIONS = void 0;
4
4
  exports.PRODUCT_IMPORT_EXPORT_PLUGIN_OPTIONS = Symbol('PRODUCT_IMPORT_EXPORT_PLUGIN_OPTIONS');
5
5
  exports.EXPORT_STORAGE_STRATEGY = Symbol('EXPORT_STORAGE_STRATEGY');
6
6
  exports.IMPORT_JOB_STORAGE_STRATEGY = Symbol('IMPORT_JOB_STORAGE_STRATEGY');
7
7
  exports.loggerCtx = 'ProductImportExportPlugin';
8
+ exports.IMPORT_ROW_VALIDATION_STRATEGY = Symbol('IMPORT_ROW_VALIDATION_STRATEGY');
8
9
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../packages/product-import-export-plugin/src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,oCAAoC,GAAG,MAAM,CAAC,sCAAsC,CAAC,CAAA;AACrF,QAAA,uBAAuB,GAAG,MAAM,CAAC,yBAAyB,CAAC,CAAA;AAC3D,QAAA,2BAA2B,GAAG,MAAM,CAAC,6BAA6B,CAAC,CAAA;AACnE,QAAA,SAAS,GAAG,2BAA2B,CAAA"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../packages/product-import-export-plugin/src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,oCAAoC,GAAG,MAAM,CAAC,sCAAsC,CAAC,CAAA;AACrF,QAAA,uBAAuB,GAAG,MAAM,CAAC,yBAAyB,CAAC,CAAA;AAC3D,QAAA,2BAA2B,GAAG,MAAM,CAAC,6BAA6B,CAAC,CAAA;AACnE,QAAA,SAAS,GAAG,2BAA2B,CAAA;AACvC,QAAA,8BAA8B,GAAG,MAAM,CAAC,gCAAgC,CAAC,CAAA"}
@@ -36,6 +36,7 @@ const AVAILABLE_EXPORT_FIELDS: ExportFields = [
36
36
  'description',
37
37
  'id',
38
38
  'assets',
39
+ 'channels',
39
40
  'facets',
40
41
  'optionGroups',
41
42
  'sku',
@@ -18,6 +18,7 @@ export type ProductFields =
18
18
  | 'slug'
19
19
  | 'description'
20
20
  | 'assets'
21
+ | 'channels'
21
22
  | 'facets'
22
23
  | 'optionGroups'
23
24
 
@@ -0,0 +1,8 @@
1
+ import { Type, VendureEntity } from '@vendure/core';
2
+ import { DataSource } from 'typeorm';
3
+ import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';
4
+ /**
5
+ * A plain text column of the entity, so a CSV cell compares to it without a cast. Relation and
6
+ * embedded columns (custom fields among them) are not lookups.
7
+ */
8
+ export declare function findTextColumn(dataSource: DataSource, entity: Type<VendureEntity>, propertyName: string): ColumnMetadata | undefined;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findTextColumn = findTextColumn;
4
+ /** Column types a CSV cell can be matched against without a cast. */
5
+ const TEXT_COLUMN_TYPES = new Set([
6
+ 'varchar',
7
+ 'character varying',
8
+ 'nvarchar',
9
+ 'text',
10
+ 'char',
11
+ 'character',
12
+ 'nchar',
13
+ 'citext',
14
+ ]);
15
+ /**
16
+ * A plain text column of the entity, so a CSV cell compares to it without a cast. Relation and
17
+ * embedded columns (custom fields among them) are not lookups.
18
+ */
19
+ function findTextColumn(dataSource, entity, propertyName) {
20
+ // Not findColumnWithPropertyName: it can return a custom field (embedded) column of that name.
21
+ const column = dataSource
22
+ .getMetadata(entity)
23
+ .columns.find((candidate) => candidate.propertyName === propertyName &&
24
+ !candidate.embeddedMetadata &&
25
+ !candidate.relationMetadata);
26
+ if (!column) {
27
+ return undefined;
28
+ }
29
+ const isText = column.type === String || TEXT_COLUMN_TYPES.has(String(column.type));
30
+ return isText ? column : undefined;
31
+ }
32
+ //# sourceMappingURL=find-text-column.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-text-column.js","sourceRoot":"","sources":["../../../../../packages/product-import-export-plugin/src/helpers/find-text-column.ts"],"names":[],"mappings":";;AAoBA,wCAmBC;AAnCD,qEAAqE;AACrE,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,SAAS;IACT,mBAAmB;IACnB,UAAU;IACV,MAAM;IACN,MAAM;IACN,WAAW;IACX,OAAO;IACP,QAAQ;CACT,CAAC,CAAA;AAEF;;;GAGG;AACH,SAAgB,cAAc,CAC5B,UAAsB,EACtB,MAA2B,EAC3B,YAAoB;IAEpB,+FAA+F;IAC/F,MAAM,MAAM,GAAG,UAAU;SACtB,WAAW,CAAC,MAAM,CAAC;SACnB,OAAO,CAAC,IAAI,CACX,CAAC,SAAS,EAAE,EAAE,CACZ,SAAS,CAAC,YAAY,KAAK,YAAY;QACvC,CAAC,SAAS,CAAC,gBAAgB;QAC3B,CAAC,SAAS,CAAC,gBAAgB,CAC9B,CAAA;IACH,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;IACnF,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;AACpC,CAAC"}
package/src/index.d.ts CHANGED
@@ -6,4 +6,5 @@ export * from './services/export-storage/s3-export-storage-strategy';
6
6
  export * from './services/import-storage/import-job-storage-strategy';
7
7
  export * from './services/import-storage/local-import-job-storage-strategy';
8
8
  export * from './services/import-storage/s3-import-job-storage-strategy';
9
+ export * from './strategies/import-row-validation-strategy';
9
10
  export type { S3ExportStorageConfig } from './services/export-storage.util';
package/src/index.js CHANGED
@@ -21,4 +21,5 @@ __exportStar(require("./services/export-storage/s3-export-storage-strategy"), ex
21
21
  __exportStar(require("./services/import-storage/import-job-storage-strategy"), exports);
22
22
  __exportStar(require("./services/import-storage/local-import-job-storage-strategy"), exports);
23
23
  __exportStar(require("./services/import-storage/s3-import-job-storage-strategy"), exports);
24
+ __exportStar(require("./strategies/import-row-validation-strategy"), exports);
24
25
  //# sourceMappingURL=index.js.map
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/product-import-export-plugin/src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iEAA8C;AAG9C,oFAAiE;AACjE,0FAAuE;AACvE,uFAAoE;AACpE,wFAAqE;AACrE,8FAA2E;AAC3E,2FAAwE"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/product-import-export-plugin/src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iEAA8C;AAG9C,oFAAiE;AACjE,0FAAuE;AACvE,uFAAoE;AACpE,wFAAqE;AACrE,8FAA2E;AAC3E,2FAAwE;AACxE,8EAA2D"}
@@ -1,8 +1,18 @@
1
+ import { OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common';
1
2
  import { Type } from '@vendure/core';
2
3
  import { AdminUiExtension } from '@vendure/ui-devkit/compiler';
4
+ import { ModuleRef } from '@nestjs/core';
3
5
  import { PluginInitOptions } from './types';
4
- export declare class ProductImportExportPlugin {
6
+ import { ImportRowValidationStrategy } from './strategies/import-row-validation-strategy';
7
+ export declare class ProductImportExportPlugin implements OnApplicationBootstrap, OnApplicationShutdown {
8
+ private moduleRef;
9
+ private rowValidationStrategy;
5
10
  static options: PluginInitOptions;
11
+ constructor(moduleRef: ModuleRef, rowValidationStrategy: ImportRowValidationStrategy);
12
+ /** @internal */
13
+ onApplicationBootstrap(): Promise<void>;
14
+ /** @internal */
15
+ onApplicationShutdown(): Promise<void>;
6
16
  /**
7
17
  * @description
8
18
  * Initialise the plugin with the given import/export options, applying sensible
@@ -38,9 +38,16 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  return result;
39
39
  };
40
40
  })();
41
+ var __metadata = (this && this.__metadata) || function (k, v) {
42
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
+ };
44
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
45
+ return function (target, key) { decorator(target, key, paramIndex); }
46
+ };
41
47
  var ProductImportExportPlugin_1;
42
48
  Object.defineProperty(exports, "__esModule", { value: true });
43
49
  exports.ProductImportExportPlugin = void 0;
50
+ const common_1 = require("@nestjs/common");
44
51
  const core_1 = require("@vendure/core");
45
52
  const core_2 = require("@nestjs/core");
46
53
  const path = __importStar(require("path"));
@@ -59,11 +66,13 @@ const product_import_service_1 = require("./services/product-import.service");
59
66
  const extended_fast_importer_service_1 = require("./services/extended-fast-importer.service");
60
67
  const product_export_service_1 = require("./services/product-export.service");
61
68
  const product_export_queue_service_1 = require("./services/product-export-queue.service");
62
- const sku_relation_resolver_1 = require("./services/sku-relation-resolver");
69
+ const relation_lookup_resolver_1 = require("./services/relation-lookup-resolver");
70
+ const channel_column_resolver_1 = require("./services/channel-column-resolver");
63
71
  const local_export_storage_strategy_1 = require("./services/export-storage/local-export-storage-strategy");
64
72
  const local_import_job_storage_strategy_1 = require("./services/import-storage/local-import-job-storage-strategy");
73
+ const import_row_validation_strategy_1 = require("./strategies/import-row-validation-strategy");
65
74
  const RESERVED_EXPORT_COLUMN_NAMES = new Set([
66
- 'id', 'name', 'slug', 'description', 'assets', 'facets', 'optionGroups', 'optionValues',
75
+ 'id', 'name', 'slug', 'description', 'assets', 'channels', 'facets', 'optionGroups', 'optionValues',
67
76
  'sku', 'price', 'taxCategory', 'stockOnHand', 'trackInventory', 'variantAssets',
68
77
  'variantFacets', 'enabled',
69
78
  ]);
@@ -87,7 +96,21 @@ function validateCustomExportColumns(columns) {
87
96
  }
88
97
  let ProductImportExportPlugin = class ProductImportExportPlugin {
89
98
  static { ProductImportExportPlugin_1 = this; }
99
+ moduleRef;
100
+ rowValidationStrategy;
90
101
  static options;
102
+ constructor(moduleRef, rowValidationStrategy) {
103
+ this.moduleRef = moduleRef;
104
+ this.rowValidationStrategy = rowValidationStrategy;
105
+ }
106
+ /** @internal */
107
+ async onApplicationBootstrap() {
108
+ await this.rowValidationStrategy.init?.(new core_1.Injector(this.moduleRef));
109
+ }
110
+ /** @internal */
111
+ async onApplicationShutdown() {
112
+ await this.rowValidationStrategy.destroy?.();
113
+ }
91
114
  /**
92
115
  * @description
93
116
  * Initialise the plugin with the given import/export options, applying sensible
@@ -216,6 +239,11 @@ exports.ProductImportExportPlugin = ProductImportExportPlugin = ProductImportExp
216
239
  },
217
240
  inject: [core_2.ModuleRef],
218
241
  },
242
+ {
243
+ provide: constants_1.IMPORT_ROW_VALIDATION_STRATEGY,
244
+ useFactory: () => ProductImportExportPlugin.options?.importOptions?.rowValidationStrategy ??
245
+ new import_row_validation_strategy_1.DefaultImportRowValidationStrategy(),
246
+ },
219
247
  product_import_service_1.ProductImportService,
220
248
  product_importer_1.ProductImporter,
221
249
  extended_fast_importer_service_1.ExtendedFastImporterService,
@@ -223,7 +251,8 @@ exports.ProductImportExportPlugin = ProductImportExportPlugin = ProductImportExp
223
251
  asset_importer_1.ExtendedAssetImporter,
224
252
  product_export_service_1.ProductExportService,
225
253
  product_export_queue_service_1.ProductExportQueueService,
226
- sku_relation_resolver_1.SkuRelationResolver,
254
+ relation_lookup_resolver_1.RelationLookupResolver,
255
+ channel_column_resolver_1.ChannelColumnResolver,
227
256
  ],
228
257
  controllers: [
229
258
  product_import_controller_1.ProductImportController,
@@ -267,5 +296,8 @@ exports.ProductImportExportPlugin = ProductImportExportPlugin = ProductImportExp
267
296
  *
268
297
  * @category Plugin
269
298
  */
299
+ ,
300
+ __param(1, (0, common_1.Inject)(constants_1.IMPORT_ROW_VALIDATION_STRATEGY)),
301
+ __metadata("design:paramtypes", [core_2.ModuleRef, Object])
270
302
  ], ProductImportExportPlugin);
271
303
  //# sourceMappingURL=product-import-export.plugin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"product-import-export.plugin.js","sourceRoot":"","sources":["../../../../packages/product-import-export-plugin/src/product-import-export.plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"product-import-export.plugin.js","sourceRoot":"","sources":["../../../../packages/product-import-export-plugin/src/product-import-export.plugin.ts"],"names":[],"mappings":"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"}
@@ -46,6 +46,8 @@ export interface ParsedAsset {
46
46
  * @docsPage ImportParser
47
47
  */
48
48
  export interface ParsedProductVariant {
49
+ /** The file line of the variant's row (the header is line 1, blank lines are not counted). */
50
+ line?: number;
49
51
  sku: string;
50
52
  price: number | undefined;
51
53
  taxCategory?: string;
@@ -73,11 +75,18 @@ export interface ParsedProductVariant {
73
75
  * @docsPage ImportParser
74
76
  */
75
77
  export interface ParsedProduct {
78
+ /** The file line of the product's first row (the header is line 1, blank lines are not counted). */
79
+ line?: number;
76
80
  assetPaths: string[];
77
81
  assetsJson?: ParsedAsset[];
78
82
  optionGroups: ParsedOptionGroup[];
79
83
  optionGroupsProvided: boolean;
80
84
  facets: ParsedFacet[];
85
+ /**
86
+ * The tokens in the `channels` column: channels the product is assigned to on top of the
87
+ * default channel and the channel the import runs in. Empty when the column is missing or empty.
88
+ */
89
+ channelTokens: string[];
81
90
  translations: Array<{
82
91
  languageCode: LanguageCode;
83
92
  name: string;
@@ -71,14 +71,18 @@ let ImportParser = class ImportParser {
71
71
  return new Promise((resolve, reject) => {
72
72
  let errors = [];
73
73
  if (typeof input === 'string') {
74
- (0, csv_parse_1.parse)(input, options, async (err, records) => {
74
+ (0, csv_parse_1.parse)(input, options, (err, records) => {
75
75
  if (err) {
76
76
  errors = errors.concat(err);
77
77
  }
78
78
  if (records) {
79
- const parseResult = await this.processRawRecords(records, mainLanguage);
80
- errors = errors.concat(parseResult.errors);
81
- resolve({ results: parseResult.results, errors, processed: parseResult.processed });
79
+ // A throw here must reject, or the caller (an upload request) waits forever.
80
+ this.processRawRecords(records, mainLanguage)
81
+ .then((parseResult) => {
82
+ errors = errors.concat(parseResult.errors);
83
+ resolve({ results: parseResult.results, errors, processed: parseResult.processed });
84
+ })
85
+ .catch(reject);
82
86
  }
83
87
  else {
84
88
  resolve({ results: [], errors, processed: 0 });
@@ -159,8 +163,10 @@ let ImportParser = class ImportParser {
159
163
  }
160
164
  }
161
165
  currentRow = {
162
- product: await this.parseProductFromRecord(r, usedLanguages, mainLanguage),
163
- variants: [await this.parseVariantFromRecord(r, usedLanguages, mainLanguage)],
166
+ product: { line, ...(await this.parseProductFromRecord(r, usedLanguages, mainLanguage)) },
167
+ variants: [
168
+ { line, ...(await this.parseVariantFromRecord(r, usedLanguages, mainLanguage)) },
169
+ ],
164
170
  };
165
171
  currentRowRejected = false;
166
172
  }
@@ -172,7 +178,10 @@ let ImportParser = class ImportParser {
172
178
  `enter it on the product's first row (the row with a name) on line ${line}`);
173
179
  currentRowRejected = true;
174
180
  }
175
- currentRow.variants.push(await this.parseVariantFromRecord(r, usedLanguages, mainLanguage));
181
+ currentRow.variants.push({
182
+ line,
183
+ ...(await this.parseVariantFromRecord(r, usedLanguages, mainLanguage)),
184
+ });
176
185
  }
177
186
  }
178
187
  const optionError = validateOptionValueCount(r, currentRow, mainLanguage);
@@ -422,6 +431,7 @@ let ImportParser = class ImportParser {
422
431
  optionGroups,
423
432
  optionGroupsProvided,
424
433
  facets,
434
+ channelTokens: (0, unique_1.unique)(parseStringArray(r.channels)),
425
435
  translations: await Promise.all(translations),
426
436
  };
427
437
  return parsedProduct;
@@ -600,7 +610,15 @@ function mapRowToObject(columns, row) {
600
610
  }, {});
601
611
  }
602
612
  /** Columns read from a product's first row only (their translated forms too, e.g. `slug:en`). */
603
- const PRODUCT_COLUMNS = new Set(['name', 'slug', 'description', 'assets', 'facets', 'optionGroups']);
613
+ const PRODUCT_COLUMNS = new Set([
614
+ 'name',
615
+ 'slug',
616
+ 'description',
617
+ 'assets',
618
+ 'facets',
619
+ 'optionGroups',
620
+ 'channels',
621
+ ]);
604
622
  /**
605
623
  * Returns the first filled product column in a variant row (a row without a name), if any.
606
624
  * `id` is not one: the exporter repeats the product id on every row of a product.