@adobe/aio-cli-plugin-api-mesh 4.0.1-beta.1 → 4.1.0-beta.1

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":"4.0.1-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"},"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:log-get-bulk":{"id":"api-mesh:log-get-bulk","description":"Download all mesh logs for a selected time period.","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},"startTime":{"name":"startTime","type":"option","description":"Start time for the logs in UTC","required":true},"endTime":{"name":"endTime","type":"option","description":"End time for the logs in UTC","required":true},"filename":{"name":"filename","type":"option","description":"Path to the output file for logs","required":true}},"args":[]},"api-mesh:log-get":{"id":"api-mesh:log-get","description":"Get the Log of a given mesh by RayId","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":"rayId","description":"Fetch a single log by rayID","required":true}]},"api-mesh:log-list":{"id":"api-mesh:log-list","description":"Get recent logs of requests made to the API 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},"filename":{"name":"filename","type":"option","description":"Name of CSV file to export the recent logs to"}},"args":[]},"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"}]}}}
1
+ {"version":"4.1.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"},"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:log-get-bulk":{"id":"api-mesh:log-get-bulk","description":"Download all mesh logs for a selected time period.","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},"startTime":{"name":"startTime","type":"option","description":"Start time for the logs in UTC","required":true},"endTime":{"name":"endTime","type":"option","description":"End time for the logs in UTC","required":true},"filename":{"name":"filename","type":"option","description":"Path to the output file for logs","required":true}},"args":[]},"api-mesh:log-get":{"id":"api-mesh:log-get","description":"Get the Log of a given mesh by RayId","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":"rayId","description":"Fetch a single log by rayID","required":true}]},"api-mesh:log-list":{"id":"api-mesh:log-list","description":"Get recent logs of requests made to the API 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},"filename":{"name":"filename","type":"option","description":"Name of CSV file to export the recent logs to"}},"args":[]},"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": "4.0.1-beta.1",
3
+ "version": "4.1.0-beta.1",
4
4
  "description": "Adobe I/O CLI plugin to develop and manage API mesh sources",
5
5
  "keywords": [
6
6
  "oclif-plugin"
@@ -45,11 +45,13 @@
45
45
  "@adobe/aio-lib-ims": "^7.0.1",
46
46
  "@adobe/plugin-hooks": "0.3.2",
47
47
  "@adobe/plugin-on-fetch": "0.1.1",
48
+ "@adobe/plugin-source-headers": "^0.0.1",
48
49
  "@graphql-mesh/cli": "0.82.30",
49
50
  "@graphql-mesh/graphql": "0.34.13",
51
+ "@graphql-mesh/http": "^0.96.9",
50
52
  "@graphql-mesh/json-schema": "0.35.38",
51
53
  "@graphql-mesh/openapi": "0.33.39",
52
- "@graphql-mesh/plugin-http-details-extensions": "0.1.21",
54
+ "@graphql-mesh/plugin-http-details-extensions": "^0.103.4",
53
55
  "@graphql-mesh/runtime": "0.46.21",
54
56
  "@graphql-mesh/soap": "0.14.25",
55
57
  "@graphql-mesh/store": "0.9.20",
@@ -88,7 +90,7 @@
88
90
  "lru-cache": "^7.14.1",
89
91
  "node-clipboardy": "^1.0.3",
90
92
  "node-fetch": "2.6.1",
91
- "pino": "^7.9.2",
93
+ "pino": "^9.5.0",
92
94
  "pino-pretty": "^7.6.0",
93
95
  "pupa": "^3.1.0",
94
96
  "source-registry-storage-adapter": "github:devx-services/source-registry-storage-adapter#main",
@@ -98,6 +100,7 @@
98
100
  },
99
101
  "devDependencies": {
100
102
  "@babel/eslint-parser": "^7.15.8",
103
+ "@graphql-mesh/plugin-http-details-extensions": "0.1.21",
101
104
  "@oclif/dev-cli": "1.26.10",
102
105
  "@oclif/plugin-help": "2.2.3",
103
106
  "@trivago/prettier-plugin-sort-imports": "^3.1.1",
@@ -117,7 +120,8 @@
117
120
  "jest": "^29.2.2",
118
121
  "jest-junit": "^6.0.0",
119
122
  "prettier": "2.2.1",
120
- "stdout-stderr": "^0.1.9"
123
+ "stdout-stderr": "^0.1.9",
124
+ "wrangler": "^3.80.4"
121
125
  },
