@contrail/cli 3.5.4-alpha.f60c4df → 3.5.5-alpha.177136f
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 +112 -23
- package/dist/api/feature-flags.d.ts +3 -0
- package/dist/api/feature-flags.js +29 -0
- package/dist/api/org-config.d.ts +3 -0
- package/dist/api/org-config.js +28 -0
- package/dist/api/users.d.ts +1 -0
- package/dist/api/users.js +10 -1
- package/dist/commands/feature-flags/export.d.ts +19 -0
- package/dist/commands/feature-flags/export.js +66 -0
- package/dist/commands/feature-flags/export.spec.d.ts +1 -0
- package/dist/commands/feature-flags/export.spec.js +109 -0
- package/dist/commands/feature-flags/import.d.ts +42 -0
- package/dist/commands/feature-flags/import.js +191 -0
- package/dist/commands/feature-flags/import.spec.d.ts +1 -0
- package/dist/commands/feature-flags/import.spec.js +194 -0
- package/dist/commands/groups/export.js +1 -13
- package/dist/commands/groups/import.js +2 -15
- package/dist/commands/instance/export.d.ts +9 -0
- package/dist/commands/instance/export.js +18 -3
- package/dist/commands/instance/export.spec.d.ts +1 -0
- package/dist/commands/instance/export.spec.js +32 -0
- package/dist/commands/instance/import.d.ts +17 -0
- package/dist/commands/instance/import.js +32 -5
- package/dist/commands/instance/import.spec.d.ts +1 -0
- package/dist/commands/instance/import.spec.js +61 -0
- package/dist/commands/org-config/export.d.ts +13 -0
- package/dist/commands/org-config/export.js +74 -0
- package/dist/commands/org-config/import.d.ts +12 -0
- package/dist/commands/org-config/import.js +63 -0
- package/dist/commands/types/getAll.js +2 -0
- package/dist/commands/types/loadAll.d.ts +13 -1
- package/dist/commands/types/loadAll.js +41 -4
- package/dist/commands/types/loadAll.spec.d.ts +1 -0
- package/dist/commands/types/loadAll.spec.js +77 -0
- package/dist/commands/types/mergeOptionSets.js +2 -9
- package/dist/commands/types/mergeTypeProperties.js +2 -6
- package/dist/commands/types/parseOptionSetsLocalFiles.js +3 -7
- package/dist/commands/types/uploadTypeOverrides.d.ts +10 -0
- package/dist/commands/types/uploadTypeOverrides.js +19 -8
- package/dist/commands/types/uploadTypeOverrides.spec.d.ts +1 -0
- package/dist/commands/types/uploadTypeOverrides.spec.js +60 -0
- package/dist/commands/users/export.js +1 -13
- package/dist/commands/users/import.js +2 -15
- package/dist/commands/workflow-templates/export.d.ts +2 -3
- package/dist/commands/workflow-templates/export.js +23 -122
- package/dist/commands/workflow-templates/load.d.ts +0 -2
- package/dist/commands/workflow-templates/load.js +6 -63
- package/dist/helpers/app/app-config-validators.d.ts +5 -0
- package/dist/helpers/app/app-config-validators.js +102 -3
- package/dist/helpers/app/app-config-validators.spec.js +46 -0
- package/dist/helpers/app/extensions-deployer.d.ts +10 -2
- package/dist/helpers/app/extensions-deployer.js +31 -37
- package/dist/helpers/app/extensions-deployer.spec.d.ts +1 -0
- package/dist/helpers/app/extensions-deployer.spec.js +29 -0
- package/dist/helpers/error-helpers.d.ts +9 -0
- package/dist/helpers/error-helpers.js +35 -0
- package/dist/helpers/file-helpers.d.ts +2 -0
- package/dist/helpers/file-helpers.js +25 -1
- package/dist/helpers/projects/helpers.js +2 -6
- package/dist/helpers/types/type-parser.js +9 -15
- package/dist/helpers/workflow-templates/helper.d.ts +1 -2
- package/dist/helpers/workflow-templates/helper.js +6 -36
- package/dist/interfaces.d.ts +37 -1
- package/oclif.manifest.json +236 -30
- package/package.json +7 -1
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.exportUsers = exports.DEFAULT_FILE_LOCATION = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const path = tslib_1.__importStar(require("path"));
|
|
6
5
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
-
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
8
6
|
const login_1 = require("../../auth/login");
|
|
9
|
-
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
10
7
|
const core_1 = require("@oclif/core");
|
|
11
8
|
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
12
9
|
const users_1 = require("../../api/users");
|
|
@@ -63,15 +60,6 @@ async function exportUsers(fileLocation, overwrite) {
|
|
|
63
60
|
});
|
|
64
61
|
}
|
|
65
62
|
users = users.sort((one, two) => (one.email < two.email ? -1 : 1));
|
|
66
|
-
await saveData(users, fileLocation, overwrite);
|
|
63
|
+
await (0, file_helpers_1.saveData)(users, fileLocation, overwrite);
|
|
67
64
|
}
|
|
68
65
|
exports.exportUsers = exportUsers;
|
|
69
|
-
async function saveData(data, fileLocation, overwrite) {
|
|
70
|
-
const yamlStr = js_yaml_1.default.dump(data);
|
|
71
|
-
const CURRENT_DIR = process.cwd();
|
|
72
|
-
const pathToWrite = path.resolve(path.join(CURRENT_DIR, fileLocation));
|
|
73
|
-
if (fs_1.default.existsSync(pathToWrite)) {
|
|
74
|
-
await (0, file_helpers_1.safeFileOverwrite)(pathToWrite, overwrite);
|
|
75
|
-
}
|
|
76
|
-
fs_1.default.writeFileSync(pathToWrite, yamlStr, 'utf8');
|
|
77
|
-
}
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.uploadUsers = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const path = tslib_1.__importStar(require("path"));
|
|
6
5
|
const sdk_1 = require("@contrail/sdk");
|
|
7
6
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
8
|
-
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
9
7
|
const login_1 = require("../../auth/login");
|
|
10
|
-
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
11
8
|
const core_1 = require("@oclif/core");
|
|
9
|
+
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
12
10
|
const basecommand_1 = require("../../basecommand");
|
|
13
11
|
class ImportUsers extends basecommand_1.BaseCommand {
|
|
14
12
|
static description = 'Import all users in the users.yml file';
|
|
@@ -38,19 +36,8 @@ class ImportUsers extends basecommand_1.BaseCommand {
|
|
|
38
36
|
}
|
|
39
37
|
}
|
|
40
38
|
exports.default = ImportUsers;
|
|
41
|
-
function getUsersFile(fileLocation) {
|
|
42
|
-
const CURRENT_DIR = process.cwd();
|
|
43
|
-
try {
|
|
44
|
-
const fullPath = path.join(CURRENT_DIR, fileLocation);
|
|
45
|
-
const doc = js_yaml_1.default.load(fs_1.default.readFileSync(fullPath, 'utf8'));
|
|
46
|
-
return doc;
|
|
47
|
-
}
|
|
48
|
-
catch (e) {
|
|
49
|
-
throw new Error(`ERROR PARSING YAML: ${e}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
39
|
async function uploadUsers(fileLocation) {
|
|
53
|
-
const users =
|
|
40
|
+
const users = (0, file_helpers_1.loadData)(fileLocation);
|
|
54
41
|
for (const user of users) {
|
|
55
42
|
const role = user.role;
|
|
56
43
|
const email = user.email;
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { BaseCommand } from '../../basecommand';
|
|
2
|
+
export declare const DEFAULT_FILE_LOCATION = "workflows.yml";
|
|
2
3
|
export default class ExportWorkflows extends BaseCommand {
|
|
3
4
|
static description: string;
|
|
4
5
|
static examples: string[];
|
|
5
6
|
static flags: {
|
|
6
7
|
outFile: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
7
|
-
dir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
8
8
|
overwrite: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
9
|
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
10
10
|
logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
11
11
|
};
|
|
12
12
|
run(): Promise<void>;
|
|
13
13
|
}
|
|
14
|
-
export declare function
|
|
15
|
-
export declare function exportWorkflowsToFile(fileLocation: string, overwrite: boolean): Promise<void>;
|
|
14
|
+
export declare function exportWorkflows(fileLocation: string, overwrite: boolean): Promise<void>;
|
|
@@ -1,39 +1,29 @@
|
|
|
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
|
-
const path = tslib_1.__importStar(require("path"));
|
|
6
5
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
-
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
8
6
|
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
9
7
|
const login_1 = require("../../auth/login");
|
|
10
|
-
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
11
8
|
const core_1 = require("@oclif/core");
|
|
12
9
|
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
13
10
|
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
11
|
const helper_1 = require("../../helpers/workflow-templates/helper");
|
|
17
12
|
const basecommand_1 = require("../../basecommand");
|
|
13
|
+
exports.DEFAULT_FILE_LOCATION = 'workflows.yml';
|
|
18
14
|
class ExportWorkflows extends basecommand_1.BaseCommand {
|
|
19
|
-
static description = 'Export all workflows in the org.
|
|
15
|
+
static description = 'Export all workflows in the org.';
|
|
20
16
|
static examples = [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'$ contrail workflow-templates export --outFile workflows.yml',
|
|
17
|
+
`$ contrail workflow-templates export
|
|
18
|
+
`,
|
|
24
19
|
];
|
|
25
20
|
static flags = {
|
|
26
21
|
...basecommand_1.BaseCommand.baseFlags,
|
|
27
22
|
outFile: core_1.Flags.string({
|
|
28
23
|
char: 'f',
|
|
29
|
-
description: '
|
|
24
|
+
description: 'The location for the file to be saved',
|
|
30
25
|
required: false,
|
|
31
|
-
|
|
32
|
-
dir: core_1.Flags.string({
|
|
33
|
-
char: 'd',
|
|
34
|
-
description: 'Directory to write per-app workflow files',
|
|
35
|
-
required: false,
|
|
36
|
-
default: '.',
|
|
26
|
+
default: exports.DEFAULT_FILE_LOCATION,
|
|
37
27
|
}),
|
|
38
28
|
overwrite: core_1.Flags.boolean({
|
|
39
29
|
description: 'Replaces existing files if they exist',
|
|
@@ -45,12 +35,7 @@ class ExportWorkflows extends basecommand_1.BaseCommand {
|
|
|
45
35
|
try {
|
|
46
36
|
await (0, login_1.authUser)();
|
|
47
37
|
this.log(`${chalk_1.default.blue.bold('START')} Getting Workflows.`);
|
|
48
|
-
|
|
49
|
-
await exportWorkflowsToFile(this.flags.outFile, this.flags.overwrite);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
await exportWorkflowsByApp(this.flags.dir ?? '.', this.flags.overwrite);
|
|
53
|
-
}
|
|
38
|
+
await exportWorkflows(this.flags.outFile ?? exports.DEFAULT_FILE_LOCATION, this.flags.overwrite);
|
|
54
39
|
this.log(`${chalk_1.default.yellow.bold('WORKFLOWS')} Done`);
|
|
55
40
|
}
|
|
56
41
|
catch (e) {
|
|
@@ -61,106 +46,22 @@ class ExportWorkflows extends basecommand_1.BaseCommand {
|
|
|
61
46
|
}
|
|
62
47
|
}
|
|
63
48
|
exports.default = ExportWorkflows;
|
|
64
|
-
async function
|
|
65
|
-
const
|
|
66
|
-
(0, event_workflow_templates_1.getExistingWorkflowTemplates)(),
|
|
67
|
-
(0, schedules_1.getExistingSchedules)(),
|
|
68
|
-
(0, apps_1.getInstalledApps)(),
|
|
69
|
-
]);
|
|
70
|
-
const appIdToIdentifier = new Map(installedApps
|
|
71
|
-
.filter((appOrg) => appOrg.appId && appOrg.app?.identifier)
|
|
72
|
-
.map((appOrg) => [appOrg.appId, appOrg.app.identifier]));
|
|
73
|
-
const workflowsByApp = new Map();
|
|
74
|
-
const unassigned = [];
|
|
75
|
-
for (const workflow of allWorkflowsInOrg) {
|
|
76
|
-
let appIdentifier = workflow.supportingAppId
|
|
77
|
-
? appIdToIdentifier.get(workflow.supportingAppId)
|
|
78
|
-
: undefined;
|
|
79
|
-
// If no supportingAppId, try to extract app identifier from workflow identifier
|
|
80
|
-
// Format: @{appIdentifier}@{randomId}:{workflowName}
|
|
81
|
-
if (!appIdentifier && workflow.identifier) {
|
|
82
|
-
const match = workflow.identifier.match(/^@([^@]+)@/);
|
|
83
|
-
if (match) {
|
|
84
|
-
// Keep the @ prefix in the appIdentifier
|
|
85
|
-
appIdentifier = `@${match[1]}`;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (appIdentifier) {
|
|
89
|
-
if (!workflowsByApp.has(appIdentifier)) {
|
|
90
|
-
workflowsByApp.set(appIdentifier, []);
|
|
91
|
-
}
|
|
92
|
-
const workflows = workflowsByApp.get(appIdentifier);
|
|
93
|
-
if (workflows) {
|
|
94
|
-
workflows.push(workflow);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
unassigned.push(workflow);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const resolvedDir = path.resolve(process.cwd(), outputDir);
|
|
102
|
-
for (const [appIdentifier, workflows] of workflowsByApp.entries()) {
|
|
103
|
-
const exportObj = { appIdentifier, workflows: [] };
|
|
104
|
-
for (const workflow of workflows) {
|
|
105
|
-
exportObj.workflows.push(await buildExportWorkflow(workflow, allSchedules, appIdentifier));
|
|
106
|
-
}
|
|
107
|
-
const safeAppIdentifier = appIdentifier.replace(/[\\/]/g, '-');
|
|
108
|
-
const filePath = path.join(resolvedDir, `workflows-${safeAppIdentifier}.yml`);
|
|
109
|
-
await writeYamlFile(exportObj, filePath, overwrite);
|
|
110
|
-
console.log(`${chalk_1.default.green('EXPORTED')} ${filePath}`);
|
|
111
|
-
}
|
|
112
|
-
if (unassigned.length > 0) {
|
|
113
|
-
const unassignedObj = { workflows: [] };
|
|
114
|
-
for (const workflow of unassigned) {
|
|
115
|
-
unassignedObj.workflows.push(await buildExportWorkflow(workflow, allSchedules));
|
|
116
|
-
}
|
|
117
|
-
const unassignedFilePath = path.join(resolvedDir, 'workflows-unassigned.yml');
|
|
118
|
-
await writeYamlFile(unassignedObj, unassignedFilePath, overwrite);
|
|
119
|
-
console.log(`${chalk_1.default.yellow('EXPORTED')} ${unassignedFilePath} (unassigned workflows)`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
exports.exportWorkflowsByApp = exportWorkflowsByApp;
|
|
123
|
-
async function exportWorkflowsToFile(fileLocation, overwrite) {
|
|
124
|
-
const [allWorkflowsInOrg, allSchedules] = await Promise.all([
|
|
125
|
-
(0, event_workflow_templates_1.getExistingWorkflowTemplates)(),
|
|
126
|
-
(0, schedules_1.getExistingSchedules)(),
|
|
127
|
-
]);
|
|
49
|
+
async function exportWorkflows(fileLocation, overwrite) {
|
|
50
|
+
const allWorkflowsInOrg = await (0, event_workflow_templates_1.getExistingWorkflowTemplates)();
|
|
128
51
|
const workflowsObj = { workflows: [] };
|
|
129
52
|
for (const workflow of allWorkflowsInOrg) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
'isActive',
|
|
142
|
-
'messageGroup',
|
|
143
|
-
'dynamicMessageGroupId',
|
|
144
|
-
'parallelWorkerCount',
|
|
145
|
-
'workflowDefinition',
|
|
146
|
-
]);
|
|
147
|
-
const schedules = allSchedules.filter((s) => s.ownerReference === `event-workflow-template:${workflow.id}`);
|
|
148
|
-
if (schedules.length > 0) {
|
|
149
|
-
exportWorkflow.crons = schedules.map((s) => ({ name: s.name, cron: s.cron }));
|
|
150
|
-
}
|
|
151
|
-
await (0, helper_1.populateActionIdentifiers)(exportWorkflow);
|
|
152
|
-
// Shortened keys can only be resolved back to a full action identifier on
|
|
153
|
-
// import if an appIdentifier is known. Without one (e.g. unassigned
|
|
154
|
-
// workflows), keep the full identifier so the workflow stays re-importable.
|
|
155
|
-
if (appIdentifier) {
|
|
156
|
-
(0, helper_1.extractShortActionKeys)(exportWorkflow);
|
|
157
|
-
}
|
|
158
|
-
return exportWorkflow;
|
|
159
|
-
}
|
|
160
|
-
async function writeYamlFile(data, absolutePath, overwrite) {
|
|
161
|
-
if (fs_1.default.existsSync(absolutePath)) {
|
|
162
|
-
await (0, file_helpers_1.safeFileOverwrite)(absolutePath, overwrite);
|
|
53
|
+
const exportWorkflow = lodash_1.default.pick(workflow, [
|
|
54
|
+
'triggerKey',
|
|
55
|
+
'workflowDefinition',
|
|
56
|
+
'name',
|
|
57
|
+
'isActive',
|
|
58
|
+
'identifier',
|
|
59
|
+
'messageGroup',
|
|
60
|
+
'dynamicMessageGroupId',
|
|
61
|
+
]);
|
|
62
|
+
await (0, helper_1.populateActionIdentifiers)(exportWorkflow);
|
|
63
|
+
workflowsObj.workflows.push(exportWorkflow);
|
|
163
64
|
}
|
|
164
|
-
|
|
165
|
-
fs_1.default.writeFileSync(absolutePath, js_yaml_1.default.dump(data), 'utf8');
|
|
65
|
+
await (0, file_helpers_1.saveData)(workflowsObj, fileLocation, overwrite);
|
|
166
66
|
}
|
|
67
|
+
exports.exportWorkflows = exportWorkflows;
|
|
@@ -3,7 +3,6 @@ export default class LoadWorkflowTemplates extends BaseCommand {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
|
-
dir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
7
6
|
alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
8
7
|
logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
9
8
|
};
|
|
@@ -12,4 +11,3 @@ export default class LoadWorkflowTemplates extends BaseCommand {
|
|
|
12
11
|
};
|
|
13
12
|
run(): Promise<void>;
|
|
14
13
|
}
|
|
15
|
-
export declare function importWorkflowsBySelection(dir: string): Promise<void>;
|
|
@@ -1,84 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.importWorkflowsBySelection = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const path = tslib_1.__importStar(require("path"));
|
|
6
|
-
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
7
|
-
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
8
|
-
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
9
3
|
const core_1 = require("@oclif/core");
|
|
10
4
|
const login_1 = require("../../auth/login");
|
|
11
5
|
const helper_1 = require("../../helpers/workflow-templates/helper");
|
|
12
6
|
const basecommand_1 = require("../../basecommand");
|
|
13
7
|
class LoadWorkflowTemplates extends basecommand_1.BaseCommand {
|
|
14
|
-
static description = '
|
|
8
|
+
static description = 'Loads your workflow from a local file';
|
|
15
9
|
static examples = [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'$ contrail workflow-templates load --dir ./workflows',
|
|
10
|
+
`$ contrail workflow-templates load <FILE_LOCATION>
|
|
11
|
+
`,
|
|
19
12
|
];
|
|
20
13
|
static flags = {
|
|
21
14
|
...basecommand_1.BaseCommand.baseFlags,
|
|
22
|
-
dir: core_1.Flags.string({
|
|
23
|
-
char: 'd',
|
|
24
|
-
description: 'Directory to scan for workflow files when no file argument is given',
|
|
25
|
-
required: false,
|
|
26
|
-
default: '.',
|
|
27
|
-
}),
|
|
28
15
|
};
|
|
29
16
|
static args = {
|
|
30
17
|
fileLocation: core_1.Args.string({
|
|
31
|
-
description: '
|
|
32
|
-
required:
|
|
18
|
+
description: 'The workflow template file to load',
|
|
19
|
+
required: true,
|
|
33
20
|
}),
|
|
34
21
|
};
|
|
35
22
|
async run() {
|
|
36
23
|
await (0, login_1.authUser)();
|
|
37
|
-
|
|
38
|
-
await (0, helper_1.localDeployWorkflowTemplate)(this.args.fileLocation);
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
await importWorkflowsBySelection(this.flags.dir ?? '.');
|
|
42
|
-
}
|
|
24
|
+
await (0, helper_1.localDeployWorkflowTemplate)(this.args.fileLocation);
|
|
43
25
|
}
|
|
44
26
|
}
|
|
45
27
|
exports.default = LoadWorkflowTemplates;
|
|
46
|
-
async function importWorkflowsBySelection(dir) {
|
|
47
|
-
const resolvedDir = path.resolve(process.cwd(), dir);
|
|
48
|
-
const files = fs_1.default
|
|
49
|
-
.readdirSync(resolvedDir)
|
|
50
|
-
.filter((f) => f.startsWith('workflows-') && f.endsWith('.yml'))
|
|
51
|
-
.sort();
|
|
52
|
-
if (files.length === 0) {
|
|
53
|
-
console.log(chalk_1.default.yellow(`No workflow files found in ${resolvedDir}`));
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
const choices = files.map((f) => ({
|
|
57
|
-
name: f,
|
|
58
|
-
value: f,
|
|
59
|
-
checked: true,
|
|
60
|
-
}));
|
|
61
|
-
const { selectedFiles } = await inquirer_1.default.prompt([
|
|
62
|
-
{
|
|
63
|
-
type: 'checkbox',
|
|
64
|
-
name: 'selectedFiles',
|
|
65
|
-
message: 'Select workflow files to import (space to toggle, enter to confirm):',
|
|
66
|
-
choices,
|
|
67
|
-
},
|
|
68
|
-
]);
|
|
69
|
-
if (selectedFiles.length === 0) {
|
|
70
|
-
console.log(chalk_1.default.yellow('No files selected — nothing imported.'));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
//log selected file
|
|
74
|
-
console.log(chalk_1.default.blue.bold('SELECTED WORKFLOW FILES:'));
|
|
75
|
-
selectedFiles.forEach((file) => {
|
|
76
|
-
console.log(` - ${file}`);
|
|
77
|
-
});
|
|
78
|
-
for (const file of selectedFiles) {
|
|
79
|
-
console.log(`\n${chalk_1.default.blue.bold('IMPORTING')} ${file}`);
|
|
80
|
-
await (0, helper_1.localDeployWorkflowTemplate)(path.join(dir, file));
|
|
81
|
-
console.log(`${chalk_1.default.green.bold('DONE')} ${file}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
exports.importWorkflowsBySelection = importWorkflowsBySelection;
|
|
@@ -15,7 +15,12 @@ export declare const ERROR_MESSAGES: {
|
|
|
15
15
|
ignoredAccessGrants: string;
|
|
16
16
|
invalidPermission: (permission: string) => string;
|
|
17
17
|
invalidAccessGrants: (error: string) => string;
|
|
18
|
+
invalidExtensionIcon: (identifier: string) => string;
|
|
19
|
+
invalidAppliesTo: (identifier: string, problem: string) => string;
|
|
18
20
|
};
|
|
21
|
+
export declare function isValidExtensionIcon(icon: unknown): boolean;
|
|
22
|
+
/** Icons and appliesTo are checked here, since the apps service stores what it is given. */
|
|
23
|
+
export declare function validateExtensionPlacement(appDeclaration: AppDeclaration): AppConfigValidationError;
|
|
19
24
|
export declare function validateAppConfig(appConfig: AppDeclaration): void;
|
|
20
25
|
export declare function appVersionValidator(appDeclaration: AppDeclaration): AppConfigValidationError;
|
|
21
26
|
export declare function visibilityValidator(appDeclaration: AppDeclaration): AppConfigValidationError;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateAccessGrants = exports.visibilityValidator = exports.appVersionValidator = exports.validateAppConfig = exports.ERROR_MESSAGES = void 0;
|
|
3
|
+
exports.validateAccessGrants = exports.visibilityValidator = exports.appVersionValidator = exports.validateAppConfig = exports.validateExtensionPlacement = exports.isValidExtensionIcon = exports.ERROR_MESSAGES = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const interfaces_1 = require("../../interfaces");
|
|
6
6
|
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
7
|
-
const VALIDATORS = [
|
|
7
|
+
const VALIDATORS = [
|
|
8
|
+
appVersionValidator,
|
|
9
|
+
visibilityValidator,
|
|
10
|
+
validateAccessGrants,
|
|
11
|
+
validateExtensionPlacement,
|
|
12
|
+
];
|
|
8
13
|
exports.ERROR_MESSAGES = {
|
|
9
14
|
missingVersionField: 'Missing "version" field". Semantic App Version looks like "1.0.0". Use "contrail app version [patch|minor|major]" to update the app version.',
|
|
10
15
|
invalidVersionField: '"version" field is not a string. Expecting a version like "1.0.0". Manually update the "version" field to be a string in the app.yml file.',
|
|
@@ -17,7 +22,101 @@ exports.ERROR_MESSAGES = {
|
|
|
17
22
|
ignoredAccessGrants: 'App is set to public. accessGrants will be ignored.',
|
|
18
23
|
invalidPermission: (permission) => `Invalid permission found in one of the accessGrants objects: ${permission}.`,
|
|
19
24
|
invalidAccessGrants: (error) => `Invalid accessGrants. ${error}`,
|
|
25
|
+
invalidExtensionIcon: (identifier) => `Extension ${identifier}: icon must be a default icon name (like "line-builder"), { material: <icon name> } or { url: <SVG path or http(s) URL> }.`,
|
|
26
|
+
invalidAppliesTo: (identifier, problem) => `Extension ${identifier}: appliesTo ${problem}.`,
|
|
20
27
|
};
|
|
28
|
+
const STRING_LIST_FIELDS = ['typePaths', 'excludeTypePaths', 'roles'];
|
|
29
|
+
const RULE_FIELDS = [...STRING_LIST_FIELDS, 'minSelected', 'maxSelected', 'label'];
|
|
30
|
+
const APPLIES_TO_FIELDS = [...RULE_FIELDS, 'target', 'inputs'];
|
|
31
|
+
function isValidExtensionIcon(icon) {
|
|
32
|
+
if (icon === undefined || icon === null) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (typeof icon === 'string') {
|
|
36
|
+
return /^[a-z0-9-]+$/.test(icon);
|
|
37
|
+
}
|
|
38
|
+
if (typeof icon !== 'object' || Array.isArray(icon) || Object.keys(icon).length !== 1) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const { material, url } = icon;
|
|
42
|
+
if ('material' in icon) {
|
|
43
|
+
return typeof material === 'string' && /^[a-z0-9_]+$/.test(material);
|
|
44
|
+
}
|
|
45
|
+
if ('url' in icon) {
|
|
46
|
+
return typeof url === 'string' && url.length > 0 && !/^(javascript|data):/i.test(url.trim());
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
exports.isValidExtensionIcon = isValidExtensionIcon;
|
|
51
|
+
function appliesToProblem(appliesTo) {
|
|
52
|
+
const problem = ruleProblem(appliesTo, APPLIES_TO_FIELDS);
|
|
53
|
+
if (problem) {
|
|
54
|
+
return problem;
|
|
55
|
+
}
|
|
56
|
+
const { target, inputs } = appliesTo;
|
|
57
|
+
if (target !== undefined) {
|
|
58
|
+
const targetProblem = ruleProblem(target, RULE_FIELDS);
|
|
59
|
+
if (targetProblem) {
|
|
60
|
+
return `target ${targetProblem}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (inputs !== undefined) {
|
|
64
|
+
if (!Array.isArray(inputs)) {
|
|
65
|
+
return 'inputs must be a list';
|
|
66
|
+
}
|
|
67
|
+
for (const [index, input] of inputs.entries()) {
|
|
68
|
+
const inputProblem = ruleProblem(input, RULE_FIELDS);
|
|
69
|
+
if (inputProblem) {
|
|
70
|
+
return `inputs[${index}] ${inputProblem}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
function ruleProblem(rule, allowedFields) {
|
|
77
|
+
if (typeof rule !== 'object' || rule === null || Array.isArray(rule)) {
|
|
78
|
+
return 'must be an object';
|
|
79
|
+
}
|
|
80
|
+
const fields = rule;
|
|
81
|
+
const unknownField = Object.keys(fields).find((field) => !allowedFields.includes(field));
|
|
82
|
+
if (unknownField) {
|
|
83
|
+
return `has an unknown field "${unknownField}" (use ${allowedFields.join(', ')})`;
|
|
84
|
+
}
|
|
85
|
+
for (const field of STRING_LIST_FIELDS) {
|
|
86
|
+
const value = fields[field];
|
|
87
|
+
if (value !== undefined &&
|
|
88
|
+
(!Array.isArray(value) || value.some((entry) => typeof entry !== 'string'))) {
|
|
89
|
+
return `${field} must be a list of strings`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const [field, min] of [
|
|
93
|
+
['minSelected', 0],
|
|
94
|
+
['maxSelected', 1],
|
|
95
|
+
]) {
|
|
96
|
+
const value = fields[field];
|
|
97
|
+
if (value !== undefined && (!Number.isInteger(value) || value < min)) {
|
|
98
|
+
return `${field} must be a whole number of at least ${min}`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
/** Icons and appliesTo are checked here, since the apps service stores what it is given. */
|
|
104
|
+
function validateExtensionPlacement(appDeclaration) {
|
|
105
|
+
const errors = [];
|
|
106
|
+
for (const extension of appDeclaration?.extensions ?? []) {
|
|
107
|
+
if (!isValidExtensionIcon(extension.icon)) {
|
|
108
|
+
errors.push(exports.ERROR_MESSAGES.invalidExtensionIcon(extension.identifier));
|
|
109
|
+
}
|
|
110
|
+
if (extension.appliesTo !== undefined) {
|
|
111
|
+
const problem = appliesToProblem(extension.appliesTo);
|
|
112
|
+
if (problem) {
|
|
113
|
+
errors.push(exports.ERROR_MESSAGES.invalidAppliesTo(extension.identifier, problem));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { errors, warnings: [] };
|
|
118
|
+
}
|
|
119
|
+
exports.validateExtensionPlacement = validateExtensionPlacement;
|
|
21
120
|
function validateAppConfig(appConfig) {
|
|
22
121
|
const errors = [];
|
|
23
122
|
const warnings = [];
|
|
@@ -30,7 +129,7 @@ function validateAppConfig(appConfig) {
|
|
|
30
129
|
logger_1.default.warn(warnings);
|
|
31
130
|
}
|
|
32
131
|
if (errors.length) {
|
|
33
|
-
throw new Error(`Invalid app config: \n ${errors.map((x) => x
|
|
132
|
+
throw new Error(`Invalid app config: \n ${errors.map((x) => x?.error ?? x).join('\n')}`);
|
|
34
133
|
}
|
|
35
134
|
}
|
|
36
135
|
exports.validateAppConfig = validateAppConfig;
|
|
@@ -308,3 +308,49 @@ describe('validateAccessGrants', () => {
|
|
|
308
308
|
expect(validationResult).toEqual(expectedError);
|
|
309
309
|
});
|
|
310
310
|
});
|
|
311
|
+
describe('validateExtensionPlacement', () => {
|
|
312
|
+
const declarationWith = (extension) => ({
|
|
313
|
+
extensions: [{ identifier: 'line-builder', name: 'Line builder', ...extension }],
|
|
314
|
+
});
|
|
315
|
+
it('should accept default, Material and SVG icons, appliesTo, and extensions with neither', () => {
|
|
316
|
+
for (const icon of [
|
|
317
|
+
'line-builder',
|
|
318
|
+
{ material: 'grid_on' },
|
|
319
|
+
{ url: 'assets/icons/line-builder.svg' },
|
|
320
|
+
undefined,
|
|
321
|
+
]) {
|
|
322
|
+
expect((0, app_config_validators_1.validateExtensionPlacement)(declarationWith({ icon }))).toEqual(validResult);
|
|
323
|
+
}
|
|
324
|
+
expect((0, app_config_validators_1.validateExtensionPlacement)(declarationWith({
|
|
325
|
+
appliesTo: {
|
|
326
|
+
typePaths: ['custom-entity:silhouette', 'item:product'],
|
|
327
|
+
excludeTypePaths: ['item:material'],
|
|
328
|
+
roles: ['family'],
|
|
329
|
+
maxSelected: 20,
|
|
330
|
+
label: 'Designs or BOMs',
|
|
331
|
+
},
|
|
332
|
+
}))).toEqual(validResult);
|
|
333
|
+
});
|
|
334
|
+
it('should name the extension whose icon or appliesTo is wrong', () => {
|
|
335
|
+
expect((0, app_config_validators_1.validateExtensionPlacement)(declarationWith({ icon: { url: 'javascript:alert(1)' } })).errors).toEqual([app_config_validators_1.ERROR_MESSAGES.invalidExtensionIcon('line-builder')]);
|
|
336
|
+
expect((0, app_config_validators_1.validateExtensionPlacement)(declarationWith({ appliesTo: { typePath: 'item' } })).errors[0]).toContain('unknown field "typePath"');
|
|
337
|
+
expect((0, app_config_validators_1.validateExtensionPlacement)(declarationWith({ appliesTo: { maxSelected: 0 } })).errors[0]).toContain('maxSelected must be a whole number of at least 1');
|
|
338
|
+
});
|
|
339
|
+
it('should check a target and its inputs', () => {
|
|
340
|
+
expect((0, app_config_validators_1.validateExtensionPlacement)(declarationWith({
|
|
341
|
+
appliesTo: {
|
|
342
|
+
target: { typePaths: ['custom-entity:silhouette'], maxSelected: 1, label: 'a Design' },
|
|
343
|
+
inputs: [{ typePaths: ['color'], maxSelected: 30, label: 'colors' }],
|
|
344
|
+
},
|
|
345
|
+
}))).toEqual(validResult);
|
|
346
|
+
expect((0, app_config_validators_1.validateExtensionPlacement)(declarationWith({ appliesTo: { inputs: [{ typePath: 'color' }] } })).errors[0]).toContain('inputs[0] has an unknown field "typePath"');
|
|
347
|
+
expect((0, app_config_validators_1.validateExtensionPlacement)(declarationWith({ appliesTo: { target: { inputs: [] } } }))
|
|
348
|
+
.errors[0]).toContain('target has an unknown field "inputs"');
|
|
349
|
+
});
|
|
350
|
+
it('should show the message when validation fails', () => {
|
|
351
|
+
expect(() => (0, app_config_validators_1.validateAppConfig)({
|
|
352
|
+
version: '1.0.0',
|
|
353
|
+
...declarationWith({ icon: 'Bad Icon' }),
|
|
354
|
+
})).toThrow(app_config_validators_1.ERROR_MESSAGES.invalidExtensionIcon('line-builder'));
|
|
355
|
+
});
|
|
356
|
+
});
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { AppExtensionDefinition } from '@contrail/entity-types';
|
|
2
|
-
import { App, AppDeclaration, ExtensionDefinitionDeclaration } from '../../interfaces';
|
|
2
|
+
import { App, AppDeclaration, ExtensionAppliesToDeclaration, ExtensionDefinitionDeclaration, ExtensionIconDeclaration } from '../../interfaces';
|
|
3
3
|
export declare function getExtensionDefinitionsForAppVersion(app: App): Promise<AppExtensionDefinition[]>;
|
|
4
|
+
/** Entity-types' definition plus placement, which the apps service stores as declared. */
|
|
5
|
+
export type PlacedExtensionDefinition = AppExtensionDefinition & {
|
|
6
|
+
icon?: ExtensionIconDeclaration;
|
|
7
|
+
appliesTo?: ExtensionAppliesToDeclaration;
|
|
8
|
+
group?: string;
|
|
9
|
+
};
|
|
10
|
+
/** The definition published for an app.yml extension, for a new version or an update alike. */
|
|
11
|
+
export declare function buildExtensionDefinition(extension: ExtensionDefinitionDeclaration, app: App, appName?: string): PlacedExtensionDefinition;
|
|
4
12
|
export declare class ExtensionsDeployer {
|
|
5
13
|
deployExtensions(appDeclaration: AppDeclaration, app: App): Promise<void>;
|
|
6
|
-
deployExtension(extension: ExtensionDefinitionDeclaration, app: App, existingExtensions: Array<any
|
|
14
|
+
deployExtension(extension: ExtensionDefinitionDeclaration, app: App, existingExtensions: Array<any>, appName?: string): Promise<void>;
|
|
7
15
|
private createNewExtensionDefinition;
|
|
8
16
|
private updateActionDefinition;
|
|
9
17
|
}
|