@adobe/aio-cli-plugin-api-mesh 3.3.0-beta.1 → 3.3.1-alpha

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.
@@ -1 +1 @@
1
- {"version":"3.3.0-beta.1","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},"json":{"name":"json","type":"boolean","description":"Output JSON","allowNo":false},"env":{"name":"env","type":"option","char":"e","description":"Path to env file","default":".env"}},"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},"json":{"name":"json","type":"boolean","description":"Output JSON","allowNo":false}},"args":[{"name":"file"}]},"api-mesh:init":{"id":"api-mesh:init","description":"This command will create a workspace where you can organise your API mesh configuration and other files","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":[{"description":"API mesh workspace init","command":"aio api-mesh init commerce-mesh"},{"description":"API mesh workspace init with flags","command":"aio api-mesh init commerce-mesh --path ./mesh_projects/test_mesh --git y --packageManager yarn"}],"flags":{"path":{"name":"path","type":"option","char":"p","default":"."},"packageManager":{"name":"packageManager","type":"option","char":"m","options":["npm","yarn"]},"git":{"name":"git","type":"option","char":"g","options":["y","n"]}},"args":[{"name":"projectName","description":"Project name","required":true}]},"api-mesh:run":{"id":"api-mesh:run","description":"Run a local development server that builds and compiles a mesh locally","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":[],"flags":{"port":{"name":"port","type":"option","char":"p","description":"Port number for the local dev server"},"debug":{"name":"debug","type":"boolean","description":"Enable debugging mode","allowNo":false},"env":{"name":"env","type":"option","char":"e","description":"Path to env file","default":".env"},"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},"select":{"name":"select","type":"boolean","description":"Retrieve existing artifacts from the mesh","allowNo":false}},"args":[{"name":"file","description":"Mesh File"}]},"api-mesh:status":{"id":"api-mesh:status","description":"Get a mesh status with a given meshid.","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: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},"env":{"name":"env","type":"option","char":"e","description":"Path to env file","default":".env"}},"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":{"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm install action prompt. CLI will not check ask user to install source.","allowNo":false}},"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 -s=<version>@<source_name>","$ aio api-mesh:source:get -s<source_name>","$ aio api-mesh:source:get -m"],"flags":{"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm print action prompt. CLI will not check ask user to print source.","allowNo":false},"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":{"source":{"name":"source","type":"option","char":"s","description":"Source name"},"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm override action prompt. CLI will not check ask user to override source.","allowNo":false},"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"}]}}}
1
+ {"version":"3.3.1-alpha","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},"json":{"name":"json","type":"boolean","description":"Output JSON","allowNo":false},"env":{"name":"env","type":"option","char":"e","description":"Path to env file","default":".env"},"secrets":{"name":"secrets","type":"option","char":"s","description":"Path to secrets file","default":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},"json":{"name":"json","type":"boolean","description":"Output JSON","allowNo":false}},"args":[{"name":"file"}]},"api-mesh:init":{"id":"api-mesh:init","description":"This command will create a workspace where you can organise your API mesh configuration and other files","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":[{"description":"API mesh workspace init","command":"aio api-mesh init commerce-mesh"},{"description":"API mesh workspace init with flags","command":"aio api-mesh init commerce-mesh --path ./mesh_projects/test_mesh --git y --packageManager yarn"}],"flags":{"path":{"name":"path","type":"option","char":"p","default":"."},"packageManager":{"name":"packageManager","type":"option","char":"m","options":["npm","yarn"]},"git":{"name":"git","type":"option","char":"g","options":["y","n"]}},"args":[{"name":"projectName","description":"Project name","required":true}]},"api-mesh:run":{"id":"api-mesh:run","description":"Run a local development server that builds and compiles a mesh locally","pluginName":"@adobe/aio-cli-plugin-api-mesh","pluginType":"core","aliases":[],"examples":[],"flags":{"port":{"name":"port","type":"option","char":"p","description":"Port number for the local dev server"},"debug":{"name":"debug","type":"boolean","description":"Enable debugging mode","allowNo":false},"env":{"name":"env","type":"option","char":"e","description":"Path to env file","default":".env"},"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},"select":{"name":"select","type":"boolean","description":"Retrieve existing artifacts from the mesh","allowNo":false},"secrets":{"name":"secrets","type":"option","char":"s","description":"Path to secrets file","default":false}},"args":[{"name":"file","description":"Mesh File"}]},"api-mesh:status":{"id":"api-mesh:status","description":"Get a mesh status with a given meshid.","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: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},"env":{"name":"env","type":"option","char":"e","description":"Path to env file","default":".env"},"secrets":{"name":"secrets","type":"option","char":"s","description":"Path to secrets file","default":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":{"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm install action prompt. CLI will not check ask user to install source.","allowNo":false}},"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 -s=<version>@<source_name>","$ aio api-mesh:source:get -s<source_name>","$ aio api-mesh:source:get -m"],"flags":{"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm print action prompt. CLI will not check ask user to print source.","allowNo":false},"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":{"source":{"name":"source","type":"option","char":"s","description":"Source name"},"confirm":{"name":"confirm","type":"boolean","char":"c","description":"Auto confirm override action prompt. CLI will not check ask user to override source.","allowNo":false},"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": "3.3.0-beta.1",
3
+ "version": "3.3.1-alpha",
4
4
  "description": "Adobe I/O CLI plugin to develop and manage API mesh sources",
5
5
  "keywords": [
6
6
  "oclif-plugin"
@@ -52,6 +52,7 @@
52
52
  "@graphql-mesh/plugin-http-details-extensions": "0.1.21",
53
53
  "@graphql-mesh/runtime": "0.46.21",
54
54
  "@graphql-mesh/soap": "0.14.25",
55
+ "@graphql-mesh/store": "0.9.20",
55
56
  "@graphql-mesh/transform-encapsulate": "0.4.21",
56
57
  "@graphql-mesh/transform-federation": "0.11.14",
57
58
  "@graphql-mesh/transform-filter-schema": "0.15.23",
@@ -64,7 +65,6 @@
64
65
  "@graphql-mesh/transform-resolvers-composition": "0.13.20",
65
66
  "@graphql-mesh/transform-type-merging": "0.5.20",
66
67
  "@graphql-mesh/types": "0.91.12",
67
- "@graphql-mesh/store": "0.9.20",
68
68
  "@oclif/command": "^1.6.1",
69
69
  "@oclif/config": "^1.15.1",
70
70
  "@oclif/core": "^1.14.1",
@@ -74,6 +74,7 @@
74
74
  "child_process": "^1.0.2",
75
75
  "content-disposition": "^0.5.4",
76
76
  "dotenv": "^16.0.3",
77
+ "envsub": "^4.1.0",
77
78
  "eslint-plugin-promise": "^6.0.0",
78
79
  "eslint-plugin-security": "^1.5.0",
79
80
  "eslint-plugin-sonarjs": "^0.16.0",
@@ -92,7 +93,8 @@
92
93
  "pupa": "^3.1.0",
93
94
  "source-registry-storage-adapter": "github:devx-services/source-registry-storage-adapter#main",
94
95
  "util": "^0.12.5",
95
- "uuid": "^8.3.2"
96
+ "uuid": "^8.3.2",
97
+ "yaml": "^2.4.2"
96
98
  },
97
99
  "devDependencies": {
98
100
  "@babel/eslint-parser": "^7.15.8",
@@ -0,0 +1,18 @@
1
+ {
2
+ "meshConfig": {
3
+ "sources": [
4
+ {
5
+ "name": "Commerce",
6
+ "handler": {
7
+ "graphql": {
8
+ "endpoint": "https://venia.magento.com/graphql",
9
+ "operationHeaders": {
10
+ "Authorization": "{context.secrets.Token}"
11
+ },
12
+ "useGETForQueries": true
13
+ }
14
+ }
15
+ }
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1,3 @@
1
+ HOME: 'home'
2
+ TOKEN: "dummy-token"
3
+ TOKEN: "dummy-token-duplicate"
@@ -0,0 +1,2 @@
1
+ HOME: 'home'
2
+ TOKEN: "dummy-token"
@@ -0,0 +1,4 @@
1
+ HOME: 'home'
2
+ TOKEN: "dummy-token"
3
+ batchHome: '$HOME'
4
+ USER: '${USER}'
@@ -47,12 +47,38 @@ const {
47
47
  } = require('../../../lib/devConsole');
48
48
 
49
49
  const selectedOrg = { id: '1234', code: 'CODE1234@AdobeOrg', name: 'ORG01', type: 'entp' };
50
+
51
+ const os = require('os');
52
+
50
53
  const selectedProject = { id: '5678', title: 'Project01' };
51
54
  const selectedWorkspace = { id: '123456789', title: 'Workspace01' };
52
55
 
56
+ jest.mock('@adobe/aio-cli-lib-console', () => ({
57
+ init: jest.fn().mockResolvedValue(mockConsoleCLIInstance),
58
+ cleanStdOut: jest.fn(),
59
+ }));
60
+
61
+ jest.mock('axios');
62
+ jest.mock('@adobe/aio-lib-ims');
63
+ jest.mock('@adobe/aio-lib-env');
64
+ jest.mock('@adobe/aio-cli-lib-console');
65
+ jest.mock('../../../helpers', () => ({
66
+ initSdk: jest.fn().mockResolvedValue({}),
67
+ initRequestId: jest.fn().mockResolvedValue({}),
68
+ promptConfirm: jest.fn().mockResolvedValue(true),
69
+ interpolateMesh: jest.fn().mockResolvedValue({}),
70
+ importFiles: jest.fn().mockResolvedValue(),
71
+ }));
72
+ jest.mock('../../../lib/devConsole');
73
+ jest.mock('chalk', () => ({
74
+ red: jest.fn(text => text), // Return the input text without any color formatting
75
+ bold: jest.fn(text => text),
76
+ }));
77
+
53
78
  let logSpy = null;
54
79
  let errorLogSpy = null;
55
80
  let parseSpy = null;
81
+ let platformSpy = null;
56
82
 
57
83
  const mockIgnoreCacheFlag = Promise.resolve(true);
58
84
  const mockAutoApproveAction = Promise.resolve(false);
@@ -69,6 +95,12 @@ describe('create command tests', () => {
69
95
  projectName: selectedProject.title,
70
96
  });
71
97
 
98
+ global.requestId = 'dummy_request_id';
99
+
100
+ logSpy = jest.spyOn(CreateCommand.prototype, 'log');
101
+ errorLogSpy = jest.spyOn(CreateCommand.prototype, 'error');
102
+ platformSpy = jest.spyOn(os, 'platform');
103
+
72
104
  createMesh.mockResolvedValue({
73
105
  mesh: {
74
106
  meshId: 'dummy_mesh_id',
@@ -109,6 +141,10 @@ describe('create command tests', () => {
109
141
  });
110
142
  });
111
143
 
144
+ afterEach(() => {
145
+ platformSpy.mockRestore();
146
+ });
147
+
112
148
  test('must return proper object structure used by adobe/generator-app-api-mesh', async () => {
113
149
  parseSpy.mockResolvedValueOnce({
114
150
  args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
@@ -171,6 +207,15 @@ describe('create command tests', () => {
171
207
  "parse": [Function],
172
208
  "type": "boolean",
173
209
  },
210
+ "secrets": {
211
+ "char": "s",
212
+ "default": false,
213
+ "description": "Path to secrets file",
214
+ "input": [],
215
+ "multiple": false,
216
+ "parse": [Function],
217
+ "type": "option",
218
+ },
174
219
  }
175
220
  `);
176
221
  expect(CreateCommand.aliases).toMatchInlineSnapshot(`[]`);
@@ -1831,4 +1876,205 @@ describe('create command tests', () => {
1831
1876
  'https://graph.adobe.io/api/dummy_mesh_id/graphql?api_key=dummy_api_key',
1832
1877
  );
1833
1878
  });
1879
+
1880
+ test('should return error if mesh has placeholders and the provided secrets file is invalid', async () => {
1881
+ parseSpy.mockResolvedValueOnce({
1882
+ args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
1883
+ flags: {
1884
+ ignoreCache: mockIgnoreCacheFlag,
1885
+ autoConfirmAction: Promise.resolve(true),
1886
+ secrets: 'src/commands/__fixtures__/secrets_invalid.yaml',
1887
+ },
1888
+ });
1889
+
1890
+ const runResult = CreateCommand.run();
1891
+
1892
+ await expect(runResult).rejects.toEqual(
1893
+ new Error('Unable to import secrets. Please check the file and try again.'),
1894
+ );
1895
+
1896
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
1897
+ [
1898
+ [
1899
+ "Unable to import secrets. Please check the file and try again.",
1900
+ ],
1901
+ ]
1902
+ `);
1903
+ });
1904
+
1905
+ test('should return error if mesh has placeholders and the provided secrets file is not yaml or yml', async () => {
1906
+ parseSpy.mockResolvedValueOnce({
1907
+ args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
1908
+ flags: {
1909
+ ignoreCache: mockIgnoreCacheFlag,
1910
+ autoConfirmAction: Promise.resolve(true),
1911
+ secrets: 'src/commands/__fixtures__/.secrets_file.env',
1912
+ },
1913
+ });
1914
+
1915
+ const runResult = CreateCommand.run();
1916
+
1917
+ await expect(runResult).rejects.toEqual(
1918
+ new Error('Unable to import secrets. Please check the file and try again.'),
1919
+ );
1920
+
1921
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`
1922
+ [
1923
+ [
1924
+ "Invalid file format. Please provide a YAML file (.yaml or .yml).",
1925
+ ],
1926
+ ]
1927
+ `);
1928
+
1929
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
1930
+ [
1931
+ [
1932
+ "Unable to import secrets. Please check the file and try again.",
1933
+ ],
1934
+ ]
1935
+ `);
1936
+ });
1937
+
1938
+ test('should successfully create a mesh if provided secrets file is valid', async () => {
1939
+ parseSpy.mockResolvedValueOnce({
1940
+ args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
1941
+ flags: {
1942
+ ignoreCache: mockIgnoreCacheFlag,
1943
+ autoConfirmAction: Promise.resolve(true),
1944
+ secrets: 'src/commands/__fixtures__/secrets_valid.yaml',
1945
+ },
1946
+ });
1947
+
1948
+ const runResult = await CreateCommand.run();
1949
+ expect(runResult).toMatchInlineSnapshot(`
1950
+ {
1951
+ "apiKey": "dummy_api_key",
1952
+ "mesh": {
1953
+ "meshConfig": {
1954
+ "sources": [
1955
+ {
1956
+ "handler": {
1957
+ "graphql": {
1958
+ "endpoint": "<gql_endpoint>",
1959
+ },
1960
+ },
1961
+ "name": "<api_name>",
1962
+ },
1963
+ ],
1964
+ },
1965
+ "meshId": "dummy_mesh_id",
1966
+ },
1967
+ "sdkList": [
1968
+ "dummy_service",
1969
+ ],
1970
+ }
1971
+ `);
1972
+ });
1973
+
1974
+ test('should return error if ran against windows platform with batch variables', async () => {
1975
+ platformSpy.mockReturnValue('win32');
1976
+ parseSpy.mockResolvedValueOnce({
1977
+ args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
1978
+ flags: {
1979
+ ignoreCache: mockIgnoreCacheFlag,
1980
+ autoConfirmAction: Promise.resolve(true),
1981
+ secrets: 'src/commands/__fixtures__/secrets_with_batch_variables.yaml',
1982
+ },
1983
+ });
1984
+
1985
+ const runResult = CreateCommand.run();
1986
+
1987
+ await expect(runResult).rejects.toEqual(
1988
+ new Error('Unable to import secrets. Please check the file and try again.'),
1989
+ );
1990
+
1991
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`
1992
+ [
1993
+ [
1994
+ "Batch variables are not supported in YAML files on Windows.",
1995
+ ],
1996
+ ]
1997
+ `);
1998
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
1999
+ [
2000
+ [
2001
+ "Unable to import secrets. Please check the file and try again.",
2002
+ ],
2003
+ ]
2004
+ `);
2005
+ });
2006
+
2007
+ test('should pass if ran against linux platform with batch variables', async () => {
2008
+ platformSpy.mockReturnValue('linux');
2009
+ parseSpy.mockResolvedValueOnce({
2010
+ args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
2011
+ flags: {
2012
+ ignoreCache: mockIgnoreCacheFlag,
2013
+ autoConfirmAction: Promise.resolve(true),
2014
+ secrets: 'src/commands/__fixtures__/secrets_with_batch_variables.yaml',
2015
+ },
2016
+ });
2017
+
2018
+ const runResult = await CreateCommand.run();
2019
+ expect(runResult).toMatchInlineSnapshot(`
2020
+ {
2021
+ "apiKey": "dummy_api_key",
2022
+ "mesh": {
2023
+ "meshConfig": {
2024
+ "sources": [
2025
+ {
2026
+ "handler": {
2027
+ "graphql": {
2028
+ "endpoint": "<gql_endpoint>",
2029
+ },
2030
+ },
2031
+ "name": "<api_name>",
2032
+ },
2033
+ ],
2034
+ },
2035
+ "meshId": "dummy_mesh_id",
2036
+ },
2037
+ "sdkList": [
2038
+ "dummy_service",
2039
+ ],
2040
+ }
2041
+ `);
2042
+ });
2043
+
2044
+ test('should pass if ran against darwin(macOS) platform with batch variables', async () => {
2045
+ platformSpy.mockReturnValue('darwin');
2046
+ parseSpy.mockResolvedValueOnce({
2047
+ args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
2048
+ flags: {
2049
+ ignoreCache: mockIgnoreCacheFlag,
2050
+ autoConfirmAction: Promise.resolve(true),
2051
+ secrets: 'src/commands/__fixtures__/secrets_with_batch_variables.yaml',
2052
+ },
2053
+ });
2054
+
2055
+ const runResult = await CreateCommand.run();
2056
+ expect(runResult).toMatchInlineSnapshot(`
2057
+ {
2058
+ "apiKey": "dummy_api_key",
2059
+ "mesh": {
2060
+ "meshConfig": {
2061
+ "sources": [
2062
+ {
2063
+ "handler": {
2064
+ "graphql": {
2065
+ "endpoint": "<gql_endpoint>",
2066
+ },
2067
+ },
2068
+ "name": "<api_name>",
2069
+ },
2070
+ ],
2071
+ },
2072
+ "meshId": "dummy_mesh_id",
2073
+ },
2074
+ "sdkList": [
2075
+ "dummy_service",
2076
+ ],
2077
+ }
2078
+ `);
2079
+ });
1834
2080
  });
@@ -18,6 +18,7 @@ const {
18
18
  promptConfirm,
19
19
  setUpTenantFiles,
20
20
  initSdk,
21
+ writeSecretsFile,
21
22
  } = require('../../../helpers');
22
23
  const { getMeshId, getMeshArtifact } = require('../../../lib/devConsole');
23
24
  require('@adobe-apimesh/mesh-builder');
@@ -35,12 +36,16 @@ jest.mock('../../../helpers', () => ({
35
36
  importFiles: jest.fn().mockResolvedValue(),
36
37
  promptConfirm: jest.fn().mockResolvedValue(true),
37
38
  setUpTenantFiles: jest.fn().mockResolvedValue(),
39
+ writeSecretsFile: jest.fn().mockResolvedValue(),
38
40
  }));
39
41
 
40
42
  jest.mock('../../../lib/devConsole', () => ({
41
43
  getMeshId: jest.fn().mockResolvedValue('mockMeshId'),
42
44
  getMeshArtifact: jest.fn().mockResolvedValue(),
43
45
  }));
46
+ jest.mock('chalk', () => ({
47
+ red: jest.fn(text => text), // Return the input text without any color formatting
48
+ }));
44
49
 
45
50
  jest.mock('@adobe-apimesh/mesh-builder', () => {
46
51
  return {
@@ -55,12 +60,15 @@ jest.mock('@adobe-apimesh/mesh-builder', () => {
55
60
  let logSpy = null;
56
61
  let errorLogSpy = null;
57
62
  let parseSpy = null;
63
+ let platformSpy = null;
58
64
 
59
65
  const originalEnv = {
60
66
  API_MESH_TIER: 'NON-TI',
61
67
  };
62
68
 
63
69
  const defaultPort = 5000;
70
+ const os = require('os');
71
+
64
72
  describe('run command tests', () => {
65
73
  beforeEach(() => {
66
74
  global.requestId = 'dummy_request_id';
@@ -68,10 +76,14 @@ describe('run command tests', () => {
68
76
  logSpy = jest.spyOn(RunCommand.prototype, 'log');
69
77
  errorLogSpy = jest.spyOn(RunCommand.prototype, 'error');
70
78
  parseSpy = jest.spyOn(RunCommand.prototype, 'parse');
79
+ platformSpy = jest.spyOn(os, 'platform');
71
80
  process.env = {
72
81
  ...originalEnv,
73
82
  };
74
83
  });
84
+ afterEach(() => {
85
+ platformSpy.mockRestore();
86
+ });
75
87
 
76
88
  test('snapshot run command description', () => {
77
89
  expect(RunCommand.description).toMatchInlineSnapshot(
@@ -121,6 +133,15 @@ describe('run command tests', () => {
121
133
  "parse": [Function],
122
134
  "type": "option",
123
135
  },
136
+ "secrets": {
137
+ "char": "s",
138
+ "default": false,
139
+ "description": "Path to secrets file",
140
+ "input": [],
141
+ "multiple": false,
142
+ "parse": [Function],
143
+ "type": "option",
144
+ },
124
145
  "select": {
125
146
  "allowNo": false,
126
147
  "default": false,
@@ -836,4 +857,109 @@ describe('run command tests', () => {
836
857
  );
837
858
  expect(setUpTenantFiles).toHaveBeenCalled();
838
859
  });
860
+
861
+ test('should return error for run command if mesh has placeholders and the provided secrets file is invalid', async () => {
862
+ parseSpy.mockResolvedValueOnce({
863
+ args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
864
+ flags: {
865
+ secrets: 'src/commands/__fixtures__/secrets_invalid.yaml',
866
+ },
867
+ });
868
+
869
+ const runResult = RunCommand.run();
870
+
871
+ await expect(runResult).rejects.toEqual(
872
+ new Error('Unable to import secrets. Please check the file and try again.'),
873
+ );
874
+ });
875
+
876
+ test('should return error for run command if mesh has placeholders and the provided secrets file is not yaml or yml', async () => {
877
+ parseSpy.mockResolvedValueOnce({
878
+ args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
879
+ flags: {
880
+ secrets: 'src/commands/__fixtures__/.secrets_file.env',
881
+ },
882
+ });
883
+
884
+ const runResult = RunCommand.run();
885
+
886
+ await expect(runResult).rejects.toEqual(
887
+ new Error('Unable to import secrets. Please check the file and try again.'),
888
+ );
889
+
890
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`
891
+ [
892
+ [
893
+ "Invalid file format. Please provide a YAML file (.yaml or .yml).",
894
+ ],
895
+ ]
896
+ `);
897
+ });
898
+
899
+ test('should successfully run the mesh if provided secrets file is valid', async () => {
900
+ parseSpy.mockResolvedValueOnce({
901
+ args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
902
+ flags: {
903
+ secrets: 'src/commands/__fixtures__/secrets_valid.yaml',
904
+ debug: false,
905
+ },
906
+ });
907
+
908
+ await RunCommand.run();
909
+ expect(writeSecretsFile).toHaveBeenCalled();
910
+ expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
911
+ });
912
+
913
+ test('should return error if ran with secrets against windows platform with batch variables', async () => {
914
+ platformSpy.mockReturnValue('win32');
915
+ parseSpy.mockResolvedValueOnce({
916
+ args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
917
+ flags: {
918
+ secrets: 'src/commands/__fixtures__/secrets_with_batch_variables.yaml',
919
+ },
920
+ });
921
+
922
+ const runResult = RunCommand.run();
923
+ await expect(runResult).rejects.toEqual(
924
+ new Error('Unable to import secrets. Please check the file and try again.'),
925
+ );
926
+
927
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`
928
+ [
929
+ [
930
+ "Batch variables are not supported in YAML files on Windows.",
931
+ ],
932
+ ]
933
+ `);
934
+ });
935
+
936
+ test('should pass if ran with secrets against linux platform with batch variables', async () => {
937
+ platformSpy.mockReturnValue('linux');
938
+ parseSpy.mockResolvedValueOnce({
939
+ args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
940
+ flags: {
941
+ secrets: 'src/commands/__fixtures__/secrets_with_batch_variables.yaml',
942
+ debug: false,
943
+ },
944
+ });
945
+
946
+ await RunCommand.run();
947
+ expect(writeSecretsFile).toHaveBeenCalled();
948
+ expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
949
+ });
950
+
951
+ test('should pass if ran with secrets against darwin(macOS) platform with batch variables', async () => {
952
+ platformSpy.mockReturnValue('darwin');
953
+ parseSpy.mockResolvedValueOnce({
954
+ args: { file: 'src/commands/__fixtures__/sample_secrets_mesh.json' },
955
+ flags: {
956
+ secrets: 'src/commands/__fixtures__/secrets_with_batch_variables.yaml',
957
+ debug: false,
958
+ },
959
+ });
960
+
961
+ await RunCommand.run();
962
+ expect(writeSecretsFile).toHaveBeenCalled();
963
+ expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
964
+ });
839
965
  });
@@ -19,9 +19,12 @@ const {
19
19
  jsonFlag,
20
20
  getFilesInMeshConfig,
21
21
  envFileFlag,
22
+ secretsFlag,
22
23
  checkPlaceholders,
23
24
  readFileContents,
24
25
  validateAndInterpolateMesh,
26
+ interpolateSecrets,
27
+ validateSecretsFile,
25
28
  } = require('../../utils');
26
29
  const { createMesh, getTenantFeatures } = require('../../lib/devConsole');
27
30
  const { buildEdgeMeshUrl, buildMeshUrl } = require('../../urlBuilder');
@@ -33,6 +36,7 @@ class CreateCommand extends Command {
33
36
  autoConfirmAction: autoConfirmActionFlag,
34
37
  json: jsonFlag,
35
38
  env: envFileFlag,
39
+ secrets: secretsFlag,
36
40
  };
37
41
 
38
42
  static enableJsonFlag = true;
@@ -53,6 +57,7 @@ class CreateCommand extends Command {
53
57
  const ignoreCache = await flags.ignoreCache;
54
58
  const autoConfirmAction = await flags.autoConfirmAction;
55
59
  const envFilePath = await flags.env;
60
+ const secretsFilePath = await flags.secrets;
56
61
  const {
57
62
  imsOrgId,
58
63
  imsOrgCode,
@@ -103,6 +108,17 @@ class CreateCommand extends Command {
103
108
  }
104
109
  }
105
110
 
111
+ // if secrets is present, include that in data.secrets
112
+ if (secretsFilePath) {
113
+ try {
114
+ await validateSecretsFile(secretsFilePath);
115
+ data.secrets = await interpolateSecrets(secretsFilePath, this);
116
+ } catch (err) {
117
+ this.log(err.message);
118
+ this.error('Unable to import secrets. Please check the file and try again.');
119
+ }
120
+ }
121
+
106
122
  let shouldContinue = true;
107
123
 
108
124
  if (!autoConfirmAction) {
@@ -15,11 +15,14 @@ const {
15
15
  debugFlag,
16
16
  selectFlag,
17
17
  envFileFlag,
18
+ secretsFlag,
18
19
  autoConfirmActionFlag,
19
20
  readFileContents,
20
21
  validateAndInterpolateMesh,
21
22
  checkPlaceholders,
22
23
  getFilesInMeshConfig,
24
+ validateSecretsFile,
25
+ interpolateSecrets,
23
26
  } = require('../../utils');
24
27
  const meshBuilder = require('@adobe-apimesh/mesh-builder');
25
28
  const fs = require('fs');
@@ -31,6 +34,7 @@ const {
31
34
  startGraphqlServer,
32
35
  importFiles,
33
36
  setUpTenantFiles,
37
+ writeSecretsFile,
34
38
  } = require('../../helpers');
35
39
  const logger = require('../../classes/logger');
36
40
  const { getMeshId, getMeshArtifact } = require('../../lib/devConsole');
@@ -55,6 +59,7 @@ class RunCommand extends Command {
55
59
  env: envFileFlag,
56
60
  autoConfirmAction: autoConfirmActionFlag,
57
61
  select: selectFlag,
62
+ secrets: secretsFlag,
58
63
  };
59
64
 
60
65
  static enableJsonFlag = true;
@@ -67,6 +72,7 @@ class RunCommand extends Command {
67
72
  logger.info(`RequestId: ${global.requestId}`);
68
73
 
69
74
  const { args, flags } = await this.parse(RunCommand);
75
+ const secretsFilePath = await flags.secrets;
70
76
 
71
77
  //Initialize the meshId based on
72
78
  let meshId = null;
@@ -165,6 +171,17 @@ class RunCommand extends Command {
165
171
  }
166
172
 
167
173
  let portNo;
174
+ //secrets management
175
+ if (secretsFilePath) {
176
+ try {
177
+ await validateSecretsFile(secretsFilePath);
178
+ const stringifiedSecrets = await interpolateSecrets(secretsFilePath, this);
179
+ await writeSecretsFile(stringifiedSecrets, meshId);
180
+ } catch (error) {
181
+ this.log(error.message);
182
+ this.error('Unable to import secrets. Please check the file and try again.');
183
+ }
184
+ }
168
185
 
169
186
  //To set the port number using the environment file
170
187
  if (process.env.PORT !== undefined) {
@@ -17,10 +17,13 @@ const {
17
17
  ignoreCacheFlag,
18
18
  autoConfirmActionFlag,
19
19
  envFileFlag,
20
+ secretsFlag,
20
21
  checkPlaceholders,
21
22
  readFileContents,
22
23
  validateAndInterpolateMesh,
23
24
  getFilesInMeshConfig,
25
+ interpolateSecrets,
26
+ validateSecretsFile,
24
27
  } = require('../../utils');
25
28
  const { getMeshId, updateMesh } = require('../../lib/devConsole');
26
29
 
@@ -30,6 +33,7 @@ class UpdateCommand extends Command {
30
33
  ignoreCache: ignoreCacheFlag,
31
34
  autoConfirmAction: autoConfirmActionFlag,
32
35
  env: envFileFlag,
36
+ secrets: secretsFlag,
33
37
  };
34
38
 
35
39
  async run() {
@@ -48,6 +52,7 @@ class UpdateCommand extends Command {
48
52
  const ignoreCache = await flags.ignoreCache;
49
53
  const autoConfirmAction = await flags.autoConfirmAction;
50
54
  const envFilePath = await flags.env;
55
+ const secretsFilePath = await flags.secrets;
51
56
 
52
57
  const { imsOrgId, projectId, workspaceId, orgName, projectName, workspaceName } = await initSdk(
53
58
  {
@@ -103,6 +108,17 @@ class UpdateCommand extends Command {
103
108
  }
104
109
  }
105
110
 
111
+ // if secrets is present, include that in data.secrets
112
+ if (secretsFilePath) {
113
+ try {
114
+ await validateSecretsFile(secretsFilePath);
115
+ data.secrets = await interpolateSecrets(secretsFilePath, this);
116
+ } catch (err) {
117
+ this.log(err.message);
118
+ this.error('Unable to import secrets. Please check the file and try again.');
119
+ }
120
+ }
121
+
106
122
  if (meshId) {
107
123
  let shouldContinue = true;
108
124
 
package/src/helpers.js CHANGED
@@ -860,6 +860,26 @@ async function setUpTenantFiles(meshId) {
860
860
  }
861
861
  }
862
862
 
863
+ /**
864
+ * This function is to create secrets.yaml in mesh-artifacts for respective meshId. Used for local development run command
865
+ *
866
+ * @secretsData secretsData
867
+ * @meshId meshId
868
+ */
869
+ async function writeSecretsFile(secretsData, meshId) {
870
+ if (!fs.existsSync(path.resolve(process.cwd(), 'mesh-artifact', meshId))) {
871
+ throw new Error(`Unexpected Error: issue creating secrets file.`);
872
+ }
873
+ try {
874
+ const secretsFileName = 'secrets.yaml';
875
+ const folderPath = path.join(process.cwd(), 'mesh-artifact', meshId);
876
+ const filePath = path.join(folderPath, secretsFileName);
877
+ fs.writeFileSync(filePath, secretsData);
878
+ } catch (error) {
879
+ throw new Error(error.message);
880
+ }
881
+ }
882
+
863
883
  module.exports = {
864
884
  objToString,
865
885
  promptInput,
@@ -876,4 +896,5 @@ module.exports = {
876
896
  updateFilesArray,
877
897
  startGraphqlServer,
878
898
  setUpTenantFiles,
899
+ writeSecretsFile,
879
900
  };
package/src/server.js CHANGED
@@ -13,6 +13,7 @@ const {
13
13
  removeRequestHeaders,
14
14
  prepSourceResponseHeaders,
15
15
  processResponseHeaders,
16
+ readSecretsFile,
16
17
  } = require('./serverUtils');
17
18
 
18
19
  let yogaServer = null;
@@ -64,6 +65,8 @@ const getYogaServer = async () => {
64
65
  const tenantMesh = await getBuiltMesh();
65
66
  const corsOptions = getCORSOptions();
66
67
 
68
+ const secrets = readSecretsFile(meshId);
69
+
67
70
  logger.info('Creating graphQL server');
68
71
 
69
72
  meshConfig = readMeshConfig(meshId);
@@ -73,6 +76,10 @@ const getYogaServer = async () => {
73
76
  graphqlEndpoint: `/graphql`,
74
77
  graphiql: true,
75
78
  cors: corsOptions,
79
+ context: initialContext => ({
80
+ ...initialContext,
81
+ secrets,
82
+ }),
76
83
  });
77
84
 
78
85
  return yogaServer;
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const LRUCache = require('lru-cache');
4
4
  const logger = require('./classes/logger');
5
+ const YAML = require('yaml');
5
6
 
6
7
  const headersCache = new LRUCache({
7
8
  max: parseInt(process.env.CACHE_OPT_MAX || '500', 10),
@@ -315,9 +316,29 @@ function ccDirectivesToString(directives) {
315
316
  return chStr.toString();
316
317
  }
317
318
 
319
+ /**
320
+ * Returns secrets content from artifacts
321
+ * @param meshId
322
+ * @returns
323
+ */
324
+ function readSecretsFile(meshId) {
325
+ let secrets = {};
326
+ try {
327
+ const filePath = path.resolve(process.cwd(), 'mesh-artifact', `${meshId}`, 'secrets.yaml');
328
+ if (fs.existsSync(filePath)) {
329
+ secrets = YAML.parse(fs.readFileSync(filePath, 'utf8'));
330
+ }
331
+ } catch (error) {
332
+ logger.error('Unexpected error: unable to locate secrets file in mesh artifacts.');
333
+ throw new Error(error.message);
334
+ }
335
+ return secrets;
336
+ }
337
+
318
338
  module.exports = {
319
339
  readMeshConfig,
320
340
  removeRequestHeaders,
321
341
  prepSourceResponseHeaders,
322
342
  processResponseHeaders,
343
+ readSecretsFile,
323
344
  };
package/src/utils.js CHANGED
@@ -5,6 +5,10 @@ const { Flags } = require('@oclif/core');
5
5
  const { readFile } = require('fs/promises');
6
6
  const { interpolateMesh } = require('./helpers');
7
7
  const dotenv = require('dotenv');
8
+ const YAML = require('yaml');
9
+ const parseEnv = require('envsub/js/envsub-parser');
10
+ const os = require('os');
11
+ const chalk = require('chalk');
8
12
 
9
13
  /**
10
14
  * @returns returns the root directory of the project
@@ -41,6 +45,12 @@ const envFileFlag = Flags.string({
41
45
  default: '.env',
42
46
  });
43
47
 
48
+ const secretsFlag = Flags.string({
49
+ char: 's',
50
+ description: 'Path to secrets file',
51
+ default: false,
52
+ });
53
+
44
54
  const portNoFlag = Flags.integer({
45
55
  char: 'p',
46
56
  description: 'Port number for the local dev server',
@@ -391,6 +401,97 @@ async function validateAndInterpolateMesh(inputMeshData, envFilePath, command) {
391
401
  }
392
402
  }
393
403
 
404
+ /**
405
+ * Validate secrets file
406
+ *
407
+ * @param secretsFile Validates that secrets file extension is in yaml
408
+ */
409
+ async function validateSecretsFile(secretsFile) {
410
+ try {
411
+ const validExtensions = ['.yaml', '.yml'];
412
+ const fileExtension = secretsFile.split('.').pop().toLowerCase();
413
+ if (!validExtensions.includes('.' + fileExtension)) {
414
+ throw new Error(
415
+ chalk.red('Invalid file format. Please provide a YAML file (.yaml or .yml).'),
416
+ );
417
+ }
418
+ } catch (error) {
419
+ logger.error(error.message);
420
+ throw new Error(error.message);
421
+ }
422
+ }
423
+
424
+ /**
425
+ * Read the secrets file, checks validation and interpolate mesh
426
+ *
427
+ * @param secretsFilePath Secrets file path
428
+ * @param command
429
+ */
430
+ async function interpolateSecrets(secretsFilePath, command) {
431
+ try {
432
+ const secretsContent = await readFileContents(secretsFilePath, command, 'secrets');
433
+
434
+ // Check if environment variables are used in the file content
435
+ if (os.platform() === 'win32' && /\$({)?[a-zA-Z_][a-zA-Z0-9_]*}?/.test(secretsContent)) {
436
+ throw new Error(chalk.red('Batch variables are not supported in YAML files on Windows.'));
437
+ }
438
+ const secrets = await parseSecrets(secretsContent);
439
+ return secrets;
440
+ } catch (err) {
441
+ logger.error(err.message);
442
+ throw new Error(err.message);
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Parse secrets YAML content.
448
+ *
449
+ * @param secretsFilePath Secrets file path
450
+ */
451
+ async function parseSecrets(secretsContent) {
452
+ try {
453
+ const envParserConfig = {
454
+ outputFile: null,
455
+ options: {
456
+ all: false,
457
+ diff: false,
458
+ protect: false,
459
+ syntax: 'dollar-both',
460
+ },
461
+ cli: false,
462
+ };
463
+ const compiledSecretsFileContent = parseEnv(secretsContent, envParserConfig);
464
+ const parsedSecrets = YAML.parse(compiledSecretsFileContent);
465
+ //check if secrets file is empty
466
+ if (!parsedSecrets) {
467
+ throw new Error(chalk.red('Invalid YAML file contents. Please verify and try again.'));
468
+ }
469
+ //check if parsedSecrets is string and not in k:v pair
470
+ if (typeof parsedSecrets === 'string') {
471
+ throw new Error(chalk.red('Please provide a valid YAML in key:value format.'));
472
+ }
473
+ const secretsYamlString = YAML.stringify(parsedSecrets);
474
+ return secretsYamlString; //TODO: here we will encrypt secrets and return.
475
+ } catch (err) {
476
+ throw new Error(chalk.red(getSecretsYamlParseError(err)));
477
+ }
478
+ }
479
+
480
+ /**
481
+ * This function returns user friendly errors that occurs while YAML.parse
482
+ *
483
+ * @param error errors from YAML.parse
484
+ */
485
+ function getSecretsYamlParseError(error) {
486
+ if (error.code === 'BAD_INDENT') {
487
+ return 'Invalid YAML - Bad Indentation: ' + error.message;
488
+ } else if (error.code === 'DUPLICATE_KEY') {
489
+ return 'Invalid YAML - Found Duplicate Keys: ' + error.message;
490
+ } else {
491
+ return 'Unexpected Error: ' + error.message;
492
+ }
493
+ }
494
+
394
495
  module.exports = {
395
496
  ignoreCacheFlag,
396
497
  autoConfirmActionFlag,
@@ -405,4 +506,7 @@ module.exports = {
405
506
  portNoFlag,
406
507
  debugFlag,
407
508
  selectFlag,
509
+ secretsFlag,
510
+ interpolateSecrets,
511
+ validateSecretsFile,
408
512
  };