@contentstack/cli-variants 2.0.0-beta.11 → 2.0.0-beta.12

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.
@@ -27,7 +27,7 @@ class ExportAttributes extends utils_1.PersonalizationAdapter {
27
27
  this.exportConfig = exportConfig;
28
28
  this.personalizeConfig = exportConfig.modules.personalize;
29
29
  this.attributesConfig = exportConfig.modules.attributes;
30
- this.attributesFolderPath = (0, node_path_1.resolve)((0, cli_utilities_1.sanitizePath)(exportConfig.exportDir), (0, cli_utilities_1.sanitizePath)(exportConfig.branchName || ''), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName), (0, cli_utilities_1.sanitizePath)(this.attributesConfig.dirName));
30
+ this.attributesFolderPath = (0, node_path_1.resolve)((0, cli_utilities_1.sanitizePath)(exportConfig.exportDir), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName), (0, cli_utilities_1.sanitizePath)(this.attributesConfig.dirName));
31
31
  this.attributes = [];
32
32
  this.exportConfig.context.module = constants_1.MODULE_CONTEXTS.ATTRIBUTES;
33
33
  }
@@ -27,7 +27,7 @@ class ExportAudiences extends utils_1.PersonalizationAdapter {
27
27
  this.exportConfig = exportConfig;
28
28
  this.personalizeConfig = exportConfig.modules.personalize;
29
29
  this.audiencesConfig = exportConfig.modules.audiences;
30
- this.audiencesFolderPath = (0, node_path_1.resolve)((0, cli_utilities_1.sanitizePath)(exportConfig.exportDir), (0, cli_utilities_1.sanitizePath)(exportConfig.branchName || ''), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName), (0, cli_utilities_1.sanitizePath)(this.audiencesConfig.dirName));
30
+ this.audiencesFolderPath = (0, node_path_1.resolve)((0, cli_utilities_1.sanitizePath)(exportConfig.exportDir), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName), (0, cli_utilities_1.sanitizePath)(this.audiencesConfig.dirName));
31
31
  this.audiences = [];
32
32
  this.exportConfig.context.module = constants_1.MODULE_CONTEXTS.AUDIENCES;
33
33
  }
@@ -27,7 +27,7 @@ class ExportEvents extends utils_1.PersonalizationAdapter {
27
27
  this.exportConfig = exportConfig;
28
28
  this.personalizeConfig = exportConfig.modules.personalize;
29
29
  this.eventsConfig = exportConfig.modules.events;
30
- this.eventsFolderPath = (0, node_path_1.resolve)((0, cli_utilities_1.sanitizePath)(exportConfig.exportDir), (0, cli_utilities_1.sanitizePath)(exportConfig.branchName || ''), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName), (0, cli_utilities_1.sanitizePath)(this.eventsConfig.dirName));
30
+ this.eventsFolderPath = (0, node_path_1.resolve)((0, cli_utilities_1.sanitizePath)(exportConfig.exportDir), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName), (0, cli_utilities_1.sanitizePath)(this.eventsConfig.dirName));
31
31
  this.events = [];
32
32
  this.exportConfig.context.module = constants_1.MODULE_CONTEXTS.EVENTS;
33
33
  }
@@ -59,7 +59,7 @@ class ExportExperiences extends utils_1.PersonalizationAdapter {
59
59
  });
60
60
  this.exportConfig = exportConfig;
61
61
  this.personalizeConfig = exportConfig.modules.personalize;
62
- this.experiencesFolderPath = path.resolve((0, cli_utilities_1.sanitizePath)(exportConfig.exportDir), (0, cli_utilities_1.sanitizePath)(exportConfig.branchName || ''), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName), 'experiences');
62
+ this.experiencesFolderPath = path.resolve((0, cli_utilities_1.sanitizePath)(exportConfig.exportDir), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName), 'experiences');
63
63
  this.exportConfig.context.module = constants_1.MODULE_CONTEXTS.EXPERIENCES;
64
64
  }
65
65
  start() {
@@ -22,7 +22,7 @@ class ExportProjects extends utils_1.PersonalizationAdapter {
22
22
  });
23
23
  this.exportConfig = exportConfig;
24
24
  this.personalizeConfig = exportConfig.modules.personalize;
