@contrail/cli 3.5.4-alpha.f60c4df → 3.5.5-alpha.5c010ac

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 (54) hide show
  1. package/README.md +112 -23
  2. package/dist/api/feature-flags.d.ts +3 -0
  3. package/dist/api/feature-flags.js +29 -0
  4. package/dist/api/org-config.d.ts +3 -0
  5. package/dist/api/org-config.js +28 -0
  6. package/dist/api/users.d.ts +1 -0
  7. package/dist/api/users.js +10 -1
  8. package/dist/commands/feature-flags/export.d.ts +19 -0
  9. package/dist/commands/feature-flags/export.js +66 -0
  10. package/dist/commands/feature-flags/export.spec.d.ts +1 -0
  11. package/dist/commands/feature-flags/export.spec.js +109 -0
  12. package/dist/commands/feature-flags/import.d.ts +28 -0
  13. package/dist/commands/feature-flags/import.js +151 -0
  14. package/dist/commands/feature-flags/import.spec.d.ts +1 -0
  15. package/dist/commands/feature-flags/import.spec.js +125 -0
  16. package/dist/commands/groups/export.js +1 -13
  17. package/dist/commands/groups/import.js +2 -15
  18. package/dist/commands/instance/import.d.ts +14 -0
  19. package/dist/commands/instance/import.js +27 -5
  20. package/dist/commands/instance/import.spec.d.ts +1 -0
  21. package/dist/commands/instance/import.spec.js +57 -0
  22. package/dist/commands/org-config/export.d.ts +13 -0
  23. package/dist/commands/org-config/export.js +74 -0
  24. package/dist/commands/org-config/import.d.ts +12 -0
  25. package/dist/commands/org-config/import.js +63 -0
  26. package/dist/commands/types/getAll.js +2 -0
  27. package/dist/commands/types/loadAll.d.ts +13 -1
  28. package/dist/commands/types/loadAll.js +41 -4
  29. package/dist/commands/types/loadAll.spec.d.ts +1 -0
  30. package/dist/commands/types/loadAll.spec.js +77 -0
  31. package/dist/commands/types/mergeOptionSets.js +2 -9
  32. package/dist/commands/types/mergeTypeProperties.js +2 -6
  33. package/dist/commands/types/parseOptionSetsLocalFiles.js +3 -7
  34. package/dist/commands/types/uploadTypeOverrides.d.ts +10 -0
  35. package/dist/commands/types/uploadTypeOverrides.js +19 -8
  36. package/dist/commands/types/uploadTypeOverrides.spec.d.ts +1 -0
  37. package/dist/commands/types/uploadTypeOverrides.spec.js +60 -0
  38. package/dist/commands/users/export.js +1 -13
  39. package/dist/commands/users/import.js +2 -15
  40. package/dist/commands/workflow-templates/export.d.ts +2 -3
  41. package/dist/commands/workflow-templates/export.js +23 -122
  42. package/dist/commands/workflow-templates/load.d.ts +0 -2
  43. package/dist/commands/workflow-templates/load.js +6 -63
  44. package/dist/helpers/error-helpers.d.ts +9 -0
  45. package/dist/helpers/error-helpers.js +35 -0
  46. package/dist/helpers/file-helpers.d.ts +2 -0
  47. package/dist/helpers/file-helpers.js +25 -1
  48. package/dist/helpers/projects/helpers.js +2 -6
  49. package/dist/helpers/types/type-parser.js +9 -15
  50. package/dist/helpers/workflow-templates/helper.d.ts +1 -2
  51. package/dist/helpers/workflow-templates/helper.js +6 -36
  52. package/dist/interfaces.d.ts +1 -1
  53. package/oclif.manifest.json +236 -30
  54. package/package.json +7 -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.4-alpha.f60c4df linux-x64 node-v24.18.0
20
+ @contrail/cli/3.5.5-alpha.5c010ac linux-x64 node-v24.20.0
21
21
  $ contrail --help [COMMAND]
22
22
  USAGE
23
23
  $ contrail COMMAND
