@contrail/cli 3.5.3-alpha.0e9434c → 3.5.3-alpha.317fe63
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/README.md +14 -14
- package/dist/commands/projects/export.d.ts +1 -0
- package/dist/commands/projects/export.js +19 -5
- package/dist/commands/workflow-templates/export.d.ts +1 -3
- package/dist/commands/workflow-templates/export.js +28 -105
- package/dist/helpers/projects/helpers.d.ts +2 -1
- package/dist/helpers/projects/helpers.js +37 -2
- package/dist/helpers/projects/post-processor.d.ts +131 -0
- package/dist/helpers/projects/post-processor.js +519 -0
- package/dist/helpers/workflow-templates/helper.d.ts +0 -1
- package/dist/helpers/workflow-templates/helper.js +6 -27
- package/oclif.manifest.json +15 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ $ npm install -g @contrail/cli
|
|
|
17
17
|
$ contrail COMMAND
|
|
18
18
|
running command...
|
|
19
19
|
$ contrail (--version)
|
|
20
|
-
@contrail/cli/3.5.3-alpha.
|
|
20
|
+
@contrail/cli/3.5.3-alpha.317fe63 linux-x64 node-v24.19.0
|
|
21
21
|
$ contrail --help [COMMAND]
|
|
22
22
|
USAGE
|
|
23
23
|
$ contrail COMMAND
|
|
@@ -779,23 +779,29 @@ EXAMPLES
|
|
|
779
779
|
|
|
780
780
|
## `contrail projects export PROJECTNAME`
|
|
781
781
|
|
|
782
|
-
Export a project.
|
|
782
|
+
Export a project with optional post-processing.
|
|
783
783
|
|
|
784
784
|
```
|
|
785
785
|
USAGE
|
|
786
|
-
$ contrail projects export PROJECTNAME [-l error|warn|info|debug] [-e true|false]
|
|
786
|
+
$ contrail projects export PROJECTNAME [-l error|warn|info|debug] [-e true|false] [--process]
|
|
787
|
+
|
|
788
|
+
ARGUMENTS
|
|
789
|
+
PROJECTNAME Name or identifier of the project to export
|
|
787
790
|
|
|
788
791
|
FLAGS
|
|
789
792
|
-e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
|
|
790
793
|
<options: true|false>
|
|
791
794
|
-l, --logLevel=<option> The Log Level
|
|
792
795
|
<options: error|warn|info|debug>
|
|
796
|
+
--process Enable post-processing of exported project data
|
|
793
797
|
|
|
794
798
|
DESCRIPTION
|
|
795
|
-
Export a project.
|
|
799
|
+
Export a project with optional post-processing.
|
|
796
800
|
|
|
797
801
|
EXAMPLES
|
|
798
802
|
$ contrail project export <PROJECT_NAME>
|
|
803
|
+
|
|
804
|
+
$ contrail project export <PROJECT_NAME> --process
|
|
799
805
|
```
|
|
800
806
|
|
|
801
807
|
## `contrail projects list`
|
|
@@ -1611,31 +1617,25 @@ EXAMPLES
|
|
|
1611
1617
|
|
|
1612
1618
|
## `contrail workflow-templates export`
|
|
1613
1619
|
|
|
1614
|
-
Export all workflows in the org.
|
|
1620
|
+
Export all workflows in the org.
|
|
1615
1621
|
|
|
1616
1622
|
```
|
|
1617
1623
|
USAGE
|
|
1618
|
-
$ contrail workflow-templates export [-l error|warn|info|debug] [-e true|false] [-f <value>] [
|
|
1619
|
-
[--overwrite]
|
|
1624
|
+
$ contrail workflow-templates export [-l error|warn|info|debug] [-e true|false] [-f <value>] [--overwrite]
|
|
1620
1625
|
|
|
1621
1626
|
FLAGS
|
|
1622
|
-
-d, --dir=<value> [default: .] Directory to write per-app workflow files
|
|
1623
1627
|
-e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
|
|
1624
1628
|
<options: true|false>
|
|
1625
|
-
-f, --outFile=<value>
|
|
1629
|
+
-f, --outFile=<value> [default: workflows.yml] The location for the file to be saved
|
|
1626
1630
|
-l, --logLevel=<option> The Log Level
|
|
1627
1631
|
<options: error|warn|info|debug>
|
|
1628
1632
|
--overwrite Replaces existing files if they exist
|
|
1629
1633
|
|
|
1630
1634
|
DESCRIPTION
|
|
1631
|
-
Export all workflows in the org.
|
|
1635
|
+
Export all workflows in the org.
|
|
1632
1636
|
|
|
1633
1637
|
EXAMPLES
|
|
1634
1638
|
$ contrail workflow-templates export
|
|
1635
|
-
|
|
1636
|
-
$ contrail workflow-templates export --dir ./workflows
|
|
1637
|
-
|
|
1638
|
-
$ contrail workflow-templates export --outFile workflows.yml
|
|
1639
1639
|
```
|
|
1640
1640
|
|
|
1641
1641
|
## `contrail workflow-templates list`
|
|
@@ -3,6 +3,7 @@ export default class ExportProjects extends BaseCommand {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
|
+
process: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
6
7
|
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
7
8
|
logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
8
9
|
};
|
|
@@ -7,23 +7,37 @@ const login_1 = require("../../auth/login");
|
|
|
7
7
|
const helpers_1 = require("../../helpers/projects/helpers");
|
|
8
8
|
const basecommand_1 = require("../../basecommand");
|
|
9
9
|
class ExportProjects extends basecommand_1.BaseCommand {
|
|
10
|
-
static description = 'Export a project.';
|
|
10
|
+
static description = 'Export a project with optional post-processing.';
|
|
11
11
|
static examples = [
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
'$ contrail project export <PROJECT_NAME>',
|
|
13
|
+
'$ contrail project export <PROJECT_NAME> --process',
|
|
14
14
|
];
|
|
15
15
|
static flags = {
|
|
16
16
|
...basecommand_1.BaseCommand.baseFlags,
|
|
17
|
+
process: core_1.Flags.boolean({
|
|
18
|
+
description: 'Enable post-processing of exported project data',
|
|
19
|
+
default: false,
|
|
20
|
+
}),
|
|
17
21
|
};
|
|
18
22
|
static args = {
|
|
19
|
-
projectName: core_1.Args.string({
|
|
23
|
+
projectName: core_1.Args.string({
|
|
24
|
+
description: 'Name or identifier of the project to export',
|
|
25
|
+
required: true,
|
|
26
|
+
}),
|
|
20
27
|
};
|
|
21
28
|
async run() {
|
|
22
29
|
try {
|
|
23
30
|
await (0, login_1.authUser)();
|
|
24
31
|
this.log(`${chalk_1.default.blue.bold('START')} Exporting Project.`);
|
|
32
|
+
const processingOptions = {
|
|
33
|
+
removeBackings: true,
|
|
34
|
+
fixMissingIdentifiers: true,
|
|
35
|
+
cleanProperties: true,
|
|
36
|
+
analyzeOnly: false,
|
|
37
|
+
verbose: false,
|
|
38
|
+
};
|
|
25
39
|
this.log(`${chalk_1.default.yellow.bold('PROJECT')} Exporting`);
|
|
26
|
-
await (0, helpers_1.exportProject)(this.args.projectName);
|
|
40
|
+
await (0, helpers_1.exportProject)(this.args.projectName, this.flags.process ? processingOptions : null);
|
|
27
41
|
this.log(`${chalk_1.default.yellow.bold('PROJECT')} Exported`);
|
|
28
42
|
}
|
|
29
43
|
catch (e) {
|
|
@@ -5,12 +5,10 @@ export default class ExportWorkflows extends BaseCommand {
|
|
|
5
5
|
static examples: string[];
|
|
6
6
|
static flags: {
|
|
7
7
|
outFile: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
8
|
-
dir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
9
8
|
overwrite: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
9
|
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
11
10
|
logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
12
11
|
};
|
|
13
12
|
run(): Promise<void>;
|
|
14
13
|
}
|
|
15
|
-
export declare function
|
|
16
|
-
export declare function exportWorkflowsToFile(fileLocation: string, overwrite: boolean): Promise<void>;
|
|
14
|
+
export declare function exportWorkflows(fileLocation: string, overwrite: boolean): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.exportWorkflows = exports.DEFAULT_FILE_LOCATION = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const path = tslib_1.__importStar(require("path"));
|
|
6
6
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
@@ -11,30 +11,22 @@ const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
|
11
11
|
const core_1 = require("@oclif/core");
|
|
12
12
|
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
13
13
|
const event_workflow_templates_1 = require("../../api/event-workflow-templates");
|
|
14
|
-
const schedules_1 = require("../../api/schedules");
|
|
15
|
-
const apps_1 = require("../../api/apps");
|
|
16
14
|
const helper_1 = require("../../helpers/workflow-templates/helper");
|
|
17
15
|
const basecommand_1 = require("../../basecommand");
|
|
18
16
|
exports.DEFAULT_FILE_LOCATION = 'workflows.yml';
|
|
19
17
|
class ExportWorkflows extends basecommand_1.BaseCommand {
|
|
20
|
-
static description = 'Export all workflows in the org.
|
|
18
|
+
static description = 'Export all workflows in the org.';
|
|
21
19
|
static examples = [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
'$ contrail workflow-templates export --outFile workflows.yml',
|
|
20
|
+
`$ contrail workflow-templates export
|
|
21
|
+
`,
|
|
25
22
|
];
|
|
26
23
|
static flags = {
|
|
27
24
|
...basecommand_1.BaseCommand.baseFlags,
|
|
28
25
|
outFile: core_1.Flags.string({
|
|
29
26
|
char: 'f',
|
|
30
|
-
description: '
|
|
27
|
+
description: 'The location for the file to be saved',
|
|
31
28
|
required: false,
|
|
32
|
-
|
|
33
|
-
dir: core_1.Flags.string({
|
|
34
|
-
char: 'd',
|
|
35
|
-
description: 'Directory to write per-app workflow files',
|
|
36
|
-
required: false,
|
|
37
|
-
default: '.',
|
|
29
|
+
default: exports.DEFAULT_FILE_LOCATION,
|
|
38
30
|
}),
|
|
39
31
|
overwrite: core_1.Flags.boolean({
|
|
40
32
|
description: 'Replaces existing files if they exist',
|
|
@@ -46,12 +38,7 @@ class ExportWorkflows extends basecommand_1.BaseCommand {
|
|
|
46
38
|
try {
|
|
47
39
|
await (0, login_1.authUser)();
|
|
48
40
|
this.log(`${chalk_1.default.blue.bold('START')} Getting Workflows.`);
|
|
49
|
-
|
|
50
|
-
await exportWorkflowsToFile(this.flags.outFile, this.flags.overwrite);
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
await exportWorkflowsByApp(this.flags.dir ?? '.', this.flags.overwrite);
|
|
54
|
-
}
|
|
41
|
+
await exportWorkflows(this.flags.outFile ?? exports.DEFAULT_FILE_LOCATION, this.flags.overwrite);
|
|
55
42
|
this.log(`${chalk_1.default.yellow.bold('WORKFLOWS')} Done`);
|
|
56
43
|
}
|
|
57
44
|
catch (e) {
|
|
@@ -62,95 +49,31 @@ class ExportWorkflows extends basecommand_1.BaseCommand {
|
|
|
62
49
|
}
|
|
63
50
|
}
|
|
64
51
|
exports.default = ExportWorkflows;
|
|
65
|
-
async function
|
|
52
|
+
async function exportWorkflows(fileLocation, overwrite) {
|
|
66
53
|
const allWorkflowsInOrg = await (0, event_workflow_templates_1.getExistingWorkflowTemplates)();
|
|
67
|
-
const allSchedules = await (0, schedules_1.getExistingSchedules)();
|
|
68
|
-
const installedApps = await (0, apps_1.getInstalledApps)();
|
|
69
|
-
const appIdToIdentifier = new Map(installedApps.map((a) => [a.appId, a.app.identifier]));
|
|
70
|
-
const workflowsByApp = new Map();
|
|
71
|
-
const unassigned = [];
|
|
72
|
-
for (const workflow of allWorkflowsInOrg) {
|
|
73
|
-
let appIdentifier = workflow.supportingAppId
|
|
74
|
-
? appIdToIdentifier.get(workflow.supportingAppId)
|
|
75
|
-
: undefined;
|
|
76
|
-
// If no supportingAppId, try to extract app identifier from workflow identifier
|
|
77
|
-
// Format: @{appIdentifier}@{randomId}:{workflowName}
|
|
78
|
-
if (!appIdentifier && workflow.identifier) {
|
|
79
|
-
const match = workflow.identifier.match(/^@([^@]+)@/);
|
|
80
|
-
if (match) {
|
|
81
|
-
// Keep the @ prefix in the appIdentifier
|
|
82
|
-
appIdentifier = `@${match[1]}`;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (appIdentifier) {
|
|
86
|
-
if (!workflowsByApp.has(appIdentifier)) {
|
|
87
|
-
workflowsByApp.set(appIdentifier, []);
|
|
88
|
-
}
|
|
89
|
-
const workflows = workflowsByApp.get(appIdentifier);
|
|
90
|
-
if (workflows) {
|
|
91
|
-
workflows.push(workflow);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
unassigned.push(workflow);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const resolvedDir = path.resolve(process.cwd(), outputDir);
|
|
99
|
-
for (const [appIdentifier, workflows] of workflowsByApp.entries()) {
|
|
100
|
-
const exportObj = { appIdentifier, workflows: [] };
|
|
101
|
-
for (const workflow of workflows) {
|
|
102
|
-
exportObj.workflows.push(await buildExportWorkflow(workflow, allSchedules));
|
|
103
|
-
}
|
|
104
|
-
const safeAppIdentifier = appIdentifier.replace(/[\\/]/g, '-');
|
|
105
|
-
const filePath = path.join(resolvedDir, `workflows-${safeAppIdentifier}.yml`);
|
|
106
|
-
await writeYamlFile(exportObj, filePath, overwrite);
|
|
107
|
-
console.log(`${chalk_1.default.green('EXPORTED')} ${filePath}`);
|
|
108
|
-
}
|
|
109
|
-
if (unassigned.length > 0) {
|
|
110
|
-
const unassignedObj = { workflows: [] };
|
|
111
|
-
for (const workflow of unassigned) {
|
|
112
|
-
unassignedObj.workflows.push(await buildExportWorkflow(workflow, allSchedules));
|
|
113
|
-
}
|
|
114
|
-
const unassignedFilePath = path.join(resolvedDir, 'workflows-unassigned.yml');
|
|
115
|
-
await writeYamlFile(unassignedObj, unassignedFilePath, overwrite);
|
|
116
|
-
console.log(`${chalk_1.default.yellow('EXPORTED')} ${unassignedFilePath} (unassigned workflows)`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
exports.exportWorkflowsByApp = exportWorkflowsByApp;
|
|
120
|
-
async function exportWorkflowsToFile(fileLocation, overwrite) {
|
|
121
|
-
const allWorkflowsInOrg = await (0, event_workflow_templates_1.getExistingWorkflowTemplates)();
|
|
122
|
-
const allSchedules = await (0, schedules_1.getExistingSchedules)();
|
|
123
54
|
const workflowsObj = { workflows: [] };
|
|
124
55
|
for (const workflow of allWorkflowsInOrg) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
'isActive',
|
|
137
|
-
'messageGroup',
|
|
138
|
-
'dynamicMessageGroupId',
|
|
139
|
-
'parallelWorkerCount',
|
|
140
|
-
'workflowDefinition',
|
|
141
|
-
]);
|
|
142
|
-
const schedules = allSchedules.filter((s) => s.ownerReference === `event-workflow-template:${workflow.id}`);
|
|
143
|
-
if (schedules.length > 0) {
|
|
144
|
-
exportWorkflow.crons = schedules.map((s) => ({ name: s.name, cron: s.cron }));
|
|
56
|
+
const exportWorkflow = lodash_1.default.pick(workflow, [
|
|
57
|
+
'triggerKey',
|
|
58
|
+
'workflowDefinition',
|
|
59
|
+
'name',
|
|
60
|
+
'isActive',
|
|
61
|
+
'identifier',
|
|
62
|
+
'messageGroup',
|
|
63
|
+
'dynamicMessageGroupId',
|
|
64
|
+
]);
|
|
65
|
+
await (0, helper_1.populateActionIdentifiers)(exportWorkflow);
|
|
66
|
+
workflowsObj.workflows.push(exportWorkflow);
|
|
145
67
|
}
|
|
146
|
-
await (
|
|
147
|
-
(0, helper_1.extractShortActionKeys)(exportWorkflow);
|
|
148
|
-
return exportWorkflow;
|
|
68
|
+
await saveData(workflowsObj, fileLocation, overwrite);
|
|
149
69
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
70
|
+
exports.exportWorkflows = exportWorkflows;
|
|
71
|
+
async function saveData(data, fileLocation, overwrite) {
|
|
72
|
+
const yamlStr = js_yaml_1.default.dump(data);
|
|
73
|
+
const CURRENT_DIR = process.cwd();
|
|
74
|
+
const pathToWrite = path.resolve(path.join(CURRENT_DIR, fileLocation));
|
|
75
|
+
if (fs_1.default.existsSync(pathToWrite)) {
|
|
76
|
+
await (0, file_helpers_1.safeFileOverwrite)(pathToWrite, overwrite);
|
|
153
77
|
}
|
|
154
|
-
fs_1.default.
|
|
155
|
-
fs_1.default.writeFileSync(absolutePath, js_yaml_1.default.dump(data), 'utf8');
|
|
78
|
+
fs_1.default.writeFileSync(pathToWrite, yamlStr, 'utf8');
|
|
156
79
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { ProjectConfiguration } from '../../interfaces';
|
|
2
|
+
import { ProcessingOptions } from './post-processor';
|
|
2
3
|
export declare function getProjectFile(fileName: any): ProjectConfiguration;
|
|
3
|
-
export declare function exportProject(name:
|
|
4
|
+
export declare function exportProject(name: string, processingOptions?: ProcessingOptions | null): Promise<void>;
|
|
@@ -8,6 +8,7 @@ const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
|
8
8
|
const sdk_1 = require("@contrail/sdk");
|
|
9
9
|
const util_1 = require("@contrail/util");
|
|
10
10
|
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
11
|
+
const post_processor_1 = require("./post-processor");
|
|
11
12
|
function getProjectFile(fileName) {
|
|
12
13
|
const CURRENT_DIR = process.cwd();
|
|
13
14
|
try {
|
|
@@ -21,7 +22,7 @@ function getProjectFile(fileName) {
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
exports.getProjectFile = getProjectFile;
|
|
24
|
-
async function exportProject(name) {
|
|
25
|
+
async function exportProject(name, processingOptions) {
|
|
25
26
|
const allWorkspaces = await new sdk_1.Entities().get({
|
|
26
27
|
entityName: 'workspace',
|
|
27
28
|
useSearch: false,
|
|
@@ -42,7 +43,41 @@ async function exportProject(name) {
|
|
|
42
43
|
const allAssortments = [];
|
|
43
44
|
await getAssortmentsAndSubFolders(workspace.id, projectConfiguration.assortments, projectConfiguration.subFolders, allAssortments);
|
|
44
45
|
await convertRelationshipsInFolders(projectConfiguration, allAssortments);
|
|
45
|
-
|
|
46
|
+
let finalData = { projectConfiguration: projectConfiguration };
|
|
47
|
+
// Apply post-processing if requested
|
|
48
|
+
if (processingOptions) {
|
|
49
|
+
logger_1.default.info('Starting post-processing...');
|
|
50
|
+
const processor = new post_processor_1.ProjectPostProcessor(processingOptions);
|
|
51
|
+
const { data, results } = processor.processProjectData(finalData);
|
|
52
|
+
finalData = data;
|
|
53
|
+
// Log analysis results
|
|
54
|
+
if (processingOptions.verbose || processingOptions.analyzeOnly) {
|
|
55
|
+
logger_1.default.info('========== ANALYSIS RESULTS ==========');
|
|
56
|
+
logger_1.default.info(`Total objects analyzed: ${results.stats.totalObjects}`);
|
|
57
|
+
logger_1.default.info(`- Assortments: ${results.stats.totalAssortments}`);
|
|
58
|
+
logger_1.default.info(`- Plans: ${results.stats.totalPlans}`);
|
|
59
|
+
logger_1.default.info(`- Backings: ${results.stats.totalBackings}`);
|
|
60
|
+
logger_1.default.info(`- Folders: ${results.stats.totalFolders}`);
|
|
61
|
+
logger_1.default.info('\nIDENTIFIER ANALYSIS:');
|
|
62
|
+
logger_1.default.info(`- Missing identifiers: ${results.stats.missingIdentifiers}`);
|
|
63
|
+
logger_1.default.info(`- Duplicate identifiers: ${results.stats.duplicateIdentifiers}`);
|
|
64
|
+
logger_1.default.info(`- Empty identifiers: ${results.stats.emptyIdentifiers}`);
|
|
65
|
+
if (Object.keys(results.duplicates).length > 0) {
|
|
66
|
+
logger_1.default.info('\n=== DUPLICATE IDENTIFIERS ===');
|
|
67
|
+
for (const [id, info] of Object.entries(results.duplicates)) {
|
|
68
|
+
logger_1.default.info(`\n"${id}" (${info.count} occurrences):`);
|
|
69
|
+
info.names.forEach((nameItem, i) => {
|
|
70
|
+
logger_1.default.info(` ${i + 1}. "${nameItem}"`);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (processingOptions.analyzeOnly) {
|
|
76
|
+
logger_1.default.info('Analysis complete. No file written (analyze-only mode).');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
saveData(finalData, name);
|
|
46
81
|
}
|
|
47
82
|
exports.exportProject = exportProject;
|
|
48
83
|
async function getAssortmentsAndSubFolders(parentId, assortments, subFolders, allAssortments) {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration interface for processing options
|
|
3
|
+
*/
|
|
4
|
+
export interface ProcessingOptions {
|
|
5
|
+
removeBackings: boolean;
|
|
6
|
+
fixMissingIdentifiers: boolean;
|
|
7
|
+
cleanProperties: boolean;
|
|
8
|
+
analyzeOnly: boolean;
|
|
9
|
+
verbose: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Statistics tracking for all operations
|
|
13
|
+
*/
|
|
14
|
+
export interface ProcessingStats {
|
|
15
|
+
itemTypeConverted: number;
|
|
16
|
+
nbFXRatesRemoved: number;
|
|
17
|
+
createdByRemoved: number;
|
|
18
|
+
updatedByRemoved: number;
|
|
19
|
+
emptyAssortmentAttsRemoved: number;
|
|
20
|
+
itemTypeIdRemoved?: number;
|
|
21
|
+
identifiersCreated: number;
|
|
22
|
+
planIdentifiers: number;
|
|
23
|
+
backingIdentifiers: number;
|
|
24
|
+
folderIdentifiers: number;
|
|
25
|
+
viewsIdentifiers: number;
|
|
26
|
+
assortmentIdentifiers: number;
|
|
27
|
+
backingsRemoved: number;
|
|
28
|
+
removedBackingNames: string[];
|
|
29
|
+
totalObjects: number;
|
|
30
|
+
totalAssortments: number;
|
|
31
|
+
totalPlans: number;
|
|
32
|
+
totalBackings: number;
|
|
33
|
+
totalFolders: number;
|
|
34
|
+
duplicateIdentifiers: number;
|
|
35
|
+
emptyIdentifiers: number;
|
|
36
|
+
missingIdentifiers: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Duplicate identifier information
|
|
40
|
+
*/
|
|
41
|
+
export interface DuplicateInfo {
|
|
42
|
+
count: number;
|
|
43
|
+
names: string[];
|
|
44
|
+
paths: string[];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Analysis results interface
|
|
48
|
+
*/
|
|
49
|
+
export interface AnalysisResults {
|
|
50
|
+
stats: ProcessingStats;
|
|
51
|
+
duplicates: Record<string, DuplicateInfo>;
|
|
52
|
+
emptyIdentifierObjects: Array<{
|
|
53
|
+
name: string;
|
|
54
|
+
path: string;
|
|
55
|
+
}>;
|
|
56
|
+
missingIdentifierObjects: {
|
|
57
|
+
assortments: Array<{
|
|
58
|
+
name: string;
|
|
59
|
+
path: string;
|
|
60
|
+
}>;
|
|
61
|
+
plans: Array<{
|
|
62
|
+
name: string;
|
|
63
|
+
path: string;
|
|
64
|
+
}>;
|
|
65
|
+
folders: Array<{
|
|
66
|
+
name: string;
|
|
67
|
+
path: string;
|
|
68
|
+
}>;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Main class for processing project YAML data
|
|
73
|
+
*/
|
|
74
|
+
export declare class ProjectPostProcessor {
|
|
75
|
+
private stats;
|
|
76
|
+
private options;
|
|
77
|
+
private projectId;
|
|
78
|
+
constructor(options?: Partial<ProcessingOptions>);
|
|
79
|
+
private initializeStats;
|
|
80
|
+
/**
|
|
81
|
+
* Main processing method for project data
|
|
82
|
+
*/
|
|
83
|
+
processProjectData(projectData: any): {
|
|
84
|
+
data: any;
|
|
85
|
+
results: AnalysisResults;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Extract project ID from project data
|
|
89
|
+
*/
|
|
90
|
+
private extractProjectId;
|
|
91
|
+
/**
|
|
92
|
+
* Analyze project data for identifiers, types, and structure
|
|
93
|
+
*/
|
|
94
|
+
private analyzeProjectData;
|
|
95
|
+
/**
|
|
96
|
+
* Clean properties from project data
|
|
97
|
+
*/
|
|
98
|
+
private cleanProperties;
|
|
99
|
+
/**
|
|
100
|
+
* Process assortmentAtts object
|
|
101
|
+
*/
|
|
102
|
+
private processAssortmentAtts;
|
|
103
|
+
/**
|
|
104
|
+
* Fix missing identifiers
|
|
105
|
+
*/
|
|
106
|
+
private fixMissingIdentifiers;
|
|
107
|
+
/**
|
|
108
|
+
* Remove backing assortments
|
|
109
|
+
*/
|
|
110
|
+
private removeBackings;
|
|
111
|
+
/**
|
|
112
|
+
* Generate identifier for an object
|
|
113
|
+
*/
|
|
114
|
+
private generateIdentifier;
|
|
115
|
+
/**
|
|
116
|
+
* Determine object type
|
|
117
|
+
*/
|
|
118
|
+
private getObjectType;
|
|
119
|
+
/**
|
|
120
|
+
* Check if object should have an identifier
|
|
121
|
+
*/
|
|
122
|
+
private shouldHaveIdentifier;
|
|
123
|
+
/**
|
|
124
|
+
* Log processing summary
|
|
125
|
+
*/
|
|
126
|
+
private logProcessingSummary;
|
|
127
|
+
/**
|
|
128
|
+
* Get processing statistics
|
|
129
|
+
*/
|
|
130
|
+
getStats(): ProcessingStats;
|
|
131
|
+
}
|
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProjectPostProcessor = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
6
|
+
/**
|
|
7
|
+
* Main class for processing project YAML data
|
|
8
|
+
*/
|
|
9
|
+
class ProjectPostProcessor {
|
|
10
|
+
stats;
|
|
11
|
+
options;
|
|
12
|
+
projectId = null;
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.options = {
|
|
15
|
+
removeBackings: true,
|
|
16
|
+
fixMissingIdentifiers: true,
|
|
17
|
+
cleanProperties: true,
|
|
18
|
+
analyzeOnly: false,
|
|
19
|
+
verbose: false,
|
|
20
|
+
...options,
|
|
21
|
+
};
|
|
22
|
+
this.stats = this.initializeStats();
|
|
23
|
+
}
|
|
24
|
+
initializeStats() {
|
|
25
|
+
return {
|
|
26
|
+
itemTypeConverted: 0,
|
|
27
|
+
nbFXRatesRemoved: 0,
|
|
28
|
+
createdByRemoved: 0,
|
|
29
|
+
updatedByRemoved: 0,
|
|
30
|
+
emptyAssortmentAttsRemoved: 0,
|
|
31
|
+
identifiersCreated: 0,
|
|
32
|
+
planIdentifiers: 0,
|
|
33
|
+
backingIdentifiers: 0,
|
|
34
|
+
folderIdentifiers: 0,
|
|
35
|
+
viewsIdentifiers: 0,
|
|
36
|
+
assortmentIdentifiers: 0,
|
|
37
|
+
backingsRemoved: 0,
|
|
38
|
+
removedBackingNames: [],
|
|
39
|
+
totalObjects: 0,
|
|
40
|
+
totalAssortments: 0,
|
|
41
|
+
totalPlans: 0,
|
|
42
|
+
totalBackings: 0,
|
|
43
|
+
totalFolders: 0,
|
|
44
|
+
duplicateIdentifiers: 0,
|
|
45
|
+
emptyIdentifiers: 0,
|
|
46
|
+
missingIdentifiers: 0,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Main processing method for project data
|
|
51
|
+
*/
|
|
52
|
+
processProjectData(projectData) {
|
|
53
|
+
try {
|
|
54
|
+
if (this.options.verbose) {
|
|
55
|
+
logger_1.default.info('Starting project data processing...');
|
|
56
|
+
}
|
|
57
|
+
// Extract project ID
|
|
58
|
+
this.extractProjectId(projectData);
|
|
59
|
+
// Analysis phase
|
|
60
|
+
const analysisResults = this.analyzeProjectData(projectData);
|
|
61
|
+
if (this.options.analyzeOnly) {
|
|
62
|
+
return { data: projectData, results: analysisResults };
|
|
63
|
+
}
|
|
64
|
+
let processedData = projectData;
|
|
65
|
+
// Processing phase
|
|
66
|
+
if (this.options.cleanProperties) {
|
|
67
|
+
if (this.options.verbose)
|
|
68
|
+
logger_1.default.info('Cleaning properties...');
|
|
69
|
+
processedData = this.cleanProperties(processedData);
|
|
70
|
+
}
|
|
71
|
+
if (this.options.fixMissingIdentifiers) {
|
|
72
|
+
if (this.options.verbose)
|
|
73
|
+
logger_1.default.info('Fixing missing identifiers...');
|
|
74
|
+
processedData = this.fixMissingIdentifiers(processedData);
|
|
75
|
+
}
|
|
76
|
+
if (this.options.removeBackings) {
|
|
77
|
+
if (this.options.verbose)
|
|
78
|
+
logger_1.default.info('Removing backing assortments...');
|
|
79
|
+
processedData = this.removeBackings(processedData);
|
|
80
|
+
}
|
|
81
|
+
// Final analysis after processing
|
|
82
|
+
const finalAnalysis = this.analyzeProjectData(processedData);
|
|
83
|
+
if (this.options.verbose) {
|
|
84
|
+
this.logProcessingSummary();
|
|
85
|
+
}
|
|
86
|
+
return { data: processedData, results: finalAnalysis };
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
logger_1.default.error('Error processing project data:', error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Extract project ID from project data
|
|
95
|
+
*/
|
|
96
|
+
extractProjectId(projectData) {
|
|
97
|
+
if (projectData.projectConfiguration?.projectName) {
|
|
98
|
+
this.projectId = projectData.projectConfiguration.projectName;
|
|
99
|
+
}
|
|
100
|
+
else if (projectData.name?.match(/^S\d-\d+/)) {
|
|
101
|
+
const match = projectData.name.match(/^(S\d-\d+)/);
|
|
102
|
+
this.projectId = match ? match[1] : null;
|
|
103
|
+
}
|
|
104
|
+
if (this.options.verbose && this.projectId) {
|
|
105
|
+
logger_1.default.info(`Project ID identified: ${this.projectId}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Analyze project data for identifiers, types, and structure
|
|
110
|
+
*/
|
|
111
|
+
analyzeProjectData(projectData) {
|
|
112
|
+
const identifiers = {};
|
|
113
|
+
const duplicates = {};
|
|
114
|
+
const emptyIdentifierObjects = [];
|
|
115
|
+
const missingIdentifierObjects = {
|
|
116
|
+
assortments: [],
|
|
117
|
+
plans: [],
|
|
118
|
+
folders: [],
|
|
119
|
+
};
|
|
120
|
+
const analyzeObject = (obj, path = []) => {
|
|
121
|
+
if (!obj || typeof obj !== 'object')
|
|
122
|
+
return;
|
|
123
|
+
if (Array.isArray(obj)) {
|
|
124
|
+
obj.forEach((item, index) => {
|
|
125
|
+
analyzeObject(item, [...path, index.toString()]);
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.stats.totalObjects++;
|
|
130
|
+
// Analyze object with name
|
|
131
|
+
if (obj.name) {
|
|
132
|
+
// Determine object type
|
|
133
|
+
let objType = 'unknown';
|
|
134
|
+
if (obj.subFolders) {
|
|
135
|
+
objType = 'folder';
|
|
136
|
+
this.stats.totalFolders++;
|
|
137
|
+
}
|
|
138
|
+
if (obj.type) {
|
|
139
|
+
objType = obj.type.toLowerCase();
|
|
140
|
+
if (objType === 'plan')
|
|
141
|
+
this.stats.totalPlans++;
|
|
142
|
+
if (objType === 'assortment')
|
|
143
|
+
this.stats.totalAssortments++;
|
|
144
|
+
if (objType === 'backing')
|
|
145
|
+
this.stats.totalBackings++;
|
|
146
|
+
}
|
|
147
|
+
// Check identifier
|
|
148
|
+
if (obj.identifier !== undefined) {
|
|
149
|
+
const id = obj.identifier;
|
|
150
|
+
if (!id || id.trim() === '') {
|
|
151
|
+
this.stats.emptyIdentifiers++;
|
|
152
|
+
emptyIdentifierObjects.push({
|
|
153
|
+
name: obj.name,
|
|
154
|
+
path: path.join('.'),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
if (identifiers[id]) {
|
|
159
|
+
identifiers[id].count++;
|
|
160
|
+
identifiers[id].names.push(obj.name);
|
|
161
|
+
identifiers[id].paths.push(path.join('.'));
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
identifiers[id] = {
|
|
165
|
+
count: 1,
|
|
166
|
+
names: [obj.name],
|
|
167
|
+
paths: [path.join('.')],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else if (this.shouldHaveIdentifier(obj)) {
|
|
173
|
+
this.stats.missingIdentifiers++;
|
|
174
|
+
if (objType === 'assortment') {
|
|
175
|
+
missingIdentifierObjects.assortments.push({
|
|
176
|
+
name: obj.name,
|
|
177
|
+
path: path.join('.'),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
else if (objType === 'plan') {
|
|
181
|
+
missingIdentifierObjects.plans.push({
|
|
182
|
+
name: obj.name,
|
|
183
|
+
path: path.join('.'),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
else if (objType === 'folder') {
|
|
187
|
+
missingIdentifierObjects.folders.push({
|
|
188
|
+
name: obj.name,
|
|
189
|
+
path: path.join('.'),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Recursively analyze nested objects
|
|
195
|
+
for (const key in obj) {
|
|
196
|
+
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
197
|
+
analyzeObject(obj[key], [...path, key]);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
analyzeObject(projectData);
|
|
202
|
+
// Find duplicates
|
|
203
|
+
for (const [id, info] of Object.entries(identifiers)) {
|
|
204
|
+
if (info.count > 1) {
|
|
205
|
+
duplicates[id] = info;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
this.stats.duplicateIdentifiers = Object.keys(duplicates).length;
|
|
209
|
+
return {
|
|
210
|
+
stats: { ...this.stats },
|
|
211
|
+
duplicates,
|
|
212
|
+
emptyIdentifierObjects,
|
|
213
|
+
missingIdentifierObjects,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Clean properties from project data
|
|
218
|
+
*/
|
|
219
|
+
cleanProperties(projectData) {
|
|
220
|
+
const processObject = (obj) => {
|
|
221
|
+
if (!obj || typeof obj !== 'object')
|
|
222
|
+
return obj;
|
|
223
|
+
if (Array.isArray(obj)) {
|
|
224
|
+
return obj.map((item) => processObject(item));
|
|
225
|
+
}
|
|
226
|
+
const newObj = {};
|
|
227
|
+
for (const key in obj) {
|
|
228
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key))
|
|
229
|
+
continue;
|
|
230
|
+
if (key === 'itemType') {
|
|
231
|
+
// Handle itemType conversion
|
|
232
|
+
if (typeof obj[key] === 'object' && obj[key]?.typePath) {
|
|
233
|
+
newObj[key] = obj[key].typePath;
|
|
234
|
+
this.stats.itemTypeConverted++;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
newObj[key] = obj[key];
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else if (key === 'nbFXRates') {
|
|
241
|
+
// Remove nbFXRates
|
|
242
|
+
this.stats.nbFXRatesRemoved++;
|
|
243
|
+
if (this.options.verbose) {
|
|
244
|
+
logger_1.default.debug(`Removed nbFXRates from object: ${obj.name || 'unnamed'}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else if (key === 'updatedBy') {
|
|
248
|
+
// Remove updatedBy
|
|
249
|
+
this.stats.updatedByRemoved++;
|
|
250
|
+
}
|
|
251
|
+
else if (key === 'createdBy') {
|
|
252
|
+
// Remove createdBy
|
|
253
|
+
this.stats.createdByRemoved++;
|
|
254
|
+
}
|
|
255
|
+
else if (key === 'assortmentAtts' && typeof obj[key] === 'object') {
|
|
256
|
+
// Process assortmentAtts
|
|
257
|
+
const processedAtts = this.processAssortmentAtts(obj[key]);
|
|
258
|
+
if (Object.keys(processedAtts).length > 0) {
|
|
259
|
+
newObj[key] = processedAtts;
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
this.stats.emptyAssortmentAttsRemoved++;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (obj[key] === null) {
|
|
266
|
+
// Skip null values
|
|
267
|
+
}
|
|
268
|
+
else if (typeof obj[key] === 'object') {
|
|
269
|
+
newObj[key] = processObject(obj[key]);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
newObj[key] = obj[key];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return newObj;
|
|
276
|
+
};
|
|
277
|
+
return processObject(projectData);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Process assortmentAtts object
|
|
281
|
+
*/
|
|
282
|
+
processAssortmentAtts(assortmentAtts) {
|
|
283
|
+
const processed = {};
|
|
284
|
+
for (const key in assortmentAtts) {
|
|
285
|
+
if (key === 'updatedBy' || key === 'createdBy') {
|
|
286
|
+
// Skip these properties
|
|
287
|
+
if (key === 'updatedBy')
|
|
288
|
+
this.stats.updatedByRemoved++;
|
|
289
|
+
if (key === 'createdBy')
|
|
290
|
+
this.stats.createdByRemoved++;
|
|
291
|
+
}
|
|
292
|
+
else if (key === 'nbFXRates') {
|
|
293
|
+
// Skip nbFXRates
|
|
294
|
+
this.stats.nbFXRatesRemoved++;
|
|
295
|
+
}
|
|
296
|
+
else if (key === 'itemTypeId') {
|
|
297
|
+
// Skip itemTypeId
|
|
298
|
+
this.stats.itemTypeIdRemoved++;
|
|
299
|
+
}
|
|
300
|
+
else if (key === 'itemType' &&
|
|
301
|
+
typeof assortmentAtts[key] === 'object' &&
|
|
302
|
+
assortmentAtts[key]?.typePath) {
|
|
303
|
+
// Convert itemType object to string
|
|
304
|
+
processed[key] = assortmentAtts[key].typePath;
|
|
305
|
+
this.stats.itemTypeConverted++;
|
|
306
|
+
}
|
|
307
|
+
else if (assortmentAtts[key] !== null && assortmentAtts[key] !== undefined) {
|
|
308
|
+
// Keep other properties
|
|
309
|
+
processed[key] = assortmentAtts[key];
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return processed;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Fix missing identifiers
|
|
316
|
+
*/
|
|
317
|
+
fixMissingIdentifiers(projectData) {
|
|
318
|
+
const processObject = (obj, path = [], parentIdentifier = '') => {
|
|
319
|
+
if (!obj || typeof obj !== 'object')
|
|
320
|
+
return obj;
|
|
321
|
+
if (Array.isArray(obj)) {
|
|
322
|
+
return obj.map((item, index) => processObject(item, [...path, index.toString()], parentIdentifier));
|
|
323
|
+
}
|
|
324
|
+
const newObj = { ...obj };
|
|
325
|
+
// Generate identifier if missing
|
|
326
|
+
if (newObj.name && !newObj.identifier && this.shouldHaveIdentifier(newObj)) {
|
|
327
|
+
const identifier = this.generateIdentifier(newObj, parentIdentifier);
|
|
328
|
+
if (identifier) {
|
|
329
|
+
newObj.identifier = identifier;
|
|
330
|
+
this.stats.identifiersCreated++;
|
|
331
|
+
const objType = this.getObjectType(newObj);
|
|
332
|
+
if (objType === 'plan')
|
|
333
|
+
this.stats.planIdentifiers++;
|
|
334
|
+
else if (objType === 'assortment')
|
|
335
|
+
this.stats.assortmentIdentifiers++;
|
|
336
|
+
else if (objType === 'backing')
|
|
337
|
+
this.stats.backingIdentifiers++;
|
|
338
|
+
else if (objType === 'folder')
|
|
339
|
+
this.stats.folderIdentifiers++;
|
|
340
|
+
if (newObj.name.toLowerCase().includes('views')) {
|
|
341
|
+
this.stats.viewsIdentifiers++;
|
|
342
|
+
}
|
|
343
|
+
if (this.options.verbose) {
|
|
344
|
+
logger_1.default.debug(`Generated identifier "${identifier}" for "${newObj.name}"`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else if (newObj.identifier) {
|
|
349
|
+
parentIdentifier = newObj.identifier;
|
|
350
|
+
}
|
|
351
|
+
// Process nested objects
|
|
352
|
+
for (const key in newObj) {
|
|
353
|
+
if (typeof newObj[key] === 'object' && newObj[key] !== null && key !== 'identifier') {
|
|
354
|
+
newObj[key] = processObject(newObj[key], [...path, key], parentIdentifier);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return newObj;
|
|
358
|
+
};
|
|
359
|
+
return processObject(projectData);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Remove backing assortments
|
|
363
|
+
*/
|
|
364
|
+
removeBackings(projectData) {
|
|
365
|
+
const processObject = (obj) => {
|
|
366
|
+
if (!obj || typeof obj !== 'object')
|
|
367
|
+
return obj;
|
|
368
|
+
if (Array.isArray(obj)) {
|
|
369
|
+
const filtered = obj.filter((item) => {
|
|
370
|
+
if (item && item.type === 'BACKING') {
|
|
371
|
+
this.stats.backingsRemoved++;
|
|
372
|
+
if (item.name) {
|
|
373
|
+
this.stats.removedBackingNames.push(item.name);
|
|
374
|
+
}
|
|
375
|
+
if (this.options.verbose) {
|
|
376
|
+
logger_1.default.debug(`Removed backing: ${item.name || 'unnamed'}`);
|
|
377
|
+
}
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
return true;
|
|
381
|
+
});
|
|
382
|
+
return filtered.map((item) => processObject(item));
|
|
383
|
+
}
|
|
384
|
+
const newObj = {};
|
|
385
|
+
for (const key in obj) {
|
|
386
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
387
|
+
newObj[key] = processObject(obj[key]);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return newObj;
|
|
391
|
+
};
|
|
392
|
+
return processObject(projectData);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Generate identifier for an object
|
|
396
|
+
*/
|
|
397
|
+
generateIdentifier(obj, parentIdentifier) {
|
|
398
|
+
const objType = this.getObjectType(obj);
|
|
399
|
+
let typeSuffix = 'folder';
|
|
400
|
+
if (objType === 'plan')
|
|
401
|
+
typeSuffix = 'product.plan';
|
|
402
|
+
else if (objType === 'assortment')
|
|
403
|
+
typeSuffix = 'product.assortment';
|
|
404
|
+
else if (objType === 'backing')
|
|
405
|
+
typeSuffix = 'backing';
|
|
406
|
+
const sanitizeName = (name) => {
|
|
407
|
+
return name
|
|
408
|
+
.toLowerCase()
|
|
409
|
+
.replace(/'/g, '')
|
|
410
|
+
.replace(/&/g, 'and')
|
|
411
|
+
.replace(/\s+/g, '')
|
|
412
|
+
.replace(/[^\w]/g, '');
|
|
413
|
+
};
|
|
414
|
+
// Strategy 1: Extract from name pattern
|
|
415
|
+
const nameMatch = obj.name.match(/^(S\d-\d+)\s+(.+)$/);
|
|
416
|
+
if (nameMatch) {
|
|
417
|
+
const projectId = nameMatch[1];
|
|
418
|
+
const sanitizedName = sanitizeName(nameMatch[2]);
|
|
419
|
+
return `${projectId}:${sanitizedName}:${typeSuffix}`;
|
|
420
|
+
}
|
|
421
|
+
// Strategy 2: Views objects
|
|
422
|
+
if (obj.name.toLowerCase().includes('views')) {
|
|
423
|
+
let sanitizedName = 'main';
|
|
424
|
+
if (obj.name.startsWith('Views -')) {
|
|
425
|
+
sanitizedName = sanitizeName(obj.name.substring(8).trim());
|
|
426
|
+
}
|
|
427
|
+
else if (obj.name.toLowerCase() !== 'views') {
|
|
428
|
+
sanitizedName = sanitizeName(obj.name);
|
|
429
|
+
}
|
|
430
|
+
const baseId = this.projectId || parentIdentifier;
|
|
431
|
+
return `${baseId}:views:${sanitizedName}:${typeSuffix}`;
|
|
432
|
+
}
|
|
433
|
+
// Strategy 3: Training objects
|
|
434
|
+
if (obj.name.toLowerCase().includes('training')) {
|
|
435
|
+
let sanitizedName;
|
|
436
|
+
if (obj.name.startsWith('(Training)')) {
|
|
437
|
+
sanitizedName = sanitizeName(obj.name.substring(10).trim());
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
sanitizedName = sanitizeName(obj.name);
|
|
441
|
+
}
|
|
442
|
+
const baseId = this.projectId || parentIdentifier;
|
|
443
|
+
return `${baseId}:training:${sanitizedName}:${typeSuffix}`;
|
|
444
|
+
}
|
|
445
|
+
// Strategy 4: Seasonal/Regional patterns
|
|
446
|
+
if (obj.name.match(/^(SS|FW|SU|FA)\d{2}/i) || obj.name.match(/^(APAC|NA|EMEA|LATAM|ANZ)/i)) {
|
|
447
|
+
const sanitizedName = sanitizeName(obj.name);
|
|
448
|
+
const baseId = this.projectId || parentIdentifier;
|
|
449
|
+
if (obj.name.match(/^(SS|FW|SU|FA)\d{2}/i)) {
|
|
450
|
+
return `${baseId}:seasons:${sanitizedName}:${typeSuffix}`;
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
return `${baseId}:regions:${sanitizedName}:${typeSuffix}`;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// Strategy 5: Use parent identifier
|
|
457
|
+
if (parentIdentifier) {
|
|
458
|
+
const sanitizedName = sanitizeName(obj.name);
|
|
459
|
+
return `${parentIdentifier}:${sanitizedName}:${typeSuffix}`;
|
|
460
|
+
}
|
|
461
|
+
// Strategy 6: Use project ID
|
|
462
|
+
if (this.projectId) {
|
|
463
|
+
const sanitizedName = sanitizeName(obj.name);
|
|
464
|
+
return `${this.projectId}:${sanitizedName}:${typeSuffix}`;
|
|
465
|
+
}
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Determine object type
|
|
470
|
+
*/
|
|
471
|
+
getObjectType(obj) {
|
|
472
|
+
if (obj.subFolders)
|
|
473
|
+
return 'folder';
|
|
474
|
+
if (obj.type)
|
|
475
|
+
return obj.type.toLowerCase();
|
|
476
|
+
return 'unknown';
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Check if object should have an identifier
|
|
480
|
+
*/
|
|
481
|
+
shouldHaveIdentifier(obj) {
|
|
482
|
+
return (obj.name &&
|
|
483
|
+
(obj.type === 'ASSORTMENT' || obj.type === 'PLAN' || obj.type === 'BACKING' || obj.subFolders));
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Log processing summary
|
|
487
|
+
*/
|
|
488
|
+
logProcessingSummary() {
|
|
489
|
+
logger_1.default.info('========== PROCESSING SUMMARY ==========');
|
|
490
|
+
if (this.options.cleanProperties) {
|
|
491
|
+
logger_1.default.info('PROPERTY CLEANING:');
|
|
492
|
+
logger_1.default.info(`- ItemType objects converted: ${this.stats.itemTypeConverted}`);
|
|
493
|
+
logger_1.default.info(`- nbFXRates properties removed: ${this.stats.nbFXRatesRemoved}`);
|
|
494
|
+
logger_1.default.info(`- updatedBy properties removed: ${this.stats.updatedByRemoved}`);
|
|
495
|
+
logger_1.default.info(`- createdBy properties removed: ${this.stats.createdByRemoved}`);
|
|
496
|
+
logger_1.default.info(`- Empty assortmentAtts removed: ${this.stats.emptyAssortmentAttsRemoved}`);
|
|
497
|
+
}
|
|
498
|
+
if (this.options.fixMissingIdentifiers) {
|
|
499
|
+
logger_1.default.info('IDENTIFIER GENERATION:');
|
|
500
|
+
logger_1.default.info(`- Total identifiers created: ${this.stats.identifiersCreated}`);
|
|
501
|
+
logger_1.default.info(` - Assortments: ${this.stats.assortmentIdentifiers}`);
|
|
502
|
+
logger_1.default.info(` - Plans: ${this.stats.planIdentifiers}`);
|
|
503
|
+
logger_1.default.info(` - Backings: ${this.stats.backingIdentifiers}`);
|
|
504
|
+
logger_1.default.info(` - Folders: ${this.stats.folderIdentifiers}`);
|
|
505
|
+
logger_1.default.info(` - Views: ${this.stats.viewsIdentifiers}`);
|
|
506
|
+
}
|
|
507
|
+
if (this.options.removeBackings) {
|
|
508
|
+
logger_1.default.info('BACKING REMOVAL:');
|
|
509
|
+
logger_1.default.info(`- Backing assortments removed: ${this.stats.backingsRemoved}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Get processing statistics
|
|
514
|
+
*/
|
|
515
|
+
getStats() {
|
|
516
|
+
return { ...this.stats };
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
exports.ProjectPostProcessor = ProjectPostProcessor;
|
|
@@ -18,4 +18,3 @@ export declare class WorkflowTemplateDeployer {
|
|
|
18
18
|
export declare function deleteWorkflowTemplates(): Promise<void>;
|
|
19
19
|
export declare function localDeployWorkflowTemplate(saveFileLocation: string): Promise<void>;
|
|
20
20
|
export declare function populateActionIdentifiers(workflowTemplate: WorkflowTemplate): Promise<void>;
|
|
21
|
-
export declare function extractShortActionKeys(workflowTemplate: WorkflowTemplate): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.populateActionIdentifiers = exports.localDeployWorkflowTemplate = exports.deleteWorkflowTemplates = exports.WorkflowTemplateDeployer = exports.getActionByIdentifier = exports.getWorkflowTemplateIdentifier = exports.LocalWorkflowTemplateDeployer = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const util_1 = require("@contrail/util");
|
|
6
6
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
@@ -20,6 +20,7 @@ class LocalWorkflowTemplateDeployer {
|
|
|
20
20
|
const existingWorkflows = await (0, event_workflow_templates_1.getExistingWorkflowTemplates)();
|
|
21
21
|
// UPSERT / DELETE BASED ON WORKFLOW TEMPLATES IDENTIFIER
|
|
22
22
|
logger_1.default.info(`${chalk_1.default.yellow.bold('WORKFLOW TEMPLATES')} Deploying workflow templates.`);
|
|
23
|
+
logger_1.default.info(wtDeclaration);
|
|
23
24
|
for (const workflowKey of Object.keys(wtDeclaration.workflows)) {
|
|
24
25
|
await this.deployWorkflowTemplate(wtDeclaration.workflows[workflowKey], existingWorkflows, wtDeclaration.appIdentifier);
|
|
25
26
|
}
|
|
@@ -34,7 +35,7 @@ class LocalWorkflowTemplateDeployer {
|
|
|
34
35
|
return await this.createNewWorkflowTemplate(identifier, workflow, appIdentifier);
|
|
35
36
|
}
|
|
36
37
|
async createNewWorkflowTemplate(workflowKey, workflowTemplate, appIdentifier) {
|
|
37
|
-
this.populateActionIds(workflowTemplate, workflowKey, appIdentifier);
|
|
38
|
+
await this.populateActionIds(workflowTemplate, workflowKey, appIdentifier);
|
|
38
39
|
const newObject = {
|
|
39
40
|
identifier: getWorkflowTemplateIdentifier(workflowTemplate),
|
|
40
41
|
name: workflowTemplate.name,
|
|
@@ -43,8 +44,6 @@ class LocalWorkflowTemplateDeployer {
|
|
|
43
44
|
isActive: workflowTemplate.isActive,
|
|
44
45
|
supportingAppId: workflowTemplate.supportingAppId,
|
|
45
46
|
messageGroupId: workflowTemplate.messageGroupId,
|
|
46
|
-
messageGroup: workflowTemplate.messageGroup,
|
|
47
|
-
dynamicMessageGroupId: workflowTemplate.dynamicMessageGroupId,
|
|
48
47
|
parallelWorkerCount: workflowTemplate.parallelWorkerCount,
|
|
49
48
|
};
|
|
50
49
|
const newWorkflowTemplate = await (0, event_workflow_templates_1.createWorkflowTemplate)(newObject);
|
|
@@ -56,10 +55,8 @@ class LocalWorkflowTemplateDeployer {
|
|
|
56
55
|
}
|
|
57
56
|
async populateActionIds(workflowTemplate, workflowKey, appIdentifier) {
|
|
58
57
|
const installedApps = await (0, apps_1.getInstalledApps)();
|
|
59
|
-
const appOrg = installedApps.find((x) => x.app
|
|
60
|
-
|
|
61
|
-
throw new Error(`Could not find installed app with identifier "${appIdentifier}". Make sure the app is installed in this org before importing workflows.`);
|
|
62
|
-
}
|
|
58
|
+
const appOrg = installedApps.find((x) => x.app.identifier === appIdentifier);
|
|
59
|
+
logger_1.default.info('workflow template:', JSON.stringify(workflowTemplate, null, 2));
|
|
63
60
|
if (!workflowTemplate?.workflowDefinition?.paths) {
|
|
64
61
|
return;
|
|
65
62
|
}
|
|
@@ -89,7 +86,7 @@ class LocalWorkflowTemplateDeployer {
|
|
|
89
86
|
}
|
|
90
87
|
}
|
|
91
88
|
async createWorkflowSchedule(workflowTemplate, workflowId) {
|
|
92
|
-
if (!workflowTemplate?.crons?.length) {
|
|
89
|
+
if (!workflowTemplate?.crons?.length || !workflowTemplate.id) {
|
|
93
90
|
return;
|
|
94
91
|
}
|
|
95
92
|
const crons = [];
|
|
@@ -286,21 +283,3 @@ async function populateActionIdentifiers(workflowTemplate) {
|
|
|
286
283
|
}
|
|
287
284
|
}
|
|
288
285
|
exports.populateActionIdentifiers = populateActionIdentifiers;
|
|
289
|
-
function extractShortActionKeys(workflowTemplate) {
|
|
290
|
-
if (!workflowTemplate?.workflowDefinition?.paths) {
|
|
291
|
-
return;
|
|
292
|
-
}
|
|
293
|
-
for (const wfPath of workflowTemplate.workflowDefinition.paths) {
|
|
294
|
-
if (!wfPath?.actionDefinitionConfigs) {
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
for (const actionConfig of wfPath.actionDefinitionConfigs) {
|
|
298
|
-
if (actionConfig.action?.includes(':')) {
|
|
299
|
-
// Full identifier format: "app-id@version:action-name" -> "action-name"
|
|
300
|
-
actionConfig.action = actionConfig.action.split(':').pop();
|
|
301
|
-
}
|
|
302
|
-
delete actionConfig.identifier;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
exports.extractShortActionKeys = extractShortActionKeys;
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.5.3-alpha.
|
|
2
|
+
"version": "3.5.3-alpha.317fe63",
|
|
3
3
|
"commands": {
|
|
4
4
|
"app:create": {
|
|
5
5
|
"id": "app:create",
|
|
@@ -1241,14 +1241,15 @@
|
|
|
1241
1241
|
},
|
|
1242
1242
|
"projects:export": {
|
|
1243
1243
|
"id": "projects:export",
|
|
1244
|
-
"description": "Export a project.",
|
|
1244
|
+
"description": "Export a project with optional post-processing.",
|
|
1245
1245
|
"strict": true,
|
|
1246
1246
|
"pluginName": "@contrail/cli",
|
|
1247
1247
|
"pluginAlias": "@contrail/cli",
|
|
1248
1248
|
"pluginType": "core",
|
|
1249
1249
|
"aliases": [],
|
|
1250
1250
|
"examples": [
|
|
1251
|
-
"$ contrail project export <PROJECT_NAME
|
|
1251
|
+
"$ contrail project export <PROJECT_NAME>",
|
|
1252
|
+
"$ contrail project export <PROJECT_NAME> --process"
|
|
1252
1253
|
],
|
|
1253
1254
|
"flags": {
|
|
1254
1255
|
"logLevel": {
|
|
@@ -1277,12 +1278,18 @@
|
|
|
1277
1278
|
"false"
|
|
1278
1279
|
],
|
|
1279
1280
|
"default": "true"
|
|
1281
|
+
},
|
|
1282
|
+
"process": {
|
|
1283
|
+
"name": "process",
|
|
1284
|
+
"type": "boolean",
|
|
1285
|
+
"description": "Enable post-processing of exported project data",
|
|
1286
|
+
"allowNo": false
|
|
1280
1287
|
}
|
|
1281
1288
|
},
|
|
1282
1289
|
"args": {
|
|
1283
1290
|
"projectName": {
|
|
1284
1291
|
"name": "projectName",
|
|
1285
|
-
"description": "",
|
|
1292
|
+
"description": "Name or identifier of the project to export",
|
|
1286
1293
|
"required": true
|
|
1287
1294
|
}
|
|
1288
1295
|
}
|
|
@@ -2956,16 +2963,14 @@
|
|
|
2956
2963
|
},
|
|
2957
2964
|
"workflow-templates:export": {
|
|
2958
2965
|
"id": "workflow-templates:export",
|
|
2959
|
-
"description": "Export all workflows in the org.
|
|
2966
|
+
"description": "Export all workflows in the org.",
|
|
2960
2967
|
"strict": true,
|
|
2961
2968
|
"pluginName": "@contrail/cli",
|
|
2962
2969
|
"pluginAlias": "@contrail/cli",
|
|
2963
2970
|
"pluginType": "core",
|
|
2964
2971
|
"aliases": [],
|
|
2965
2972
|
"examples": [
|
|
2966
|
-
"$ contrail workflow-templates export"
|
|
2967
|
-
"$ contrail workflow-templates export --dir ./workflows",
|
|
2968
|
-
"$ contrail workflow-templates export --outFile workflows.yml"
|
|
2973
|
+
"$ contrail workflow-templates export\n"
|
|
2969
2974
|
],
|
|
2970
2975
|
"flags": {
|
|
2971
2976
|
"logLevel": {
|
|
@@ -2999,18 +3004,10 @@
|
|
|
2999
3004
|
"name": "outFile",
|
|
3000
3005
|
"type": "option",
|
|
3001
3006
|
"char": "f",
|
|
3002
|
-
"description": "
|
|
3003
|
-
"required": false,
|
|
3004
|
-
"multiple": false
|
|
3005
|
-
},
|
|
3006
|
-
"dir": {
|
|
3007
|
-
"name": "dir",
|
|
3008
|
-
"type": "option",
|
|
3009
|
-
"char": "d",
|
|
3010
|
-
"description": "Directory to write per-app workflow files",
|
|
3007
|
+
"description": "The location for the file to be saved",
|
|
3011
3008
|
"required": false,
|
|
3012
3009
|
"multiple": false,
|
|
3013
|
-
"default": "."
|
|
3010
|
+
"default": "workflows.yml"
|
|
3014
3011
|
},
|
|
3015
3012
|
"overwrite": {
|
|
3016
3013
|
"name": "overwrite",
|