@adobe/aio-cli-plugin-api-mesh 1.1.0 → 1.3.0
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 +8 -4
- package/oclif.manifest.json +1 -1
- package/package.json +2 -1
- package/src/commands/api-mesh/__tests__/describe.test.js +3 -17
- package/src/commands/api-mesh/describe.js +6 -4
- package/src/commands/api-mesh/source/__fixtures__/0.0.1-test-03.json +40 -0
- package/src/commands/api-mesh/source/__fixtures__/connectors-metadata.json +7 -0
- package/src/commands/api-mesh/source/__fixtures__/variables-file-invalid.json +3 -0
- package/src/commands/api-mesh/source/__tests__/install.test.js +137 -0
- package/src/commands/api-mesh/source/install.js +269 -0
- package/src/helpers.js +105 -31
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ If you want to have custom configuration instead, please follow the steps below:
|
|
|
62
62
|
2. Perform the following command to update the configuration
|
|
63
63
|
|
|
64
64
|
```
|
|
65
|
-
aio config:set api-mesh.
|
|
65
|
+
aio config:set api-mesh.cliConfig <path_to_json_file>
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
# Commands
|
|
@@ -90,9 +90,13 @@ To submit a new source, please follow the instructions provided in the [Source R
|
|
|
90
90
|
## Commands
|
|
91
91
|
|
|
92
92
|
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
aio api-mesh:source:
|
|
93
|
+
|
|
94
|
+
aio api-mesh:source:install SOURCE_NAME
|
|
95
|
+
aio api-mesh:source:install SOURCE_NAME -v VARIABLE_NAME=VARIABLE_VALUE
|
|
96
|
+
aio api-mesh:source:install SOURCE_NAME -f PATH_TO_FILE_WITH_VARIABLES
|
|
97
|
+
|
|
98
|
+
aio api-mesh:source:get SOURCE_NAME
|
|
99
|
+
aio api-mesh:source:get SOURCE_NAME@VERSION_OF_THE_SOURCE
|
|
96
100
|
aio api-mesh:source:get -m
|
|
97
101
|
aio api-mesh:source:discover
|
|
98
102
|
```
|
package/oclif.manifest.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.
|
|
1
|
+
{"version":"1.3.0","commands":{"PLUGINNAME":{"id":"PLUGINNAME","description":"Your description here","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio PLUGINNAME:some_command"],"flags":{"someflag":{"name":"someflag","type":"option","char":"f","description":"this is some flag"}},"args":[]},"api-mesh:create":{"id":"api-mesh:create","description":"Create a mesh with the given config.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false}},"args":[{"name":"file"}]},"api-mesh:delete":{"id":"api-mesh:delete","description":"Delete the config of a given mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false}},"args":[]},"api-mesh:describe":{"id":"api-mesh:describe","description":"Get details of a mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[]},"api-mesh:get":{"id":"api-mesh:get","description":"Get the config of a given mesh","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[{"name":"file"}]},"api-mesh:update":{"id":"api-mesh:update","description":"Update a mesh with the given config.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false},"autoConfirmAction":{"name":"autoConfirmAction","type":"boolean","char":"c","description":"Auto confirm action prompt. CLI will not check for user approval before executing the action.","allowNo":false}},"args":[{"name":"file"}]},"api-mesh:source:discover":{"id":"api-mesh:source:discover","description":"Return the list of avaliable sources","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"flags":{},"args":[]},"api-mesh:source:get":{"id":"api-mesh:source:get","description":"Command returns the content of a specific source.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio api-mesh:source:get <version>@<source_name>","$ aio api-mesh:source:get <source_name>","$ aio api-mesh:source:get -m"],"flags":{"source":{"name":"source","type":"option","char":"s","description":"Source name"},"multiple":{"name":"multiple","type":"boolean","char":"m","description":"Select multiple sources","allowNo":false}},"args":[]},"api-mesh:source:install":{"id":"api-mesh:source:install","description":"Command to install the source to your API mesh.","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":["$ aio api-mesh:source:install <version>@<source_name>","$ aio api-mesh:source:install <source_name> -v <variable_name>=<variable_value>","$ aio api-mesh:source:install <source_name> -f <path_to_variables_file>"],"flags":{"variable":{"name":"variable","type":"option","char":"v","description":"Variables required for the source"},"variable-file":{"name":"variable-file","type":"option","char":"f","description":"Variables file path"},"ignoreCache":{"name":"ignoreCache","type":"boolean","char":"i","description":"Ignore cache and force manual org -> project -> workspace selection","allowNo":false}},"args":[{"name":"source"}]}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/aio-cli-plugin-api-mesh",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"chalk": "^4.1.0",
|
|
19
19
|
"dotenv": "^16.0.1",
|
|
20
20
|
"inquirer": "^8.2.4",
|
|
21
|
+
"json-interpolate": "^1.0.3",
|
|
21
22
|
"node-clipboardy": "^1.0.3",
|
|
22
23
|
"pino": "^7.9.2",
|
|
23
24
|
"pino-pretty": "^7.6.0",
|
|
@@ -89,26 +89,19 @@ describe('describe command tests', () => {
|
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
test('should error if describe api has failed', async () => {
|
|
92
|
-
describeMesh.mockRejectedValueOnce(new Error('
|
|
92
|
+
describeMesh.mockRejectedValueOnce(new Error('Describe api failed'));
|
|
93
93
|
|
|
94
94
|
const runResult = DescribeCommand.run();
|
|
95
95
|
|
|
96
96
|
await expect(runResult).rejects.toEqual(
|
|
97
97
|
new Error(
|
|
98
|
-
'
|
|
98
|
+
'Describe api failed Please check the details and try again. If the error persists please contact support. RequestId: dummy_request_id',
|
|
99
99
|
),
|
|
100
100
|
);
|
|
101
|
-
expect(logSpy.mock.calls).toMatchInlineSnapshot(`
|
|
102
|
-
Array [
|
|
103
|
-
Array [
|
|
104
|
-
"describe api failed",
|
|
105
|
-
],
|
|
106
|
-
]
|
|
107
|
-
`);
|
|
108
101
|
expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
|
|
109
102
|
Array [
|
|
110
103
|
Array [
|
|
111
|
-
"
|
|
104
|
+
"Describe api failed Please check the details and try again. If the error persists please contact support. RequestId: dummy_request_id",
|
|
112
105
|
],
|
|
113
106
|
]
|
|
114
107
|
`);
|
|
@@ -124,13 +117,6 @@ describe('describe command tests', () => {
|
|
|
124
117
|
'Unable to get mesh details. Please check the details and try again. If the error persists please contact support. RequestId: dummy_request_id',
|
|
125
118
|
),
|
|
126
119
|
);
|
|
127
|
-
expect(logSpy.mock.calls).toMatchInlineSnapshot(`
|
|
128
|
-
Array [
|
|
129
|
-
Array [
|
|
130
|
-
"Unable to get mesh details",
|
|
131
|
-
],
|
|
132
|
-
]
|
|
133
|
-
`);
|
|
134
120
|
expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
|
|
135
121
|
Array [
|
|
136
122
|
Array [
|
|
@@ -68,13 +68,15 @@ class DescribeCommand extends Command {
|
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
} else {
|
|
71
|
-
throw new Error(`Unable to get mesh details
|
|
71
|
+
throw new Error(`Unable to get mesh details.`);
|
|
72
72
|
}
|
|
73
73
|
} catch (error) {
|
|
74
|
-
this.log(error.message);
|
|
75
|
-
|
|
76
74
|
this.error(
|
|
77
|
-
|
|
75
|
+
`${
|
|
76
|
+
error.message || 'Unable to get mesh details.'
|
|
77
|
+
} Please check the details and try again. If the error persists please contact support. RequestId: ${
|
|
78
|
+
global.requestId
|
|
79
|
+
}`,
|
|
78
80
|
);
|
|
79
81
|
}
|
|
80
82
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Test 03",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Mock for variable injection",
|
|
5
|
+
"author": "VladimirZaets",
|
|
6
|
+
"variables": {
|
|
7
|
+
"ENDPOINT_URL": {
|
|
8
|
+
"name": "Test API",
|
|
9
|
+
"description": "This URL will be used to query the third-party API",
|
|
10
|
+
"type": "string"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"provider": {
|
|
14
|
+
"name": "Commerce",
|
|
15
|
+
"handler": {
|
|
16
|
+
"graphql": {
|
|
17
|
+
"endpoint": "${ENDPOINT_URL}"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"transforms": [
|
|
21
|
+
{
|
|
22
|
+
"rename": {
|
|
23
|
+
"mode": "bare | wrap",
|
|
24
|
+
"renames": [
|
|
25
|
+
{
|
|
26
|
+
"from": {
|
|
27
|
+
"type": "Query",
|
|
28
|
+
"field": "compareList"
|
|
29
|
+
},
|
|
30
|
+
"to": {
|
|
31
|
+
"type": "Query",
|
|
32
|
+
"field": "productCompareList"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2021 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const mockMetadataFixture = require('../__fixtures__/connectors-metadata.json');
|
|
14
|
+
const mockSourceTest01v1Fixture = require('../__fixtures__/0.0.1-test-01.json');
|
|
15
|
+
const mockSourceTest02v1Fixture = require('../__fixtures__/0.0.1-test-02.json');
|
|
16
|
+
const mockSourceTest03v1Fixture = require('../__fixtures__/0.0.1-test-03.json');
|
|
17
|
+
const mockAdapter = require('source-registry-storage-adapter');
|
|
18
|
+
const chalk = require('chalk');
|
|
19
|
+
const { initSdk, initRequestId, promptInput } = require('../../../../helpers');
|
|
20
|
+
const mockSources = { '0.0.1-test-03': mockSourceTest03v1Fixture };
|
|
21
|
+
jest.mock('source-registry-storage-adapter');
|
|
22
|
+
jest.mock('../../../../helpers');
|
|
23
|
+
const InstallCommand = require('../install');
|
|
24
|
+
const { getMeshId, getMesh, updateMesh } = require('../../../../lib/devConsole');
|
|
25
|
+
const mockGetMeshConfig = require('../../../__fixtures__/sample_mesh.json');
|
|
26
|
+
const selectedOrg = { id: '1234', code: 'CODE1234@AdobeOrg', name: 'ORG01', type: 'entp' };
|
|
27
|
+
const selectedProject = { id: '5678', title: 'Project01' };
|
|
28
|
+
const selectedWorkspace = { id: '123456789', title: 'Workspace01' };
|
|
29
|
+
global.requestId = 'dummy_request_id';
|
|
30
|
+
jest.mock('../../../../lib/devConsole');
|
|
31
|
+
mockAdapter.mockImplementation(() => ({
|
|
32
|
+
get: jest.fn().mockResolvedValue(mockSources[`0.0.1-test-03`]),
|
|
33
|
+
getList: jest.fn().mockImplementation(() => mockMetadataFixture),
|
|
34
|
+
}));
|
|
35
|
+
getMeshId.mockResolvedValue('dummy_meshId');
|
|
36
|
+
getMesh.mockResolvedValue(mockGetMeshConfig);
|
|
37
|
+
updateMesh.mockResolvedValue({ status: 'success' });
|
|
38
|
+
|
|
39
|
+
initSdk.mockResolvedValue({
|
|
40
|
+
imsOrgId: selectedOrg.id,
|
|
41
|
+
projectId: selectedProject.id,
|
|
42
|
+
workspaceId: selectedWorkspace.id,
|
|
43
|
+
});
|
|
44
|
+
initRequestId.mockResolvedValue({});
|
|
45
|
+
promptInput.mockResolvedValueOnce('test-03');
|
|
46
|
+
|
|
47
|
+
describe('source:install command tests', () => {
|
|
48
|
+
test('Snapshot install command description', () => {
|
|
49
|
+
expect(InstallCommand.description).toMatchInlineSnapshot(
|
|
50
|
+
`"Command to install the source to your API mesh."`,
|
|
51
|
+
);
|
|
52
|
+
expect(InstallCommand.flags).toMatchInlineSnapshot(`
|
|
53
|
+
Object {
|
|
54
|
+
"ignoreCache": Object {
|
|
55
|
+
"allowNo": false,
|
|
56
|
+
"char": "i",
|
|
57
|
+
"default": false,
|
|
58
|
+
"description": "Ignore cache and force manual org -> project -> workspace selection",
|
|
59
|
+
"parse": [Function],
|
|
60
|
+
"type": "boolean",
|
|
61
|
+
},
|
|
62
|
+
"variable": Object {
|
|
63
|
+
"char": "v",
|
|
64
|
+
"description": "Variables required for the source",
|
|
65
|
+
"input": Array [],
|
|
66
|
+
"multiple": true,
|
|
67
|
+
"parse": [Function],
|
|
68
|
+
"type": "option",
|
|
69
|
+
},
|
|
70
|
+
"variable-file": Object {
|
|
71
|
+
"char": "f",
|
|
72
|
+
"description": "Variables file path",
|
|
73
|
+
"input": Array [],
|
|
74
|
+
"multiple": false,
|
|
75
|
+
"parse": [Function],
|
|
76
|
+
"type": "option",
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
`);
|
|
80
|
+
expect(InstallCommand.aliases).toMatchInlineSnapshot(`Array []`);
|
|
81
|
+
});
|
|
82
|
+
test('Check executing without parameters', async () => {
|
|
83
|
+
await InstallCommand.run([]).catch(err => {
|
|
84
|
+
expect(err.message).toEqual(
|
|
85
|
+
`The "aio api-mesh:source:install" command requires additional parameters` +
|
|
86
|
+
`\nUse "aio api-mesh:source:install --help" to see parameters details.`,
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
test('Check executing with invalid file parameter', async () => {
|
|
91
|
+
await InstallCommand.run(['test-03', '-f=notexist.json']).catch(err => {
|
|
92
|
+
expect(err.message).toEqual(
|
|
93
|
+
`Something went wrong trying to read the variables file.` +
|
|
94
|
+
`\nENOENT: no such file or directory, open 'notexist.json'`,
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
test('Check executing with invalid variable type', async () => {
|
|
99
|
+
const path = `-f=${__dirname}/../__fixtures__/variables-file-invalid.json`;
|
|
100
|
+
await InstallCommand.run(['test-03', path]).catch(err => {
|
|
101
|
+
expect(err.message).toEqual(
|
|
102
|
+
chalk.red(
|
|
103
|
+
`The next variables has invalid type.\nVariable: ENDPOINT_URL\nRequested type: string`,
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
test('Check executing without passing variables', async () => {
|
|
109
|
+
await InstallCommand.run(['test-03']);
|
|
110
|
+
expect(promptInput).toHaveBeenCalledTimes(1);
|
|
111
|
+
});
|
|
112
|
+
test('Check executing without passing variables and input data', async () => {
|
|
113
|
+
await InstallCommand.run(['test-03']).catch(err => {
|
|
114
|
+
expect(err.message).toEqual(
|
|
115
|
+
chalk.red(
|
|
116
|
+
`The next variables has invalid type.\nVariable: ENDPOINT_URL\nRequested type: string`,
|
|
117
|
+
),
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
expect(promptInput).toHaveBeenCalledTimes(1);
|
|
121
|
+
});
|
|
122
|
+
test('Check executing with passing variables in CLI', async () => {
|
|
123
|
+
const parsed =
|
|
124
|
+
'{"name":"Test 03","version":"0.0.1","description":"Mock for variable injection","author":"VladimirZaets","variables":{"ENDPOINT_URL":{"name":"Test API","description":"This URL will be used to query the third-party API","type":"string"}},"provider":{"name":"Commerce","handler":{"graphql":{"endpoint":"https:www.myendpoint.com/api"}},"transforms":[{"rename":{"mode":"bare | wrap","renames":[{"from":{"type":"Query","field":"compareList"},"to":{"type":"Query","field":"productCompareList"}}]}}]}}';
|
|
125
|
+
const res = { ...mockGetMeshConfig };
|
|
126
|
+
res.meshConfig.sources.push(JSON.parse(parsed));
|
|
127
|
+
await InstallCommand.run(['test-03', '-v ENDPOINT_URL=https:www.myendpoint.com/api']);
|
|
128
|
+
expect(promptInput).toHaveBeenCalledTimes(0);
|
|
129
|
+
expect(updateMesh).toHaveBeenCalledWith(
|
|
130
|
+
selectedOrg.id,
|
|
131
|
+
selectedProject.id,
|
|
132
|
+
selectedWorkspace.id,
|
|
133
|
+
'dummy_meshId',
|
|
134
|
+
res,
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { Command, Flags } = require('@oclif/core');
|
|
14
|
+
const SourceRegistryStorage = require('source-registry-storage-adapter');
|
|
15
|
+
const { promptConfirm, promptInput, initRequestId, initSdk } = require('../../../helpers');
|
|
16
|
+
const { ignoreCacheFlag } = require('../../../utils');
|
|
17
|
+
const config = require('@adobe/aio-lib-core-config');
|
|
18
|
+
const logger = require('../../../classes/logger');
|
|
19
|
+
const { readFile } = require('fs/promises');
|
|
20
|
+
const chalk = require('chalk');
|
|
21
|
+
const { getMeshId, getMesh, updateMesh } = require('../../../lib/devConsole');
|
|
22
|
+
const JsonInterpolate = require('json-interpolate');
|
|
23
|
+
|
|
24
|
+
class InstallCommand extends Command {
|
|
25
|
+
static args = [{ name: 'source' }];
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
super(...arguments);
|
|
29
|
+
this.sourceRegistryStorage = new SourceRegistryStorage(
|
|
30
|
+
config.get('api-mesh.sourceRegistry.path'),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async run() {
|
|
35
|
+
const { flags, args } = await this.parse(InstallCommand);
|
|
36
|
+
await initRequestId();
|
|
37
|
+
logger.info(`RequestId: ${global.requestId}`);
|
|
38
|
+
const ignoreCache = await flags.ignoreCache;
|
|
39
|
+
const { imsOrgId, projectId, workspaceId } = await initSdk({ ignoreCache });
|
|
40
|
+
const filepath = flags['variable-file'];
|
|
41
|
+
let variables = flags.variable
|
|
42
|
+
? flags.variable.reduce((obj, val) => {
|
|
43
|
+
const splited = val.split('=');
|
|
44
|
+
obj[splited[0].trim()] = splited[1].trim();
|
|
45
|
+
return obj;
|
|
46
|
+
}, {})
|
|
47
|
+
: {};
|
|
48
|
+
if (filepath) {
|
|
49
|
+
try {
|
|
50
|
+
variables = { ...variables, ...JSON.parse(await readFile(filepath, 'utf8')) };
|
|
51
|
+
} catch (e) {
|
|
52
|
+
this.error(`Something went wrong trying to read the variables file.` + `\n${e.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let meshId = null;
|
|
57
|
+
if (!args.source && !flags.source) {
|
|
58
|
+
this.error(
|
|
59
|
+
`The "aio api-mesh:source:install" command requires additional parameters` +
|
|
60
|
+
`\nUse "aio api-mesh:source:install --help" to see parameters details.`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
let sourceProviders;
|
|
64
|
+
try {
|
|
65
|
+
sourceProviders = await this.sourceRegistryStorage.getList();
|
|
66
|
+
} catch (err) {
|
|
67
|
+
this.error(`Cannot get the list of sources: ${err}. RequestId: ${global.requestId}`);
|
|
68
|
+
}
|
|
69
|
+
const sources = flags.source ? flags.source : [args.source];
|
|
70
|
+
const sourceConfigs = {sources: [], files: {}};
|
|
71
|
+
for (const source of sources) {
|
|
72
|
+
let [name, version] = source.split('@');
|
|
73
|
+
const normalizedName = this.normalizeName(name);
|
|
74
|
+
if (!sourceProviders[normalizedName]) {
|
|
75
|
+
this.error(
|
|
76
|
+
chalk.red(
|
|
77
|
+
`The source named "${name}" doesn't exist.` +
|
|
78
|
+
`\nUse "aio api-mesh:source:discover" command to see avaliable sources.`,
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
version = version || sourceProviders[normalizedName].latest;
|
|
83
|
+
if (!sourceProviders[normalizedName].versions.includes(version)) {
|
|
84
|
+
this.error(
|
|
85
|
+
chalk.red(
|
|
86
|
+
`The version "${version}" for source name "${name}" doesn't exist.` +
|
|
87
|
+
`\nUse "aio api-mesh:source:discover" command to see avaliable source versions.`,
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const sourceConfig = await this.sourceRegistryStorage.get(name, version);
|
|
92
|
+
const jsonInterpolate = new JsonInterpolate({ variablesSchema: sourceConfig.variables });
|
|
93
|
+
const sourceProviderString = JSON.stringify(sourceConfig.provider);
|
|
94
|
+
const sourceVariables = jsonInterpolate.getJsonVariables(sourceProviderString);
|
|
95
|
+
const missedVariables = jsonInterpolate.getMissedVariables(variables, sourceVariables);
|
|
96
|
+
for (const missedVariable of missedVariables) {
|
|
97
|
+
variables[missedVariable.name] = await promptInput(
|
|
98
|
+
`Enter the value for variable ${missedVariable.name}:`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { error, data } = jsonInterpolate.interpolate(
|
|
103
|
+
JSON.stringify(sourceConfig.provider),
|
|
104
|
+
variables,
|
|
105
|
+
);
|
|
106
|
+
if (error) {
|
|
107
|
+
this.error(chalk.red(`${error.message}\n${error.list.map(err => err.message).join('\n')}`));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
sourceConfigs.sources.push(JSON.parse(data));
|
|
111
|
+
sourceConfigs.files[sourceConfig.provider.name] = sourceConfig.files;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
meshId = await getMeshId(imsOrgId, projectId, workspaceId);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
this.error(
|
|
118
|
+
`Unable to get mesh ID. Please check the details and try again. RequestId: ${global.requestId}`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!meshId) {
|
|
123
|
+
this.error(
|
|
124
|
+
`Unable to get mesh config. No mesh found for Org(${imsOrgId}) -> Project(${projectId}) -> Workspace(${workspaceId}). Please check the details and try again.`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const mesh = await getMesh(imsOrgId, projectId, workspaceId, meshId);
|
|
130
|
+
|
|
131
|
+
if (!mesh) {
|
|
132
|
+
this.error(
|
|
133
|
+
`Unable to get mesh with the ID ${meshId}. Please check the mesh ID and try again. RequestId: ${global.requestId}`,
|
|
134
|
+
{ exit: false },
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const verifiedSources = this.verifySourceAlreadyExists(
|
|
138
|
+
mesh.meshConfig.sources,
|
|
139
|
+
sourceConfigs.sources,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
let override = false;
|
|
143
|
+
if (verifiedSources.installed.length) {
|
|
144
|
+
override = await promptConfirm(
|
|
145
|
+
`The following sources are already installed: ${verifiedSources.installed
|
|
146
|
+
.map(source => source.name)
|
|
147
|
+
.join(', ')}.
|
|
148
|
+
Do you want to override?`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const uniqueFiles = this.getSourceFiles(verifiedSources.unique.map(source => source.name), sourceConfigs.files);
|
|
153
|
+
const installedFiles = this.getSourceFiles(verifiedSources.installed.map(source => source.name), sourceConfigs.files);
|
|
154
|
+
let meshConfigFiles = mesh.meshConfig.files || [];
|
|
155
|
+
|
|
156
|
+
if (override) {
|
|
157
|
+
const installedMap = verifiedSources.installed.reduce((obj, source) => {
|
|
158
|
+
obj[source.name] = true;
|
|
159
|
+
return obj
|
|
160
|
+
}, {});
|
|
161
|
+
|
|
162
|
+
mesh.meshConfig.sources = [
|
|
163
|
+
...mesh.meshConfig.sources.filter(source => !installedMap[source.name]),
|
|
164
|
+
...verifiedSources.installed
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const installedFilesMap = installedFiles.reduce((obj, file) => {
|
|
168
|
+
obj[file.path] = true;
|
|
169
|
+
return obj
|
|
170
|
+
}, {});
|
|
171
|
+
|
|
172
|
+
meshConfigFiles = [
|
|
173
|
+
...meshConfigFiles.filter(file => !installedFilesMap[file.path]),
|
|
174
|
+
...installedFiles
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
mesh.meshConfig.sources = [
|
|
179
|
+
...mesh.meshConfig.sources,
|
|
180
|
+
...verifiedSources.unique
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
meshConfigFiles = [
|
|
184
|
+
...meshConfigFiles,
|
|
185
|
+
...uniqueFiles
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
if (meshConfigFiles.length) {
|
|
189
|
+
mesh.meshConfig.files = meshConfigFiles
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const response = await updateMesh(imsOrgId, projectId, workspaceId, meshId, {
|
|
194
|
+
meshConfig: mesh.meshConfig,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
this.log('Successfully updated the mesh with the id: %s', meshId);
|
|
198
|
+
|
|
199
|
+
return response;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
this.log(error.message);
|
|
202
|
+
|
|
203
|
+
this.error(
|
|
204
|
+
`Unable to update the mesh. Please check the mesh configuration file and try again. If the error persists please contact support. RequestId: ${global.requestId}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return mesh;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
this.log(error.message);
|
|
211
|
+
this.error(
|
|
212
|
+
`Unable to get mesh. Please check the details and try again. If the error persists please contact support. RequestId: ${global.requestId}`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
normalizeName(name) {
|
|
218
|
+
return name.toLowerCase().split(' ').join('-');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
getSourceFiles(sourcesList, filesList) {
|
|
222
|
+
let result = [];
|
|
223
|
+
for (const source of sourcesList) {
|
|
224
|
+
if (Array.isArray(filesList[source])) {
|
|
225
|
+
result = [...result, ...filesList[source]]
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
verifySourceAlreadyExists(meshSources, installSources) {
|
|
232
|
+
const alreadyInstalledSources = [];
|
|
233
|
+
const uniqueSourcesToInstall = [];
|
|
234
|
+
installSources.forEach(installSource => {
|
|
235
|
+
const source = meshSources.find(meshSource => meshSource.name === installSource.name);
|
|
236
|
+
if (source) {
|
|
237
|
+
alreadyInstalledSources.push(source);
|
|
238
|
+
} else {
|
|
239
|
+
uniqueSourcesToInstall.push(installSource);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
return {
|
|
243
|
+
installed: alreadyInstalledSources,
|
|
244
|
+
unique: uniqueSourcesToInstall,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
InstallCommand.flags = {
|
|
250
|
+
'variable': Flags.string({
|
|
251
|
+
char: 'v',
|
|
252
|
+
description: 'Variables required for the source',
|
|
253
|
+
multiple: true,
|
|
254
|
+
}),
|
|
255
|
+
'variable-file': Flags.string({
|
|
256
|
+
char: 'f',
|
|
257
|
+
description: 'Variables file path',
|
|
258
|
+
}),
|
|
259
|
+
'ignoreCache': ignoreCacheFlag,
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
InstallCommand.description = 'Command to install the source to your API mesh.';
|
|
263
|
+
InstallCommand.examples = [
|
|
264
|
+
'$ aio api-mesh:source:install <version>@<source_name>',
|
|
265
|
+
'$ aio api-mesh:source:install <source_name> -v <variable_name>=<variable_value>',
|
|
266
|
+
'$ aio api-mesh:source:install <source_name> -f <path_to_variables_file>',
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
module.exports = InstallCommand;
|
package/src/helpers.js
CHANGED
|
@@ -26,49 +26,103 @@ const { objToString } = require('./utils');
|
|
|
26
26
|
|
|
27
27
|
const { DEV_CONSOLE_BASE_URL, DEV_CONSOLE_API_KEY, AIO_CLI_API_KEY } = CONSTANTS;
|
|
28
28
|
|
|
29
|
+
async function getDevConsoleConfigFromFile(configFilePath) {
|
|
30
|
+
try {
|
|
31
|
+
if (!fs.existsSync(configFilePath)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Config file does not exist. Please run the command: aio config:set api-mesh.configPath <path_to_json_file> with a valid file.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const data = JSON.parse(fs.readFileSync(configFilePath, { encoding: 'utf8', flag: 'r' }));
|
|
38
|
+
|
|
39
|
+
if (!data.baseUrl || !data.apiKey) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
'Invalid config file. Please validate the file contents and try again. Config file must contain baseUrl and apiKey.',
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const baseUrl = data.baseUrl.endsWith('/')
|
|
46
|
+
? data.baseUrl.slice(0, data.baseUrl.length - 1)
|
|
47
|
+
: data.baseUrl;
|
|
48
|
+
|
|
49
|
+
const config = {
|
|
50
|
+
baseUrl: baseUrl,
|
|
51
|
+
accessToken: (await getLibConsoleCLI()).accessToken,
|
|
52
|
+
apiKey: data.apiKey,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
logger.debug(`Using cli config from ${configFilePath}: ${objToString(config)}`);
|
|
56
|
+
|
|
57
|
+
return config;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
logger.error(
|
|
60
|
+
'Please run the command: aio config:set api-mesh.configPath <path_to_json_file> with a valid config file.',
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
throw new Error(error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function getDevConsoleConfigFromObject(configObject) {
|
|
68
|
+
const { baseUrl, apiKey } = configObject;
|
|
69
|
+
const config = {
|
|
70
|
+
baseUrl,
|
|
71
|
+
accessToken: (await getLibConsoleCLI()).accessToken,
|
|
72
|
+
apiKey,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
logger.debug(`Using cli config: ${objToString(config)}`);
|
|
76
|
+
|
|
77
|
+
return config;
|
|
78
|
+
}
|
|
79
|
+
|
|
29
80
|
/**
|
|
30
81
|
* @returns {any} Returns a config object or null
|
|
31
82
|
*/
|
|
32
83
|
async function getDevConsoleConfig() {
|
|
33
|
-
const
|
|
84
|
+
const configFileOrObject = Config.get('api-mesh.cliConfig');
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Old legacy option, needs to be deprecated
|
|
88
|
+
*/
|
|
89
|
+
const configPath = Config.get('api-mesh.configPath');
|
|
90
|
+
if (configPath) {
|
|
91
|
+
if (configFileOrObject) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
'Found both cliConfig and configPath in api-mesh config. Please consider using only cliConfig.',
|
|
94
|
+
);
|
|
95
|
+
} else {
|
|
96
|
+
console.warn(
|
|
97
|
+
'Please consider using cliConfig instead of configPath on api-mesh config. configPath will be deprecated soon.',
|
|
98
|
+
);
|
|
99
|
+
logger.warn(
|
|
100
|
+
'Please consider using cliConfig instead of configPath on api-mesh config. configPath will be deprecated soon.',
|
|
101
|
+
);
|
|
34
102
|
|
|
35
|
-
|
|
36
|
-
|
|
103
|
+
return await getDevConsoleConfigFromFile(configPath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!configFileOrObject) {
|
|
108
|
+
const config = {
|
|
37
109
|
baseUrl: DEV_CONSOLE_BASE_URL,
|
|
38
110
|
accessToken: (await getLibConsoleCLI()).accessToken,
|
|
39
111
|
apiKey: DEV_CONSOLE_API_KEY,
|
|
40
112
|
};
|
|
41
|
-
} else {
|
|
42
|
-
try {
|
|
43
|
-
if (!fs.existsSync(configFile)) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Config file does not exist. Please run the command: aio config:set api-mesh.configPath <path_to_json_file> with a valid file.`,
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const data = JSON.parse(fs.readFileSync(configFile, { encoding: 'utf8', flag: 'r' }));
|
|
50
|
-
|
|
51
|
-
if (!data.baseUrl || !data.apiKey) {
|
|
52
|
-
throw new Error(
|
|
53
|
-
'Invalid config file. Please validate the file contents and try again. Config file must contain baseUrl and apiKey.',
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
113
|
|
|
57
|
-
|
|
58
|
-
? data.baseUrl.slice(0, data.baseUrl.length - 1)
|
|
59
|
-
: data.baseUrl;
|
|
114
|
+
logger.debug(`No cli config found. Using defaults: ${objToString(config)}`);
|
|
60
115
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
116
|
+
return config;
|
|
117
|
+
} else {
|
|
118
|
+
if (typeof configFileOrObject === 'object') {
|
|
119
|
+
return getDevConsoleConfigFromObject(configFileOrObject);
|
|
120
|
+
} else if (typeof configFileOrObject === 'string') {
|
|
121
|
+
return await getDevConsoleConfigFromFile(configFileOrObject);
|
|
122
|
+
} else {
|
|
123
|
+
throw new Error(
|
|
124
|
+
'Invalid config. Please validate and try again. Config should be a JSON object or a JSON file with baseUrl and apiKey.',
|
|
69
125
|
);
|
|
70
|
-
|
|
71
|
-
throw new Error(error);
|
|
72
126
|
}
|
|
73
127
|
}
|
|
74
128
|
}
|
|
@@ -380,7 +434,27 @@ async function promptSelect(message, choices) {
|
|
|
380
434
|
return selected.item;
|
|
381
435
|
}
|
|
382
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Function to run the CLI selectable list
|
|
439
|
+
*
|
|
440
|
+
* @param {string} message - prompt message
|
|
441
|
+
* @param {object[]} choices - list of options
|
|
442
|
+
* @returns {object[]} - selected options
|
|
443
|
+
*/
|
|
444
|
+
async function promptInput(message) {
|
|
445
|
+
const selected = await inquirer.prompt([
|
|
446
|
+
{
|
|
447
|
+
name: 'item',
|
|
448
|
+
message: message,
|
|
449
|
+
type: 'input',
|
|
450
|
+
},
|
|
451
|
+
]);
|
|
452
|
+
|
|
453
|
+
return selected.item;
|
|
454
|
+
}
|
|
455
|
+
|
|
383
456
|
module.exports = {
|
|
457
|
+
promptInput,
|
|
384
458
|
promptConfirm,
|
|
385
459
|
getLibConsoleCLI,
|
|
386
460
|
getDevConsoleConfig,
|