Repository: ProjectEvergreen/greenwood (js)

Issue:
expose Greenwood configuration as data
## Type of Change
- [x] New Feature Request
- [ ] Documentation / Website
- [ ] Improvement / Suggestion
- [ ] Bug
- [ ] Other (please clarify below)

## Summary
As part of #115 , with the graph being exposed through GraphQL / `@data`, doing the same for _greenwood.config.js_ would enable a better experience for actually doing this like [`<meta>` tags](https://github.com/ProjectEvergreen/greenwood/issues/125) and [`<title>`](https://github.com/ProjectEvergreen/greenwood/issues/128) tags.

## Details
In addition to creating `typeDefs` and `resolvers`, this will also require being able to extend _schema.js_ and support merging queries.

From there, if possible, implement #125 and #128.

Pull request description:
Enhancement/issue 270 greenwood configuration as data
<!--
## Submitting a Pull Request
We love contributions and appreciate any help you can offer!
-->

## Related Issue
resolves #270 

## Summary of Changes
1. Create schema (typeDefs, resolvers) and Query for Greenwood config
1. Merge Config and Graph schemas
1. Extract `compilation.config` in data server (GraphQL)
1. Created Unit tests
1. Added documentation
1. Deleted Hello example query

<img width="1631" alt="Screen Shot 2020-03-03 at 11 33 46 PM" src="https://user-images.githubusercontent.com/895923/75846193-8a567700-5da9-11ea-915c-320db202d493.png">


Patch:
diff --git a/packages/cli/src/data/queries/config.gql b/packages/cli/src/data/queries/config.gql
new file mode 100644
index 00000000..95592cea
--- /dev/null
+++ b/packages/cli/src/data/queries/config.gql
@@ -0,0 +1,18 @@
+query {
+  config {
+  	devServer {
+      port,
+      host
+    },
+    meta {
+      name,
+      rel,
+      content,
+      property,
+      value
+    },
+    publicPath,
+    title,
+    workspace
+  }
+}
\ No newline at end of file
diff --git a/packages/cli/src/data/queries/hello.gql b/packages/cli/src/data/queries/hello.gql
deleted file mode 100644
index 389bbe48..00000000
--- a/packages/cli/src/data/queries/hello.gql
+++ /dev/null
@@ -1,3 +0,0 @@
-query {
-  hello
-}
\ No newline at end of file
diff --git a/packages/cli/src/data/schema/config.js b/packages/cli/src/data/schema/config.js
new file mode 100644
index 00000000..c6fffe2b
--- /dev/null
+++ b/packages/cli/src/data/schema/config.js
@@ -0,0 +1,44 @@
+const { gql } = require('apollo-server');
+
+const getConfiguration = async (root, query, context) => {
+  return context.config;
+};
+
+// https://www.greenwoodjs.io/docs/configuration
+const configTypeDefs = gql`
+  type DevServer {
+    port: Int,
+    host: String
+  }
+
+  type Meta {
+    name: String,
+    value: String,
+    content: String,
+    rel: String,
+    property: String
+  }
+
+  type Config {
+    devServer: DevServer,
+    meta: [Meta],
+    publicPath: String,
+    title: String,
+    workspace: String
+  }
+
+  extend type Query {
+    config: Config
+  }
+`;
+
+const configResolvers = {
+  Query: {
+    config: getConfiguration
+  }
+};
+
+module.exports = {
+  configTypeDefs,
+  configResolvers
+};
\ No newline at end of file
diff --git a/packages/cli/src/data/schema/hello.js b/packages/cli/src/data/schema/hello.js
deleted file mode 100644
index df08fa39..00000000
--- a/packages/cli/src/data/schema/hello.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const { gql } = require('apollo-server');
-
-const helloTypeDefs = gql`
-  type HelloQuery {
-    hello: String
-  }
-`;
-
-const helloResolvers = {
-  HelloQuery: {
-    hello: () => 'Hello world!'
-  }
-};
-
-module.exports = {
-  helloTypeDefs,
-  helloResolvers
-};
\ No newline at end of file
diff --git a/packages/cli/src/data/schema/schema.js b/packages/cli/src/data/schema/schema.js
index 15cd4579..10f7c79a 100644
--- a/packages/cli/src/data/schema/schema.js
+++ b/packages/cli/src/data/schema/schema.js
@@ -1,17 +1,22 @@
-// TODO merging resolvers not actually working, resolve as part of #21 or #270
 const { makeExecutableSchema } = require('apollo-server-express');
-const { helloTypeDefs, helloResolvers } = require('./hello');
+const { configTypeDefs, configResolvers } = require('./config');
 const { graphTypeDefs, graphResolvers } = require('./graph');
 