25
- this.projectsFolderPath = (0, node_path_1.resolve)((0, cli_utilities_1.sanitizePath)(exportConfig.exportDir), (0, cli_utilities_1.sanitizePath)(exportConfig.branchName || ''), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName), 'projects');
25
+ this.projectsFolderPath = (0, node_path_1.resolve)((0, cli_utilities_1.sanitizePath)(exportConfig.exportDir), (0, cli_utilities_1.sanitizePath)(this.personalizeConfig.dirName), 'projects');
26
26
  this.projectsData = [];
27
27
  this.exportConfig.context.module = constants_1.MODULE_CONTEXTS.PROJECTS;
28
28
  }
@@ -68,7 +68,7 @@ class VariantEntries extends variant_api_adapter_1.default {
68
68
  this.processInitialized = false;
69
69
  this.totalVariantCount = 0;
70
70
  this.processedVariantCount = 0;
71
- this.entriesDirPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(config.exportDir), (0, cli_utilities_1.sanitizePath)(config.branchName || ''), (0, cli_utilities_1.sanitizePath)(config.modules.entries.dirName));
71
+ this.entriesDirPath = (0, path_1.resolve)((0, cli_utilities_1.sanitizePath)(config.exportDir), (0, cli_utilities_1.sanitizePath)(config.modules.entries.dirName));
72
72
  if (this.config && this.config.context) {
73
73
  this.config.context.module = 'variant-entries';
74
74
  }
