feed-common 1.0.0 → 1.0.1

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.
@@ -0,0 +1,348 @@
1
+ import {
2
+ OptionType,
3
+ OptionTypeItem,
4
+ OptionTypeGroup,
5
+ ProductUploadProfile,
6
+ } from '../types/profile.types.js';
7
+ import { ProductUploadMappings } from '../constants/profile.constants.js';
8
+ import { ProductUploadMapping } from '../types/profile.types.js';
9
+ import {
10
+ ProductUploadRuleFilterType,
11
+ RuleOperatorsType,
12
+ } from '../types/profile.types.js';
13
+ import {
14
+ ProductUploadRuleItem,
15
+ ProductUploadRuleSection,
16
+ ProductUploadRules,
17
+ } from '../types/profile.types.js';
18
+ import { code } from './utils.js';
19
+
20
+ export function checkRuleDuplication (rules: ProductUploadRuleItem[]): string[] {
21
+ return rules
22
+ .map((r) => {
23
+ for (const rule of rules) {
24
+ if (
25
+ r.id !== rule.id &&
26
+ r.attribute === rule.attribute &&
27
+ r.operator === rule.operator &&
28
+ r.value === rule.value
29
+ ) {
30
+ return r.id;
31
+ }
32
+ }
33
+ return null;
34
+ })
35
+ .filter(Boolean) as string[];
36
+ }
37
+
38
+ export function checkRuleAlwaysness (rules: ProductUploadRuleItem[]): string[] {
39
+ return rules
40
+ .map((r) => {
41
+ for (const rule of rules) {
42
+ if (isExlusive(r, rule)) {
43
+ return r.id;
44
+ }
45
+ }
46
+ return null;
47
+ })
48
+ .filter(Boolean) as string[];
49
+ }
50
+
51
+ export function checkRuleDeadlocks (
52
+ sections: ProductUploadRuleSection[]
53
+ ): string[] {
54
+ let totalList = [] as string[];
55
+
56
+ for (let i = 0; i < sections.length; i++) {
57
+ const currentSection = sections[i];
58
+ const currentList = [] as string[];
59
+
60
+ for (let y = 0; y < sections.length; y++) {
61
+ if (i === y) {
62
+ continue;
63
+ }
64
+
65
+ from_start: for (const ruleA of currentSection.ruleItems) {
66
+ for (const ruleB of sections[y].ruleItems) {
67
+ if (isExlusive(ruleA, ruleB)) {
68
+ currentList.push(ruleA.id);
69
+ continue from_start;
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ if (currentList.length === currentSection.ruleItems.length) {
76
+ totalList = [...totalList, ...currentList];
77
+ }
78
+ }
79
+
80
+ return totalList;
81
+ }
82
+
83
+ function isExlusive (
84
+ rule1: ProductUploadRuleItem,
85
+ rule2: ProductUploadRuleItem
86
+ ): boolean {
87
+ return (
88
+ rule1.id !== rule2.id &&
89
+ rule1.attribute === rule2.attribute &&
90
+ rule1.operator ===
91
+ getOperatorCounterpart(rule2.operator as keyof RuleOperatorsType) &&
92
+ rule1.value === rule2.value
93
+ );
94
+ }
95
+
96
+ function getOperatorCounterpart (
97
+ operator: keyof RuleOperatorsType
98
+ ): keyof RuleOperatorsType {
99
+ switch (operator) {
100
+ case 'equals':
101
+ return 'notEquals';
102
+ case 'notEquals':
103
+ return 'equals';
104
+ case 'contains':
105
+ return 'notContains';
106
+ case 'notContains':
107
+ return 'contains';
108
+ case 'startsWith':
109
+ return 'notStartsWith';
110
+ case 'notStartsWith':
111
+ return 'startsWith';
112
+ case 'endsWith':
113
+ return 'notEndsWith';
114
+ case 'notEndsWith':
115
+ return 'endsWith';
116
+ case 'in':
117
+ return 'notIn';
118
+ case 'notIn':
119
+ return 'in';
120
+ default:
121
+ throw new Error(`Unknown operator ${String(operator)}`);
122
+ }
123
+ }
124
+
125
+ export function getEmptyRuleItem (
126
+ attribute: ProductUploadRuleFilterType['value'] = ''
127
+ ): ProductUploadRuleItem {
128
+ return { attribute, operator: '', value: '', id: code() };
129
+ }
130
+
131
+ export function sortMappings (
132
+ a: ProductUploadMapping,
133
+ b: ProductUploadMapping
134
+ ): number {
135
+ if (!a.attribute) {
136
+ return 1;
137
+ }
138
+
139
+ if (!b.attribute) {
140
+ return -1;
141
+ }
142
+
143
+ const sourceA = ProductUploadMappings.find(
144
+ (m) => m.attribute === a.attribute
145
+ );
146
+ const sourceB = ProductUploadMappings.find(
147
+ (m) => m.attribute === b.attribute
148
+ );
149
+
150
+ if (sourceA?.required && !sourceB?.required) {
151
+ return -1;
152
+ }
153
+
154
+ if (sourceB?.required && !sourceA?.required) {
155
+ return 1;
156
+ }
157
+
158
+ if (a.attribute < b.attribute) {
159
+ return -1;
160
+ }
161
+
162
+ if (a.attribute > b.attribute) {
163
+ return 1;
164
+ }
165
+
166
+ return 0;
167
+ }
168
+
169
+ export function hasRules (rules: ProductUploadRules): boolean {
170
+ return (
171
+ Array.isArray(rules?.sections) &&
172
+ rules.sections.some((section) => section.ruleItems.length > 0)
173
+ );
174
+ }
175
+
176
+ /**
177
+ * Checks if two rules are the same
178
+ * @param a
179
+ * @param b
180
+ * @returns
181
+ */
182
+ export function compareRules (
183
+ a: ProductUploadRules = { sections: [] },
184
+ b: ProductUploadRules = { sections: [] }
185
+ ): boolean {
186
+ const matchedSections = [] as number[];
187
+ const sameRule = (
188
+ rA: ProductUploadRuleItem,
189
+ rB: ProductUploadRuleItem
190
+ ): boolean => {
191
+ return (
192
+ rA.attribute === rB.attribute &&
193
+ rA.operator === rB.operator &&
194
+ compareValues(rA.value, rB.value)
195
+ );
196
+ };
197
+
198
+ a.sections = a.sections.filter((s) => s.ruleItems.length > 0);
199
+ b.sections = b.sections.filter((s) => s.ruleItems.length > 0);
200
+
201
+ if (a.sections.length !== b.sections.length) {
202
+ return false;
203
+ }
204
+
205
+ for (let i = 0; i < a.sections.length; i++) {
206
+ const rulesA = a.sections[i].ruleItems;
207
+
208
+ for (let j = 0; j < b.sections.length; j++) {
209
+ if (matchedSections.includes(j)) {
210
+ continue;
211
+ }
212
+
213
+ const rulesB = b.sections[j].ruleItems;
214
+
215
+ if (rulesA.length !== rulesB.length) {
216
+ continue;
217
+ }
218
+
219
+ if (
220
+ rulesA.every((ruleA) => rulesB.some((ruleB) => sameRule(ruleA, ruleB)))
221
+ ) {
222
+ matchedSections.push(j);
223
+ break;
224
+ }
225
+ }
226
+ }
227
+
228
+ return matchedSections.length === a.sections.length;
229
+ }
230
+
231
+ function compareValues (a: any, b: any): boolean {
232
+ if (Array.isArray(a) && Array.isArray(b)) {
233
+ return a.length === b.length && a.every((i) => b.includes(i));
234
+ }
235
+
236
+ return a === b;
237
+ }
238
+
239
+ export function compareMappings (
240
+ a: ProductUploadMapping[],
241
+ b: ProductUploadMapping[]
242
+ ): boolean {
243
+ const matchedMappings = [] as number[];
244
+
245
+ if (a.length !== b.length) {
246
+ return false;
247
+ }
248
+
249
+ for (let i = 0; i < a.length; i++) {
250
+ const mappingA = a[i];
251
+
252
+ for (let j = 0; j < b.length; j++) {
253
+ if (matchedMappings.includes(j)) {
254
+ continue;
255
+ }
256
+
257
+ const mappingB = b[j];
258
+
259
+ if (
260
+ mappingA.attribute === mappingB.attribute &&
261
+ compareValues(mappingA.value, mappingB.value) &&
262
+ compareRules(mappingA.rules, mappingB.rules)
263
+ ) {
264
+ matchedMappings.push(j);
265
+ break;
266
+ }
267
+ }
268
+ }
269
+
270
+ return matchedMappings.length === a.length;
271
+ }
272
+ export function optionIsString (option: OptionType): option is string {
273
+ return typeof option === 'string';
274
+ }
275
+
276
+ export function optionIsItem (option: OptionType): option is OptionTypeItem {
277
+ return typeof option !== 'string' && 'value' in option;
278
+ }
279
+
280
+ export function optionIsGroup (option: OptionType): option is OptionTypeGroup {
281
+ return typeof option !== 'string' && 'options' in option;
282
+ }
283
+ export function detectProfileChange (
284
+ a: ProductUploadProfile,
285
+ b: ProductUploadProfile
286
+ ): { name: boolean; rules: boolean; mappings: boolean; id: boolean } {
287
+ const changes = {
288
+ name: false,
289
+ rules: false,
290
+ mappings: false,
291
+ id: false,
292
+ };
293
+
294
+ if (a.name !== b.name) {
295
+ changes.name = true;
296
+ }
297
+
298
+ if (a.mappings !== b.mappings) {
299
+ const mappingsA = a.mappings?.filter(
300
+ (m) =>
301
+ m.attribute !== 'targetCountry' && m.attribute !== 'contentLanguage'
302
+ );
303
+ const mappingsB = b.mappings?.filter(
304
+ (m) =>
305
+ m.attribute !== 'targetCountry' && m.attribute !== 'contentLanguage'
306
+ );
307
+ changes.mappings = !compareMappings(mappingsA, mappingsB);
308
+ }
309
+
310
+ if (
311
+ ['targetCountry', 'contentLanguage'].some((key) => {
312
+ const mappingA = a.mappings?.filter((m) => m.attribute === key) ?? [];
313
+ const mappingB = b.mappings?.filter((m) => m.attribute === key) ?? [];
314
+
315
+ return !compareMappings(mappingA, mappingB);
316
+ })
317
+ ) {
318
+ changes.id = true;
319
+ }
320
+
321
+ if (!compareRules(a.rules, b.rules)) {
322
+ changes.rules = true;
323
+ }
324
+
325
+ return changes;
326
+ }
327
+
328
+ export function sanitizeUploadProfile (
329
+ profile: ProductUploadProfile
330
+ ): ProductUploadProfile {
331
+ if (profile.rules?.sections) {
332
+ profile.rules.sections = profile.rules.sections.filter(
333
+ (section) => section.ruleItems.length > 0
334
+ );
335
+ }
336
+
337
+ profile.mappings = profile.mappings?.map((mapping) => {
338
+ if (mapping.rules?.sections) {
339
+ mapping.rules.sections = mapping.rules.sections.filter(
340
+ (section) => section.ruleItems.length > 0
341
+ );
342
+ }
343
+
344
+ return mapping;
345
+ });
346
+
347
+ return profile;
348
+ }
@@ -0,0 +1,3 @@
1
+ export function code () {
2
+ return (Math.random() * 1000000).toFixed(0);
3
+ }
@@ -1,38 +0,0 @@
1
- name: Release
2
-
3
- permissions:
4
- contents: write
5
- issues: write
6
- pull-requests: write
7
-
8
- on:
9
- push:
10
- branches:
11
- - master
12
- jobs:
13
- release:
14
- name: Release
15
- runs-on: ubuntu-latest
16
- steps:
17
- - name: Checkout
18
- uses: actions/checkout@v4
19
- with:
20
- fetch-depth: 0
21
-
22
- - name: Setup Node.js
23
- uses: actions/setup-node@v4
24
- with:
25
- node-version: "20"
26
-
27
- - name: Install dependencies
28
- run: npm ci
29
-
30
- - name: Build
31
- run: npm run build
32
-
33
- - name: Release
34
- env:
35
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
36
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
37
- SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
38
- run: npx semantic-release