@contrail/cli 3.5.4-alpha.f60c4df → 3.5.5-alpha.ae6a66f
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 +64 -0
- package/dist/commands/feature-flags/export.spec.d.ts +1 -0
- package/dist/commands/feature-flags/export.spec.js +34 -0
- package/dist/commands/feature-flags/import.d.ts +28 -0
- package/dist/commands/feature-flags/import.js +151 -0
- package/dist/commands/feature-flags/import.spec.d.ts +1 -0
- package/dist/commands/feature-flags/import.spec.js +125 -0
- package/dist/commands/groups/export.js +1 -13
- package/dist/commands/groups/import.js +2 -15
- package/dist/commands/instance/import.d.ts +14 -0
- package/dist/commands/instance/import.js +27 -5
- package/dist/commands/instance/import.spec.d.ts +1 -0
- package/dist/commands/instance/import.spec.js +57 -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/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 +1 -1
- package/oclif.manifest.json +236 -30
- 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.
|
|
20
|
+
@contrail/cli/3.5.5-alpha.ae6a66f 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
|
|
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.
|
|
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>] [
|
|
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>
|
|
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.
|
|
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
|
|
1756
|
+
## `contrail workflow-templates load FILELOCATION`
|
|
1663
1757
|
|
|
1664
|
-
|
|
1758
|
+
Loads your workflow from a local file
|
|
1665
1759
|
|
|
1666
1760
|
```
|
|
1667
1761
|
USAGE
|
|
1668
|
-
$ contrail workflow-templates load
|
|
1762
|
+
$ contrail workflow-templates load FILELOCATION [-l error|warn|info|debug] [-e true|false]
|
|
1669
1763
|
|
|
1670
1764
|
ARGUMENTS
|
|
1671
|
-
FILELOCATION
|
|
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
|
-
|
|
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,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,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;
|
package/dist/api/users.d.ts
CHANGED
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,64 @@
|
|
|
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 feature_flags_1 = require("../../api/feature-flags");
|
|
12
|
+
const basecommand_1 = require("../../basecommand");
|
|
13
|
+
exports.FEATURE_FLAGS_FILE = 'feature-flags.yml';
|
|
14
|
+
class ExportFeatureFlags extends basecommand_1.BaseCommand {
|
|
15
|
+
static description = 'Export the enabled feature flags for your Org';
|
|
16
|
+
static examples = [
|
|
17
|
+
`$ contrail feature-flags export
|
|
18
|
+
`,
|
|
19
|
+
];
|
|
20
|
+
static flags = {
|
|
21
|
+
...basecommand_1.BaseCommand.baseFlags,
|
|
22
|
+
outDir: core_1.Flags.string({
|
|
23
|
+
char: 'd',
|
|
24
|
+
description: 'The directory to which to export the feature flags. Defaults to the current directory',
|
|
25
|
+
required: false,
|
|
26
|
+
default: '.',
|
|
27
|
+
}),
|
|
28
|
+
overwrite: core_1.Flags.boolean({
|
|
29
|
+
char: 'f',
|
|
30
|
+
description: 'Replaces any existing export files in outDir',
|
|
31
|
+
required: false,
|
|
32
|
+
default: false,
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
async run() {
|
|
36
|
+
try {
|
|
37
|
+
await (0, login_1.authUser)();
|
|
38
|
+
this.log(`${chalk_1.default.blue.bold('START')} Getting Feature Flags.`);
|
|
39
|
+
this.log(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} Exporting`);
|
|
40
|
+
await exportFeatureFlags(this.flags.outDir, this.flags.overwrite);
|
|
41
|
+
this.log(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} Exported`);
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
this.log(`${chalk_1.default.red.bold('FAIL')} Feature Flags Export Failed. ${e}`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.log(`${chalk_1.default.green.bold('COMPLETE')} Feature Flags Export Complete.`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.default = ExportFeatureFlags;
|
|
51
|
+
/**
|
|
52
|
+
* Writes the org's enabled feature flags to $outDir/feature-flags.yml as a list of
|
|
53
|
+
* `- featureName: X` entries. An org with no flags enabled still gets a file (an empty
|
|
54
|
+
* list), so the export stays a faithful snapshot of what is turned on.
|
|
55
|
+
*/
|
|
56
|
+
async function exportFeatureFlags(outDir, overwrite) {
|
|
57
|
+
const featureFlags = await (0, feature_flags_1.getFeatureFlags)();
|
|
58
|
+
const featureNames = [
|
|
59
|
+
...new Set((featureFlags ?? []).map((flag) => flag?.featureName).filter(Boolean)),
|
|
60
|
+
].sort();
|
|
61
|
+
fs_1.default.mkdirSync(path.resolve(outDir), { recursive: true });
|
|
62
|
+
await (0, file_helpers_1.saveData)(featureNames.map((featureName) => ({ featureName })), path.join(outDir, exports.FEATURE_FLAGS_FILE), overwrite);
|
|
63
|
+
}
|
|
64
|
+
exports.exportFeatureFlags = exportFeatureFlags;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
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 = require("./export");
|
|
6
|
+
const file_helpers_1 = require("../../helpers/file-helpers");
|
|
7
|
+
const feature_flags_1 = require("../../api/feature-flags");
|
|
8
|
+
jest.mock('../../helpers/file-helpers');
|
|
9
|
+
jest.mock('../../api/feature-flags');
|
|
10
|
+
const mocked = (fn) => fn;
|
|
11
|
+
describe('exportFeatureFlags', () => {
|
|
12
|
+
let mkdirSyncSpy;
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
mkdirSyncSpy = jest.spyOn(fs_1.default, 'mkdirSync').mockReturnValue(undefined);
|
|
16
|
+
mocked(file_helpers_1.saveData).mockResolvedValue(undefined);
|
|
17
|
+
});
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
mkdirSyncSpy.mockRestore();
|
|
20
|
+
});
|
|
21
|
+
it('writes the enabled flags sorted, without org specific fields', async () => {
|
|
22
|
+
mocked(feature_flags_1.getFeatureFlags).mockResolvedValue([
|
|
23
|
+
{ id: '1', orgId: 'org-a', featureName: 'SHOWROOM' },
|
|
24
|
+
{ id: '2', orgId: 'org-a', featureName: 'DEV_TABS' },
|
|
25
|
+
]);
|
|
26
|
+
await (0, export_1.exportFeatureFlags)('out-dir', true);
|
|
27
|
+
expect(mocked(file_helpers_1.saveData)).toHaveBeenCalledWith([{ featureName: 'DEV_TABS' }, { featureName: 'SHOWROOM' }], 'out-dir/feature-flags.yml', true);
|
|
28
|
+
});
|
|
29
|
+
it('still writes a file when no flags are enabled, so the export is a full snapshot', async () => {
|
|
30
|
+
mocked(feature_flags_1.getFeatureFlags).mockResolvedValue([]);
|
|
31
|
+
await (0, export_1.exportFeatureFlags)('out-dir', false);
|
|
32
|
+
expect(mocked(file_helpers_1.saveData)).toHaveBeenCalledWith([], 'out-dir/feature-flags.yml', false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -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>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.uploadFeatureFlags = exports.assertCanManageFeatureFlags = exports.REQUIRED_PLATFORM_ROLES = 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 users_1 = require("../../api/users");
|
|
14
|
+
const export_1 = require("./export");
|
|
15
|
+
const basecommand_1 = require("../../basecommand");
|
|
16
|
+
const logger_1 = tslib_1.__importDefault(require("../../logger"));
|
|
17
|
+
exports.REQUIRED_PLATFORM_ROLES = ['PLATFORM_ADMIN', 'PLATFORM_MANAGER'];
|
|
18
|
+
class ImportFeatureFlags extends basecommand_1.BaseCommand {
|
|
19
|
+
static description = 'Upload an extracted set of feature flags';
|
|
20
|
+
static examples = [
|
|
21
|
+
`$ contrail feature-flags import
|
|
22
|
+
`,
|
|
23
|
+
];
|
|
24
|
+
static flags = {
|
|
25
|
+
...basecommand_1.BaseCommand.baseFlags,
|
|
26
|
+
dir: core_1.Flags.string({
|
|
27
|
+
char: 'd',
|
|
28
|
+
description: `The directory from which to import the feature flags. Defaults to the current directory. The feature flags will be loaded from $dir/${export_1.FEATURE_FLAGS_FILE}`,
|
|
29
|
+
required: false,
|
|
30
|
+
default: '.',
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
async run() {
|
|
34
|
+
try {
|
|
35
|
+
await (0, login_1.authUser)();
|
|
36
|
+
await assertCanManageFeatureFlags();
|
|
37
|
+
this.log(`${chalk_1.default.blue.bold('START')} Uploading Feature Flags.`);
|
|
38
|
+
this.log(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} Uploading`);
|
|
39
|
+
await uploadFeatureFlags(this.flags.dir);
|
|
40
|
+
this.log(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} Uploaded`);
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
this.log(`${chalk_1.default.red.bold('FAIL')} Feature Flags Upload Failed. ${(0, error_helpers_1.describeError)(e)}`);
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.log(`${chalk_1.default.green.bold('COMPLETE')} Feature Flags Upload Complete.`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
exports.default = ImportFeatureFlags;
|
|
51
|
+
/**
|
|
52
|
+
* The feature flags API only lets a platform admin/manager create or delete flags, and a
|
|
53
|
+
* missing role surfaces as a generic 403 partway through the run. Checking up front stops
|
|
54
|
+
* the command before it half-applies the file.
|
|
55
|
+
*/
|
|
56
|
+
async function assertCanManageFeatureFlags() {
|
|
57
|
+
let platformRoles;
|
|
58
|
+
try {
|
|
59
|
+
const currentUser = await (0, users_1.getCurrentUserWithPlatformRoles)();
|
|
60
|
+
platformRoles = normalizePlatformRoles(currentUser?.platformRoles);
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
throw new Error(`Could not read your platform roles. ${(0, error_helpers_1.describeError)(e)}`);
|
|
64
|
+
}
|
|
65
|
+
if (!platformRoles.some((role) => exports.REQUIRED_PLATFORM_ROLES.includes(role))) {
|
|
66
|
+
throw new Error(`Importing feature flags requires the ${exports.REQUIRED_PLATFORM_ROLES.join(' or ')} platform role. ` +
|
|
67
|
+
`Your roles: ${platformRoles.length ? platformRoles.join(', ') : 'none'}.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.assertCanManageFeatureFlags = assertCanManageFeatureFlags;
|
|
71
|
+
/**
|
|
72
|
+
* Makes the target org's enabled feature flags match $dir/feature-flags.yml: enables every
|
|
73
|
+
* flag in the file and disables any flag the target has that the file does not, the same
|
|
74
|
+
* way groups import reconciles groups. Feature flags are not editable in place, so there is
|
|
75
|
+
* no update step.
|
|
76
|
+
*
|
|
77
|
+
* Failures are collected and rethrown so an import never reports success while the org is
|
|
78
|
+
* missing flags.
|
|
79
|
+
*/
|
|
80
|
+
async function uploadFeatureFlags(dir) {
|
|
81
|
+
const filePath = path.join(dir, export_1.FEATURE_FLAGS_FILE);
|
|
82
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
83
|
+
logger_1.default.info(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} No feature flags found in ${dir}. Skipping.`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const data = (0, file_helpers_1.loadData)(filePath);
|
|
87
|
+
if (!Array.isArray(data)) {
|
|
88
|
+
throw new Error(`${filePath} must contain a YAML list of feature flags`);
|
|
89
|
+
}
|
|
90
|
+
const featureNamesToEnable = new Set(data.map(toFeatureName).filter(Boolean));
|
|
91
|
+
const existingFlags = (await (0, feature_flags_1.getFeatureFlags)()) ?? [];
|
|
92
|
+
const enabledFeatureNames = new Set(existingFlags.map((flag) => flag?.featureName));
|
|
93
|
+
const toCreate = [...featureNamesToEnable].filter((name) => !enabledFeatureNames.has(name));
|
|
94
|
+
const toDelete = existingFlags.filter((flag) => !featureNamesToEnable.has(flag?.featureName));
|
|
95
|
+
const failures = [];
|
|
96
|
+
for (const featureName of toCreate) {
|
|
97
|
+
try {
|
|
98
|
+
await (0, feature_flags_1.createFeatureFlag)(featureName);
|
|
99
|
+
logger_1.default.info(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} Enabled ${featureName}`);
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
failures.push(`could not enable ${featureName}: ${(0, error_helpers_1.describeError)(e)}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const removed = [];
|
|
106
|
+
for (const flag of toDelete) {
|
|
107
|
+
try {
|
|
108
|
+
await (0, feature_flags_1.deleteFeatureFlag)(flag.id);
|
|
109
|
+
logger_1.default.info(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} Disabled ${flag.featureName}`);
|
|
110
|
+
removed.push(flag.featureName);
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
failures.push(`could not disable ${flag.featureName}: ${(0, error_helpers_1.describeError)(e)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// call out the removals explicitly: they are the only destructive part of the import
|
|
117
|
+
if (removed.length) {
|
|
118
|
+
const removedList = removed.sort().join(', ');
|
|
119
|
+
logger_1.default.info(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} The following ${removed.length} flag(s) were ` +
|
|
120
|
+
`enabled in this org but not in the source, so they were removed: ${removedList}`);
|
|
121
|
+
}
|
|
122
|
+
if (failures.length) {
|
|
123
|
+
throw new Error(`Failed to apply feature flags. ${failures.join('; ')}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.uploadFeatureFlags = uploadFeatureFlags;
|
|
127
|
+
// the API returns platformRoles as an array, but some contexts carry it as a JSON string
|
|
128
|
+
function normalizePlatformRoles(platformRoles) {
|
|
129
|
+
if (Array.isArray(platformRoles)) {
|
|
130
|
+
return platformRoles;
|
|
131
|
+
}
|
|
132
|
+
if (typeof platformRoles === 'string') {
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(platformRoles);
|
|
135
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
// entries are exported as `- featureName: X`, but a hand written `- X` is accepted too
|
|
144
|
+
function toFeatureName(entry) {
|
|
145
|
+
const featureName = typeof entry === 'string' ? entry : entry?.featureName;
|
|
146
|
+
if (typeof featureName !== 'string' || !featureName.trim()) {
|
|
147
|
+
logger_1.default.info(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} Skipping entry with no featureName`);
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
return featureName.trim();
|
|
151
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|