Repository: ProjectEvergreen/greenwood (js)

Issue:
top level page or page template `<meta>` tags not showing up in output HTML
## Type of Change
- [ ] New Feature Request
- [ ] Documentation / Website
- [ ] Improvement / Suggestion
- [x] Bug
- [ ] Other (please clarify below)

## Summary
If you add custom `<meta>` to your top level page or page template, ex.
```html
<!DOCTYPE html>
<html lang="en" prefix="og:http://ogp.me/ns#">

  <head>
    <title>Top Level Test</title>
    <meta property="og:description" content="My custom meta content." />
  </head>
  
  <body>
    <h1>Hello from the home page!!!!</h1>
  </body>

</html>
```

It wont show up in the final _index.html_.

## Details
This seems like is an oversight on my part, as in our [**standard-plugin-html**](https://github.com/ProjectEvergreen/greenwood/blob/v0.12.0/packages/cli/src/plugins/resource/plugin-standard-html.js#L64) there is no handling for `<meta>` tags.  🤦 

Pull request description:
fix meta tag merging and update and add specs
<!--
## Submitting a Pull Request
We love contributions and appreciate any help you can offer!
-->

## Related Issue
resolves #595

## Summary of Changes
1. Add missing support for merging `<meta>` tags when building pages
1. Refactoring page templating _not_ returning an app template
1. Updated and added specs

Patch:
diff --git a/packages/cli/src/plugins/resource/plugin-standard-html.js b/packages/cli/src/plugins/resource/plugin-standard-html.js
index 0ffe32f4..349252bf 100644
--- a/packages/cli/src/plugins/resource/plugin-standard-html.js
+++ b/packages/cli/src/plugins/resource/plugin-standard-html.js
@@ -34,9 +34,6 @@ const getPageTemplate = (barePath, workspace, template) => {
   } else if (fs.existsSync(`${templatesDir}/page.html`)) {
     // else look for default page template
     contents = fs.readFileSync(`${templatesDir}/page.html`, 'utf-8');
-  } else if (fs.existsSync(`${templatesDir}/app.html`)) {
-    // fallback to just using their app template
-    contents = fs.readFileSync(`${templatesDir}/app.html`, 'utf-8');
   } else {
     // fallback to using Greenwood's stock page template
     contents = fs.readFileSync(path.join(__dirname, '../../templates/page.html'), 'utf-8');
@@ -64,10 +61,11 @@ const getAppTemplate = (contents, userWorkspace, customImports = []) => {
   const body = root.querySelector('body').innerHTML;
   const headScripts = root.querySelectorAll('head script');
   const headLinks = root.querySelectorAll('head link');
+  const headMeta = root.querySelectorAll('head meta');
   const headStyles = root.querySelectorAll('head style');
 
   appTemplateContents = appTemplateContents.replace(/<page-outlet><\/page-outlet>/, body);
-  
+
   headScripts.forEach((script) => {
     const matchNeedle = '</script>';
     const matchPos = appTemplateContents.lastIndexOf(matchNeedle);
@@ -139,7 +137,14 @@ const getAppTemplate = (contents, userWorkspace, customImports = []) => {
       }
     }
   });
-  
+
+  headMeta.forEach((meta) => {
+    appTemplateContents = appTemplateContents.replace('<head>', `
+      <head>
+        <meta ${meta.rawAttrs}/>
+    `);
+  });
+
   customImports.forEach((customImport) => {
     const extension = path.extname(customImport);
 


Test patch:
diff --git a/packages/cli/test/cases/build.config.mode-spa/build.config.mode-spa.spec.js b/packages/cli/test/cases/build.config.mode-spa/build.config.mode-spa.spec.js
index 2ebbfe9c..70772e6b 100644
--- a/packages/cli/test/cases/build.config.mode-spa/build.config.mode-spa.spec.js
+++ b/packages/cli/test/cases/build.config.mode-spa/build.config.mode-spa.spec.js
@@ -200,6 +200,14 @@ describe('Build Greenwood With: ', function() {
         expect(jsFiles.length).to.be.equal(5);
       });
 
+      it('should have custom <meta> tag in the <head>', function() {
+        const customMeta = Array.from(dom.window.document.querySelectorAll('head > meta'))
+          .filter(meta => meta.getAttribute('property') === 'og:description');
+
+        expect(customMeta.length).to.be.equal(1);
+        expect(customMeta[0].getAttribute('content')).to.be.equal('My custom meta content.');
+      });
+
       it('should only have two script tags in the <head>', function() {
         expect(htmlFiles.length).to.be.equal(1);
       });
diff --git a/packages/cli/test/cases/build.config.mode-spa/src/index.html b/packages/cli/test/cases/build.config.mode-spa/src/index.html
index aca04a64..e689f95e 100644
--- a/packages/cli/test/cases/build.config.mode-spa/src/index.html
+++ b/packages/cli/test/cases/build.config.mode-spa/src/index.html
@@ -3,6 +3,7 @@
 
   <head>
     <meta-outlet></meta-outlet>
+    <meta property="og:description" content="My custom meta content." />
     <script type="module" src="/components/footer.js"></script>
     <script type="module" src="/index.js"></script>
   </head>
diff --git a/packages/cli/test/cases/build.default.workspace-template-page/build.default.workspace-template-page.spec.js b/packages/cli/test/cases/build.default.workspace-template-page/build.default.workspace-template-page.spec.js
index bdfe4bac..366ad8f6 100644
--- a/packages/cli/test/cases/build.default.workspace-template-page/build.default.workspace-template-page.spec.js
+++ b/packages/cli/test/cases/build.default.workspace-template-page/build.default.workspace-template-page.spec.js
@@ -67,6 +67,14 @@ describe('Build Greenwood With: ', function() {
           styleTags = dom.window.document.querySelectorAll('head > style');
         });
 
+        it('should have custom <meta> tag in the <head>', function() {
+          const customMeta = Array.from(dom.window.document.querySelectorAll('head > meta'))
+            .filter(meta => meta.getAttribute('property') === 'og:description');
+  
+          expect(customMeta.length).to.be.equal(1);
+          expect(customMeta[0].getAttribute('content')).to.be.equal('My custom meta content.');
+        });
+
         it('should have 1 <script> tags in the <head>', function() {
           expect(scriptTags.length).to.equal(1);
         });
diff --git a/packages/cli/test/cases/build.default.workspace-template-page/src/templates/page.html b/packages/cli/test/cases/build.default.workspace-template-page/src/templates/page.html
index 3021703a..232cdd50 100644
--- a/packages/cli/test/cases/build.default.workspace-template-page/src/templates/page.html
+++ b/packages/cli/test/cases/build.default.workspace-template-page/src/templates/page.html
@@ -2,10 +2,9 @@
 <html lang="en" prefix="og:http://ogp.me/ns#">
 
   <head>
-    <head>
-      <script type="module" src="/scripts/main.js"></script>
-      <link rel="stylesheet" href="/styles/theme.css"></link>
-    </head>
+    <meta property="og:description" content="My custom meta content."/>
+    <script type="module" src="/scripts/main.js"></script>
+    <link rel="stylesheet" href="/styles/theme.css"></link>
   
     <style>
       .owen-test {
diff --git a/packages/cli/test/cases/build.default.workspace-top-level-pages/build.default.workspace-top-level-pages.spec.js b/packages/cli/test/cases/build.default.workspace-top-level-pages/build.default.workspace-top-level-pages.spec.js
index e5a33a38..79acc367 100644
--- a/packages/cli/test/cases/build.default.workspace-top-level-pages/build.default.workspace-top-level-pages.spec.js
+++ b/packages/cli/test/cases/build.default.workspace-top-level-pages/build.default.workspace-top-level-pages.spec.js
@@ -53,6 +53,14 @@ describe('Build Greenwood With: ', function() {
         dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, 'index.html'));
       });
 
+      it('should have custom <meta> tag in the <head>', function() {
+        const customMeta = Array.from(dom.window.document.querySelectorAll('head > meta'))
+          .filter(meta => meta.getAttribute('property') === 'og:description');
+
+        expect(customMeta.length).to.be.equal(1);
+        expect(customMeta[0].getAttribute('content')).to.be.equal('My custom meta content.');
+      });
+
       xit('should have the correct <title> for the home page', function() {
         const titleTags = dom.window.document.querySelectorAll('title');
 
diff --git a/packages/cli/test/cases/build.default.workspace-top-level-pages/src/pages/index.html b/packages/cli/test/cases/build.default.workspace-top-level-pages/src/pages/index.html
index b10aabac..29a4dcfb 100644
--- a/packages/cli/test/cases/build.default.workspace-top-level-pages/src/pages/index.html
+++ b/packages/cli/test/cases/build.default.workspace-top-level-pages/src/pages/index.html
@@ -3,6 +3,7 @@
 
   <head>
     <title>Top Level Test</title>
+    <meta property="og:description" content="My custom meta content." />
   </head>
   
   <body>
diff --git a/packages/cli/test/cases/build.default.workspace-user-directory-mapping/src/templates/page.html b/packages/cli/test/cases/build.default.workspace-user-directory-mapping/src/templates/page.html
index e6f24baf..c200e98e 100644
--- a/packages/cli/test/cases/build.default.workspace-user-directory-mapping/src/templates/page.html
+++ b/packages/cli/test/cases/build.default.workspace-user-directory-mapping/src/templates/page.html
@@ -2,8 +2,6 @@
 <html lang="en" prefix="og:http://ogp.me/ns#">
 
   <head>
-    <meta charset='utf-8'>
-    <meta name='viewport' content='width=device-width, initial-scale=1'/>
     <script type="module" src="/components/header/header.js"></script>
   </head>
 
diff --git a/test/smoke-test.js b/test/smoke-test.js
index e3d3f44e..75cec83c 100644
--- a/test/smoke-test.js
+++ b/test/smoke-test.js
@@ -97,14 +97,18 @@ function defaultIndex(label) {
         });
 
         it('should have default viewport <meta> tag', function() {
-          const viewportMeta = metaTags[1];
+          const viewportMeta = Array.from(metaTags).filter(meta => meta.getAttribute('name') === 'viewport');
           
-          expect(viewportMeta.getAttribute('name')).to.be.equal('viewport');
-          expect(viewportMeta.getAttribute('content')).to.be.equal('width=device-width, initial-scale=1');
+          expect(viewportMeta.length).to.be.equal(1);
+          expect(viewportMeta[0].getAttribute('name')).to.be.equal('viewport');
+          expect(viewportMeta[0].getAttribute('content')).to.be.equal('width=device-width, initial-scale=1');
         });
 
         it('should have default charset <meta> tag', function() {
-          expect(metaTags[0].getAttribute('charset')).to.be.equal('utf-8');
+          const chartsetMeta = Array.from(metaTags).filter(meta => meta.getAttribute('charset') === 'utf-8');
+
+          expect(chartsetMeta.length).to.be.equal(1);
+          expect(chartsetMeta[0].getAttribute('charset')).to.be.equal('utf-8');
         });
       });