@contrail/cli 3.5.3-alpha.0dcc977 → 3.5.3-alpha.297ad36

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.
Files changed (32) hide show
  1. package/README.md +4 -2
  2. package/dist/api/org-config.js +2 -0
  3. package/dist/commands/groups/import.js +2 -15
  4. package/dist/commands/instance/export.js +0 -2
  5. package/dist/commands/instance/import.d.ts +14 -0
  6. package/dist/commands/instance/import.js +26 -6
  7. package/dist/commands/instance/import.spec.d.ts +1 -0
  8. package/dist/commands/instance/import.spec.js +57 -0
  9. package/dist/commands/org-config/export.js +22 -19
  10. package/dist/commands/org-config/import.js +25 -32
  11. package/dist/commands/types/getAll.js +2 -0
  12. package/dist/commands/types/loadAll.d.ts +13 -1
  13. package/dist/commands/types/loadAll.js +41 -4
  14. package/dist/commands/types/loadAll.spec.d.ts +1 -0
  15. package/dist/commands/types/loadAll.spec.js +77 -0
  16. package/dist/commands/types/mergeOptionSets.js +2 -9
  17. package/dist/commands/types/mergeTypeProperties.js +2 -6
  18. package/dist/commands/types/parseOptionSetsLocalFiles.js +3 -7
  19. package/dist/commands/types/uploadTypeOverrides.d.ts +10 -0
  20. package/dist/commands/types/uploadTypeOverrides.js +19 -8
  21. package/dist/commands/types/uploadTypeOverrides.spec.d.ts +1 -0
  22. package/dist/commands/types/uploadTypeOverrides.spec.js +60 -0
  23. package/dist/commands/users/import.js +2 -15
  24. package/dist/commands/workflow-templates/export.js +1 -13
  25. package/dist/helpers/error-helpers.d.ts +9 -0
  26. package/dist/helpers/error-helpers.js +35 -0
  27. package/dist/helpers/file-helpers.d.ts +1 -0
  28. package/dist/helpers/file-helpers.js +14 -4
  29. package/dist/helpers/projects/helpers.js +2 -6
  30. package/dist/helpers/types/type-parser.js +9 -15
  31. package/oclif.manifest.json +8 -1
  32. package/package.json +4 -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.0dcc977 linux-x64 node-v24.19.0
20
+ @contrail/cli/3.5.3-alpha.297ad36 linux-x64 node-v24.20.0
21
21
  $ contrail --help [COMMAND]
22
22
  USAGE
23
23
  $ contrail COMMAND
@@ -968,7 +968,7 @@ loads all the type data that has been exported by getAll
968
968
 