@@ -39,7 +39,7 @@ class Audiences extends utils_1.PersonalizationAdapter {
39
39
  */
40
40
  import() {
41
41
  return __awaiter(this, void 0, void 0, function* () {
42
- var _a, _b;
42
+ var _a, _b, _c;
43
43
  try {
44
44
  cli_utilities_1.log.debug('Starting audiences import...', this.config.context);
45
45
  const [canImport, audiencesCount] = yield this.analyzeAudiences();
@@ -73,9 +73,14 @@ class Audiences extends utils_1.PersonalizationAdapter {
73
73
  progress.updateStatus(constants_1.IMPORT_PROCESS_STATUS[constants_1.PROCESS_NAMES.AUDIENCES].CREATING);
74
74
  }
75
75
  cli_utilities_1.log.debug(`Processing audience: ${name} (${uid})`, this.config.context);
76
+ // Skip Lytics audiences - they cannot be created via API (synced from Lytics)
77
+ if (((_a = audience.source) === null || _a === void 0 ? void 0 : _a.toUpperCase()) === 'LYTICS') {
78
+ cli_utilities_1.log.debug(`Skipping Lytics audience: ${name} (${uid})`, this.config.context);
79
+ continue;
80
+ }
76
81
  try {
77
82
  //check whether reference attributes exists or not
78
- if ((_a = definition.rules) === null || _a === void 0 ? void 0 : _a.length) {
83
+ if ((_b = definition.rules) === null || _b === void 0 ? void 0 : _b.length) {
79
84
  cli_utilities_1.log.debug(`Processing ${definition.rules.length} definition rules for audience: ${name}`, this.config.context);
80
85
  definition.rules = (0, utils_1.lookUpAttributes)(definition.rules, attributesUid);
81
86
  cli_utilities_1.log.debug(`Processed definition rules, remaining rules: ${definition.rules.length}`, this.config.context);
@@ -87,7 +92,7 @@ class Audiences extends utils_1.PersonalizationAdapter {
87
92
  const audienceRes = yield this.createAudience({ definition, name, description });
88
93
  //map old audience uid to new audience uid
89
94
  //mapper file is used to check whether audience created or not before creating experience
90
- this.audiencesUidMapper[uid] = (_b = audienceRes === null || audienceRes === void 0 ? void 0 : audienceRes.uid) !== null && _b !== void 0 ? _b : '';
95
+ this.audiencesUidMapper[uid] = (_c = audienceRes === null || audienceRes === void 0 ? void 0 : audienceRes.uid) !== null && _c !== void 0 ? _c : '';
91
96
  this.updateProgress(true, `audience: ${name}`, undefined, constants_1.PROCESS_NAMES.AUDIENCES);
92
97
  cli_utilities_1.log.debug(`Created audience: ${uid} -> ${audienceRes === null || audienceRes === void 0 ? void 0 : audienceRes.uid}`, this.config.context);
93
98
  }
@@ -226,7 +226,6 @@ export interface ExportConfig extends DefaultConfig {
226
226
  contentTypes?: string[];
227
227
  branches?: branch[];
228
228
  branchEnabled?: boolean;
229
- branchDir?: string;
230
229
  singleModuleExport?: boolean;
231
230
  moduleName?: Modules;
232
231
  master_locale: masterLocale;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentstack/cli-variants",
3
- "version": "2.0.0-beta.11",
3
+ "version": "2.0.0-beta.12",
4
4
  "description": "Variants plugin",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -28,10 +28,10 @@
28
28
  "typescript": "^5.8.3"
29
29
  },
30
30
  "dependencies": {
31
- "@contentstack/cli-utilities": "~2.0.0-beta.5",
31
+ "@contentstack/cli-utilities": "~2.0.0-beta.7",
32
32
  "@oclif/core": "^4.3.0",
33
33
  "@oclif/plugin-help": "^6.2.28",
34
- "lodash": "^4.17.23",
34
+ "lodash": "^4.18.1",
35
35
  "mkdirp": "^1.0.4",
36
36
  "winston": "^3.17.0"
37
37
  }
@@ -23,7 +23,6 @@ export default class ExportAttributes extends PersonalizationAdapter<ExportConfi
23
23
  this.attributesConfig = exportConfig.modules.attributes;
24
24
  this.attributesFolderPath = pResolve(
25
25
  sanitizePath(exportConfig.exportDir),
26
- sanitizePath(exportConfig.branchName || ''),
27
26
  sanitizePath(this.personalizeConfig.dirName),
28
27
  sanitizePath(this.attributesConfig.dirName),
29
28
  );
@@ -23,7 +23,6 @@ export default class ExportAudiences extends PersonalizationAdapter<ExportConfig
23
23
  this.audiencesConfig = exportConfig.modules.audiences;
24
24
  this.audiencesFolderPath = pResolve(
25
25
  sanitizePath(exportConfig.exportDir),
26
- sanitizePath(exportConfig.branchName || ''),
27
26
  sanitizePath(this.personalizeConfig.dirName),
28
27
  sanitizePath(this.audiencesConfig.dirName),
29
28
  );
@@ -23,7 +23,6 @@ export default class ExportEvents extends PersonalizationAdapter<ExportConfig> {
23
23
  this.eventsConfig = exportConfig.modules.events;
24
24
  this.eventsFolderPath = pResolve(
25
25
  sanitizePath(exportConfig.exportDir),
26
- sanitizePath(exportConfig.branchName || ''),
27
26
  sanitizePath(this.personalizeConfig.dirName),
28
27
  sanitizePath(this.eventsConfig.dirName),
29
28
  );
@@ -23,7 +23,6 @@ export default class ExportExperiences extends PersonalizationAdapter<ExportConf
23
23
  this.personalizeConfig = exportConfig.modules.personalize;
24
24
  this.experiencesFolderPath = path.resolve(
25
25
  sanitizePath(exportConfig.exportDir),
26
- sanitizePath(exportConfig.branchName || ''),
27
26
  sanitizePath(this.personalizeConfig.dirName),
28
27
  'experiences',
29
28
  );
@@ -20,7 +20,6 @@ export default class ExportProjects extends PersonalizationAdapter<ExportConfig>
20
20
  this.personalizeConfig = exportConfig.modules.personalize;
21
21
  this.projectsFolderPath = pResolve(
22
22
  sanitizePath(exportConfig.exportDir),
23
- sanitizePath(exportConfig.branchName || ''),
24
23
  sanitizePath(this.personalizeConfig.dirName),
25
24
  'projects',
26
25
  );
@@ -32,7 +32,6 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Exp
32
32
  super(Object.assign(config, conf));
33
33
  this.entriesDirPath = resolve(
34
34
  sanitizePath(config.exportDir),
35
- sanitizePath(config.branchName || ''),
36
35
  sanitizePath(config.modules.entries.dirName),
37
36
  );
38
37
  if (this.config && this.config.context) {
@@ -89,6 +89,12 @@ export default class Audiences extends PersonalizationAdapter<ImportConfig> {
89
89
  }
90
90
  log.debug(`Processing audience: ${name} (${uid})`, this.config.context);
91
91
 
92
+ // Skip Lytics audiences - they cannot be created via API (synced from Lytics)
93
+ if (audience.source?.toUpperCase() === 'LYTICS') {
94
+ log.debug(`Skipping Lytics audience: ${name} (${uid})`, this.config.context);
95
+ continue;
96
+ }
97
+
92
98
  try {
93
99
  //check whether reference attributes exists or not
94
100
  if (definition.rules?.length) {
@@ -252,7 +252,6 @@ export interface ExportConfig extends DefaultConfig {
252
252
  contentTypes?: string[];
253
253
  branches?: branch[];
254
254
  branchEnabled?: boolean;
255
- branchDir?: string;
256
255
  singleModuleExport?: boolean;
257
256
  moduleName?: Modules;
258
257
  master_locale: masterLocale;
@@ -24,6 +24,19 @@ describe('Variant Entries Export', () => {
24
24
  config = exportConf as unknown as ExportConfig;
25
25
  });
26
26
 
27
+ describe('path construction', () => {
28
+ test.it('should use exportDir as base path (no branch segment in path)', () => {
29
+ const configWithExportDir = {
30
+ ...config,
31
+ exportDir: '/base/export',
32
+ branchName: 'dev',
33
+ } as ExportConfig;
34
+ const instance = new Export.VariantEntries(configWithExportDir);
35
+ expect(instance.entriesDirPath).to.not.include('dev');
36
+ expect(instance.entriesDirPath).to.include('entries');
37
+ });
38
+ });
39
+
27
40
  describe('exportVariantEntry method', () => {
28
41
  test
29
42
  .stub(VariantHttpClient.prototype, 'variantEntries', async () => {})
@@ -0,0 +1,118 @@
1
+ import { expect } from '@oclif/test';
2
+ import cloneDeep from 'lodash/cloneDeep';
3
+ import { fancy } from '@contentstack/cli-dev-dependencies';
4
+
5
+ import importConf from '../mock/import-config.json';
6
+ import { Import, ImportConfig } from '../../../src';
7
+
8
+ describe('Audiences Import', () => {
9
+ let config: ImportConfig;
10
+ let createAudienceCalls: Array<{ name: string }> = [];
11
+
12
+ const test = fancy.stdout({ print: process.env.PRINT === 'true' || false });
13
+
14
+ beforeEach(() => {
15
+ config = cloneDeep(importConf) as unknown as ImportConfig;
16
+ createAudienceCalls = [];
17
+ // Audiences uses modules.personalize and region - add them for tests
18
+ config.modules.personalize = {
19
+ ...(config.modules as any).personalization,
20
+ dirName: 'personalize',
21
+ baseURL: {
22
+ na: 'https://personalization.na-api.contentstack.com',
23
+ eu: 'https://personalization.eu-api.contentstack.com',
24
+ },
25
+ } as any;
26
+ config.region = { name: 'eu' } as any;
27
+ config.context = config.context || {};
28
+ });
29
+
30
+ describe('import method - Lytics audience skip', () => {
31
+ test
32
+ .stub(Import.Audiences.prototype, 'init', async () => {})
33
+ .stub(Import.Audiences.prototype, 'createAudience', (async (payload: any) => {
34
+ createAudienceCalls.push({ name: payload.name });
35
+ return { uid: `new-${payload.name.replace(/\s/g, '-')}`, name: payload.name };
36
+ }) as any)
37
+ .it('should skip Lytics audiences and not call createAudience for them', async () => {
38
+ const audiencesInstance = new Import.Audiences(config);
39
+ await audiencesInstance.import();
40
+
41
+ const lyticsNames = createAudienceCalls.filter(
42
+ (c) => c.name === 'Lytics Audience' || c.name === 'Lytics Lowercase',
43
+ );
44
+ expect(lyticsNames.length).to.equal(0);
45
+ });
46
+
47
+ test
48
+ .stub(Import.Audiences.prototype, 'init', async () => {})
49
+ .stub(Import.Audiences.prototype, 'createAudience', (async (payload: any) => {
50
+ createAudienceCalls.push({ name: payload.name });
51
+ return { uid: `new-${payload.name.replace(/\s/g, '-')}`, name: payload.name };
52
+ }) as any)
53
+ .it('should process audiences with undefined source', async () => {
54
+ const audiencesInstance = new Import.Audiences(config);
55
+ await audiencesInstance.import();
56
+
57
+ const noSourceCall = createAudienceCalls.find((c) => c.name === 'No Source Audience');
58
+ expect(noSourceCall).to.not.be.undefined;
59
+ });
60
+
61
+ test
62
+ .stub(Import.Audiences.prototype, 'init', async () => {})
63
+ .stub(Import.Audiences.prototype, 'createAudience', (async (payload: any) => {
64
+ createAudienceCalls.push({ name: payload.name });
65
+ return { uid: `new-${payload.name.replace(/\s/g, '-')}`, name: payload.name };
66
+ }) as any)
67
+ .it('should skip audience with source "lytics" (lowercase)', async () => {
68
+ const audiencesInstance = new Import.Audiences(config);
69
+ await audiencesInstance.import();
70
+
71
+ const lyticsLowercaseCall = createAudienceCalls.find((c) => c.name === 'Lytics Lowercase');
72
+ expect(lyticsLowercaseCall).to.be.undefined;
73
+ });
74
+
75
+ test
76
+ .stub(Import.Audiences.prototype, 'init', async () => {})
77
+ .stub(Import.Audiences.prototype, 'createAudience', (async (payload: any) => {
78
+ createAudienceCalls.push({ name: payload.name });
79
+ return { uid: `new-uid-${payload.name}`, name: payload.name };
80
+ }) as any)
81
+ .it('should call createAudience only for non-Lytics audiences', async () => {
82
+ const audiencesInstance = new Import.Audiences(config);
83
+ await audiencesInstance.import();
84
+
85
+ // 4 audiences in mock: 2 Lytics (skip), 2 non-Lytics (Contentstack Test, No Source)
86
+ expect(createAudienceCalls.length).to.equal(2);
87
+ });
88
+
89
+ test
90
+ .stub(Import.Audiences.prototype, 'init', async () => {})
91
+ .stub(Import.Audiences.prototype, 'createAudience', (async (payload: any) => {
92
+ createAudienceCalls.push({ name: payload.name });
93
+ return { uid: 'new-contentstack-uid', name: payload.name };
94
+ }) as any)
95
+ .it('should not add Lytics audiences to audiencesUidMapper', async () => {
96
+ const audiencesInstance = new Import.Audiences(config);
97
+ await audiencesInstance.import();
98
+
99
+ const mapper = (audiencesInstance as any).audiencesUidMapper;
100
+ expect(mapper['lytics-audience-001']).to.be.undefined;
101
+ expect(mapper['lytics-lowercase-001']).to.be.undefined;
102
+ });
103
+
104
+ test
105
+ .stub(Import.Audiences.prototype, 'init', async () => {})
106
+ .stub(Import.Audiences.prototype, 'createAudience', (async (payload: any) => {
107
+ createAudienceCalls.push({ name: payload.name });
108
+ return { uid: 'new-contentstack-uid', name: payload.name };
109
+ }) as any)
110
+ .it('should add Contentstack audiences to audiencesUidMapper', async () => {
111
+ const audiencesInstance = new Import.Audiences(config);
112
+ await audiencesInstance.import();
113
+
114
+ const mapper = (audiencesInstance as any).audiencesUidMapper;
115
+ expect(mapper['contentstack-audience-001']).to.equal('new-contentstack-uid');
116
+ });
117
+ });
118
+ });
@@ -0,0 +1 @@
1
+ {"contentstack-audience-001":"new-contentstack-uid","no-source-audience-001":"new-contentstack-uid"}
@@ -0,0 +1,44 @@
1
+ [
2
+ {
3
+ "uid": "contentstack-audience-001",
4
+ "name": "Contentstack Test Audience",
5
+ "description": "Audience with rules",
6
+ "definition": {
7
+ "__type": "RuleCombination",
8
+ "combinationType": "AND",
9
+ "rules": [
10
+ {
11
+ "__type": "Rule",
12
+ "attribute": { "__type": "PresetAttributeReference", "ref": "DEVICE_TYPE" },
13
+ "attributeMatchOptions": { "__type": "StringMatchOptions", "value": "MOBILE" },
14
+ "attributeMatchCondition": "STRING_EQUALS",
15
+ "invertCondition": false
16
+ }
17
+ ]
18
+ }
19
+ },
20
+ {
21
+ "uid": "lytics-audience-001",
22
+ "name": "Lytics Audience",
23
+ "description": "From Lytics",
24
+ "slug": "lytics_audience",
25
+ "source": "LYTICS"
26
+ },
27
+ {
28
+ "uid": "lytics-lowercase-001",
29
+ "name": "Lytics Lowercase",
30
+ "description": "source is lowercase",
31
+ "slug": "lytics_lowercase",
32
+ "source": "lytics"
33
+ },
34
+ {
35
+ "uid": "no-source-audience-001",
36
+ "name": "No Source Audience",
37
+ "description": "Audience without source field",
38
+ "definition": {
39
+ "__type": "RuleCombination",
40
+ "combinationType": "AND",
41
+ "rules": []
42
+ }
43
+ }
44
+ ]