@@ -51,6 +51,8 @@ To get instructions on how to set up autocomplete for your shell, run `contrail
51
51
  * [`contrail config list`](#contrail-config-list)
52
52
  * [`contrail config set`](#contrail-config-set)
53
53
  * [`contrail config use [NAME]`](#contrail-config-use-name)
54
+ * [`contrail feature-flags export`](#contrail-feature-flags-export)
55
+ * [`contrail feature-flags import`](#contrail-feature-flags-import)
54
56
  * [`contrail file upload FILELOCATION CONTENTTYPE`](#contrail-file-upload-filelocation-contenttype)
55
57
  * [`contrail groups export`](#contrail-groups-export)
56
58
  * [`contrail groups import GROUPSFILE`](#contrail-groups-import-groupsfile)
@@ -61,6 +63,8 @@ To get instructions on how to set up autocomplete for your shell, run `contrail
61
63
  * [`contrail load custom-entity DATAFILE CONFIGFILE`](#contrail-load-custom-entity-datafile-configfile)
62
64
  * [`contrail load line-sheet DATAFILE CONFIGFILE`](#contrail-load-line-sheet-datafile-configfile)
63
65
  * [`contrail load upload-and-load INCOMINGFILE CONFIGFILENAME`](#contrail-load-upload-and-load-incomingfile-configfilename)
66
+ * [`contrail org-config export`](#contrail-org-config-export)
67
+ * [`contrail org-config import`](#contrail-org-config-import)
64
68
  * [`contrail projects export PROJECTNAME`](#contrail-projects-export-projectname)
65
69
  * [`contrail projects list`](#contrail-projects-list)
66
70
  * [`contrail projects load TEMPLATEFILELOCATION`](#contrail-projects-load-templatefilelocation)
@@ -97,7 +101,7 @@ To get instructions on how to set up autocomplete for your shell, run `contrail
97
101
  * [`contrail workflow-templates delete`](#contrail-workflow-templates-delete)
98
102
  * [`contrail workflow-templates export`](#contrail-workflow-templates-export)
99
103
  * [`contrail workflow-templates list`](#contrail-workflow-templates-list)
100
- * [`contrail workflow-templates load [FILELOCATION]`](#contrail-workflow-templates-load-filelocation)
104
+ * [`contrail workflow-templates load FILELOCATION`](#contrail-workflow-templates-load-filelocation)
101
105
 
102
106
  ## `contrail app create`
103
107
 
@@ -535,6 +539,53 @@ EXAMPLES
535
539
  $ contrail config use demo-org
536
540
  ```
537
541
 
542
+ ## `contrail feature-flags export`
543
+
544
+ Export the enabled feature flags for your Org
545
+
546
+ ```
547
+ USAGE
548
+ $ contrail feature-flags export [-l error|warn|info|debug] [-e true|false] [-d <value>] [-f]
549
+
550
+ FLAGS
551
+ -d, --outDir=<value> [default: .] The directory to which to export the feature flags. Defaults to the
552
+ current directory
553
+ -e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
554
+ <options: true|false>
555
+ -f, --overwrite Replaces any existing export files in outDir
556
+ -l, --logLevel=<option> The Log Level
557
+ <options: error|warn|info|debug>
558
+
559
+ DESCRIPTION
560
+ Export the enabled feature flags for your Org
561
+
562
+ EXAMPLES
563
+ $ contrail feature-flags export
564
+ ```
565
+
566
+ ## `contrail feature-flags import`
567
+
568
+ Upload an extracted set of feature flags
569
+
570
+ ```
571
+ USAGE
572
+ $ contrail feature-flags import [-l error|warn|info|debug] [-e true|false] [-d <value>]
573
+
574
+ FLAGS
575
+ -d, --dir=<value> [default: .] The directory from which to import the feature flags. Defaults to the
576
+ current directory. The feature flags will be loaded from $dir/feature-flags.yml
577
+ -e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
578
+ <options: true|false>
579
+ -l, --logLevel=<option> The Log Level
580
+ <options: error|warn|info|debug>
581
+
582
+ DESCRIPTION
583
+ Upload an extracted set of feature flags
584
+
585
+ EXAMPLES
586
+ $ contrail feature-flags import
587
+ ```
588
+
538
589
  ## `contrail file upload FILELOCATION CONTENTTYPE`
539
590
 
540
591
  Uploads a file to the VibeIQ platform
@@ -777,6 +828,53 @@ EXAMPLES
777
828
  $ contrail load-processor upload-and-load <File Path> <Config Path>
778
829
  ```
779
830
 
831
+ ## `contrail org-config export`
832
+
833
+ Export the org config for your Org
834
+
835
+ ```
836
+ USAGE
837
+ $ contrail org-config export [-l error|warn|info|debug] [-e true|false] [-d <value>] [-f]
838
+
839
+ FLAGS
840
+ -d, --outDir=<value> [default: .] The directory to which to export the org config data. Defaults to the
841
+ current directory
842
+ -e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
843
+ <options: true|false>
844
+ -f, --overwrite Replaces any existing export files in outDir
845
+ -l, --logLevel=<option> The Log Level
846
+ <options: error|warn|info|debug>
847
+
848
+ DESCRIPTION
849
+ Export the org config for your Org
850
+
851
+ EXAMPLES
852
+ $ contrail org-config export
853
+ ```
854
+
855
+ ## `contrail org-config import`
856
+
857
+ Upload an extracted org config
858
+
859
+ ```
860
+ USAGE
861
+ $ contrail org-config import [-l error|warn|info|debug] [-e true|false] [-d <value>]
862
+
863
+ FLAGS
864
+ -d, --dir=<value> [default: .] The directory from which to import the org config data. Defaults to the
865
+ current directory. The org config data will be loaded from $dir/org-config.yml
866
+ -e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
867
+ <options: true|false>
868
+ -l, --logLevel=<option> The Log Level
869
+ <options: error|warn|info|debug>
870
+
871
+ DESCRIPTION
872
+ Upload an extracted org config
873
+
874
+ EXAMPLES
875
+ $ contrail org-config import
876
+ ```
877
+
780
878
  ## `contrail projects export PROJECTNAME`
781
879
 
782
880
  Export a project.
@@ -919,7 +1017,7 @@ loads all the type data that has been exported by getAll
919
1017
 
920
1018
  ```
921
1019
  USAGE
922
- $ contrail types loadAll [-l error|warn|info|debug] [-e true|false] [-d <value>]
1020
+ $ contrail types loadAll [-l error|warn|info|debug] [-e true|false] [-d <value>] [--orgConfig]
923
1021
 
924
1022
  FLAGS
925
1023
  -d, --dir=<value> [default: .] The directory from which to load the types data. Defaults to the current
@@ -928,6 +1026,8 @@ FLAGS
928
1026
  <options: true|false>
929
1027
  -l, --logLevel=<option> The Log Level
930
1028
  <options: error|warn|info|debug>
1029
+ --[no-]orgConfig Also load org config data from this directory. If not specified, you will be
1030
+ prompted.
931
1031
 
932
1032
  DESCRIPTION
933
1033
  loads all the type data that has been exported by getAll
@@ -1611,31 +1711,25 @@ EXAMPLES
1611
1711
 
1612
1712
  ## `contrail workflow-templates export`
1613
1713
 
1614
- Export all workflows in the org. By default exports one file per app to the current directory.
1714
+ Export all workflows in the org.
1615
1715
 
1616
1716
  ```
1617
1717
  USAGE
1618
- $ contrail workflow-templates export [-l error|warn|info|debug] [-e true|false] [-f <value>] [-d <value>]
1619
- [--overwrite]
1718
+ $ contrail workflow-templates export [-l error|warn|info|debug] [-e true|false] [-f <value>] [--overwrite]
1620
1719
 
1621
1720
  FLAGS
1622
- -d, --dir=<value> [default: .] Directory to write per-app workflow files
1623
1721
  -e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
1624
1722
  <options: true|false>
1625
- -f, --outFile=<value> Export all workflows to a single file at this path (overrides per-app export)
1723
+ -f, --outFile=<value> [default: workflows.yml] The location for the file to be saved
1626
1724
  -l, --logLevel=<option> The Log Level
1627
1725
  <options: error|warn|info|debug>
1628
1726
  --overwrite Replaces existing files if they exist
1629
1727
 
1630
1728
  DESCRIPTION
1631
- Export all workflows in the org. By default exports one file per app to the current directory.
1729
+ Export all workflows in the org.
1632
1730
 
1633
1731
  EXAMPLES
1634
1732
  $ contrail workflow-templates export
1635
-
1636
- $ contrail workflow-templates export --dir ./workflows
1637
-
1638
- $ contrail workflow-templates export --outFile workflows.yml
1639
1733
  ```
1640
1734
 
1641
1735
  ## `contrail workflow-templates list`
@@ -1659,32 +1753,27 @@ EXAMPLES
1659
1753
  $ contrail workflow-templates list
1660
1754
  ```
1661
1755
 
1662
- ## `contrail workflow-templates load [FILELOCATION]`
1756
+ ## `contrail workflow-templates load FILELOCATION`
1663
1757
 
1664
- Load workflow templates from a file or scan a directory for per-app workflow files
1758
+ Loads your workflow from a local file
1665
1759
 
1666
1760
  ```
1667
1761
  USAGE
1668
- $ contrail workflow-templates load [FILELOCATION] [-l error|warn|info|debug] [-e true|false] [-d <value>]
1762
+ $ contrail workflow-templates load FILELOCATION [-l error|warn|info|debug] [-e true|false]
1669
1763
 
1670
1764
  ARGUMENTS
1671
- FILELOCATION A specific workflow template file to load
1765
+ FILELOCATION The workflow template file to load
1672
1766
 
1673
1767
  FLAGS
1674
- -d, --dir=<value> [default: .] Directory to scan for workflow files when no file argument is given
1675
1768
  -e, --alwaysConfirmEnv=<option> [default: true] The Environment variables
1676
1769
  <options: true|false>
1677
1770
  -l, --logLevel=<option> The Log Level
1678
1771
  <options: error|warn|info|debug>
1679
1772
 
1680
1773
  DESCRIPTION
1681
- Load workflow templates from a file or scan a directory for per-app workflow files
1774
+ Loads your workflow from a local file
1682
1775
 
1683
1776
  EXAMPLES
1684
1777
  $ contrail workflow-templates load <FILE_LOCATION>
1685
-
1686
- $ contrail workflow-templates load
1687
-
1688
- $ contrail workflow-templates load --dir ./workflows
1689
1778
  ```
1690
1779
  <!-- commandsstop -->
@@ -0,0 +1,3 @@
1
+ export declare function getFeatureFlags(options?: any): Promise<any[]>;
2
+ export declare function createFeatureFlag(featureName: string): Promise<any>;
3
+ export declare function deleteFeatureFlag(id: string): Promise<any>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteFeatureFlag = exports.createFeatureFlag = exports.getFeatureFlags = void 0;
4
+ const sdk_1 = require("@contrail/sdk");
5
+ // a feature flag record exists only for features that are turned ON for the org,
6
+ // and carries no configuration beyond its featureName
7
+ async function getFeatureFlags(options) {
8
+ return new sdk_1.Entities().get({
9
+ entityName: 'feature-flag',
10
+ ...options,
11
+ });
12
+ }
13
+ exports.getFeatureFlags = getFeatureFlags;
14
+ // the API treats create as an upsert on featureName, so creating an already
15
+ // enabled flag returns the existing record instead of failing
16
+ async function createFeatureFlag(featureName) {
17
+ return new sdk_1.Entities().create({
18
+ entityName: 'feature-flag',
19
+ object: { featureName },
20
+ });
21
+ }
22
+ exports.createFeatureFlag = createFeatureFlag;
23
+ async function deleteFeatureFlag(id) {
24
+ return new sdk_1.Entities().delete({
25
+ entityName: 'feature-flag',
26
+ id,
27
+ });
28
+ }
29
+ exports.deleteFeatureFlag = deleteFeatureFlag;
@@ -0,0 +1,3 @@
1
+ export declare function getOrgConfigs(options?: any): Promise<any[]>;
2
+ export declare function updateOrgConfig(id: string, changes: any): Promise<any>;
3
+ export declare function createOrgConfig(orgConfig: any): Promise<any>;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createOrgConfig = exports.updateOrgConfig = exports.getOrgConfigs = void 0;
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
7
+ async function getOrgConfigs(options) {
8
+ return new sdk_1.Entities().get({
9
+ entityName: 'org-config',
10
+ ...options,
11
+ });
12
+ }
13
+ exports.getOrgConfigs = getOrgConfigs;
14
+ async function updateOrgConfig(id, changes) {
15
+ return new sdk_1.Entities().update({
16
+ entityName: 'org-config',
17
+ id,
18
+ object: changes,
19
+ });
20
+ }
21
+ exports.updateOrgConfig = updateOrgConfig;
22
+ async function createOrgConfig(orgConfig) {
23
+ return new sdk_1.Entities().create({
24
+ entityName: 'org-config',
25
+ object: orgConfig,
26
+ });
27
+ }
28
+ exports.createOrgConfig = createOrgConfig;
@@ -1,2 +1,3 @@
1
1
  export declare function getUserOrgs(options?: any): Promise<any[]>;
2
2
  export declare function getGroups(options?: any): Promise<any[]>;
3
+ export declare function getCurrentUserWithPlatformRoles(): Promise<any>;
package/dist/api/users.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getGroups = exports.getUserOrgs = void 0;
3
+ exports.getCurrentUserWithPlatformRoles = exports.getGroups = exports.getUserOrgs = void 0;
4
4
  const sdk_1 = require("@contrail/sdk");
5
5
  async function getUserOrgs(options) {
6
6
  return new sdk_1.Entities().get({
@@ -16,3 +16,12 @@ async function getGroups(options) {
16
16
  });
17
17
  }
18
18
  exports.getGroups = getGroups;
19
+ // GET /users/current only populates platformRoles when it is asked for as a relation
20
+ async function getCurrentUserWithPlatformRoles() {
21
+ return new sdk_1.Entities().get({
22
+ entityName: 'user',
23
+ id: 'current',
24
+ relations: ['platformRoles'],
25
+ });
26
+ }
27
+ exports.getCurrentUserWithPlatformRoles = getCurrentUserWithPlatformRoles;
@@ -0,0 +1,19 @@
1
+ import { BaseCommand } from '../../basecommand';
2
+ export declare const FEATURE_FLAGS_FILE = "feature-flags.yml";
3
+ export default class ExportFeatureFlags extends BaseCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ outDir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
8
+ overwrite: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
+ alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
10
+ logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
14
+ /**
15
+ * Writes the org's enabled feature flags to $outDir/feature-flags.yml as a list of
16
+ * `- featureName: X` entries. An org with no flags enabled still gets a file (an empty
17
+ * list), so the export stays a faithful snapshot of what is turned on.
18
+ */
19
+ export declare function exportFeatureFlags(outDir: string, overwrite: boolean): Promise<void>;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exportFeatureFlags = exports.FEATURE_FLAGS_FILE = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const path = tslib_1.__importStar(require("path"));
6
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
+ const fs_1 = tslib_1.__importDefault(require("fs"));
8
+ const login_1 = require("../../auth/login");
9
+ const core_1 = require("@oclif/core");
10
+ const file_helpers_1 = require("../../helpers/file-helpers");
11
+ const error_helpers_1 = require("../../helpers/error-helpers");
12
+ const feature_flags_1 = require("../../api/feature-flags");
13
+ const basecommand_1 = require("../../basecommand");
14
+ exports.FEATURE_FLAGS_FILE = 'feature-flags.yml';
15
+ class ExportFeatureFlags extends basecommand_1.BaseCommand {
16
+ static description = 'Export the enabled feature flags for your Org';
17
+ static examples = [
18
+ `$ contrail feature-flags export
19
+ `,
20
+ ];
21
+ static flags = {
22
+ ...basecommand_1.BaseCommand.baseFlags,
23
+ outDir: core_1.Flags.string({
24
+ char: 'd',
25
+ description: 'The directory to which to export the feature flags. Defaults to the current directory',
26
+ required: false,
27
+ default: '.',
28
+ }),
29
+ overwrite: core_1.Flags.boolean({
30
+ char: 'f',
31
+ description: 'Replaces any existing export files in outDir',
32
+ required: false,
33
+ default: false,
34
+ }),
35
+ };
36
+ async run() {
37
+ try {
38
+ await (0, login_1.authUser)();
39
+ this.log(`${chalk_1.default.blue.bold('START')} Getting Feature Flags.`);
40
+ this.log(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} Exporting`);
41
+ await exportFeatureFlags(this.flags.outDir, this.flags.overwrite);
42
+ this.log(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} Exported`);
43
+ }
44
+ catch (e) {
45
+ this.log(`${chalk_1.default.red.bold('FAIL')} Feature Flags Export Failed. ${(0, error_helpers_1.describeError)(e)}`);
46
+ process.exitCode = 1;
47
+ return;
48
+ }
49
+ this.log(`${chalk_1.default.green.bold('COMPLETE')} Feature Flags Export Complete.`);
50
+ }
51
+ }
52
+ exports.default = ExportFeatureFlags;
53
+ /**
54
+ * Writes the org's enabled feature flags to $outDir/feature-flags.yml as a list of
55
+ * `- featureName: X` entries. An org with no flags enabled still gets a file (an empty
56
+ * list), so the export stays a faithful snapshot of what is turned on.
57
+ */
58
+ async function exportFeatureFlags(outDir, overwrite) {
59
+ const featureFlags = await (0, feature_flags_1.getFeatureFlags)();
60
+ const featureNames = [
61
+ ...new Set((featureFlags ?? []).map((flag) => flag?.featureName).filter(Boolean)),
62
+ ].sort();
63
+ fs_1.default.mkdirSync(path.resolve(outDir), { recursive: true });
64
+ await (0, file_helpers_1.saveData)(featureNames.map((featureName) => ({ featureName })), path.join(outDir, exports.FEATURE_FLAGS_FILE), overwrite);
65
+ }
66
+ exports.exportFeatureFlags = exportFeatureFlags;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,109 @@
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 export_1 = tslib_1.__importStar(require("./export"));
6
+ const file_helpers_1 = require("../../helpers/file-helpers");
7
+ const feature_flags_1 = require("../../api/feature-flags");
8
+ const login_1 = require("../../auth/login");
9
+ jest.mock('../../helpers/file-helpers');
10
+ jest.mock('../../api/feature-flags');
11
+ jest.mock('../../auth/login');
12
+ const mocked = (fn) => fn;
13
+ describe('exportFeatureFlags', () => {
14
+ let mkdirSyncSpy;
15
+ beforeEach(() => {
16
+ jest.clearAllMocks();
17
+ mkdirSyncSpy = jest.spyOn(fs_1.default, 'mkdirSync').mockReturnValue(undefined);
18
+ mocked(file_helpers_1.saveData).mockResolvedValue(undefined);
19
+ });
20
+ afterEach(() => {
21
+ mkdirSyncSpy.mockRestore();
22
+ });
23
+ it('writes the enabled flags sorted, without org specific fields', async () => {
24
+ mocked(feature_flags_1.getFeatureFlags).mockResolvedValue([
25
+ { id: '1', orgId: 'org-a', featureName: 'SHOWROOM' },
26
+ { id: '2', orgId: 'org-a', featureName: 'DEV_TABS' },
27
+ ]);
28
+ await (0, export_1.exportFeatureFlags)('out-dir', true);
29
+ expect(mocked(file_helpers_1.saveData)).toHaveBeenCalledWith([{ featureName: 'DEV_TABS' }, { featureName: 'SHOWROOM' }], 'out-dir/feature-flags.yml', true);
30
+ });
31
+ it('still writes a file when no flags are enabled, so the export is a full snapshot', async () => {
32
+ mocked(feature_flags_1.getFeatureFlags).mockResolvedValue([]);
33
+ await (0, export_1.exportFeatureFlags)('out-dir', false);
34
+ expect(mocked(file_helpers_1.saveData)).toHaveBeenCalledWith([], 'out-dir/feature-flags.yml', false);
35
+ });
36
+ });
37
+ describe('ExportFeatureFlags command', () => {
38
+ let mkdirSyncSpy;
39
+ let logs;
40
+ // oclif's init() parses argv and reads the on-disk config, so drive run() with flags set directly
41
+ const runCommand = async () => {
42
+ const command = new export_1.default([], {});
43
+ command.flags = { outDir: 'out-dir', overwrite: true };
44
+ jest.spyOn(command, 'log').mockImplementation((message) => logs.push(String(message)));
45
+ await command.run();
46
+ };
47
+ beforeEach(() => {
48
+ jest.clearAllMocks();
49
+ logs = [];
50
+ process.exitCode = undefined;
51
+ mkdirSyncSpy = jest.spyOn(fs_1.default, 'mkdirSync').mockReturnValue(undefined);
52
+ mocked(login_1.authUser).mockResolvedValue({});
53
+ mocked(file_helpers_1.saveData).mockResolvedValue(undefined);
54
+ mocked(feature_flags_1.getFeatureFlags).mockResolvedValue([]);
55
+ });
56
+ afterEach(() => {
57
+ mkdirSyncSpy.mockRestore();
58
+ process.exitCode = undefined;
59
+ });
60
+ it('leaves the exit code alone on success', async () => {
61
+ await runCommand();
62
+ expect(process.exitCode).toBeUndefined();
63
+ expect(logs.join('\n')).toContain('Feature Flags Export Complete');
64
+ });
65
+ const failures = [
66
+ [
67
+ 'authentication fails',
68
+ () => {
69
+ mocked(login_1.authUser).mockRejectedValue(new Error('boom'));
70
+ },
71
+ ],
72
+ [
73
+ 'the API read fails',
74
+ () => {
75
+ mocked(feature_flags_1.getFeatureFlags).mockRejectedValue(new Error('boom'));
76
+ },
77
+ ],
78
+ [
79
+ 'the directory cannot be created',
80
+ () => {
81
+ mkdirSyncSpy.mockImplementation(() => {
82
+ throw new Error('boom');
83
+ });
84
+ },
85
+ ],
86
+ [
87
+ 'the file write fails',
88
+ () => {
89
+ mocked(file_helpers_1.saveData).mockRejectedValue(new Error('boom'));
90
+ },
91
+ ],
92
+ ];
93
+ it.each(failures)('exits non-zero when %s, so automation does not read it as a success', async (_, fail) => {
94
+ fail();
95
+ await runCommand();
96
+ expect(process.exitCode).toBe(1);
97
+ const output = logs.join('\n');
98
+ expect(output).toContain('Feature Flags Export Failed');
99
+ expect(output).not.toContain('Feature Flags Export Complete');
100
+ });
101
+ it('reports the API message rather than a bare "Error"', async () => {
102
+ // the SDK's ApiError has an undefined message and keeps the body in details
103
+ mocked(feature_flags_1.getFeatureFlags).mockRejectedValue({
104
+ details: '{"message":"Forbidden resource","statusCode":403}',
105
+ });
106
+ await runCommand();
107
+ expect(logs.join('\n')).toContain('Forbidden resource');
108
+ });
109
+ });
@@ -0,0 +1,28 @@
1
+ import { BaseCommand } from '../../basecommand';
2
+ export declare const REQUIRED_PLATFORM_ROLES: string[];
3
+ export default class ImportFeatureFlags extends BaseCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ dir: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
8
+ alwaysConfirmEnv: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
9
+ logLevel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
13
+ /**
14
+ * The feature flags API only lets a platform admin/manager create or delete flags, and a
15
+ * missing role surfaces as a generic 403 partway through the run. Checking up front stops
16
+ * the command before it half-applies the file.
17
+ */
18
+ export declare function assertCanManageFeatureFlags(): Promise<void>;
19
+ /**
20
+ * Makes the target org's enabled feature flags match $dir/feature-flags.yml: enables every
21
+ * flag in the file and disables any flag the target has that the file does not, the same
22
+ * way groups import reconciles groups. Feature flags are not editable in place, so there is
23
+ * no update step.
24
+ *
25
+ * Failures are collected and rethrown so an import never reports success while the org is
26
+ * missing flags.
27
+ */
28
+ export declare function uploadFeatureFlags(dir: string): Promise<void>;