122
126
  "engines": {
123
127
  "node": "^16.13 || >=18.0.0",
@@ -12,7 +12,6 @@ governing permissions and limitations under the License.
12
12
 
13
13
  const RunCommand = require('../run');
14
14
  const {
15
- startGraphqlServer,
16
15
  interpolateMesh,
17
16
  importFiles,
18
17
  promptConfirm,
@@ -20,6 +19,7 @@ const {
20
19
  initSdk,
21
20
  writeSecretsFile,
22
21
  } = require('../../../helpers');
22
+ const { runServer } = require('../../../server');
23
23
  const { getMeshId, getMeshArtifact } = require('../../../lib/devConsole');
24
24
  require('@adobe-apimesh/mesh-builder');
25
25
 
@@ -31,7 +31,6 @@ jest.mock('../../../helpers', () => ({
31
31
  workspaceName: 'mockWorkspaceTitle',
32
32
  }),
33
33
  initRequestId: jest.fn().mockResolvedValue({}),
34
- startGraphqlServer: jest.fn().mockResolvedValue({}),
35
34
  interpolateMesh: jest.fn().mockResolvedValue({}),
36
35
  importFiles: jest.fn().mockResolvedValue(),
37
36
  promptConfirm: jest.fn().mockResolvedValue(true),
@@ -39,6 +38,10 @@ jest.mock('../../../helpers', () => ({
39
38
  writeSecretsFile: jest.fn().mockResolvedValue(),
40
39
  }));
41
40
 
41
+ jest.mock('../../../server', () => ({
42
+ runServer: jest.fn().mockResolvedValue(),
43
+ }));
44
+
42
45
  jest.mock('../../../lib/devConsole', () => ({
43
46
  getMeshId: jest.fn().mockResolvedValue('mockMeshId'),
44
47
  getMeshArtifact: jest.fn().mockResolvedValue(),
@@ -91,6 +94,10 @@ describe('run command tests', () => {
91
94
  platformSpy.mockRestore();
92
95
  });
93
96
 
97
+ beforeAll(() => {
98
+ jest.spyOn(RunCommand.prototype, 'copyMeshContent').mockImplementation(() => {});
99
+ });
100
+
94
101
  test('snapshot run command description', () => {
95
102
  expect(RunCommand.description).toMatchInlineSnapshot(
96
103
  `"Run a local development server that builds and compiles a mesh locally"`,
@@ -189,11 +196,7 @@ describe('run command tests', () => {
189
196
  parseSpy.mockResolvedValue(parseOutput);
190
197
 
191
198
  await RunCommand.run();
192
- expect(startGraphqlServer).toHaveBeenCalledWith(
193
- expect.anything(),
194
- parseOutput.flags.port,
195
- false,
196
- );
199
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), parseOutput.flags.port);
197
200
  });
198
201
 
199
202
  test('should use the port number provided in the .env file if there is no port', async () => {
@@ -210,7 +213,7 @@ describe('run command tests', () => {
210
213
  parseSpy.mockResolvedValue(parseOutput);
211
214
 
212
215
  await RunCommand.run();
213
- expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), process.env.PORT, false);
216
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), process.env.PORT);
214
217
  });
215
218
 
216
219
  test('should use the default port if port number is not provided explicitly', async () => {
@@ -226,7 +229,7 @@ describe('run command tests', () => {
226
229
  parseSpy.mockResolvedValue(parseOutput);
227
230
 
228
231
  await RunCommand.run();
229
- expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
232
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), defaultPort);
230
233
  });
231
234
 
232
235
  test('should return error for run command if the mesh has placeholders and env file provided using --env flag is not found', async () => {
@@ -393,7 +396,7 @@ describe('run command tests', () => {
393
396
  });
394
397
 
395
398
  await RunCommand.run();
396
- expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
399
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), defaultPort);
397
400
  });
398
401
 
399
402
  // file import tests
@@ -439,7 +442,7 @@ describe('run command tests', () => {
439
442
  });
440
443
 
441
444
  await RunCommand.run();
442
- expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
445
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), defaultPort);
443
446
  });
444
447
 
445
448
  test('should fail if the file name is more than 25 characters', async () => {
@@ -615,7 +618,7 @@ describe('run command tests', () => {
615
618
  });
616
619
 
617
620
  await RunCommand.run();
618
- expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
621
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), defaultPort);
619
622
  });
620
623
 
621
624
  test('should override if prompt returns Yes, if there is files array', async () => {
@@ -662,7 +665,7 @@ describe('run command tests', () => {
662
665
 
663
666
  await RunCommand.run();
664
667
 
665
- expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
668
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), defaultPort);
666
669
  });
667
670
 
668
671
  test('should pass for a fully-qualified meshConfig even if the file does not exist in fileSystem', async () => {
@@ -709,7 +712,7 @@ describe('run command tests', () => {
709
712
  });
710
713
 
711
714
  await RunCommand.run();
712
- expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
715
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), defaultPort);
713
716
  });
714
717
 
715
718
  test('should pass if the file is located in subdirectory of mesh directory', async () => {
@@ -755,7 +758,7 @@ describe('run command tests', () => {
755
758
 
756
759
  await RunCommand.run();
757
760
 
758
- expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
761
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), defaultPort);
759
762
  });
760
763
 
