@contentstack/cli-variants 1.3.3 → 2.0.0-beta
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.
- package/lib/export/attributes.d.ts +2 -2
- package/lib/export/attributes.js +51 -23
- package/lib/export/audiences.d.ts +2 -2
- package/lib/export/audiences.js +50 -24
- package/lib/export/events.d.ts +2 -2
- package/lib/export/events.js +52 -24
- package/lib/export/experiences.js +87 -54
- package/lib/export/projects.d.ts +3 -2
- package/lib/export/projects.js +55 -63
- package/lib/export/variant-entries.d.ts +19 -0
- package/lib/export/variant-entries.js +76 -1
- package/lib/import/attribute.d.ts +2 -0
- package/lib/import/attribute.js +83 -37
- package/lib/import/audiences.d.ts +2 -0
- package/lib/import/audiences.js +85 -41
- package/lib/import/events.d.ts +3 -1
- package/lib/import/events.js +86 -30
- package/lib/import/experiences.d.ts +2 -0
- package/lib/import/experiences.js +93 -39
- package/lib/import/project.d.ts +2 -0
- package/lib/import/project.js +81 -22
- package/lib/import/variant-entries.d.ts +10 -0
- package/lib/import/variant-entries.js +132 -47
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/types/export-config.d.ts +0 -2
- package/lib/types/import-config.d.ts +0 -1
- package/lib/types/utils.d.ts +1 -1
- package/lib/utils/constants.d.ts +91 -0
- package/lib/utils/constants.js +93 -0
- package/lib/utils/personalization-api-adapter.d.ts +34 -1
- package/lib/utils/personalization-api-adapter.js +171 -44
- package/lib/utils/variant-api-adapter.d.ts +28 -1
- package/lib/utils/variant-api-adapter.js +75 -0
- package/package.json +1 -1
- package/src/export/attributes.ts +84 -34
- package/src/export/audiences.ts +87 -41
- package/src/export/events.ts +84 -41
- package/src/export/experiences.ts +155 -83
- package/src/export/projects.ts +71 -39
- package/src/export/variant-entries.ts +136 -12
- package/src/import/attribute.ts +105 -49
- package/src/import/audiences.ts +110 -54
- package/src/import/events.ts +104 -41
- package/src/import/experiences.ts +140 -62
- package/src/import/project.ts +108 -38
- package/src/import/variant-entries.ts +179 -65
- package/src/index.ts +2 -1
- package/src/types/export-config.ts +0 -2
- package/src/types/import-config.ts +0 -1
- package/src/types/utils.ts +1 -1
- package/src/utils/constants.ts +98 -0
- package/src/utils/personalization-api-adapter.ts +202 -66
- package/src/utils/variant-api-adapter.ts +82 -1
- package/tsconfig.json +1 -1
package/src/import/audiences.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync } from 'fs';
|
|
|
3
3
|
import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities';
|
|
4
4
|
import { APIConfig, AudienceStruct, ImportConfig } from '../types';
|
|
5
5
|
import { PersonalizationAdapter, fsUtil, lookUpAttributes } from '../utils';
|
|
6
|
+
import { PROCESS_NAMES, MODULE_CONTEXTS, IMPORT_PROCESS_STATUS } from '../utils/constants';
|
|
6
7
|
|
|
7
8
|
export default class Audiences extends PersonalizationAdapter<ImportConfig> {
|
|
8
9
|
private mapperDirPath: string;
|
|
@@ -13,15 +14,16 @@ export default class Audiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
13
14
|
private personalizeConfig: ImportConfig['modules']['personalize'];
|
|
14
15
|
private audienceConfig: ImportConfig['modules']['personalize']['audiences'];
|
|
15
16
|
public attributeConfig: ImportConfig['modules']['personalize']['attributes'];
|
|
17
|
+
private audiences: AudienceStruct[];
|
|
16
18
|
|
|
17
|
-
constructor(public readonly config: ImportConfig
|
|
19
|
+
constructor(public readonly config: ImportConfig) {
|
|
18
20
|
const conf: APIConfig = {
|
|
19
21
|
config,
|
|
20
22
|
baseURL: config.modules.personalize.baseURL[config.region.name],
|
|
21
23
|
headers: { 'X-Project-Uid': config.modules.personalize.project_id },
|
|
22
24
|
};
|
|
23
25
|
super(Object.assign(config, conf));
|
|
24
|
-
|
|
26
|
+
|
|
25
27
|
this.personalizeConfig = this.config.modules.personalize;
|
|
26
28
|
this.audienceConfig = this.personalizeConfig.audiences;
|
|
27
29
|
this.attributeConfig = this.personalizeConfig.attributes;
|
|
@@ -38,68 +40,122 @@ export default class Audiences extends PersonalizationAdapter<ImportConfig> {
|
|
|
38
40
|
'uid-mapping.json',
|
|
39
41
|
);
|
|
40
42
|
this.audiencesUidMapper = {};
|
|
41
|
-
this.config.context.module =
|
|
43
|
+
this.config.context.module = MODULE_CONTEXTS.AUDIENCES;
|
|
44
|
+
this.audiences = [];
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
/**
|
|
45
48
|
* The function asynchronously imports audiences from a JSON file and creates them in the system.
|
|
46
49
|
*/
|
|
47
|
-
async import() {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
log.debug(`Created mapper directory: ${this.audienceMapperDirPath}`, this.config.context);
|
|
51
|
-
|
|
52
|
-
const { dirName, fileName } = this.audienceConfig;
|
|
53
|
-
const audiencesPath = resolve(
|
|
54
|
-
sanitizePath(this.config.data),
|
|
55
|
-
sanitizePath(this.personalizeConfig.dirName),
|
|
56
|
-
sanitizePath(dirName),
|
|
57
|
-
sanitizePath(fileName),
|
|
58
|
-
);
|
|
50
|
+
async import() {
|
|
51
|
+
try {
|
|
52
|
+
log.debug('Starting audiences import...', this.config.context);
|
|
59
53
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
54
|
+
const [canImport, audiencesCount] = await this.analyzeAudiences();
|
|
55
|
+
if (!canImport) {
|
|
56
|
+
log.info('No audiences found to import', this.config.context);
|
|
57
|
+
// Still need to mark as complete for parent progress
|
|
58
|
+
if (this.parentProgressManager) {
|
|
59
|
+
this.parentProgressManager.tick(true, 'audiences module (no data)', null, PROCESS_NAMES.AUDIENCES);
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Don't create own progress manager if we have a parent
|
|
65
|
+
let progress;
|
|
66
|
+
if (this.parentProgressManager) {
|
|
67
|
+
progress = this.parentProgressManager;
|
|
68
|
+
log.debug('Using parent progress manager for audiences import', this.config.context);
|
|
69
|
+
this.parentProgressManager.updateProcessTotal(PROCESS_NAMES.AUDIENCES, audiencesCount);
|
|
70
|
+
} else {
|
|
71
|
+
progress = this.createSimpleProgress(PROCESS_NAMES.AUDIENCES, audiencesCount);
|
|
72
|
+
log.debug('Created standalone progress manager for audiences import', this.config.context);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await this.init();
|
|
76
|
+
await fsUtil.makeDirectory(this.audienceMapperDirPath);
|
|
77
|
+
log.debug(`Created mapper directory: ${this.audienceMapperDirPath}`, this.config.context);
|
|
78
|
+
|
|
79
|
+
const attributesUid = (fsUtil.readFile(this.attributesMapperPath, true) as Record<string, string>) || {};
|
|
80
|
+
log.debug(
|
|
81
|
+
`Loaded ${Object.keys(attributesUid).length} attribute mappings for audience processing`,
|
|
82
|
+
this.config.context,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
for (const audience of this.audiences) {
|
|
86
|
+
let { name, definition, description, uid } = audience;
|
|
87
|
+
if (!this.parentProgressManager) {
|
|
88
|
+
progress.updateStatus(IMPORT_PROCESS_STATUS[PROCESS_NAMES.AUDIENCES].CREATING);
|
|
89
|
+
}
|
|
90
|
+
log.debug(`Processing audience: ${name} (${uid})`, this.config.context);
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
//check whether reference attributes exists or not
|
|
94
|
+
if (definition.rules?.length) {
|
|
95
|
+
log.debug(
|
|
96
|
+
`Processing ${definition.rules.length} definition rules for audience: ${name}`,
|
|
97
|
+
this.config.context,
|
|
98
|
+
);
|
|
99
|
+
definition.rules = lookUpAttributes(definition.rules, attributesUid);
|
|
100
|
+
log.debug(`Processed definition rules, remaining rules: ${definition.rules.length}`, this.config.context);
|
|
101
|
+
} else {
|
|
102
|
+
log.debug(`No definition rules found for audience: ${name}`, this.config.context);
|
|
92
103
|
}
|
|
104
|
+
|
|
105
|
+
log.debug(`Creating audience: ${name}`, this.config.context);
|
|
106
|
+
const audienceRes = await this.createAudience({ definition, name, description });
|
|
107
|
+
//map old audience uid to new audience uid
|
|
108
|
+
//mapper file is used to check whether audience created or not before creating experience
|
|
109
|
+
this.audiencesUidMapper[uid] = audienceRes?.uid ?? '';
|
|
110
|
+
|
|
111
|
+
this.updateProgress(true, `audience: ${name}`, undefined, PROCESS_NAMES.AUDIENCES);
|
|
112
|
+
log.debug(`Created audience: ${uid} -> ${audienceRes?.uid}`, this.config.context);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
this.updateProgress(false, `audience: ${name}`, (error as any)?.message, PROCESS_NAMES.AUDIENCES);
|
|
115
|
+
handleAndLogError(error, this.config.context, `Failed to create audience: ${name} (${uid})`);
|
|
93
116
|
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fsUtil.writeFile(this.audiencesUidMapperPath, this.audiencesUidMapper);
|
|
120
|
+
log.debug(`Saved ${Object.keys(this.audiencesUidMapper).length} audience mappings`, this.config.context);
|
|
94
121
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
} catch (error) {
|
|
99
|
-
handleAndLogError(error, this.config.context);
|
|
122
|
+
// Only complete progress if we own the progress manager (no parent)
|
|
123
|
+
if (!this.parentProgressManager) {
|
|
124
|
+
this.completeProgress(true);
|
|
100
125
|
}
|
|
101
|
-
|
|
102
|
-
log.
|
|
126
|
+
|
|
127
|
+
log.success(`Audiences imported successfully! Total audiences: ${audiencesCount}`, this.config.context);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (!this.parentProgressManager) {
|
|
130
|
+
this.completeProgress(false, (error as any)?.message || 'Audiences import failed');
|
|
131
|
+
}
|
|
132
|
+
handleAndLogError(error, this.config.context);
|
|
133
|
+
throw error;
|
|
103
134
|
}
|
|
104
135
|
}
|
|
136
|
+
|
|
137
|
+
private async analyzeAudiences(): Promise<[boolean, number]> {
|
|
138
|
+
return this.withLoadingSpinner('AUDIENCES: Analyzing import data...', async () => {
|
|
139
|
+
const { dirName, fileName } = this.audienceConfig;
|
|
140
|
+
const audiencesPath = resolve(
|
|
141
|
+
sanitizePath(this.config.data),
|
|
142
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
143
|
+
sanitizePath(dirName),
|
|
144
|
+
sanitizePath(fileName),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
log.debug(`Checking for audiences file: ${audiencesPath}`, this.config.context);
|
|
148
|
+
|
|
149
|
+
if (!existsSync(audiencesPath)) {
|
|
150
|
+
log.warn(`Audiences file not found: ${audiencesPath}`, this.config.context);
|
|
151
|
+
return [false, 0];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
this.audiences = fsUtil.readFile(audiencesPath, true) as AudienceStruct[];
|
|
155
|
+
const audiencesCount = this.audiences?.length || 0;
|
|
156
|
+
|
|
157
|
+
log.debug(`Found ${audiencesCount} audiences to import`, this.config.context);
|
|
158
|
+
return [audiencesCount > 0, audiencesCount];
|
|
159
|
+
});
|
|
160
|
+
}
|
|
105
161
|
}
|
package/src/import/events.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync } from 'fs';
|
|
|
3
3
|
import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities';
|
|
4
4
|
import { PersonalizationAdapter, fsUtil } from '../utils';
|
|
5
5
|
import { APIConfig, EventStruct, ImportConfig } from '../types';
|
|
6
|
+
import { PROCESS_NAMES, MODULE_CONTEXTS, IMPORT_PROCESS_STATUS } from '../utils/constants';
|
|
6
7
|
|
|
7
8
|
export default class Events extends PersonalizationAdapter<ImportConfig> {
|
|
8
9
|
private mapperDirPath: string;
|
|
@@ -10,7 +11,8 @@ export default class Events extends PersonalizationAdapter<ImportConfig> {
|
|
|
10
11
|
private eventsUidMapperPath: string;
|
|
11
12
|
private eventsUidMapper: Record<string, unknown>;
|
|
12
13
|
private personalizeConfig: ImportConfig['modules']['personalize'];
|
|
13
|
-
private
|
|
14
|
+
private eventConfig: ImportConfig['modules']['personalize']['events'];
|
|
15
|
+
private events: EventStruct[];
|
|
14
16
|
|
|
15
17
|
constructor(public readonly config: ImportConfig) {
|
|
16
18
|
const conf: APIConfig = {
|
|
@@ -19,65 +21,126 @@ export default class Events extends PersonalizationAdapter<ImportConfig> {
|
|
|
19
21
|
headers: { 'X-Project-Uid': config.modules.personalize.project_id },
|
|
20
22
|
};
|
|
21
23
|
super(Object.assign(config, conf));
|
|
22
|
-
|
|
24
|
+
|
|
23
25
|
this.personalizeConfig = this.config.modules.personalize;
|
|
24
|
-
this.
|
|
26
|
+
this.eventConfig = this.personalizeConfig.events;
|
|
25
27
|
this.mapperDirPath = resolve(
|
|
26
28
|
sanitizePath(this.config.backupDir),
|
|
27
29
|
'mapper',
|
|
28
30
|
sanitizePath(this.personalizeConfig.dirName),
|
|
29
31
|
);
|
|
30
|
-
this.eventMapperDirPath = resolve(sanitizePath(this.mapperDirPath), sanitizePath(this.
|
|
32
|
+
this.eventMapperDirPath = resolve(sanitizePath(this.mapperDirPath), sanitizePath(this.eventConfig.dirName));
|
|
31
33
|
this.eventsUidMapperPath = resolve(sanitizePath(this.eventMapperDirPath), 'uid-mapping.json');
|
|
32
34
|
this.eventsUidMapper = {};
|
|
33
|
-
this.
|
|
35
|
+
this.events = [];
|
|
36
|
+
this.config.context.module = MODULE_CONTEXTS.EVENTS;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
40
|
* The function asynchronously imports events from a JSON file and creates them in the system.
|
|
38
41
|
*/
|
|
39
42
|
async import() {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
try {
|
|
44
|
+
log.debug('Starting events import...', this.config.context);
|
|
45
|
+
|
|
46
|
+
const [canImport, eventsCount] = await this.analyzeEvents();
|
|
47
|
+
if (!canImport) {
|
|
48
|
+
log.info('No events found to import', this.config.context);
|
|
49
|
+
// Still need to mark as complete for parent progress
|
|
50
|
+
if (this.parentProgressManager) {
|
|
51
|
+
this.parentProgressManager.tick(true, 'events module (no data)', null, PROCESS_NAMES.EVENTS);
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Don't create own progress manager if we have a parent
|
|
57
|
+
let progress;
|
|
58
|
+
if (this.parentProgressManager) {
|
|
59
|
+
progress = this.parentProgressManager;
|
|
60
|
+
log.debug('Using parent progress manager for events import', this.config.context);
|
|
61
|
+
this.parentProgressManager.updateProcessTotal(PROCESS_NAMES.EVENTS, eventsCount);
|
|
62
|
+
} else {
|
|
63
|
+
progress = this.createSimpleProgress(PROCESS_NAMES.EVENTS, eventsCount);
|
|
64
|
+
log.debug('Created standalone progress manager for events import', this.config.context);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await this.init();
|
|
68
|
+
await fsUtil.makeDirectory(this.eventMapperDirPath);
|
|
69
|
+
log.debug(`Created mapper directory: ${this.eventMapperDirPath}`, this.config.context);
|
|
70
|
+
|
|
71
|
+
log.info(`Processing ${eventsCount} events`, this.config.context);
|
|
72
|
+
|
|
73
|
+
for (const event of this.events) {
|
|
74
|
+
const { key, description, uid } = event;
|
|
75
|
+
if (!this.parentProgressManager) {
|
|
76
|
+
progress.updateStatus(IMPORT_PROCESS_STATUS[PROCESS_NAMES.EVENTS].CREATING);
|
|
77
|
+
}
|
|
78
|
+
log.debug(`Processing event: ${key} (${uid})`, this.config.context);
|
|
51
79
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
handleAndLogError(error, this.config.context, `Failed to create event: ${key} (${uid})`);
|
|
80
|
+
try {
|
|
81
|
+
log.debug(`Creating event: ${key}`, this.config.context);
|
|
82
|
+
const eventRes = await this.createEvents({ key, description });
|
|
83
|
+
this.eventsUidMapper[uid] = eventRes?.uid ?? '';
|
|
84
|
+
|
|
85
|
+
// For parent progress manager, we don't need to specify process name as it will be handled automatically
|
|
86
|
+
if (this.parentProgressManager) {
|
|
87
|
+
this.updateProgress(true, `event: ${key}`);
|
|
88
|
+
} else {
|
|
89
|
+
this.updateProgress(true, `event: ${key}`, undefined, PROCESS_NAMES.EVENTS);
|
|
90
|
+
}
|
|
91
|
+
log.debug(`Created event: ${uid} -> ${eventRes?.uid}`, this.config.context);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (this.parentProgressManager) {
|
|
94
|
+
this.updateProgress(false, `event: ${key}`, (error as any)?.message);
|
|
95
|
+
} else {
|
|
96
|
+
this.updateProgress(false, `event: ${key}`, (error as any)?.message, PROCESS_NAMES.EVENTS);
|
|
70
97
|
}
|
|
98
|
+
handleAndLogError(error, this.config.context, `Failed to create event: ${key} (${uid})`);
|
|
71
99
|
}
|
|
100
|
+
}
|
|
72
101
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
102
|
+
fsUtil.writeFile(this.eventsUidMapperPath, this.eventsUidMapper);
|
|
103
|
+
log.debug(`Saved ${Object.keys(this.eventsUidMapper).length} event mappings`, this.config.context);
|
|
104
|
+
|
|
105
|
+
// Only complete progress if we own the progress manager (no parent)
|
|
106
|
+
if (!this.parentProgressManager) {
|
|
107
|
+
this.completeProgress(true);
|
|
108
|
+
}
|
|
109
|
+
log.success(
|
|
110
|
+
`Events imported successfully! Total events: ${eventsCount} - personalization enabled`,
|
|
111
|
+
this.config.context,
|
|
112
|
+
);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (!this.parentProgressManager) {
|
|
115
|
+
this.completeProgress(false, (error as any)?.message || 'Events import failed');
|
|
78
116
|
}
|
|
79
|
-
|
|
80
|
-
|
|
117
|
+
handleAndLogError(error, this.config.context);
|
|
118
|
+
throw error;
|
|
81
119
|
}
|
|
82
120
|
}
|
|
121
|
+
|
|
122
|
+
private async analyzeEvents(): Promise<[boolean, number]> {
|
|
123
|
+
return this.withLoadingSpinner('EVENTS: Analyzing import data...', async () => {
|
|
124
|
+
const { dirName, fileName } = this.eventConfig;
|
|
125
|
+
const eventsPath = resolve(
|
|
126
|
+
sanitizePath(this.config.data),
|
|
127
|
+
sanitizePath(this.personalizeConfig.dirName),
|
|
128
|
+
sanitizePath(dirName),
|
|
129
|
+
sanitizePath(fileName),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
log.debug(`Checking for events file: ${eventsPath}`, this.config.context);
|
|
133
|
+
|
|
134
|
+
if (!existsSync(eventsPath)) {
|
|
135
|
+
log.warn(`Events file not found: ${eventsPath}`, this.config.context);
|
|
136
|
+
return [false, 0];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.events = fsUtil.readFile(eventsPath, true) as EventStruct[];
|
|
140
|
+
const eventsCount = this.events?.length || 0;
|
|
141
|
+
|
|
142
|
+
log.debug(`Found ${eventsCount} events to import`, this.config.context);
|
|
143
|
+
return [eventsCount > 0, eventsCount];
|
|
144
|
+
});
|
|
145
|
+
}
|
|
83
146
|
}
|