969
969
  ```
970
970
  USAGE
971
- $ contrail types loadAll [-l error|warn|info|debug] [-e true|false] [-d <value>]
971
+ $ contrail types loadAll [-l error|warn|info|debug] [-e true|false] [-d <value>] [--orgConfig]
972
972
 
973
973
  FLAGS
974
974
  -d, --dir=<value> [default: .] The directory from which to load the types data. Defaults to the current
@@ -977,6 +977,8 @@ FLAGS
977
977
  <options: true|false>
978
978
  -l, --logLevel=<option> The Log Level
979
979
  <options: error|warn|info|debug>
980
+ --[no-]orgConfig Also load org config data from this directory. If not specified, you will be
981
+ prompted.
980
982
 
981
983
  DESCRIPTION
982
984
  loads all the type data that has been exported by getAll
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createOrgConfig = exports.updateOrgConfig = exports.getOrgConfigs = void 0;
4
4
  const sdk_1 = require("@contrail/sdk");
5
+ // every org has exactly one org-config record (created at provisioning),
6
+ // so callers can safely use the first element of the returned list
5
7
  async function getOrgConfigs(options) {
6
8
  return new sdk_1.Entities().get({
7
9
  entityName: 'org-config',
@@ -2,13 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.uploadGroups = 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
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
14
12
  class ImportGroups extends basecommand_1.BaseCommand {
@@ -39,23 +37,12 @@ class ImportGroups extends basecommand_1.BaseCommand {
39
37
  }
40
38
  }
41
39
  exports.default = ImportGroups;
42
- function getGroupsFile(fileLocation) {
43
- const CURRENT_DIR = process.cwd();
44
- try {
45
- const fullPath = path.join(CURRENT_DIR, fileLocation);
46
- const doc = js_yaml_1.default.load(fs_1.default.readFileSync(fullPath, 'utf8'));
47
- return doc;
48
- }
49
- catch (e) {
50
- throw new Error(`ERROR PARSING YAML: ${e}`);
51
- }
52
- }
53
40
  async function uploadGroups(fileLocation) {
54
41
  const existingGroups = await new sdk_1.Entities().get({
55
42
  entityName: 'group',
56
43
  relations: ['groupPrincipals'],
57
44
  });
58
- const groupsToImport = getGroupsFile(fileLocation);
45
+ const groupsToImport = (0, file_helpers_1.loadData)(fileLocation);
59
46
  const orgUsers = await new sdk_1.Entities().get({
60
47
  entityName: 'user-org',
61
48
  });
@@ -6,7 +6,6 @@ const core_1 = require("@oclif/core");
6
6
  const getAll_1 = require("../types/getAll");
7
7
  const export_1 = require("../users/export");
8
8
  const export_2 = require("../groups/export");
9
- const export_3 = require("../org-config/export");
10
9
  const basecommand_1 = require("../../basecommand");
11
10
  class ExportInstance extends basecommand_1.BaseCommand {
12
11
  static description = 'Exports an data of an instance including types and users';
@@ -33,7 +32,6 @@ class ExportInstance extends basecommand_1.BaseCommand {
33
32
  await (0, getAll_1.getAll)(this.flags.outDir, this.flags.overwrite);
34
33
  await (0, export_1.exportUsers)(path.join(this.flags.outDir, 'users.yml'), this.flags.overwrite);
35
34
  await (0, export_2.exportGroups)(path.join(this.flags.outDir, 'groups.yml'), this.flags.overwrite);
36
- await (0, export_3.exportOrgConfig)(this.flags.outDir, this.flags.overwrite);
37
35
  }
38
36
  }
39
37
  exports.default = ExportInstance;
@@ -9,3 +9,17 @@ export default class ImportInstance extends BaseCommand {
9
9
  };
10
10
  run(): Promise<void>;
11
11
  }
12
+ /**
13
+ * Imports an instance export in dependency order:
14
+ * 1. option sets, types, option set hierarchies, rule sets
15
+ * 2. users and groups (policy principals)
16
+ * 3. type property policies and type policies
17
+ * 4. type overrides — after policies, because they reference policies, rule sets and option
18
+ * set hierarchies by slug and the API rejects the whole upload if any of them is missing
19
+ * 5. org config
20
+ *
21
+ * Fails (rejects) if the type overrides could not be applied, so the import does not
22
+ * report success while the org is missing customizations carried by overrides
23
+ * (labels/formulas/editability of core properties, subtype-level property changes).
24
+ */
25
+ export declare function importInstance(dir: string): Promise<void>;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.importInstance = void 0;
3
4
  const tslib_1 = require("tslib");
4
5
  const path = tslib_1.__importStar(require("path"));
5
6
  const core_1 = require("@oclif/core");
@@ -8,6 +9,7 @@ const import_1 = require("../users/import");
8
9
  const import_2 = require("../groups/import");
9
10
  const uploadTypePropertyPolicies_1 = require("../types/uploadTypePropertyPolicies");
10
11
  const uploadTypePolicies_1 = require("../types/uploadTypePolicies");
12
+ const uploadTypeOverrides_1 = require("../types/uploadTypeOverrides");
11
13
  const import_3 = require("../org-config/import");
12
14
  const basecommand_1 = require("../../basecommand");
13
15
  class ImportInstance extends basecommand_1.BaseCommand {
@@ -26,12 +28,30 @@ class ImportInstance extends basecommand_1.BaseCommand {
26
28
  }),
27
29
  };
28
30
  async run() {
29
- await (0, loadAll_1.loadAll)(this.flags.dir, true);
30
- await (0, import_1.uploadUsers)(path.join(this.flags.dir, 'users.yml'));
31
- await (0, import_2.uploadGroups)(path.join(this.flags.dir, 'groups.yml'));
32
- await (0, uploadTypePropertyPolicies_1.uploadTypePropertyPolicies)(this.flags.dir);
33
- await (0, uploadTypePolicies_1.uploadTypePolicies)(this.flags.dir);
34
- await (0, import_3.uploadOrgConfig)(this.flags.dir);
31
+ await importInstance(this.flags.dir);
35
32
  }
36
33
  }
37
34
  exports.default = ImportInstance;
35
+ /**
36
+ * Imports an instance export in dependency order:
37
+ * 1. option sets, types, option set hierarchies, rule sets
38
+ * 2. users and groups (policy principals)
39
+ * 3. type property policies and type policies
40
+ * 4. type overrides — after policies, because they reference policies, rule sets and option
41
+ * set hierarchies by slug and the API rejects the whole upload if any of them is missing
42
+ * 5. org config
43
+ *
44
+ * Fails (rejects) if the type overrides could not be applied, so the import does not
45
+ * report success while the org is missing customizations carried by overrides
46
+ * (labels/formulas/editability of core properties, subtype-level property changes).
47
+ */
48
+ async function importInstance(dir) {
49
+ await (0, loadAll_1.loadAll)(dir, true, false);
50
+ await (0, import_1.uploadUsers)(path.join(dir, 'users.yml'));
51
+ await (0, import_2.uploadGroups)(path.join(dir, 'groups.yml'));
52
+ await (0, uploadTypePropertyPolicies_1.uploadTypePropertyPolicies)(dir);
53
+ await (0, uploadTypePolicies_1.uploadTypePolicies)(dir);
54
+ await (0, uploadTypeOverrides_1.uploadTypeOverrides)(dir);
55
+ await (0, import_3.uploadOrgConfig)(dir);
56
+ }
57
+ exports.importInstance = importInstance;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const import_1 = require("./import");
4
+ const loadAll_1 = require("../types/loadAll");
5
+ const import_2 = require("../users/import");
6
+ const import_3 = require("../groups/import");
7
+ const uploadTypePropertyPolicies_1 = require("../types/uploadTypePropertyPolicies");
8
+ const uploadTypePolicies_1 = require("../types/uploadTypePolicies");
9
+ const uploadTypeOverrides_1 = require("../types/uploadTypeOverrides");
10
+ const import_4 = require("../org-config/import");
11
+ jest.mock('../types/loadAll');
12
+ jest.mock('../users/import');
13
+ jest.mock('../groups/import');
14
+ jest.mock('../types/uploadTypePropertyPolicies');
15
+ jest.mock('../types/uploadTypePolicies');
16
+ jest.mock('../types/uploadTypeOverrides');
17
+ jest.mock('../org-config/import');
18
+ const mocked = (fn) => fn;
19
+ const STEPS = [
20
+ loadAll_1.loadAll,
21
+ import_2.uploadUsers,
22
+ import_3.uploadGroups,
23
+ uploadTypePropertyPolicies_1.uploadTypePropertyPolicies,
24
+ uploadTypePolicies_1.uploadTypePolicies,
25
+ uploadTypeOverrides_1.uploadTypeOverrides,
26
+ import_4.uploadOrgConfig,
27
+ ];
28
+ describe('importInstance', () => {
29
+ beforeEach(() => {
30
+ jest.clearAllMocks();
31
+ for (const fn of STEPS) {
32
+ mocked(fn).mockResolvedValue(undefined);
33
+ }
34
+ });
35
+ it('skips policies/overrides/org-config in loadAll and uploads them after users and groups', async () => {
36
+ await (0, import_1.importInstance)('export-dir');
37
+ expect(mocked(loadAll_1.loadAll)).toHaveBeenCalledWith('export-dir', true, false);
38
+ expect(mocked(uploadTypeOverrides_1.uploadTypeOverrides)).toHaveBeenCalledWith('export-dir');
39
+ expect(mocked(import_4.uploadOrgConfig)).toHaveBeenCalledWith('export-dir');
40
+ const order = STEPS.map((fn) => mocked(fn).mock.invocationCallOrder[0]);
41
+ // strictly increasing => called in exactly this order
42
+ for (let i = 1; i < order.length; i++) {
43
+ expect(order[i]).toBeGreaterThan(order[i - 1]);
44
+ }
45
+ });
46
+ it('uploads type overrides after type policies (CR-2095)', async () => {
47
+ await (0, import_1.importInstance)('export-dir');
48
+ const policiesOrder = mocked(uploadTypePolicies_1.uploadTypePolicies).mock.invocationCallOrder[0];
49
+ const overridesOrder = mocked(uploadTypeOverrides_1.uploadTypeOverrides).mock.invocationCallOrder[0];
50
+ expect(overridesOrder).toBeGreaterThan(policiesOrder);
51
+ });
52
+ it('rejects when the type override upload fails instead of reporting success', async () => {
53
+ mocked(uploadTypeOverrides_1.uploadTypeOverrides).mockRejectedValue(new Error('policy with identifier X not found'));
54
+ await expect((0, import_1.importInstance)('export-dir')).rejects.toThrow('policy with identifier X not found');
55
+ expect(mocked(import_4.uploadOrgConfig)).not.toHaveBeenCalled();
56
+ });
57
+ });
@@ -4,6 +4,7 @@ exports.exportOrgConfig = 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"));
7
+ const fs_1 = tslib_1.__importDefault(require("fs"));
7
8
  const login_1 = require("../../auth/login");
8
9
  const core_1 = require("@oclif/core");
9
10
  const file_helpers_1 = require("../../helpers/file-helpers");
@@ -42,30 +43,32 @@ class ExportOrgConfig extends basecommand_1.BaseCommand {
42
43
  }),
43
44
  };
44
45
  async run() {
45
- await exportOrgConfig(this.flags.outDir, this.flags.overwrite);
46
+ try {
47
+ await (0, login_1.authUser)();
48
+ this.log(`${chalk_1.default.blue.bold('START')} Getting Org Config.`);
49
+ this.log(`${chalk_1.default.yellow.bold('ORG CONFIG')} Exporting`);
50
+ await exportOrgConfig(this.flags.outDir, this.flags.overwrite);
51
+ this.log(`${chalk_1.default.yellow.bold('ORG CONFIG')} Exported`);
52
+ }
53
+ catch (e) {
54
+ this.log(`${chalk_1.default.red.bold('FAIL')} Org Config Export Failed. ${e}`);
55
+ return;
56
+ }
57
+ this.log(`${chalk_1.default.green.bold('COMPLETE')} Org Config Export Complete.`);
46
58
  }
47
59
  }
48
60
  exports.default = ExportOrgConfig;
49
61
  async function exportOrgConfig(outDir, overwrite) {
50
- try {
51
- await (0, login_1.authUser)();
52
- logger_1.default.info(`${chalk_1.default.yellow.bold('ORG CONFIG')} Exporting`);
53
- const orgConfigs = await (0, org_config_1.getOrgConfigs)();
54
- if (!Array.isArray(orgConfigs) || orgConfigs.length === 0) {
55
- logger_1.default.info(`${chalk_1.default.yellow.bold('ORG CONFIG')} No org config found. Skipping.`);
56
- return;
57
- }
58
- const orgConfig = { ...orgConfigs[0] };
59
- for (const field of ORG_SPECIFIC_FIELDS) {
60
- delete orgConfig[field];
61
- }
62
- await (0, file_helpers_1.saveData)(orgConfig, path.join(outDir, 'org-config.yml'), overwrite);
63
- logger_1.default.info(`${chalk_1.default.yellow.bold('ORG CONFIG')} Exported`);
64
- }
65
- catch (e) {
66
- logger_1.default.info(`${chalk_1.default.red.bold('FAIL')} Org Config Export Failed. ${e}`);
62
+ const orgConfigs = await (0, org_config_1.getOrgConfigs)();
63
+ if (!orgConfigs?.length) {
64
+ logger_1.default.info(`${chalk_1.default.yellow.bold('ORG CONFIG')} No org config found. Skipping.`);
67
65
  return;
68
66
  }
69
- logger_1.default.info(`${chalk_1.default.green.bold('COMPLETE')} Org Config Export Complete.`);
67
+ const orgConfig = { ...orgConfigs[0] };
68
+ for (const field of ORG_SPECIFIC_FIELDS) {
69
+ delete orgConfig[field];
70
+ }
71
+ fs_1.default.mkdirSync(path.resolve(outDir), { recursive: true });
72
+ await (0, file_helpers_1.saveData)(orgConfig, path.join(outDir, 'org-config.yml'), overwrite);
70
73
  }
71
74
  exports.exportOrgConfig = exportOrgConfig;
@@ -6,8 +6,8 @@ const path = tslib_1.__importStar(require("path"));
6
6
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
7
  const fs_1 = tslib_1.__importDefault(require("fs"));
8
8
  const login_1 = require("../../auth/login");
9
- const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
10
9
  const core_1 = require("@oclif/core");
10
+ const file_helpers_1 = require("../../helpers/file-helpers");
11
11
  const org_config_1 = require("../../api/org-config");
12
12
  const basecommand_1 = require("../../basecommand");
13
13
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
@@ -27,44 +27,37 @@ class ImportOrgConfig extends basecommand_1.BaseCommand {
27
27
  }),
28
28
  };
29
29
  async run() {
30
- await uploadOrgConfig(this.flags.dir);
31
- }
32
- }
33
- exports.default = ImportOrgConfig;
34
- async function uploadOrgConfig(dir) {
35
- try {
36
- await (0, login_1.authUser)();
37
- logger_1.default.info(`${chalk_1.default.yellow.bold('ORG CONFIG')} Uploading`);
38
- const orgConfig = loadOrgConfig(dir);
39
- if (!orgConfig || typeof orgConfig !== 'object') {
40
- logger_1.default.info(`${chalk_1.default.yellow.bold('ORG CONFIG')} No org config found in ${dir}. Skipping.`);
41
- return;
42
- }
43
- const existingOrgConfigs = await (0, org_config_1.getOrgConfigs)();
44
- if (Array.isArray(existingOrgConfigs) && existingOrgConfigs.length > 0) {
45
- await (0, org_config_1.updateOrgConfig)(existingOrgConfigs[0].id, orgConfig);
30
+ try {
31
+ await (0, login_1.authUser)();
32
+ this.log(`${chalk_1.default.blue.bold('START')} Uploading Org Config.`);
33
+ this.log(`${chalk_1.default.yellow.bold('ORG CONFIG')} Uploading`);
34
+ await uploadOrgConfig(this.flags.dir);
35
+ this.log(`${chalk_1.default.yellow.bold('ORG CONFIG')} Uploaded`);
46
36
  }
47
- else {
48
- await (0, org_config_1.createOrgConfig)(orgConfig);
37
+ catch (e) {
38
+ this.log(`${chalk_1.default.red.bold('FAIL')} Org Config Upload Failed. ${e}`);
39
+ return;
49
40
  }
50
- logger_1.default.info(`${chalk_1.default.yellow.bold('ORG CONFIG')} Uploaded`);
41
+ this.log(`${chalk_1.default.green.bold('COMPLETE')} Org Config Upload Complete.`);
51
42
  }
52
- catch (e) {
53
- logger_1.default.info(`${chalk_1.default.red.bold('FAIL')} Org Config Upload Failed. ${e}`);
54
- return;
55
- }
56
- logger_1.default.info(`${chalk_1.default.green.bold('COMPLETE')} Org Config Upload Complete.`);
57
43
  }
58
- exports.uploadOrgConfig = uploadOrgConfig;
59
- function loadOrgConfig(fromDir) {
60
- const filePath = path.join(fromDir, 'org-config.yml');
44
+ exports.default = ImportOrgConfig;
45
+ async function uploadOrgConfig(dir) {
46
+ const filePath = path.join(dir, 'org-config.yml');
61
47
  if (!fs_1.default.existsSync(filePath)) {
48
+ logger_1.default.info(`${chalk_1.default.yellow.bold('ORG CONFIG')} No org config found in ${dir}. Skipping.`);
62
49
  return;
63
50
  }
64
- try {
65
- return js_yaml_1.default.load(fs_1.default.readFileSync(filePath, 'utf8'));
51
+ const orgConfig = (0, file_helpers_1.loadData)(filePath);
52
+ if (!orgConfig || typeof orgConfig !== 'object' || Array.isArray(orgConfig)) {
53
+ throw new Error(`${filePath} must contain a single YAML object`);
54
+ }
55
+ const existingOrgConfigs = await (0, org_config_1.getOrgConfigs)();
56
+ if (existingOrgConfigs?.length) {
57
+ await (0, org_config_1.updateOrgConfig)(existingOrgConfigs[0].id, orgConfig);
66
58
  }
67
- catch (e) {
68
- throw new Error(`ERROR PARSING YAML: ${e}`);
59
+ else {
60
+ await (0, org_config_1.createOrgConfig)(orgConfig);
69
61
  }
70
62
  }
63
+ exports.uploadOrgConfig = uploadOrgConfig;
@@ -11,6 +11,7 @@ const type_extractor_1 = require("../../helpers/types/type-extractor");
11
11
  const types_1 = require("../../api/types");
12
12
  const types_2 = require("@contrail/org-config-migration/lib/types");
13
13
  const file_helpers_1 = require("../../helpers/file-helpers");
14
+ const export_1 = require("../org-config/export");
14
15
  const basecommand_1 = require("../../basecommand");
15
16
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
16
17
  const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
@@ -64,6 +65,7 @@ async function getAll(outDir, overwrite) {
64
65
  };
65
66
  const reducedData = await (0, types_2.getAllReducedTypes)(data);
66
67
  await saveData(reducedData, outDir, overwrite);
68
+ await (0, export_1.exportOrgConfig)(outDir, overwrite);
67
69
  logger_1.default.info(`${chalk_1.default.yellow.bold('TYPES')} Retrieved`);
68
70
  }
69
71
  catch (e) {
@@ -4,9 +4,21 @@ export default class LoadAllTypes extends BaseCommand {
4
4
  static examples: string[];
5
5
  static flags: {
6
6
  dir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
7
+ orgConfig: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
8
  alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
8
9
  logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
9
10
  };
10
11
  run(): Promise<void>;
11
12
  }
12
- export declare function loadAll(dir: string, skipPolicies?: boolean): Promise<void>;
13
+ /**
14
+ * Loads everything exported by `types getAll`.
15
+ *
16
+ * @param dir directory containing the export
17
+ * @param skipPolicies when true, type property policies, type policies AND type overrides are
18
+ * not uploaded. Policies need users/groups to exist first, and type overrides can reference
19
+ * policies (as well as rule sets and option set hierarchies), so the API rejects the whole
20
+ * override upload if a referenced policy is missing. Callers that pass `true` (e.g.
21
+ * `instance import`) must upload policies and then overrides themselves, in that order.
22
+ * @param loadOrgConfig whether to also load org-config.yml; prompts when undefined
23
+ */
24
+ export declare function loadAll(dir: string, skipPolicies?: boolean, loadOrgConfig?: boolean): Promise<void>;
@@ -4,6 +4,7 @@ exports.loadAll = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const core_1 = require("@oclif/core");
6
6
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
+ const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
7
8
  const login_1 = require("../../auth/login");
8
9
  const uploadOptionSets_1 = require("./uploadOptionSets");
9
10
  const uploadOptionSetHierarchies_1 = require("./uploadOptionSetHierarchies");
@@ -12,8 +13,10 @@ const uploadTypePropertyPolicies_1 = require("./uploadTypePropertyPolicies");
12
13
  const type_uploader_1 = require("../../helpers/types/type-uploader");
13
14
  const uploadTypeOverrides_1 = require("./uploadTypeOverrides");
14
15
  const uploadTypePolicies_1 = require("./uploadTypePolicies");
16
+ const import_1 = require("../org-config/import");
15
17
  const basecommand_1 = require("../../basecommand");
16
18
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
19
+ const error_helpers_1 = require("../../helpers/error-helpers");
17
20
  class LoadAllTypes extends basecommand_1.BaseCommand {
18
21
  static description = 'loads all the type data that has been exported by getAll';
19
22
  static examples = [
@@ -28,13 +31,30 @@ class LoadAllTypes extends basecommand_1.BaseCommand {
28
31
  required: false,
29
32
  default: '.',
30
33
  }),
34
+ orgConfig: core_1.Flags.boolean({
35
+ description: 'Also load org config data from this directory. If not specified, you will be prompted.',
36
+ required: false,
37
+ default: undefined,
38
+ allowNo: true,
39
+ }),
31
40
  };
32
41
  async run() {
33
- await loadAll(this.flags.dir);
42
+ await loadAll(this.flags.dir, false, this.flags.orgConfig);
34
43
  }
35
44
  }
36
45
  exports.default = LoadAllTypes;
37
- async function loadAll(dir, skipPolicies = false) {
46
+ /**
47
+ * Loads everything exported by `types getAll`.
48
+ *
49
+ * @param dir directory containing the export
50
+ * @param skipPolicies when true, type property policies, type policies AND type overrides are
51
+ * not uploaded. Policies need users/groups to exist first, and type overrides can reference
52
+ * policies (as well as rule sets and option set hierarchies), so the API rejects the whole
53
+ * override upload if a referenced policy is missing. Callers that pass `true` (e.g.
54
+ * `instance import`) must upload policies and then overrides themselves, in that order.
55
+ * @param loadOrgConfig whether to also load org-config.yml; prompts when undefined
56
+ */
57
+ async function loadAll(dir, skipPolicies = false, loadOrgConfig) {
38
58
  try {
39
59
  await (0, login_1.authUser)();
40
60
  await (0, uploadOptionSets_1.uploadOptionSets)(dir);
@@ -44,11 +64,28 @@ async function loadAll(dir, skipPolicies = false) {
44
64
  if (!skipPolicies) {
45
65
  await (0, uploadTypePropertyPolicies_1.uploadTypePropertyPolicies)(dir);
46
66
  await (0, uploadTypePolicies_1.uploadTypePolicies)(dir);
67
+ // must run after policies, rule sets and option set hierarchies: overrides reference them by slug
68
+ await (0, uploadTypeOverrides_1.uploadTypeOverrides)(dir);
69
+ }
70
+ let shouldLoadOrgConfig = loadOrgConfig;
71
+ if (shouldLoadOrgConfig === undefined) {
72
+ const answers = await inquirer_1.default.prompt([
73
+ {
74
+ type: 'confirm',
75
+ name: 'loadOrgConfig',
76
+ message: 'Also load org config details from this directory?',
77
+ default: false,
78
+ },
79
+ ]);
80
+ shouldLoadOrgConfig = answers.loadOrgConfig;
81
+ }
82
+ if (shouldLoadOrgConfig) {
83
+ await (0, import_1.uploadOrgConfig)(dir);
47
84
  }
48
- await (0, uploadTypeOverrides_1.uploadTypeOverrides)(dir);
49
85
  }
50
86
  catch (e) {
51
- logger_1.default.info(`${chalk_1.default.red.bold('FAIL')} Types Upload Failed. ${e}`);
87
+ logger_1.default.error(`${chalk_1.default.red.bold('FAIL')} Types Upload Failed. ${(0, error_helpers_1.describeError)(e)}`);
88
+ process.exitCode = 1;
52
89
  return;
53
90
  }
54
91
  logger_1.default.info(`${chalk_1.default.green.bold('COMPLETE')} Types Upload Complete.`);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const loadAll_1 = require("./loadAll");
4
+ const login_1 = require("../../auth/login");
5
+ const uploadOptionSets_1 = require("./uploadOptionSets");
6
+ const uploadOptionSetHierarchies_1 = require("./uploadOptionSetHierarchies");
7
+ const uploadRuleSets_1 = require("./uploadRuleSets");
8
+ const uploadTypePropertyPolicies_1 = require("./uploadTypePropertyPolicies");
9
+ const uploadTypePolicies_1 = require("./uploadTypePolicies");
10
+ const uploadTypeOverrides_1 = require("./uploadTypeOverrides");
11
+ const type_uploader_1 = require("../../helpers/types/type-uploader");
12
+ const import_1 = require("../org-config/import");
13
+ jest.mock('../../auth/login');
14
+ jest.mock('./uploadOptionSets');
15
+ jest.mock('./uploadOptionSetHierarchies');
16
+ jest.mock('./uploadRuleSets');
17
+ jest.mock('./uploadTypePropertyPolicies');
18
+ jest.mock('./uploadTypePolicies');
19
+ jest.mock('./uploadTypeOverrides');
20
+ jest.mock('../../helpers/types/type-uploader');
21
+ jest.mock('../org-config/import');
22
+ const mocked = (fn) => fn;
23
+ describe('loadAll', () => {
24
+ const originalExitCode = process.exitCode;
25
+ beforeEach(() => {
26
+ jest.clearAllMocks();
27
+ process.exitCode = originalExitCode;
28
+ mocked(login_1.authUser).mockResolvedValue({ orgSlug: 'test-org' });
29
+ for (const fn of [
30
+ uploadOptionSets_1.uploadOptionSets,
31
+ type_uploader_1.uploadTypes,
32
+ uploadOptionSetHierarchies_1.uploadOptionSetHierarchies,
33
+ uploadRuleSets_1.uploadRuleSets,
34
+ uploadTypePropertyPolicies_1.uploadTypePropertyPolicies,
35
+ uploadTypePolicies_1.uploadTypePolicies,
36
+ uploadTypeOverrides_1.uploadTypeOverrides,
37
+ import_1.uploadOrgConfig,
38
+ ]) {
39
+ mocked(fn).mockResolvedValue(undefined);
40
+ }
41
+ });
42
+ afterAll(() => {
43
+ process.exitCode = originalExitCode;
44
+ });
45
+ it('uploads type overrides after policies, rule sets and option set hierarchies', async () => {
46
+ await (0, loadAll_1.loadAll)('dir', false, false);
47
+ expect(mocked(uploadTypeOverrides_1.uploadTypeOverrides)).toHaveBeenCalledWith('dir');
48
+ expect(mocked(import_1.uploadOrgConfig)).not.toHaveBeenCalled();
49
+ const overridesOrder = mocked(uploadTypeOverrides_1.uploadTypeOverrides).mock.invocationCallOrder[0];
50
+ for (const fn of [
51
+ uploadOptionSetHierarchies_1.uploadOptionSetHierarchies,
52
+ uploadRuleSets_1.uploadRuleSets,
53
+ uploadTypePropertyPolicies_1.uploadTypePropertyPolicies,
54
+ uploadTypePolicies_1.uploadTypePolicies,
55
+ ]) {
56
+ expect(overridesOrder).toBeGreaterThan(mocked(fn).mock.invocationCallOrder[0]);
57
+ }
58
+ });
59
+ it('skips policies and type overrides when skipPolicies is set', async () => {
60
+ await (0, loadAll_1.loadAll)('dir', true, false);
61
+ expect(mocked(type_uploader_1.uploadTypes)).toHaveBeenCalledWith('dir');
62
+ expect(mocked(uploadRuleSets_1.uploadRuleSets)).toHaveBeenCalledWith('dir');
63
+ expect(mocked(uploadTypePropertyPolicies_1.uploadTypePropertyPolicies)).not.toHaveBeenCalled();
64
+ expect(mocked(uploadTypePolicies_1.uploadTypePolicies)).not.toHaveBeenCalled();
65
+ expect(mocked(uploadTypeOverrides_1.uploadTypeOverrides)).not.toHaveBeenCalled();
66
+ });
67
+ it('sets a non-zero exit code when a step fails', async () => {
68
+ mocked(uploadTypeOverrides_1.uploadTypeOverrides).mockRejectedValue(new Error('boom'));
69
+ await (0, loadAll_1.loadAll)('dir', false, false);
70
+ expect(process.exitCode).toBe(1);
71
+ });
72
+ it('loads org config when requested, after everything else', async () => {
73
+ await (0, loadAll_1.loadAll)('dir', false, true);
74
+ expect(mocked(import_1.uploadOrgConfig)).toHaveBeenCalledWith('dir');
75
+ expect(mocked(import_1.uploadOrgConfig).mock.invocationCallOrder[0]).toBeGreaterThan(mocked(uploadTypeOverrides_1.uploadTypeOverrides).mock.invocationCallOrder[0]);
76
+ });
77
+ });
@@ -1,16 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
- const path = tslib_1.__importStar(require("path"));
5
4
  const sdk_1 = require("@contrail/sdk");
6
- const fs_1 = tslib_1.__importDefault(require("fs"));
7
5
  const login_1 = require("../../auth/login");
8
6
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
9
7
  const core_1 = require("@oclif/core");
10
8
  const basecommand_1 = require("../../basecommand");
11
9
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
12
- const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
13
10
  const option_set_helper_1 = require("../../helpers/types/option-set-helper");
11
+ const file_helpers_1 = require("../../helpers/file-helpers");
14
12
  class MergeOptionSets extends basecommand_1.BaseCommand {
15
13
  static description = 'Merge the OptionSets from an existing org and the OptionSets passed in. OptionSets File must be .yml or a directory with yml files. The files can contain a single option set or an array of option sets.';
16
14
  static examples = [
@@ -88,7 +86,7 @@ async function mergeOptionSets(fileLocation) {
88
86
  mergedOptionSets.push(currentOptionSet);
89
87
  }
90
88
  }
91
- saveData(mergedOptionSets);
89
+ await (0, file_helpers_1.saveData)(mergedOptionSets, 'merged_optionSets.yml', true);
92
90
  }
93
91
  function mergeOptionSet(existingOptionSet, uploadedOptionSet) {
94
92
  const merged = {
@@ -110,8 +108,3 @@ function mergeOptionSet(existingOptionSet, uploadedOptionSet) {
110
108
  }
111
109
  return merged;
112
110
  }
113
- function saveData(data) {
114
- const yamlStr = js_yaml_1.default.dump(data);
115
- const CURRENT_DIR = process.cwd();
116
- fs_1.default.writeFileSync(path.resolve(path.join(CURRENT_DIR, 'merged_optionSets.yml')), yamlStr, 'utf8');
117
- }
@@ -11,6 +11,7 @@ const types_1 = require("@contrail/org-config-migration/lib/types");
11
11
  const types_2 = require("../../api/types");
12
12
  const basecommand_1 = require("../../basecommand");
13
13
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
14
+ const file_helpers_1 = require("../../helpers/file-helpers");
14
15
  const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
15
16
  class MergeTypeProperties extends basecommand_1.BaseCommand {
16
17
  static description = 'Merge the TypeProperties from an existing org and the TypeProperties passed in. Add to Types via TypePath File must be .yml';
@@ -74,7 +75,7 @@ async function mergeTypeProperties(fileLocation, typePaths) {
74
75
  }
75
76
  mergeType(foundType, uploadedTypeProperties);
76
77
  }
77
- saveData({ types: reducedData.reducedTypes });
78
+ await (0, file_helpers_1.saveData)({ types: reducedData.reducedTypes }, 'merged_types.yml', true);
78
79
  }
79
80
  function mergeType(reducedType, typePropertiesToAdd) {
80
81
  for (const typeProperty of typePropertiesToAdd) {
@@ -84,8 +85,3 @@ function mergeType(reducedType, typePropertiesToAdd) {
84
85
  }
85
86
  }
86
87
  }
87
- function saveData(data) {
88
- const yamlStr = js_yaml_1.default.dump(data);
89
- const CURRENT_DIR = process.cwd();
90
- fs_1.default.writeFileSync(path.resolve(path.join(CURRENT_DIR, 'merged_types.yml')), yamlStr, 'utf8');
91
- }
@@ -12,6 +12,7 @@ const core_1 = require("@oclif/core");
12
12
  const basecommand_1 = require("../../basecommand");
13
13
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
14
14
  const common_1 = require("../../common");
15
+ const file_helpers_1 = require("../../helpers/file-helpers");
15
16
  class ParseOptionSetsLocalFiles extends basecommand_1.BaseCommand {
16
17
  static description = 'Parse CSV files into OptionSets. Style denotes the format the csv is in. If the csv file has a column for option set' +
17
18
  'name, option key, and option label, use "NameAndLabelAndKey". If the file has one column for each option set with ' +
@@ -109,22 +110,17 @@ async function parseOptionSetsLocalFiles(fileLocation, csvStyle) {
109
110
  const uploadedOptionData = await loadOptionSetCSV(fileLocation);
110
111
  if (csvStyle === 'NameAndLabelAndKey') {
111
112
  const parsedOptionSets = parseNameAndLabelAndKey(uploadedOptionData);
112
- saveData(parsedOptionSets);
113
+ await (0, file_helpers_1.saveData)(parsedOptionSets, 'parsed_local_optionSets.yml', true);
113
114
  }
114
115
  if (csvStyle === 'LabelsInColumn') {
115
116
  const parsedOptionSets = parseLabelsInColumn(uploadedOptionData);
116
- saveData(parsedOptionSets);
117
+ await (0, file_helpers_1.saveData)(parsedOptionSets, 'parsed_local_optionSets.yml', true);
117
118
  }
118
119
  }
119
120
  async function parseOptionSetsToFolderStructure(fileLocation) {
120
121
  const uploadedOptionData = await loadOptionSets(fileLocation);
121
122
  saveDataInFolders(uploadedOptionData);
122
123
  }
123
- function saveData(data) {
124
- const yamlStr = js_yaml_1.default.dump(data);
125
- const CURRENT_DIR = process.cwd();
126
- fs_1.default.writeFileSync(path.resolve(path.join(CURRENT_DIR, 'parsed_local_optionSets.yml')), yamlStr, 'utf8');
127
- }
128
124
  function saveDataInFolders(data) {
129
125
  const folder = { filename: 'option-sets', split: true, fileLabel: 'name' };
130
126
  const dir = 'merged_option_sets';
@@ -9,4 +9,14 @@ export default class UploadTypeOverrides extends BaseCommand {
9
9
  };
10
10
  run(): Promise<void>;
11
11
  }
12
+ /**
13
+ * Uploads $dir/type-overrides.yml to the org.
14
+ *
15
+ * Type overrides can reference type policies, rule sets and option set hierarchies by
16
+ * slug, and the API rejects the whole upload (nothing is applied) if any referenced
17
+ * entity does not exist yet. Callers must therefore upload those entities first.
18
+ *
19
+ * Throws on failure so that callers such as `instance import` do not report success
20
+ * when the overrides were not applied.
21
+ */
12
22
  export declare function uploadTypeOverrides(dir: string): Promise<void>;
@@ -11,6 +11,7 @@ const basecommand_1 = require("../../basecommand");
11
11
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
12
12
  const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
13
13
  const types_1 = require("../../api/types");
14
+ const error_helpers_1 = require("../../helpers/error-helpers");
14
15
  class UploadTypeOverrides extends basecommand_1.BaseCommand {
15
16
  static description = 'Upload type overrides';
16
17
  static examples = [
@@ -31,21 +32,31 @@ class UploadTypeOverrides extends basecommand_1.BaseCommand {
31
32
  }
32
33
  }
33
34
  exports.default = UploadTypeOverrides;
35
+ /**
36
+ * Uploads $dir/type-overrides.yml to the org.
37
+ *
38
+ * Type overrides can reference type policies, rule sets and option set hierarchies by
39
+ * slug, and the API rejects the whole upload (nothing is applied) if any referenced
40
+ * entity does not exist yet. Callers must therefore upload those entities first.
41
+ *
42
+ * Throws on failure so that callers such as `instance import` do not report success
43
+ * when the overrides were not applied.
44
+ */
34
45
  async function uploadTypeOverrides(dir) {
46
+ await (0, login_1.authUser)();
47
+ const reducedOverrides = readTypeOverridesFromFile(dir);
48
+ if (!reducedOverrides || reducedOverrides.length === 0) {
49
+ logger_1.default.info(`${chalk_1.default.yellow.bold('TYPE OVERRIDE')} No overrides found in type-overrides.yml — skipping upload to avoid wiping org overrides`);
50
+ return;
51
+ }
35
52
  try {
36
- await (0, login_1.authUser)();
37
- const reducedOverrides = readTypeOverridesFromFile(dir);
38
- if (!reducedOverrides || reducedOverrides.length === 0) {
39
- logger_1.default.info(`${chalk_1.default.yellow.bold('TYPE OVERRIDE')} No overrides found in type-overrides.yml — skipping upload to avoid wiping org overrides`);
40
- return;
41
- }
42
53
  logger_1.default.info(`${chalk_1.default.yellow.bold('TYPE OVERRIDE')} Uploading`);
43
54
  await (0, types_1.uploadTypeOverrideObjects)(reducedOverrides);
44
55
  logger_1.default.info(`${chalk_1.default.yellow.bold('TYPE OVERRIDE')} Uploaded`);
45
56
  }
46
57
  catch (e) {
47
- logger_1.default.info(`${chalk_1.default.red.bold('FAIL')} Type Override Upload Failed . ${e}`);
48
- return;
58
+ logger_1.default.error(`${chalk_1.default.red.bold('FAIL')} Type Override Upload Failed. None of the ${reducedOverrides.length} overrides were applied: ${(0, error_helpers_1.describeError)(e)}`);
59
+ throw e;
49
60
  }
50
61
  logger_1.default.info(`${chalk_1.default.green.bold('COMPLETE')} Type Override Upload Complete.`);
51
62
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const fs_1 = tslib_1.__importDefault(require("fs"));
5
+ const uploadTypeOverrides_1 = require("./uploadTypeOverrides");
6
+ const login_1 = require("../../auth/login");
7
+ const types_1 = require("../../api/types");
8
+ const logger_1 = tslib_1.__importDefault(require("../../logger"));
9
+ jest.mock('../../auth/login');
10
+ jest.mock('../../api/types');
11
+ const OVERRIDES_YML = `
12
+ - typePath: item
13
+ attribute:
14
+ type: policy
15
+ overrideType: add
16
+ referenceSlug: Limit-Create
17
+ `;
18
+ describe('uploadTypeOverrides', () => {
19
+ let errorSpy;
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+ login_1.authUser.mockResolvedValue({
23
+ orgSlug: 'test-org',
24
+ });
25
+ jest.spyOn(fs_1.default, 'existsSync').mockReturnValue(true);
26
+ jest.spyOn(fs_1.default, 'readFileSync').mockReturnValue(OVERRIDES_YML);
27
+ errorSpy = jest.spyOn(logger_1.default, 'error').mockImplementation(() => logger_1.default);
28
+ jest.spyOn(logger_1.default, 'info').mockImplementation(() => logger_1.default);
29
+ });
30
+ afterEach(() => {
31
+ jest.restoreAllMocks();
32
+ });
33
+ it('uploads the overrides from type-overrides.yml', async () => {
34
+ types_1.uploadTypeOverrideObjects.mockResolvedValue(undefined);
35
+ await (0, uploadTypeOverrides_1.uploadTypeOverrides)('dir');
36
+ expect(types_1.uploadTypeOverrideObjects).toHaveBeenCalledWith([
37
+ {
38
+ typePath: 'item',
39
+ attribute: { type: 'policy', overrideType: 'add', referenceSlug: 'Limit-Create' },
40
+ },
41
+ ]);
42
+ });
43
+ it('skips the upload when the file has no overrides', async () => {
44
+ jest.spyOn(fs_1.default, 'readFileSync').mockReturnValue('[]');
45
+ await (0, uploadTypeOverrides_1.uploadTypeOverrides)('dir');
46
+ expect(types_1.uploadTypeOverrideObjects).not.toHaveBeenCalled();
47
+ });
48
+ it('rethrows API failures and logs the API message', async () => {
49
+ // shape of the SDK ApiError: undefined message, raw response body in `details`
50
+ const apiError = new Error();
51
+ apiError.details = JSON.stringify({
52
+ message: 'policy with identifier Limit-Create not found',
53
+ error: 'Bad Request',
54
+ statusCode: 400,
55
+ });
56
+ types_1.uploadTypeOverrideObjects.mockRejectedValue(apiError);
57
+ await expect((0, uploadTypeOverrides_1.uploadTypeOverrides)('dir')).rejects.toBe(apiError);
58
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('policy with identifier Limit-Create not found'));
59
+ });
60
+ });
@@ -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 = getUsersFile(fileLocation);
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;
@@ -2,12 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
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");
@@ -65,15 +62,6 @@ async function exportWorkflows(fileLocation, overwrite) {
65
62
  await (0, helper_1.populateActionIdentifiers)(exportWorkflow);
66
63
  workflowsObj.workflows.push(exportWorkflow);
67
64
  }
68
- await saveData(workflowsObj, fileLocation, overwrite);
65
+ await (0, file_helpers_1.saveData)(workflowsObj, fileLocation, overwrite);
69
66
  }
70
67
  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);
77
- }
78
- fs_1.default.writeFileSync(pathToWrite, yamlStr, 'utf8');
79
- }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Returns a human readable message for an error thrown by the SDK or by the CLI.
3
+ *
4
+ * The SDK's ApiError is constructed with an undefined message and keeps the raw
5
+ * response body in `details` (usually a JSON string such as
6
+ * `{"message":"policy with identifier X not found","statusCode":400}`), so
7
+ * `${error}` only prints "Error". This digs the API message out of `details`.
8
+ */
9
+ export declare function describeError(error: any): string;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.describeError = void 0;
4
+ /**
5
+ * Returns a human readable message for an error thrown by the SDK or by the CLI.
6
+ *
7
+ * The SDK's ApiError is constructed with an undefined message and keeps the raw
8
+ * response body in `details` (usually a JSON string such as
9
+ * `{"message":"policy with identifier X not found","statusCode":400}`), so
10
+ * `${error}` only prints "Error". This digs the API message out of `details`.
11
+ */
12
+ function describeError(error) {
13
+ const details = error?.details;
14
+ if (typeof details === 'string' && details.length) {
15
+ try {
16
+ const parsed = JSON.parse(details);
17
+ const message = parsed?.message;
18
+ if (Array.isArray(message)) {
19
+ return message.join(', ');
20
+ }
21
+ if (message) {
22
+ return String(message);
23
+ }
24
+ }
25
+ catch {
26
+ // not JSON, fall through and use the raw body
27
+ }
28
+ return details;
29
+ }
30
+ if (details?.message) {
31
+ return String(details.message);
32
+ }
33
+ return error?.message || String(error);
34
+ }
35
+ exports.describeError = describeError;
@@ -1,3 +1,4 @@
1
1
  export declare function safeFileOverwrite(fileLocation: string, overwrite: boolean): Promise<boolean>;
2
2
  export declare function parseCSVData(data: string): any;
3
3
  export declare function saveData(data: any, fileLocation: string, overwrite: boolean): Promise<void>;
4
+ export declare function loadData(fileLocation: string): any;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.saveData = exports.parseCSVData = exports.safeFileOverwrite = void 0;
3
+ exports.loadData = exports.saveData = exports.parseCSVData = exports.safeFileOverwrite = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const path = tslib_1.__importStar(require("path"));
6
6
  const fs_1 = tslib_1.__importDefault(require("fs"));
@@ -37,14 +37,24 @@ function parseCSVData(data) {
37
37
  });
38
38
  }
39
39
  exports.parseCSVData = parseCSVData;
40
- //saves data as yaml to fileLocation (relative to the current directory), prompting before overwriting unless overwrite is passed
40
+ //saves data as yaml to fileLocation (resolved against the current directory), prompting before overwriting unless overwrite is passed
41
41
  async function saveData(data, fileLocation, overwrite) {
42
42
  const yamlStr = js_yaml_1.default.dump(data);
43
- const CURRENT_DIR = process.cwd();
44
- const pathToWrite = path.resolve(path.join(CURRENT_DIR, fileLocation));
43
+ const pathToWrite = path.resolve(fileLocation);
45
44
  if (fs_1.default.existsSync(pathToWrite)) {
46
45
  await safeFileOverwrite(pathToWrite, overwrite);
47
46
  }
48
47
  fs_1.default.writeFileSync(pathToWrite, yamlStr, 'utf8');
49
48
  }
50
49
  exports.saveData = saveData;
50
+ //loads yaml data from fileLocation (resolved against the current directory)
51
+ function loadData(fileLocation) {
52
+ const pathToRead = path.resolve(fileLocation);
53
+ try {
54
+ return js_yaml_1.default.load(fs_1.default.readFileSync(pathToRead, 'utf8'));
55
+ }
56
+ catch (e) {
57
+ throw new Error(`ERROR PARSING YAML: ${e}`);
58
+ }
59
+ }
60
+ exports.loadData = loadData;
@@ -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 file_helpers_1 = require("../file-helpers");
11
12
  function getProjectFile(fileName) {
12
13
  const CURRENT_DIR = process.cwd();
13
14
  try {
@@ -42,7 +43,7 @@ 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
- saveData({ projectConfiguration: projectConfiguration }, name);
46
+ await (0, file_helpers_1.saveData)({ projectConfiguration: projectConfiguration }, `${name}-export.yml`, true);
46
47
  }
47
48
  exports.exportProject = exportProject;
48
49
  async function getAssortmentsAndSubFolders(parentId, assortments, subFolders, allAssortments) {
@@ -160,8 +161,3 @@ function deleteNonUserKeys(assortment) {
160
161
  delete assortment.updatedOn;
161
162
  delete assortment.workspaceId;
162
163
  }
163
- function saveData(data, name) {
164
- const yamlStr = js_yaml_1.default.dump(data);
165
- const CURRENT_DIR = process.cwd();
166
- fs_1.default.writeFileSync(path.join(CURRENT_DIR, `${name}-export.yml`), yamlStr, 'utf8');
167
- }
@@ -4,12 +4,11 @@ exports.parseTypesFromFiles = exports.parseTypesFromTemplateAndDataFromLocalFile
4
4
  const tslib_1 = require("tslib");
5
5
  const path = tslib_1.__importStar(require("path"));
6
6
  const types_1 = require("@contrail/types");
7
- const fs_1 = tslib_1.__importDefault(require("fs"));
8
7
  const csvToJson = tslib_1.__importStar(require("csvtojson"));
9
8
  const login_1 = require("../../auth/login");
10
9
  const csv_parser_1 = require("../../helpers/types/csv-parser");
11
10
  const logger_1 = tslib_1.__importDefault(require("../../logger"));
12
- const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
11
+ const file_helpers_1 = require("../file-helpers");
13
12
  async function loadDataFromLocalCSV(fileName) {
14
13
  const CURRENT_DIR = process.cwd();
15
14
  try {
@@ -37,8 +36,8 @@ async function parseTypesFromLocalFiles(fileLocations) {
37
36
  }
38
37
  logger_1.default.info(`CSV imports were successful. ${csvJSONData?.length} total rows found.`);
39
38
  const suggestions = (0, types_1.GetSuggestedTypes)(csvJSONData);
40
- saveData(suggestions.OptionSets, 'parsed_optionSets.yml');
41
- saveData({ typeProperties: suggestions.TypeProperties }, 'parsed_typeProperties.yml');
39
+ await (0, file_helpers_1.saveData)(suggestions.OptionSets, 'parsed_optionSets.yml', true);
40
+ await (0, file_helpers_1.saveData)({ typeProperties: suggestions.TypeProperties }, 'parsed_typeProperties.yml', true);
42
41
  }
43
42
  exports.parseTypesFromLocalFiles = parseTypesFromLocalFiles;
44
43
  async function parseTypesFromTemplateFromLocalFiles(templateFileLocation, dataFileLocation) {
@@ -57,8 +56,8 @@ async function parseTypesFromTemplateFromLocalFiles(templateFileLocation, dataFi
57
56
  throw e;
58
57
  }
59
58
  const types = (0, types_1.GetTypesFromTemplate)(templateCSVData, dataCSVData);
60
- saveData(types.OptionSets, 'parsed_optionSets.yml');
61
- saveData({ TypePropertiesByEntity: types.TypePropertiesByEntity }, 'parsed_TypePropertiesByEntity.yml');
59
+ await (0, file_helpers_1.saveData)(types.OptionSets, 'parsed_optionSets.yml', true);
60
+ await (0, file_helpers_1.saveData)({ TypePropertiesByEntity: types.TypePropertiesByEntity }, 'parsed_TypePropertiesByEntity.yml', true);
62
61
  }
63
62
  exports.parseTypesFromTemplateFromLocalFiles = parseTypesFromTemplateFromLocalFiles;
64
63
  async function parseTypesFromTemplateAndDataFromLocalFiles(templateFileLocation, dataFileLocations) {
@@ -86,8 +85,8 @@ async function parseTypesFromTemplateAndDataFromLocalFiles(templateFileLocation,
86
85
  }
87
86
  }
88
87
  const types = (0, types_1.GetTypesFromTemplateWithOptionSetsFromData)(templateCSVData, dataCSVData);
89
- saveData(types.OptionSets, 'parsed_optionSets.yml');
90
- saveData({ TypePropertiesByEntity: types.TypePropertiesByEntity }, 'parsed_TypePropertiesByEntity.yml');
88
+ await (0, file_helpers_1.saveData)(types.OptionSets, 'parsed_optionSets.yml', true);
89
+ await (0, file_helpers_1.saveData)({ TypePropertiesByEntity: types.TypePropertiesByEntity }, 'parsed_TypePropertiesByEntity.yml', true);
91
90
  }
92
91
  exports.parseTypesFromTemplateAndDataFromLocalFiles = parseTypesFromTemplateAndDataFromLocalFiles;
93
92
  async function parseTypesFromFiles(fileIds) {
@@ -108,12 +107,7 @@ async function parseTypesFromFiles(fileIds) {
108
107
  }
109
108
  logger_1.default.info(`CSV downloads were successful. ${csvJSONData?.length} total rows found.`);
110
109
  const suggestions = (0, types_1.GetSuggestedTypes)(csvJSONData);
111
- saveData(suggestions.OptionSets, 'parsed_optionSets.yml');
112
- saveData({ typeProperties: suggestions.TypeProperties }, 'parsed_typeProperties.yml');
110
+ await (0, file_helpers_1.saveData)(suggestions.OptionSets, 'parsed_optionSets.yml', true);
111
+ await (0, file_helpers_1.saveData)({ typeProperties: suggestions.TypeProperties }, 'parsed_typeProperties.yml', true);
113
112
  }
114
113
  exports.parseTypesFromFiles = parseTypesFromFiles;
115
- function saveData(data, name) {
116
- const yamlStr = js_yaml_1.default.dump(data);
117
- const CURRENT_DIR = process.cwd();
118
- fs_1.default.writeFileSync(path.resolve(path.join(CURRENT_DIR, name, yamlStr)), 'utf8');
119
- }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.5.3-alpha.0dcc977",
2
+ "version": "3.5.3-alpha.297ad36",
3
3
  "commands": {
4
4
  "app:create": {
5
5
  "id": "app:create",
@@ -1703,6 +1703,13 @@
1703
1703
  "required": false,
1704
1704
  "multiple": false,
1705
1705
  "default": "."
1706
+ },
1707
+ "orgConfig": {
1708
+ "name": "orgConfig",
1709
+ "type": "boolean",
1710
+ "description": "Also load org config data from this directory. If not specified, you will be prompted.",
1711
+ "required": false,
1712
+ "allowNo": true
1706
1713
  }
1707
1714
  },
1708
1715
  "args": {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/cli",
3
- "version": "3.5.3-alpha.0dcc977",
3
+ "version": "3.5.3-alpha.297ad36",
4
4
  "description": "VibeIQ's Contrail Platform CLI.",
5
5
  "bin": {
6
6
  "contrail-cli": "./bin/run",
@@ -113,6 +113,9 @@
113
113
  "groups": {
114
114
  "description": "Export and import user groups"
115
115
  },
116
+ "org-config": {
117
+ "description": "Export and import org-level configuration"
118
+ },
116
119
  "webhooks": {
117
120
  "description": "Manage VibeIQ webhooks"
118
121
  },