@contrail/cli 3.5.5-alpha.960e382 → 3.5.5-alpha.a4b6430
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 +1 -1
- package/dist/api/apps.js +2 -0
- package/dist/api/apps.spec.d.ts +1 -0
- package/dist/api/apps.spec.js +21 -0
- package/dist/commands/feature-flags/import.d.ts +4 -0
- package/dist/commands/feature-flags/import.js +10 -1
- package/dist/commands/feature-flags/import.spec.js +20 -0
- package/dist/interfaces.d.ts +1 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ $ npm install -g @contrail/cli
|
|
|
17
17
|
$ contrail COMMAND
|
|
18
18
|
running command...
|
|
19
19
|
$ contrail (--version)
|
|
20
|
-
@contrail/cli/3.5.5-alpha.
|
|
20
|
+
@contrail/cli/3.5.5-alpha.a4b6430 linux-x64 node-v24.21.0
|
|
21
21
|
$ contrail --help [COMMAND]
|
|
22
22
|
USAGE
|
|
23
23
|
$ contrail COMMAND
|
package/dist/api/apps.js
CHANGED
|
@@ -26,6 +26,8 @@ async function upgradeAppVersion(appOrg, appVersionId) {
|
|
|
26
26
|
id: appOrg.id,
|
|
27
27
|
object: {
|
|
28
28
|
appVersionId: appVersionId,
|
|
29
|
+
// The API then updates installed workflows to the new version's configuration, keeping any value the org changed.
|
|
30
|
+
syncEventWorkflowTemplates: true,
|
|
29
31
|
},
|
|
30
32
|
});
|
|
31
33
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const update = jest.fn();
|
|
4
|
+
jest.mock('@contrail/sdk', () => ({
|
|
5
|
+
Auth: jest.fn(),
|
|
6
|
+
Entities: jest.fn(() => ({
|
|
7
|
+
update: (...args) => update(...args),
|
|
8
|
+
})),
|
|
9
|
+
}));
|
|
10
|
+
const apps_1 = require("./apps");
|
|
11
|
+
describe('upgradeAppVersion', () => {
|
|
12
|
+
it('should ask the API to apply the new version workflow configuration along with the version change', async () => {
|
|
13
|
+
update.mockResolvedValue({ id: 'app-org-1' });
|
|
14
|
+
await (0, apps_1.upgradeAppVersion)({ id: 'app-org-1' }, 'version-2');
|
|
15
|
+
expect(update).toHaveBeenCalledWith({
|
|
16
|
+
entityName: 'app-org',
|
|
17
|
+
id: 'app-org-1',
|
|
18
|
+
object: { appVersionId: 'version-2', syncEventWorkflowTemplates: true },
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -34,5 +34,9 @@ export declare function uploadFeatureFlagsIfPermitted(dir: string): Promise<void
|
|
|
34
34
|
*
|
|
35
35
|
* Failures are collected and rethrown so an import never reports success while the org is
|
|
36
36
|
* missing flags.
|
|
37
|
+
*
|
|
38
|
+
* A file that lists no flags at all is skipped rather than applied: reconciling against an
|
|
39
|
+
* empty list would disable every flag in the target org, which is almost never what a copy
|
|
40
|
+
* is meant to do. Flags that should be off in the target have to be disabled by hand.
|
|
37
41
|
*/
|
|
38
42
|
export declare function uploadFeatureFlags(dir: string): Promise<void>;
|
|
@@ -107,6 +107,10 @@ exports.uploadFeatureFlagsIfPermitted = uploadFeatureFlagsIfPermitted;
|
|
|
107
107
|
*
|
|
108
108
|
* Failures are collected and rethrown so an import never reports success while the org is
|
|
109
109
|
* missing flags.
|
|
110
|
+
*
|
|
111
|
+
* A file that lists no flags at all is skipped rather than applied: reconciling against an
|
|
112
|
+
* empty list would disable every flag in the target org, which is almost never what a copy
|
|
113
|
+
* is meant to do. Flags that should be off in the target have to be disabled by hand.
|
|
110
114
|
*/
|
|
111
115
|
async function uploadFeatureFlags(dir) {
|
|
112
116
|
const filePath = path.join(dir, export_1.FEATURE_FLAGS_FILE);
|
|
@@ -114,11 +118,16 @@ async function uploadFeatureFlags(dir) {
|
|
|
114
118
|
logger_1.default.info(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} No feature flags found in ${dir}. Skipping.`);
|
|
115
119
|
return;
|
|
116
120
|
}
|
|
117
|
-
const data = (0, file_helpers_1.loadData)(filePath);
|
|
121
|
+
const data = (0, file_helpers_1.loadData)(filePath) ?? [];
|
|
118
122
|
if (!Array.isArray(data)) {
|
|
119
123
|
throw new Error(`${filePath} must contain a YAML list of feature flags`);
|
|
120
124
|
}
|
|
121
125
|
const featureNamesToEnable = new Set(data.map(toFeatureName).filter(Boolean));
|
|
126
|
+
if (featureNamesToEnable.size === 0) {
|
|
127
|
+
logger_1.default.info(`${chalk_1.default.yellow.bold('FEATURE FLAGS')} ${filePath} lists no feature flags. Skipping, so ` +
|
|
128
|
+
'the flags currently enabled in this org are left as they are.');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
122
131
|
const existingFlags = (await (0, feature_flags_1.getFeatureFlags)()) ?? [];
|
|
123
132
|
const enabledFeatureNames = new Set(existingFlags.map((flag) => flag?.featureName));
|
|
124
133
|
const toCreate = [...featureNamesToEnable].filter((name) => !enabledFeatureNames.has(name));
|
|
@@ -58,6 +58,26 @@ describe('uploadFeatureFlags', () => {
|
|
|
58
58
|
expect(mocked(feature_flags_1.getFeatureFlags)).not.toHaveBeenCalled();
|
|
59
59
|
expect(mocked(feature_flags_1.createFeatureFlag)).not.toHaveBeenCalled();
|
|
60
60
|
});
|
|
61
|
+
it('skips an export that lists no flags instead of disabling everything in the target org', async () => {
|
|
62
|
+
mocked(file_helpers_1.loadData).mockReturnValue([]);
|
|
63
|
+
mocked(feature_flags_1.getFeatureFlags).mockResolvedValue([
|
|
64
|
+
{ id: '1', featureName: 'SHOWROOM' },
|
|
65
|
+
{ id: '2', featureName: 'DEV_TABS' },
|
|
66
|
+
]);
|
|
67
|
+
const infoSpy = jest.spyOn(logger_1.default, 'info').mockImplementation(() => undefined);
|
|
68
|
+
await (0, import_1.uploadFeatureFlags)('export-dir');
|
|
69
|
+
expect(mocked(feature_flags_1.deleteFeatureFlag)).not.toHaveBeenCalled();
|
|
70
|
+
expect(mocked(feature_flags_1.createFeatureFlag)).not.toHaveBeenCalled();
|
|
71
|
+
expect(infoSpy).toHaveBeenCalledWith(expect.stringContaining('lists no feature flags'));
|
|
72
|
+
infoSpy.mockRestore();
|
|
73
|
+
});
|
|
74
|
+
it('treats a blank feature-flags.yml the same as an empty list', async () => {
|
|
75
|
+
mocked(file_helpers_1.loadData).mockReturnValue(undefined);
|
|
76
|
+
mocked(feature_flags_1.getFeatureFlags).mockResolvedValue([{ id: '1', featureName: 'SHOWROOM' }]);
|
|
77
|
+
await (0, import_1.uploadFeatureFlags)('export-dir');
|
|
78
|
+
expect(mocked(feature_flags_1.getFeatureFlags)).not.toHaveBeenCalled();
|
|
79
|
+
expect(mocked(feature_flags_1.deleteFeatureFlag)).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
61
81
|
it('enables every flag from the export that the target org is missing', async () => {
|
|
62
82
|
mocked(file_helpers_1.loadData).mockReturnValue([{ featureName: 'SHOWROOM' }, { featureName: 'PROMOTE' }]);
|
|
63
83
|
mocked(feature_flags_1.getFeatureFlags).mockResolvedValue([{ id: '1', featureName: 'SHOWROOM' }]);
|
package/dist/interfaces.d.ts
CHANGED
package/oclif.manifest.json
CHANGED