761
764
  test('should fail if the file is outside the workspace directory', async () => {
@@ -926,7 +929,7 @@ describe('run command tests', () => {
926
929
 
927
930
  await RunCommand.run();
928
931
  expect(writeSecretsFile).toHaveBeenCalled();
929
- expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
932
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), defaultPort);
930
933
  });
931
934
 
932
935
  test('should return error if ran with secrets against windows platform with batch variables', async () => {
@@ -964,7 +967,7 @@ describe('run command tests', () => {
964
967
 
965
968
  await RunCommand.run();
966
969
  expect(writeSecretsFile).toHaveBeenCalled();
967
- expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
970
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), defaultPort);
968
971
  });
969
972
 
970
973
  test('should pass if ran with secrets against darwin(macOS) platform with batch variables', async () => {
@@ -979,7 +982,7 @@ describe('run command tests', () => {
979
982
 
980
983
  await RunCommand.run();
981
984
  expect(writeSecretsFile).toHaveBeenCalled();
982
- expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
985
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), defaultPort);
983
986
  });
984
987
 
985
988
  test('should escape variables that are preceded by backslash symbol', async () => {
@@ -996,6 +999,6 @@ describe('run command tests', () => {
996
999
  'Home: rootPath\nHomeString: $HOME\nHomeWithSlash: \\rootPath\nHomeStringWithSlash: \\$HOME\n',
997
1000
  expect.anything(),
998
1001
  );
999
- expect(startGraphqlServer).toHaveBeenCalledWith(expect.anything(), defaultPort, false);
1002
+ expect(runServer).toHaveBeenCalledWith(expect.anything(), defaultPort);
1000
1003
  });
1001
1004
  });
@@ -26,12 +26,10 @@ const {
26
26
  } = require('../../utils');
27
27
  const meshBuilder = require('@adobe-apimesh/mesh-builder');
28
28
  const fs = require('fs');
29
- const UUID = require('../../uuid');
30
29
  const path = require('path');