+const mergedResolvers = Object.assign({}, {
+  Query: {
+    ...configResolvers.Query,
+    ...graphResolvers.Query
+  }
+});
+
 const schema = makeExecutableSchema({
   typeDefs: [
-    graphTypeDefs,
-    helloTypeDefs
+    configTypeDefs,
+    graphTypeDefs
   ],
-  resolvers: Object.assign({}, 
-    graphResolvers,
-    helloResolvers
-  )
+  resolvers: [
+    mergedResolvers
+  ]
 });
 
 module.exports = schema;
\ No newline at end of file
diff --git a/packages/cli/src/data/server.js b/packages/cli/src/data/server.js
index 1ab89820..66209250 100644
--- a/packages/cli/src/data/server.js
+++ b/packages/cli/src/data/server.js
@@ -3,7 +3,7 @@ const schema = require('./schema/schema');
 const createCache = require('./cache');
 
 module.exports = (compilation) => {
-  const { graph, context } = compilation;
+  const { config, graph, context } = compilation;
 
   // Create schema
   const server = new ApolloServer({
@@ -22,6 +22,7 @@ module.exports = (compilation) => {
       }
       
       return {
+        config,
         graph 
       };
     }
diff --git a/www/components/shelf/shelf.js b/www/components/shelf/shelf.js
index 23c81498..f6bdbbab 100644
--- a/www/components/shelf/shelf.js
+++ b/www/components/shelf/shelf.js
@@ -67,6 +67,7 @@ class Shelf extends LitElement {
       console.log('response from the shelf (data.children)', response.data.children);
 
       this.shelfList = list;
+      console.log('shelf list', this.shelfList);
     }
   }
 
diff --git a/www/pages/docs/data.md b/www/pages/docs/data.md
index 5b7b5b17..4a2d7a86 100644
--- a/www/pages/docs/data.md
+++ b/www/pages/docs/data.md
@@ -34,7 +34,8 @@ Greenwood exposes a [GraphQL](https://graphql.org/) + [Apollo](https://www.apoll
 ![graphql-playground](/assets/graphql-playground.png)
 
 #### Schema
-To kick things off, let's review what is availalble to you.  Currently, the main "API" is just a list of all pages in your _pages/_ directory, represented as a `Page` [type defintion](https://graphql.org/graphql-js/basic-types/).   This is called Greenwood's `graph`.
+To kick things off, let's review what is availalble to you.  Currently, the main "API" is just a list of all pages in your _pages/_ directory, represented as a `Page` [type definition](https://graphql.org/graphql-js/basic-types/).   This is called Greenwood's `graph`.
+
 
 This is what the schema looks like:
 ```render javascript
@@ -65,7 +66,7 @@ Below are the queries available:
 ##### Graph
 The Graph query returns an array of all pages.
 
-###### Defintion
+###### Definition
 ```render javascript
 query {
   graph {
@@ -125,7 +126,7 @@ This will return the full `graph` of all pages as an array
 ##### Navigation
 The Navigation query returns an array of Page "like" objects, representing the top most root pages of your project.
 
-###### Defintion
+###### Definition
 ```render javascript
 query {
   navigation {
@@ -171,7 +172,7 @@ This will return the full `graph` of all top level routes as a Page array
 ##### Children
 The Children query returns an array of all pages below a given top level route.
 
-###### Defintion
+###### Definition
 ```render javascript
 query {
   children {
@@ -231,6 +232,69 @@ This will return the full `graph` of all pages as an array that are under a give
 ]
 ```
 
+##### Config
+The Config query returns the configuration values from your _greenwood.config.js_.  Useful for populating tags like `<title>` and `<meta>`.
+
+###### Definition
+```render javascript
+query {
+  config {
+  	devServer {
+      port,
+      host
+    },
+    meta {
+      name,
+      rel,
+      content,
+      property,
+      value
+    },
+    publicPath,
+    title,
+    workspace
+  }
+}
+```
+
+###### Usage
+`import` the query in your component
+```render javascript
+import client from '@greenwood/cli/data/client';
+import ConfigQuery from '@greenwood/cli/data/queries/config';
+
+.
+.
+.
+
+async connectedCallback() {
+  super.connectedCallback();
+  const response = await client.query({
+    query: GraphQuery
+  });
+
+  this.meta = response.data.config.meta;
+}
+```
+
+###### Response
+This will return an object of youf _greenwood.config.js_ as an object.  Example:
+```render javascript
+{
+  devServer: {
+    port: 1984,
+    host: 'localhost'
+  },
+  meta: [
+    { name: 'twitter:site', content: '@PrjEvergreen' },
+    { rel: 'icon', href: '/assets/favicon.ico' }
+  ],
+  publicPath: '/some-dir',
+  title: 'My App',
+  workspace: 'src'
+}
+```
+
 ##### Custom
 You can of course come up with your own as needed!  Greenwood provides the [`gql-tag`](https://github.com/apollographql/graphql-tag) module and will also resolve _.gql_ or _.graphql_ file extensions!
 
diff --git a/www/templates/home-template.js b/www/templates/home-template.js
index 2d0979e7..59500515 100644
--- a/www/templates/home-template.js
+++ b/www/templates/home-template.js
@@ -1,4 +1,6 @@
 import { html, LitElement } from 'lit-element';
+import client from '@greenwood/cli/data/client';
+import ConfigQuery from '@greenwood/cli/data/queries/config';
 import '../components/banner/banner';
 import '../components/card/card';
 import '../components/header/header';
@@ -13,6 +15,17 @@ METAIMPORT;
 METADATA;
 
 class HomeTemplate extends LitElement {
+  
+  async connectedCallback() {
+    super.connectedCallback();
+
+    const response = await client.query({
+      query: ConfigQuery
+    });
+
+    console.log('config', response.data.config);
+  }
+
   render() {
     return html`
       <style>
@@ -23,11 +36,11 @@ class HomeTemplate extends LitElement {
         <eve-header></eve-header>
         <eve-banner></eve-banner>
         <div class='content-wrapper'>
-        <eve-container fluid>
-          <div class='page-template content single-column'>
-            <entry></entry>
-          </div>
-        </eve-container>
+          <eve-container fluid>
+            <div class='page-template content single-column'>
+              <entry></entry>
+            </div>
+          </eve-container>
         </div>
         <eve-footer></eve-footer>
       </div>
diff --git a/www/templates/page-template.js b/www/templates/page-template.js
index 8378df02..428f8b3d 100644
--- a/www/templates/page-template.js
+++ b/www/templates/page-template.js
@@ -16,9 +16,6 @@ class PageTemplate extends LitElement {
 
   static get properties() {
     return {
-      shelfList: {
-        type: Array
-      },
       route: {
         type: String
       }


Test patch:
diff --git a/packages/cli/test/cases/build.data.graph/build.data.graph.spec.js b/packages/cli/test/cases/build.data.graph/build.data.graph.spec.js
index 4e4ec88d..25a37f89 100644
--- a/packages/cli/test/cases/build.data.graph/build.data.graph.spec.js
+++ b/packages/cli/test/cases/build.data.graph/build.data.graph.spec.js
@@ -78,6 +78,7 @@ describe('Build Greenwood With: ', function() {
         const apolloScriptTags = Array.prototype.slice.call(scriptTags).filter(script => {
           return script.getAttribute('data-state') === 'apollo';
         });
+
         const innerHTML = apolloScriptTags[0].innerHTML;
 
         expect(apolloScriptTags.length).to.equal(1);
diff --git a/packages/cli/test/unit/data/mocks/config.js b/packages/cli/test/unit/data/mocks/config.js
new file mode 100644
index 00000000..c8efdbfa
--- /dev/null
+++ b/packages/cli/test/unit/data/mocks/config.js
@@ -0,0 +1,17 @@
+const MOCK_CONFIG = {
+  config: {
+    devServer: {
+      port: 1984,
+      host: 'localhost'
+    },
+    meta: [
+      { name: 'twitter:site', content: '@PrjEvergreen' },
+      { rel: 'icon', href: '/assets/favicon.ico' }
+    ],
+    publicPath: '/some-dir',
+    title: 'My App',
+    workspace: 'src'
+  }
+};
+
+module.exports = MOCK_CONFIG;
\ No newline at end of file
diff --git a/packages/cli/test/unit/data/mocks/graph.js b/packages/cli/test/unit/data/mocks/graph.js
index 58224a4d..ac330734 100644
--- a/packages/cli/test/unit/data/mocks/graph.js
+++ b/packages/cli/test/unit/data/mocks/graph.js
@@ -1,4 +1,4 @@
-const GRAPH_MOCK = { 
+const MOCK_GRAPH = {
   graph: [ 
     { mdFile: './index.md',
       label: '37b24fd9f163e7b',
@@ -276,4 +276,4 @@ const GRAPH_MOCK = {
   ]
 };
 
-module.exports = GRAPH_MOCK;
\ No newline at end of file
+module.exports = MOCK_GRAPH;
\ No newline at end of file
diff --git a/packages/cli/test/unit/data/schema/config.spec.js b/packages/cli/test/unit/data/schema/config.spec.js
new file mode 100644
index 00000000..73897a66
--- /dev/null
+++ b/packages/cli/test/unit/data/schema/config.spec.js
@@ -0,0 +1,83 @@
+const expect = require('chai').expect;
+const MOCK_CONFIG = require('../mocks/config');
+const { configResolvers } = require('../../../../src/data/schema/config');
+
+describe('Unit Test: Data', function() {
+
+  describe('Schema', function() {
+
+    describe('Config', function() {
+      let config = {};
+
+      before(async function() {
+        config = await configResolvers.Query.config(undefined, {}, MOCK_CONFIG);
+      });
+
+      describe('Dev Server', function() {
+        const { devServer } = MOCK_CONFIG.config;
+
+        it('should have the expected devServer.port', function() {
+          expect(config.devServer.port).to.equal(devServer.port);
+        });
+  
+        it('should have the expected devServer.host', function() {  
+          expect(config.devServer.host).to.equal(devServer.host);
+        });
+      });
+
+      describe('Meta', function() {
+        const { meta } = MOCK_CONFIG.config;
+
+        it('should have the expected name meta in the first indexx', function() {
+          const nameMeta = config.meta[0];
+
+          expect(nameMeta.name).to.equal(meta[0].name);
+        });
+  
+        it('should have the expected devServer.host', function() {  
+          const nameMeta = config.meta[0];
+
+          expect(nameMeta.content).to.equal(meta[0].content);
+        });
+
+        it('should have the expected rel meta in the second index', function() {
+          const relMeta = config.meta[1];
+
+          expect(relMeta.rel).to.equal(meta[1].rel);
+        });
+  
+        it('should have the expected devServer.host', function() {  
+          const relMeta = config.meta[1];
+
+          expect(relMeta.content).to.equal(meta[1].content);
+        });
+      });
+
+      describe('Public Path', function() {
+        const { publicPath } = MOCK_CONFIG.config;
+
+        it('should have the expected publicPath', function() {  
+          expect(publicPath).to.equal(config.publicPath);
+        });
+      });
+
+      describe('Title', function() {
+        const { title } = MOCK_CONFIG.config;
+
+        it('should have the expected title', function() {  
+          expect(title).to.equal(config.title);
+        });
+      });
+
+      describe('Workspace', function() {
+        const { workspace } = MOCK_CONFIG.config;
+
+        it('should have the expected title', function() {  
+          expect(workspace).to.equal(config.workspace);
+        });
+      });
+
+    });
+
+  });
+});
\ No newline at end of file
diff --git a/packages/cli/test/unit/data/schema/graph.spec.js b/packages/cli/test/unit/data/schema/graph.spec.js
index 239daaa7..7fb0f4eb 100644
--- a/packages/cli/test/unit/data/schema/graph.spec.js
+++ b/packages/cli/test/unit/data/schema/graph.spec.js
@@ -1,21 +1,5 @@
-/*
- * Use Case
- * Run Greenwood with empty config object and default workspace.
- *
- * Uaer Result
- * Should generate a bare bones Greenwood build.  (same as build.default.spec.js)
- *
- * User Command
- * greenwood build
- *
- * User Config
- * {}
- *
- * User Workspace
- * Greenwood default (src/)
- */
 const expect = require('chai').expect;
-const GRAPH_MOCK = require('../mocks/graph');
+const MOCK_GRAPH = require('../mocks/graph');
 const { graphResolvers } = require('../../../../src/data/schema/graph');
 
 describe('Unit Test: Data', function() {
@@ -28,7 +12,7 @@ describe('Unit Test: Data', function() {
         let pages = [];
 
         before(async function() {
-          pages = await graphResolvers.Query.graph(undefined, {}, GRAPH_MOCK);
+          pages = await graphResolvers.Query.graph(undefined, {}, MOCK_GRAPH);
         });
 
         it('should have 27 pages', function() {
@@ -51,7 +35,7 @@ describe('Unit Test: Data', function() {
         let navigation = [];
 
         before(async function() {
-          navigation = await graphResolvers.Query.navigation(undefined, {}, GRAPH_MOCK);
+          navigation = await graphResolvers.Query.navigation(undefined, {}, MOCK_GRAPH);
         });
 
         it('should have 4 children', function() {
@@ -91,7 +75,7 @@ describe('Unit Test: Data', function() {
         let children = [];
 
         before(async function() {
-          children = await graphResolvers.Query.children(undefined, { parent: 'getting-started' }, GRAPH_MOCK);
+          children = await graphResolvers.Query.children(undefined, { parent: 'getting-started' }, MOCK_GRAPH);
         });
 
         it('should have 8 children', function() {