31
30
  const {
32
31
  initSdk,
33
32
  initRequestId,
34
- startGraphqlServer,
35
33
  importFiles,
36
34
  setUpTenantFiles,
37
35
  writeSecretsFile,
@@ -39,6 +37,8 @@ const {
39
37
  const logger = require('../../classes/logger');
40
38
  const { getMeshId, getMeshArtifact } = require('../../lib/devConsole');
41
39
  require('dotenv').config();
40
+ const { runServer } = require('../../server');
41
+ const { fixPlugins } = require('../../fixPlugins');
42
42
 
43
43
  const { validateMesh, buildMesh, compileMesh } = meshBuilder.default;
44
44
 
@@ -163,13 +163,12 @@ class RunCommand extends Command {
163
163
  }
164
164
 
165
165
  //Generating unique mesh id
166
- meshId = UUID.newUuid().toString();
166
+ meshId = 'testMesh';
167
167
 
168
168
  await validateMesh(data.meshConfig);
169
169
  await buildMesh(meshId, data.meshConfig);
170
170
  await compileMesh(meshId);
171
171
  }
172
-
173
172
  let portNo;
174
173
  //secrets management
175
174
  if (secretsFilePath) {
@@ -183,6 +182,8 @@ class RunCommand extends Command {
183
182
  }
184
183
  }
185
184
 
185
+ await this.copyMeshContent(meshId);
186
+
186
187
  //To set the port number using the environment file
187
188
  if (process.env.PORT !== undefined) {
188
189
  if (isNaN(process.env.PORT) || !Number.isInteger(parseInt(process.env.PORT))) {
@@ -201,9 +202,8 @@ class RunCommand extends Command {
201
202
  if (!portNo) {
202
203
  portNo = 5000;
203
204
  }
204
-
205
- this.log(`Starting server on port : ${portNo}`);
206
- await startGraphqlServer(meshId, portNo, flags.debug);
205
+ meshId = '000000000000-0000-0000-0000-000000000000';
206
+ runServer(meshId, portNo);
207
207
  } else {
208
208
  throw new Error(
209
209
  '`aio api-mesh run` cannot be executed because there is no package.json file in the current directory. Use `aio api-mesh init` to set up a package.',
@@ -213,6 +213,31 @@ class RunCommand extends Command {
213
213
  this.error(error.message);
214
214
  }
215
215
  }
216
+
217
+ async copyMeshContent(meshId) {
218
+ // Remove mesh artifact directory if exists
219
+ if (fs.existsSync('.mesh')) {
220
+ fs.rmdirSync('.mesh', { recursive: true });
221
+ }
222
+ // Move built mesh artifact to expect directory
223
+ fs.renameSync(`mesh-artifact/${meshId}`, '.mesh');
224
+ // Remove tenant files directory if exists
225
+ if (fs.existsSync('tenantFiles')) {
226
+ fs.rmdirSync('tenantFiles', { recursive: true });
227
+ }
228
+ // Move built tenant files if exists
229
+ if (fs.existsSync('mesh-artifact/tenantFiles')) {
230
+ fs.cpSync('mesh-artifact/tenantFiles', '.mesh/tenantFiles', { recursive: true });
231
+ fs.renameSync('mesh-artifact/tenantFiles', 'tenantFiles');
232
+ }
233
+
234
+ await fixPlugins('.mesh/index.js');
235
+
236
+ if (fs.existsSync(`${__dirname}/../../../.mesh`)) {
237
+ fs.rmdirSync(`${__dirname}/../../../.mesh`, { recursive: true });
238
+ }
239
+ fs.cpSync('.mesh', `${__dirname}/../../../.mesh`, { recursive: true });
240
+ }
216
241
  }
217
242
 
218
243
  module.exports = RunCommand;
package/src/cors.js ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Get default CORS options.
3
+ * @param env Environment.
4
+ */
5
+ const getDefaultCorsOptions = env => {
6
+ return env.CORS_DEFAULT_URL
7
+ ? {
8
+ origin: env.CORS_DEFAULT_URL,
9
+ }
10
+ : {};
11
+ };
12
+
13
+ /**
14
+ * Get CORS options. Merges default CORS options with custom specific options if present in the mesh configuration.
15
+ * Custom CORS options overwrite the default options to ensure that specific requirements can be met without altering
16
+ * the default configuration.
17
+ * @param env Environment.
18
+ * @param meshConfig Mesh configuration.
19
+ */
20
+ const getCorsOptions = (env, meshConfig) => {
21
+ const defaultCorsOptions = getDefaultCorsOptions(env);
22
+ return {
23
+ ...defaultCorsOptions,
24
+ ...meshConfig.responseConfig?.CORS,
25
+ };
26
+ };
27
+
28
+ module.exports = { getCorsOptions };
@@ -0,0 +1,28 @@
1
+ /* eslint-disable no-console */
2
+ const fs = require('fs');
3
+ /**
4
+ * Modifies `index.js` of a mesh artifact to mutate plugin references for edge compatibility. Current forces
5
+ * `@graphql-mesh/plugin-http-details-extensions` to resolve to local fork instead.
6
+ */
7
+ async function fixPlugins(meshArtifactPath) {
8
+ try {
9
+ console.log('Modifying mesh artifact to fix plugins for edge compatibility.');
10
+ const data = fs.readFileSync(meshArtifactPath, 'utf8');
11
+ const updatedData = data.replace(
12
+ /@graphql-mesh\/plugin-http-details-extensions/g,
13
+ '../src/plugins/httpDetailsExtensions',
14
+ );
15
+ fs.writeFileSync(meshArtifactPath, updatedData, 'utf8');
16
+ } catch (err) {
17
+ console.error(err);
18
+ }
19
+ }
20
+
21
+ // Execute fixPlugins if run directly from CLI
22
+ if (require.main === module) {
23
+ fixPlugins();
24
+ }
25
+
26
+ module.exports = {
27
+ fixPlugins,
28
+ };
package/src/index.js ADDED
@@ -0,0 +1,44 @@
1
+ import { bindedlogger as logger } from '../utils/logger';
2
+ import { getRequestId } from '../utils/requestId';
3
+ import { ServedTier, addServedHeader } from './served';
4
+ import { buildServer } from './wranglerServer';
5
+
6
+ let server;
7
+
8
+ export default {
9
+ setServer(newServer) {
10
+ server = newServer;
11
+ },
12
+ /**
13
+ * Fetch.
14
+ * @param request Request.
15
+ * @param env Environment.
16
+ * @param ctx Event context.
17
+ */
18
+ async fetch(request, env, ctx) {
19
+ const requestId = getRequestId(request);
20
+ // Retrieve environment variables
21
+ const { MESH_ID: meshId, LOG_LEVEL: logLevel } = env;
22
+ const loggerInstance = logger({ logLevel, meshId, requestId });
23
+ const meshArtifacts = await import('../.mesh');
24
+ const rawMesh = await import('../.mesh/.meshrc.json');
25
+
26
+ if (!server) {
27
+ server = await this.buildAndCacheServer(env, loggerInstance, meshArtifacts, rawMesh);
28
+ }
29
+
30
+ loggerInstance.debug('WORKER HOT: Fetching via worker');
31
+ const response = await server.fetch(request, ctx);
32
+ addServedHeader(response, ServedTier.WORKER_HOT);
33
+ return response;
34
+ },
35
+ /**
36
+ * Build and cache mesh instance/server in global variable.
37
+ * @param env
38
+ * @param loggerInstance
39
+ */
40
+ async buildAndCacheServer(env, loggerInstance, meshArtifacts, meshConfig) {
41
+ server = await buildServer(loggerInstance, env, meshArtifacts, meshConfig);
42
+ return server;
43
+ },
44
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * CF-Connecting-IP provides the client IP address connecting to Cloudflare to the origin web server. This header will only be sent on the
3
+ * traffic from Cloudflare’s edge to your origin web server. Upstream requests will the Cloudflare Worker client IP address of
4
+ * `2a06:98c0:3600::103`.
5
+ * @see https://developers.cloudflare.com/fundamentals/reference/http-request-headers/#cf-connecting-ip
6
+ */
7
+ const CF_CONNECTING_IP_HEADER = 'cf-connecting-ip';
8
+
9
+ /**
10
+ * The X-Forwarded-For (XFF) request header is a de-facto standard header for identifying the originating IP address of a client connecting
11
+ * to a web server through a proxy server.
12
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
13
+ */
14
+ const X_FORWARDED_FOR_HEADER = 'x-forwarded-for';
15
+
16
+ /**
17
+ * Add `x-forwarded-for` header from request context to each fetch.
18
+ * @param context Request context.
19
+ * @param headers Fetch headers.
20
+ */
21
+ const addXForwardedForHeader = (context, headers) => {
22
+ // `cf-connecting-ip` header contains the original visitor's IP address
23
+ const connectingIp = context?.request.headers.get(CF_CONNECTING_IP_HEADER);
24
+ const xForwardedFor = context?.request.headers.get(X_FORWARDED_FOR_HEADER);
25
+ if (connectingIp) {
26
+ if (!xForwardedFor) {
27
+ // Construct new `x-forwarded-for` header if not present in original request
28
+ headers.set(X_FORWARDED_FOR_HEADER, connectingIp);
29
+ } else {
30
+ // Construct `x-forwarded-for` header using original header
31
+ headers.set(X_FORWARDED_FOR_HEADER, `${xForwardedFor}, ${connectingIp}`);
32
+ }
33
+ }
34
+ };
35
+
36
+ /**
37
+ * Adds compliance headers to source fetch requests.
38
+ */
39
+ function useComplianceHeaders() {
40
+ return {
41
+ onFetch({ context, options }) {
42
+ // Construct mutable headers from options passed to each fetch
43
+ const headers = new Headers(options.headers);
44
+ addXForwardedForHeader(context, headers);
45
+ options.headers = headers;
46
+ },
47
+ };
48
+ }
49
+
50
+ module.exports = {
51
+ useComplianceHeaders,
52
+ CF_CONNECTING_IP_HEADER,
53
+ X_FORWARDED_FOR_HEADER,
54
+ addXForwardedForHeader,
55
+ };
@@ -0,0 +1,2 @@
1
+ const { useComplianceHeaders } = require('./complianceHeaders');
2
+ module.exports = useComplianceHeaders;
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Uri Goldshtein
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Fork of https://github.com/ardatan/graphql-mesh/blob/%40graphql-mesh/plugin-http-details-extensions%400.1.21/packages/plugins/http-details-extensions/src/index.ts
3
+ * Version: 0.1.21
4
+ * TODO: Extract to a separate repository/artifact after approach has been validated.
5
+ */
6
+
7
+ /* eslint-disable */
8
+
9
+ const { isAsyncIterable } = require('@envelop/core');
10
+ const { getHeadersObj } = require('@graphql-mesh/utils');
11
+
12
+ function useIncludeHttpDetailsInExtensions(opts) {
13
+ if (!opts.if) {
14
+ return {};
15
+ }
16
+
17
+ const httpDetailsByContext = new WeakMap();
18
+
19
+ function getHttpDetailsByContext(context) {
20
+ let httpDetails = httpDetailsByContext.get(context);
21
+ if (!httpDetails) {
22
+ httpDetails = [];
23
+ httpDetailsByContext.set(context, httpDetails);
24
+ }
25
+ return httpDetails;
26
+ }
27
+
28
+ return {
29
+ onFetch({ url, context, info, options }) {
30
+ if (context != null) {
31
+ const requestTimestamp = Date.now();
32
+ return ({ response }) => {
33
+ const responseTimestamp = Date.now();
34
+ const responseTime = responseTimestamp - requestTimestamp;
35
+ const httpDetailsList = getHttpDetailsByContext(context);
36
+ const httpDetails = {
37
+ sourceName: (info)?.sourceName,
38
+ path: info?.path,
39
+ request: {
40
+ timestamp: requestTimestamp,
41
+ url,
42
+ method: options.method || 'GET',
43
+ headers: getHeadersObj(options.headers),
44
+ },
45
+ response: {
46
+ timestamp: responseTimestamp,
47
+ status: response.status,
48
+ statusText: response.statusText,
49
+ headers: getHeadersObj(response.headers),
50
+ // Added to interface to account for edge fetch implementation/behavior
51
+ cookies: response.headers.getSetCookie(),
52
+ },
53
+ responseTime,
54
+ };
55
+ httpDetailsList.push(httpDetails);
56
+ };
57
+ }
58
+ return undefined;
59
+ },
60
+ onExecute({ args: { contextValue } }) {
61
+ return {
62
+ onExecuteDone({ result, setResult }) {
63
+ if (!isAsyncIterable(result)) {
64
+ const httpDetailsList = httpDetailsByContext.get(contextValue);
65
+ if (httpDetailsList != null) {
66
+ setResult({
67
+ ...result,
68
+ extensions: {
69
+ ...result.extensions,
70
+ httpDetails: httpDetailsList,
71
+ },
72
+ });
73
+ }
74
+ }
75
+ },
76
+ };
77
+ },
78
+ };
79
+ }
80
+
81
+ module.exports = useIncludeHttpDetailsInExtensions;
package/src/secrets.js ADDED
@@ -0,0 +1,34 @@
1
+ // Parse the yaml secrets string from env to json object
2
+ function loadMeshSecrets(logger, secret) {
3
+ let parsedSecrets = {};
4
+
5
+ try {
6
+ // Replace escaped backslashes with a single backslash
7
+ secret = secret.replace(/\\"/g, '"');
8
+ parsedSecrets = JSON.parse(secret);
9
+ } catch (err) {
10
+ logger.error('Error parsing secrets.');
11
+ }
12
+
13
+ return parsedSecrets;
14
+ }
15
+
16
+ // Custom get secrets handler
17
+ const getSecretsHandler = {
18
+ get: function (target, prop, receiver) {
19
+ if (prop === 'toJSON') {
20
+ // Handle the toJSON case
21
+ return () => target;
22
+ }
23
+ if (prop in target) {
24
+ return Reflect.get(target, prop, receiver);
25
+ } else {
26
+ throw new Error(`The secret ${String(prop)} is not available.`);
27
+ }
28
+ },
29
+ set: function () {
30
+ throw new Error('Setting secrets is not allowed');
31
+ },
32
+ };
33
+
34
+ module.exports = { loadMeshSecrets, getSecretsHandler };
package/src/served.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Header to indicate which tier served the request.
3
+ */
4
+ const SERVE_TIER_HEADER = 'x-api-mesh-served';
5
+
6
+ /**
7
+ * Tiers that served the request.
8
+ */
9
+ const ServedTier = {
10
+ WORKER_HOT: 0,
11
+ };
12
+
13
+ /**
14
+ * Add the served header to the response. Requires mutable headers on the response object.
15
+ * @param response Response.
16
+ * @param servedTier Tier that served the request.
17
+ */
18
+ const addServedHeader = (response, servedTier) => {
19
+ response.headers.set(SERVE_TIER_HEADER, servedTier.toString());
20
+ };
21
+
22
+ module.exports = { ServedTier, addServedHeader };
package/src/server.js CHANGED
@@ -1,200 +1,32 @@
1
- // Resolve the path to 'fastify' and 'graphql-yoga' within the local 'node_modules'
2
- const fastifyPath = require.resolve('fastify', { paths: [process.cwd()] });
3
- const yogaPath = require.resolve('graphql-yoga', { paths: [process.cwd()] });
4
-
5
- // Load 'fastify' and 'graphql-yoga' using the resolved paths
6
- const fastify = require(fastifyPath);
7
- const { createYoga } = require(yogaPath);
8
- const logger = require('./classes/logger');
9
-
10
- //Load the functions from serverUtils.js
11
- const {
12
- readMeshConfig,
13
- removeRequestHeaders,
14
- prepSourceResponseHeaders,
15
- processResponseHeaders,
16
- readSecretsFile,
17
- } = require('./serverUtils');
18
-
19
- let yogaServer = null;
20
- let meshConfig;
21
-
22
- // catch unhandled promise rejections
23
- process.on('unhandledRejection', reason => {
24
- logger.error('Unhandled Rejection at:', reason.stack || reason);
25
- });
26
-
27
- // catch uncaught exceptions
28
- process.on('uncaughtException', err => {
29
- logger.error('Uncaught Exception thrown');
30
- logger.error(err.stack);
31
- process.exit(1);
32
- });
33
-
34
- // get meshId from command line arguments
35
- const meshId = process.argv[2];
36
-
37
- // get PORT number from command line arguments
38
- const portNo = parseInt(process.argv[3]);
39
-
40
- const getCORSOptions = () => {
41
- try {
42
- const currentWorkingDirectory = process.cwd();
43
- const meshConfigPath = `${currentWorkingDirectory}/mesh-artifact/${meshId}/.meshrc.json`;
44
-
45
- const meshConfig = require(meshConfigPath);
46
- const { responseConfig } = meshConfig;
47
- const { CORS } = responseConfig;
48
-
49
- return CORS;
50
- } catch (e) {
51
- return {};
52
- }
53
- };
54
-
55
- // Custom get secrets handler
56
- const getSecretsHandler = {
57
- get: function (target, prop, receiver) {
58
- if (prop === 'toJSON') {
59
- // Handle the toJSON case
60
- return () => target;
61
- }
62
- if (prop in target) {
63
- return Reflect.get(target, prop, receiver);
64
- } else {
65
- throw new Error(`The secret ${String(prop)} is not available.`);
66
- }
67
- },
68
- set: function () {
69
- throw new Error('Setting secrets is not allowed');
70
- },
1
+ const { spawn } = require('child_process');
2
+ const { readSecretsFile } = require('./serverUtils');
3
+
4
+ const runServer = (meshId, portNo) => {
5
+ const wranglerPath = `${__dirname}/../node_modules/.bin/wrangler`;
6
+ const indexFilePath = `${__dirname}/index.js`;
7
+ const filePath = '.mesh';
8
+ const secrets = readSecretsFile(filePath);
9
+ const commandArgs = [
10
+ 'dev',
11
+ indexFilePath,
12
+ '--var',
13
+ `MESH_ID:${meshId}`,
14
+ `Secret:${JSON.stringify(secrets)}`,
15
+ '--port',
16
+ portNo,
17
+ ];
18
+
19
+ const wrangler = spawn(wranglerPath, commandArgs, {
20
+ stdio: 'inherit',
21
+ });
22
+
23
+ wrangler.on('close', code => {
24
+ console.log(`wrangler dev process exited with code ${code}`);
25
+ });
26
+
27
+ wrangler.on('error', error => {
28
+ console.error(`Failed to start wrangler dev: ${error.message}`);
29
+ });
71
30
  };
72
31
 
73
- const getYogaServer = async () => {
74
- if (yogaServer) {
75
- return yogaServer;
76
- } else {
77
- const currentWorkingDirectory = process.cwd();
78
- const meshArtifactsPath = `${currentWorkingDirectory}/mesh-artifact/${meshId}`;
79
-
80
- const meshArtifacts = require(meshArtifactsPath);
81
- const { getBuiltMesh } = meshArtifacts;
82
-
83
- const tenantMesh = await getBuiltMesh();
84
- const corsOptions = getCORSOptions();
85
-
86
- const secrets = readSecretsFile(meshId);
87
-
88
- const secretsProxy = new Proxy(secrets, getSecretsHandler);
89
-
90
- logger.info('Creating graphQL server');
91
-
92
- meshConfig = readMeshConfig(meshId);
93
-
94
- yogaServer = createYoga({
95
- plugins: tenantMesh.plugins,
96
- graphqlEndpoint: `/graphql`,
97
- graphiql: true,
98
- maskedErrors: false,
99
- cors: corsOptions,
100
- context: initialContext => ({
101
- ...initialContext,
102
- secrets: secretsProxy,
103
- }),
104
- });
105
-
106
- return yogaServer;
107
- }
108
- };
109
-
110
- const app = fastify();
111
-
112
- app.route({
113
- method: ['GET', 'POST'],
114
- url: '/graphql',
115
- handler: async (req, res) => {
116
- logger.info('Request received: ', req.body);
117
-
118
- let body = null;
119
- let responseBody = null;
120
- let includeMetaData = false;
121
-
122
- if (req.headers['x-include-metadata'] && req.headers['x-include-metadata'].length > 0) {
123
- if (req.headers['x-include-metadata'].toLowerCase() === 'true') {
124
- includeMetaData = true;
125
- }
126
- }
127
-
128
- const response = await yogaServer.handleNodeRequest(req, {
129
- req,
130
- reply: res,
131
- });
132
-
133
- try {
134
- try {
135
- body = await response.text();
136
- if (body) {
137
- responseBody = JSON.parse(body);
138
- }
139
- } catch (err) {
140
- logger.error(`Error parsing response body: ${err}`);
141
- logger.error(response);
142
- throw new Error(`Error parsing response body: ${err}`);
143
- }
144
- //Set the value of includeHTTPDetails flag
145
-
146
- const includeHTTPDetails = !!meshConfig?.responseConfig?.includeHTTPDetails;
147
- const meshHTTPDetails = responseBody?.extensions?.httpDetails;
148
- logger.info('Mesh HTTP Details: ', meshHTTPDetails);
149
-
150
- /* the logic for handling mesh response headers using includeMetaData */
151
- prepSourceResponseHeaders(meshHTTPDetails, req.id);
152
- const responseHeaders = processResponseHeaders(meshId, req.id, includeMetaData, req.method);
153
-
154
- /** Adding the yoga response headers to the response */
155
- response.headers?.forEach((value, key) => {
156
- res.header(key, value);
157
- });
158
-
159
- // Delete the httpDetails extensions details if mesh owner has disabled those details in the config
160
- if (includeHTTPDetails !== true) {
161
- delete responseBody?.extensions?.httpDetails;
162
- }
163
-
164
- //make sure to remove the request headers from cache after the request is complete
165
- removeRequestHeaders(req.id);
166
- const fastifyResponseBody = JSON.stringify(responseBody);
167
- res.status(response.status).headers(responseHeaders).send(fastifyResponseBody);
168
- } catch (err) {
169
- logger.error(`Error parsing response body: ${err}`);
170
- //we have this fallback catch clause if someone wants to load the graphiql engine. This returns the default headers back
171
- response.headers?.forEach((value, key) => {
172
- res.header(key, value);
173
- });
174
- res.status(response.status);
175
- res.send(response.body);
176
- }
177
-
178
- return res;
179
- },
180
- });
181
-
182
- app.listen(
183
- {
184
- //set the port no of the server based on the input value
185
- port: portNo,
186
- },
187
- async err => {
188
- try {
189
- if (err) {
190
- throw new Error(`Server setup error: ${err.message}`);
191
- }
192
- yogaServer = await getYogaServer();
193
- } catch (error) {
194
- console.error(error);
195
- process.exit(1);
196
- }
197
-
198
- console.log(`Server is running on http://localhost:${portNo}/graphql`);
199
- },
200
- );
32
+ module.exports = { runServer };
@@ -318,13 +318,13 @@ function ccDirectivesToString(directives) {
318
318
 
319
319
  /**
320
320
  * Returns secrets content from artifacts
321
- * @param meshId
321
+ * @param meshPath
322
322
  * @returns
323
323
  */
324
- function readSecretsFile(meshId) {
324
+ function readSecretsFile(meshPath) {
325
325
  let secrets = {};
326
326
  try {
327
- const filePath = path.resolve(process.cwd(), 'mesh-artifact', `${meshId}`, 'secrets.yaml');
327
+ const filePath = path.resolve(process.cwd(), `${meshPath}`, 'secrets.yaml');
328
328
  if (fs.existsSync(filePath)) {
329
329
  secrets = YAML.parse(fs.readFileSync(filePath, 'utf8'));
330
330
  }
@@ -0,0 +1,75 @@
1
+ import { getMesh } from '@graphql-mesh/runtime';
2
+
3
+ const { getCorsOptions } = require('./cors');
4
+ const { createYoga } = require('graphql-yoga');
5
+ const { GraphQLError } = require('graphql/error');
6
+
7
+ const { loadMeshSecrets, getSecretsHandler } = require('./secrets');
8
+ const useComplianceHeaders = require('./plugins/complianceHeaders');
9
+ const UseHttpDetailsExtensions = require('./plugins/httpDetailsExtensions');
10
+ const useSourceHeaders = require('@adobe/plugin-source-headers');
11
+
12
+ let meshInstance$;
13
+
14
+ async function buildMeshInstance(meshArtifacts, meshConfig) {
15
+ const { getMeshOptions } = meshArtifacts;
16
+ const options = await getMeshOptions();
17
+
18
+ options.additionalEnvelopPlugins = (options.additionalEnvelopPlugins || []).concat(
19
+ useComplianceHeaders(),
20
+ UseHttpDetailsExtensions({
21
+ // Get the details of responseConfig.includeHTTPDetails and store in Cache
22
+ if: meshConfig.responseConfig?.includeHTTPDetails || false,
23
+ }),
24
+ useSourceHeaders(meshConfig),
25
+ );
26
+
27
+ return getMesh(options).then(mesh => {
28
+ const id = mesh.pubsub.subscribe('destroy', () => {
29
+ meshInstance$ = undefined;
30
+ mesh.pubsub.unsubscribe(id);
31
+ });
32
+ return mesh;
33
+ });
34
+ }
35
+
36
+ async function getBuiltMesh(meshArtifacts, meshConfig) {
37
+ if (meshInstance$ == null) {
38
+ meshInstance$ = buildMeshInstance(meshArtifacts, meshConfig);
39
+ }
40
+ return meshInstance$;
41
+ }
42
+
43
+ const buildServer = async (loggerInstance, env, meshArtifacts, meshConfig) => {
44
+ const { MESH_ID: meshId, Secret: secret } = env;
45
+ const tenantMesh = await getBuiltMesh(meshArtifacts, meshConfig);
46
+ const meshSecrets = loadMeshSecrets(loggerInstance, secret);
47
+ return await buildYogaServer(env, tenantMesh, meshId, meshConfig, meshSecrets);
48
+ };
49
+
50
+ async function buildYogaServer(env, tenantMesh, meshId, meshConfig, meshSecrets) {
51
+ const secretsProxy = new Proxy(meshSecrets, getSecretsHandler);
52
+ return createYoga({
53
+ plugins: tenantMesh.plugins,
54
+ graphqlEndpoint: `/api/${meshId}/graphql`,
55
+ cors: getCorsOptions(env, meshConfig),
56
+ context: initialContext => ({
57
+ ...initialContext,
58
+ secrets: secretsProxy,
59
+ }),
60
+ maskedErrors: {
61
+ maskError: maskError,
62
+ },
63
+ logging: 'debug',
64
+ });
65
+ }
66
+
67
+ const maskError = error => {
68
+ if (error instanceof GraphQLError && error.extensions?.http?.headers) {
69
+ delete error.extensions.http.headers;
70
+ }
71
+
72
+ return error;
73
+ };
74
+
75
+ module.exports = { buildServer };