浏览代码

Update CV and use grapefruit for output

Craig Fletcher 6 年之前
父节点
当前提交
f0ef1d12cb

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+node_modules
+output
+last-build.log

+ 112 - 0
.npm-scripts/build-pipeline.js

@@ -0,0 +1,112 @@
+const path = require("path");
+
+function getNiceNameFromFilename(filename) {
+  return (filename.charAt(0).toUpperCase() + filename.slice(1)).replace(
+    /-/g,
+    " "
+  );
+}
+
+function meta(config, item, meta) {
+  const outputDir = path.join(meta.dir.replace(config.baseDir, ""));
+  return {
+    ...item,
+    name: meta.name,
+    niceName: getNiceNameFromFilename(meta.name),
+    outputPath: `${path.join(outputDir, meta.name)}`,
+    outputExtension: ".html"
+  };
+}
+
+function addBlurb(config, item, meta) {
+  const introStart = item.content.indexOf("<p>") + 3;
+  const introEnd = item.content.indexOf("</p>");
+  let cutoff;
+  if (introEnd - introStart > config.cutoffLength) {
+    cutoff = item.content.indexOf(".", introStart + config.cutoffLength) + 1;
+  } else {
+    cutoff = introEnd;
+  }
+  const blurb = item.content.substring(introStart, cutoff);
+  return {
+    ...item,
+    blurb: blurb
+  };
+}
+
+module.exports = {
+  initialState: [
+    {
+      path: "./cv.md",
+      tags: ["markdown", "content"]
+    },
+    {
+      path: "./styles.css",
+      tags: ["styles"]
+    },
+    {
+      path: "./template.handlebars",
+      tags: ["template"]
+    }
+  ],
+  steps: [
+    [
+      {
+        func: "readInFile",
+        selector: ({ selectMany }) => selectMany(item => Boolean(item.path))
+      }
+    ],
+    [
+      {
+        func: "decorateFileObject",
+        selector: ({ selectMany }) => selectMany(item => true),
+        config: {
+          decorators: [meta]
+        }
+      }
+    ],
+    [
+      {
+        func: "markdownToHtml",
+        selector: ({ selectByTag }) => selectByTag("markdown")
+      },
+      {
+        func: "compileTemplates",
+        selector: ({ selectByTag }) => selectByTag("template")
+      },
+      {
+        func: "copy",
+        selector: ({ selectByTag }) => selectByTag("styles")
+      }
+    ],
+    [
+      {
+        func: "renderTemplate",
+        selector: ({ selectOne }) =>
+          selectOne(
+            item => item.tags.includes("markdown") && item.name === "cv"
+          ),
+        getConfig: ({ selectByTag, selectOne }) => ({
+          meta: {
+            styles: selectOne(item => item.name === "styles")
+          },
+          template: selectOne(
+            item => item.tags.includes("template") && item.name === "template"
+          )
+        })
+      }
+    ],
+    [
+      {
+        func: "writeOutFile",
+        selector: ({ selectMany }) => selectMany(item => true),
+        config: { outputDir: "./output" }
+      },
+      {
+        func: "writeOutPDF",
+        selector: ({ selectMany }) => selectMany(item => true),
+        config: { outputDir: "./output" }
+      }
+    ]
+  ]
+};

+ 53 - 0
.npm-scripts/build.js

@@ -0,0 +1,53 @@
+const Grapefruit = require("grapefruit");
+const careless = require("careless-fs");
+const buildPipeline = require("./build-pipeline");
+const history = new Grapefruit.History();
+
+const runner = new Grapefruit({
+  emitter: history.push,
+  funcs: {
+    decorateFileObject: require("./funcs/decorateFileObject.js"),
+    readInFile: require("./funcs/readInFile.js"),
+    writeOutFile: require("./funcs/writeOutFile.js"),
+    writeOutPDF: require("./funcs/writeOutPDF.js"),
+    compileTemplates: require("./funcs/compileTemplates.js"),
+    renderTemplate: require("./funcs/renderTemplate.js"),
+    markdownToHtml: require("./funcs/markdownToHtml.js"),
+    copy: require("./funcs/copy.js")
+  }
+});
+
+const pipeline = runner.runPipeline(buildPipeline);
+pipeline
+  .then(function(res) {
+    careless
+      .write({
+        path: "last-build.log",
+        content: JSON.stringify(history.get(), null, 2)
+      })
+      .then(file => {
+        console.log(`History written to ${file.path}`);
+      })
+      .catch(e => {
+        console.log(
+          `History could not be written: ${JSON.stringify(e, null, 2)}`
+        );
+      });
+    console.log("Pipeline complete.");
+  })
+  .catch(function(err) {
+    careless
+      .write({
+        path: "last-build.log",
+        content: JSON.stringify(history.get(), null, 2)
+      })
+      .then(file => {
+        console.log(`History written to ${file.path}`);
+      })
+      .catch(e => {
+        console.log(
+          `History could not be written: ${JSON.stringify(e, null, 2)}`
+        );
+      });
+    console.log("Pipeline error:", JSON.stringify(err.message, null, 2));
+  });

+ 11 - 0
.npm-scripts/funcs/compileTemplates.js

@@ -0,0 +1,11 @@
+const Handlebars = require("handlebars");
+const handlebarsCompiler = Handlebars.compile;
+
+module.exports = function(config, item) {
+  return new Promise(function(resolve) {
+    resolve({
+      ...item,
+      template: handlebarsCompiler(item.content)
+    });
+  });
+};

+ 8 - 0
.npm-scripts/funcs/copy.js

@@ -0,0 +1,8 @@
+module.exports = function(config, item) {
+  return new Promise(function(resolve) {
+    resolve({
+      ...item
+    });
+  });
+};
+

+ 28 - 0
.npm-scripts/funcs/copyFile.js

@@ -0,0 +1,28 @@
+const fsN = require("fs");
+const path = require("path");
+const mkdirp = require("mkdirp");
+
+module.exports = function(input, options) {
+  return new Promise(function(resolve, reject) {
+    Promise.all(
+      input.currentJob.files.map(file => {
+        return new Promise(function(resolve, reject) {
+          const outputPath = path.join(options.outputDir, file.outputPath);
+          mkdirp(path.dirname(outputPath), function(err) {
+            if (err) {
+              reject(err);
+            }
+            fsN.copyFile(file.path, outputPath, function(err) {
+              if (err) {
+                reject(err);
+              }
+              resolve(file);
+            });
+          });
+        });
+      })
+    )
+      .then(res => resolve(input))
+      .catch(err => reject(err));
+  });
+};

+ 13 - 0
.npm-scripts/funcs/decorateFileObject.js

@@ -0,0 +1,13 @@
+const path = require("path");
+
+module.exports = function(config, item) {
+  return new Promise(function(resolve, reject) {
+    const meta = path.parse(item.path);
+    resolve(
+      Object.assign(
+        item,
+        ...config.decorators.map(decorator => decorator(config, item, meta))
+      )
+    );
+  });
+};

+ 25 - 0
.npm-scripts/funcs/listDirectory.js

@@ -0,0 +1,25 @@
+const fsN = require("fs");
+const path = require("path");
+
+module.exports = function(config, item) {
+  return new Promise(function(resolve, reject) {
+    fsN.readdir(config.directory, { withFileTypes: true }, function(
+      err,
+      dirListing
+    ) {
+      if (err) {
+        reject(err);
+      }
+      resolve(
+        dirListing.reduce((filenames, fileDirent) => {
+          return fileDirent.isFile()
+            ? filenames.concat({
+                ...item,
+                path: path.join(config.directory, fileDirent.name)
+              })
+            : filenames;
+        }, [])
+      );
+    });
+  });
+};

+ 10 - 0
.npm-scripts/funcs/markdownToHtml.js

@@ -0,0 +1,10 @@
+const markdown = require("markdown").markdown;
+
+module.exports = function(config, item) {
+  return new Promise(function(resolve) {
+    resolve({
+      ...item,
+      content: markdown.toHTML(item.content)
+    });
+  });
+};

+ 15 - 0
.npm-scripts/funcs/minifyHtml.js

@@ -0,0 +1,15 @@
+const minify = require("html-minifier").minify;
+
+module.exports = function(config, item) {
+  return new Promise(function(resolve, reject) {
+    resolve({
+      ...item,
+      content: minify(item.content, {
+        removeAttributeQuotes: true,
+        minifyCSS: true,
+        minifyJS: true,
+        collapseWhitespace: true
+      })
+    });
+  });
+};

+ 11 - 0
.npm-scripts/funcs/parseJson.js

@@ -0,0 +1,11 @@
+module.exports = function(input, options) {
+  return new Promise(function(resolve, reject) {
+    input.currentJob.files = input.currentJob.files.map(file => {
+      return {
+        ...file,
+        content: JSON.parse(file.content),
+      };
+    });
+    resolve(input);
+  });
+};

+ 16 - 0
.npm-scripts/funcs/readInFile.js

@@ -0,0 +1,16 @@
+const fs = require("careless-fs");
+
+module.exports = function(config, item) {
+  return new Promise(function(resolve, reject) {
+    fs.read(item.path)
+      .then(function(data) {
+        resolve({
+          ...item,
+          ...data
+        });
+      })
+      .catch(function(err) {
+        reject(err);
+      });
+  });
+};

+ 22 - 0
.npm-scripts/funcs/renderTemplate.js

@@ -0,0 +1,22 @@
+function toObject(arr) {
+  let rv = {};
+  let i;
+  for (i = 0; i < arr.length; ++i) rv[arr[i].name] = arr[i];
+  return rv;
+}
+module.exports = function(config, item) {
+  return new Promise(function(resolve, reject) {
+    if (!config.template || !config.template.template) {
+      reject({ message: "Template not found", item, config });
+    }
+    try {
+      const content = config.template.template({ item, meta: config.meta });
+      resolve({
+        ...item,
+        content
+      });
+    } catch (e) {
+      reject({ message: "Could not render template", funcError: e });
+    }
+  });
+};

+ 7 - 0
.npm-scripts/funcs/writeOutFile.js

@@ -0,0 +1,7 @@
+const fs = require("careless-fs");
+module.exports = function(config, item) {
+  return fs.write({
+    path: `${config.outputDir}/${item.outputPath}${item.outputExtension}`,
+    content: item.content
+  });
+};

+ 17 - 0
.npm-scripts/funcs/writeOutPDF.js

@@ -0,0 +1,17 @@
+const pdf = require("html-pdf");
+module.exports = function(config, item) {
+  return new Promise((resolve, reject) => {
+    const path = `${config.outputDir}/${item.outputPath}.pdf`;
+
+    pdf.create(item.content).toFile(path, function(err, res) {
+      if (err) {
+        reject(err);
+      } else {
+        resolve({
+          ...item,
+          path: res.filename
+        });
+      }
+    });
+  });
+};

+ 0 - 135
cv.html

@@ -1,135 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<link rel="stylesheet" href="styles.css">
-<title></title>
-</head>
-<body>
-<section>
-<header>
-<h1>Hello, I'm Craig Fletcher.</h1>
-</header>
-<p>You can call me on 07480924132, email me at <a href=
-"mailto:hi@leakypixel.net">hi@leakypixel.net</a>, or check out what
-I'm getting on with at <a href=
-"https://www.leakypixel.net/">leakypixel.net</a>.</p>
-<p>I've been developing for the web for around 10 years and I'm
-still as eager as ever to push my skills and learn new things. I
-specialise in JavaScript - particularly React, but have worked with
-many languages, libraries and tools appropriate to the task at
-hand.</p>
-<h2>Experience</h2>
-<p>A short description of my last few roles and what they entailed.
-I'd be happy to discuss these further, but for now I'll keep it
-brief.</p>
-<h3>Erlang Solutions - JavaScript consultant (9 months)</h3>
-<p><em>2018 to 2019</em></p>
-<p>Worked closely with a worldwide distributed team to develop a
-deliveroo-like service for fuel and related product delivery. As
-the only JavaScript developer on the team, I built a react and
-apollo based web application to interact with an erlang graphql
-service that was being developed in tandem by other members of the
-team.</p>
-<h3>The Co-op Group - JavaScript consultant (6 months)</h3>
-<p><em>2018</em></p>
-<p>As part of the ventures team, built a new marketplace
-application using react for the frontend and python with flask for
-the backend. Worked in a team of 2 to get the project off the
-ground and into production. This project was more focused toward
-user research and constant iteration than technical challenges,
-something I really enjoyed.</p>
-<h3>Life's Great - JavaScript consultant (6 months)</h3>
-<p><em>2017 to 2018</em></p>
-<p>Upskilled team into react development and assisted with building
-a greenfield project:</p>
-<ul>
-<li>Upskilling team with limited to no prior knowledge of react
-through pairing, presentations and walkthroughs.</li>
-<li>Built a new application to gather information from customers
-through a conditional, staged questionnaire with complex
-dependencies.</li>
-<li>Various devops related tasks from scratch, such as docker
-scripts.</li>
-<li>Advising management on various technical decisions vital for
-the company.</li>
-</ul>
-<h3>Earlier</h3>
-<p><em>2017 - Tyres on the Drive - JavaScript consultant (12
-months)</em></p>
-<p><em>2016 - The Hut Group - JavaScript developer (12
-months)</em></p>
-<p><em>2015 - Bet365 - JavaScript developer (6 months)</em></p>
-<p><em>2013 to 2015 - On The Beach ltd. - Front end developer,
-Permanent</em></p>
-<h2>Skills</h2>
-<p>Some of the skills I've picked up along the way and tools I've
-used.</p>
-<h3>JavaScript</h3>
-<ul>
-<li>React, Redux, Apollo GraphQL</li>
-<li>NodeJS</li>
-<li>Document DBs (Mongo/Couchbase)</li>
-<li>Client-side templating (Handlebars/Mustache/jst)</li>
-<li>Module loading & dependency resolution</li>
-<li>Testing & automation (Jasmine/Karma/Istanbul/Selenium)</li>
-<li>Build systems (Grunt/Gulp/Webpack)</li>
-<li>WebSockets (Pusher/native)</li>
-<li>Modern ECMA script</li>
-</ul>
-<h3>HTML & CSS</h3>
-<ul>
-<li>Sass/SCSS/LESS</li>
-<li>Responsive design</li>
-<li>Modular design & web components</li>
-<li>Accessibility/Information hierarchy</li>
-<li>Progressive enhancement</li>
-</ul>
-<h3>Others</h3>
-<ul>
-<li>AWS and Digital ocean</li>
-<li>Continuous integration (Jenkins/Travis)</li>
-<li>Git (GitHub/CLI/GitHooks/Workflows)</li>
-<li>Docker & docker-compose</li>
-<li>Agile, Kanban & Scrum</li>
-<li>Project management tools (JIRA/Trello)</li>
-<li>Building appliances/discreet servers (Raspberry Pi/Intel Atom
-SoC)</li>
-<li>Analytics (Google/server stats/pingdom/custom built)</li>
-<li>Basic server administration (nginx/Apache/caddy)</li>
-<li>Versioning (npm/git tags/artifactory/docker images)</li>
-<li>Linux/UNIX-like OSes (Debian/Arch/FreeBSD)</li>
-<li>Bash (day to day environment & scripting)</li>
-<li>Python</li>
-</ul>
-<h3>Experience of</h3>
-<ul>
-<li>Java (Spring)</li>
-<li>Ruby (Rails)</li>
-<li>Golang (Iris)</li>
-<li>Cordova/PhoneGap</li>
-<li>Erlang (Phoenix)</li>
-</ul>
-<h2>I'm not boring</h2>
-<p>I have quite a few hobbies outside of my development work, and
-try to keep myself as active as I can - especially during the
-summer, when I can get outside.</p>
-<ul>
-<li>Rock climbing and bouldering are my big hobbies... though they
-don't look a lot different from the outside, there's quite a gap. I
-vary between the two, but practice indoors twice a week and get
-outside when I can.</li>
-<li>Somehow I ended up being a Scout leader, too. I was part of the
-organisation when I was younger, and after a couple of years break,
-I went back to help out. It has greatly helped me in maintaining
-leadership skills, and organising larger groups of people.</li>
-<li>I also spend quite a lot of my free time tinkering and working
-on personal projects, such as building raspberry pi based camera
-systems, monitors, or arcade systems.</li>
-<li>To keep up with the fast-paced world of development, I spend
-quite a lot of my free time reading, or listening to podcasts. Some
-of my favourites are 99 percent invisible, bad voltage and project
-bouldering.</li>
-</ul>
-</section>
-</body>
-</html>

+ 23 - 9
cv.md

@@ -8,7 +8,15 @@ I've been developing for the web for around 10 years and I'm still as eager as e
 A short description of my last few roles and what they entailed. I'd be happy to
 discuss these further, but for now I'll keep it brief.
 
-### Erlang Solutions - JavaScript consultant (9 months)
+### AKQA - JavaScript consultant (3 months, fully remote)
+*2019*
+
+Worked on a high-profile project for a well-known international clothing brand,
+using React, Netlify, Next.js, AWS lambda, nodeJS and AWS dynamoDB. Working remotely as
+part of a small, internationally distributed team to deliver a time-critical
+project with many challenges and tight deadlines.
+
+### Erlang Solutions - JavaScript consultant (9 months, fully remote)
 *2018 to 2019*
 
 Worked closely with a worldwide distributed team to develop a deliveroo-like
@@ -17,7 +25,7 @@ on the team, I built a react and apollo based web application to interact with
 an erlang graphql service that was being developed in tandem by other members 
 of the team.
 
-### The Co-op Group - JavaScript consultant (6 months)
+### The Co-op Group - JavaScript consultant (6 months, part remote)
 *2018* 
 
 As part of the ventures team, built a new marketplace application using react
@@ -26,16 +34,20 @@ get the project off the ground and into production. This project was more
 focused toward user research and constant iteration than technical challenges,
 something I really enjoyed.
 
-### Life's Great - JavaScript consultant (6 months)
+### Life's Great - JavaScript consultant (6 months, part remote)
 *2017 to 2018* 
 
 Upskilled team into react development and assisted with building a greenfield
 project:
+
 * Upskilling team with limited to no prior knowledge of react through pairing,
     presentations and walkthroughs.
+
 * Built a new application to gather information from customers through a
     conditional, staged questionnaire with complex dependencies.
+
 * Various devops related tasks from scratch, such as docker scripts.
+
 * Advising management on various technical decisions vital for the company.
 
 ### Earlier
@@ -53,7 +65,7 @@ Some of the skills I've picked up along the way and tools I've used.
 ### JavaScript
 * React, Redux, Apollo GraphQL
 * NodeJS
-* Document DBs (Mongo/Couchbase)
+* Document DBs (Mongo/Couchbase/DynamoDB)
 * Client-side templating (Handlebars/Mustache/jst)
 * Module loading & dependency resolution
 * Testing & automation (Jasmine/Karma/Istanbul/Selenium)
@@ -73,19 +85,21 @@ Some of the skills I've picked up along the way and tools I've used.
 * Cross browser compatibility
 * HTML 4 & 5
 
-### Others
+### Devops related
 * AWS and Digital ocean
 * Continuous integration (Jenkins/Travis)
+* Docker & docker-compose
+* Basic server administration (nginx/Apache/caddy)
+* Versioning (npm/git tags/artifactory/docker images)
+* Linux/UNIX-like OSes (Debian/Arch/FreeBSD)
+
+### Others
 * UX and data-driven design
 * Git (GitHub/CLI/GitHooks/Workflows)
-* Docker & docker-compose
 * Agile, Kanban & Scrum
 * Project management tools (JIRA/Trello)
 * Building appliances/discreet servers (Raspberry Pi/Intel Atom SoC)
 * Analytics (Google/server stats/pingdom/custom built)
-* Basic server administration (nginx/Apache/caddy)
-* Versioning (npm/git tags/artifactory/docker images)
-* Linux/UNIX-like OSes (Debian/Arch/FreeBSD)
 * Bash (day to day environment & scripting)
 * Python
 

+ 891 - 0
package-lock.json

@@ -0,0 +1,891 @@
+{
+  "name": "CV",
+  "version": "0.0.1",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "abbrev": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+    },
+    "ajv": {
+      "version": "6.10.2",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+      "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+      "optional": true,
+      "requires": {
+        "fast-deep-equal": "^2.0.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "optional": true,
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
+    "assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+      "optional": true
+    },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+      "optional": true
+    },
+    "aws-sign2": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+      "optional": true
+    },
+    "aws4": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+      "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
+      "optional": true
+    },
+    "bcrypt-pbkdf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "optional": true,
+      "requires": {
+        "tweetnacl": "^0.14.3"
+      }
+    },
+    "buffer-from": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+      "optional": true
+    },
+    "camel-case": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
+      "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
+      "requires": {
+        "no-case": "^2.2.0",
+        "upper-case": "^1.1.1"
+      }
+    },
+    "careless-fs": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/careless-fs/-/careless-fs-1.0.3.tgz",
+      "integrity": "sha512-tMj0HKbhMkVZ+nedgUxTShb4AtFSxBX62PyEuTsVKCNbI0rCOSIwBnwgctRmZq2pM/WfScliCmESOW7qb+5KCA==",
+      "requires": {
+        "mkdirp": "^0.5.1"
+      }
+    },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+      "optional": true
+    },
+    "charset": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz",
+      "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg=="
+    },
+    "clean-css": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
+      "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==",
+      "requires": {
+        "source-map": "~0.6.0"
+      }
+    },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "optional": true,
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
+    "commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "optional": true
+    },
+    "concat-stream": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+      "optional": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.2.2",
+        "typedarray": "^0.0.6"
+      }
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+      "optional": true
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "optional": true,
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "optional": true,
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+      "optional": true
+    },
+    "ecc-jsbn": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "optional": true,
+      "requires": {
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.1.0"
+      }
+    },
+    "ecstatic": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-4.1.2.tgz",
+      "integrity": "sha512-lnrAOpU2f7Ra8dm1pW0D1ucyUxQIEk8RjFrvROg1YqCV0ueVu9hzgiSEbSyROqXDDiHREdqC4w3AwOTb23P4UQ==",
+      "requires": {
+        "charset": "^1.0.1",
+        "he": "^1.1.1",
+        "mime": "^2.4.1",
+        "minimist": "^1.1.0",
+        "on-finished": "^2.3.0",
+        "url-join": "^4.0.0"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+        }
+      }
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "es6-promise": {
+      "version": "4.2.8",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
+      "optional": true
+    },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+      "optional": true
+    },
+    "extract-zip": {
+      "version": "1.6.7",
+      "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
+      "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=",
+      "optional": true,
+      "requires": {
+        "concat-stream": "1.6.2",
+        "debug": "2.6.9",
+        "mkdirp": "0.5.1",
+        "yauzl": "2.4.1"
+      }
+    },
+    "extsprintf": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+      "optional": true
+    },
+    "fast-deep-equal": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+      "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+      "optional": true
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+      "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
+      "optional": true
+    },
+    "fd-slicer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
+      "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
+      "optional": true,
+      "requires": {
+        "pend": "~1.2.0"
+      }
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+      "optional": true
+    },
+    "form-data": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "optional": true,
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "fs-extra": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz",
+      "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=",
+      "optional": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "jsonfile": "^2.1.0",
+        "klaw": "^1.0.0"
+      }
+    },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "optional": true,
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
+      "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
+      "optional": true
+    },
+    "grapefruit": {
+      "version": "github:leakypixel/grapefruit#85a03f60c8ad3eaee9cc09e530fce01c66ead7fc",
+      "from": "github:leakypixel/grapefruit"
+    },
+    "handlebars": {
+      "version": "4.5.1",
+      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
+      "integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
+      "requires": {
+        "neo-async": "^2.6.0",
+        "optimist": "^0.6.1",
+        "source-map": "^0.6.1",
+        "uglify-js": "^3.1.4"
+      }
+    },
+    "har-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+      "optional": true
+    },
+    "har-validator": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+      "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+      "optional": true,
+      "requires": {
+        "ajv": "^6.5.5",
+        "har-schema": "^2.0.0"
+      }
+    },
+    "hasha": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz",
+      "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=",
+      "optional": true,
+      "requires": {
+        "is-stream": "^1.0.1",
+        "pinkie-promise": "^2.0.0"
+      }
+    },
+    "he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
+    },
+    "html-minifier": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz",
+      "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==",
+      "requires": {
+        "camel-case": "3.0.x",
+        "clean-css": "4.2.x",
+        "commander": "2.17.x",
+        "he": "1.2.x",
+        "param-case": "2.1.x",
+        "relateurl": "0.2.x",
+        "uglify-js": "3.4.x"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.17.1",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
+          "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
+        },
+        "uglify-js": {
+          "version": "3.4.10",
+          "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
+          "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==",
+          "requires": {
+            "commander": "~2.19.0",
+            "source-map": "~0.6.1"
+          },
+          "dependencies": {
+            "commander": {
+              "version": "2.19.0",
+              "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
+              "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg=="
+            }
+          }
+        }
+      }
+    },
+    "html-pdf": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/html-pdf/-/html-pdf-2.2.0.tgz",
+      "integrity": "sha1-S8+Rwky1YOR6o/rP0DPg4b8kG5E=",
+      "requires": {
+        "phantomjs-prebuilt": "^2.1.4"
+      }
+    },
+    "http-signature": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "optional": true,
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "jsprim": "^1.2.2",
+        "sshpk": "^1.7.0"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "optional": true
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "optional": true
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+      "optional": true
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+      "optional": true
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "optional": true
+    },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+      "optional": true
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+      "optional": true
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+      "optional": true
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "optional": true
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+      "optional": true
+    },
+    "jsonfile": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
+      "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
+      "optional": true,
+      "requires": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "optional": true,
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
+    "kew": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz",
+      "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=",
+      "optional": true
+    },
+    "klaw": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
+      "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
+      "optional": true,
+      "requires": {
+        "graceful-fs": "^4.1.9"
+      }
+    },
+    "lower-case": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
+      "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw="
+    },
+    "markdown": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/markdown/-/markdown-0.5.0.tgz",
+      "integrity": "sha1-KCBbVlqK51kt4gdGPWY33BgnIrI=",
+      "requires": {
+        "nopt": "~2.1.1"
+      }
+    },
+    "mime": {
+      "version": "2.4.4",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
+      "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
+    },
+    "mime-db": {
+      "version": "1.40.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+      "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
+      "optional": true
+    },
+    "mime-types": {
+      "version": "2.1.24",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+      "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+      "optional": true,
+      "requires": {
+        "mime-db": "1.40.0"
+      }
+    },
+    "minimist": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+      "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+      "requires": {
+        "minimist": "0.0.8"
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+      "optional": true
+    },
+    "neo-async": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
+      "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw=="
+    },
+    "no-case": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
+      "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
+      "requires": {
+        "lower-case": "^1.1.1"
+      }
+    },
+    "nopt": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz",
+      "integrity": "sha1-bMzZd7gBMqB3MdbozljCyDA8+a8=",
+      "requires": {
+        "abbrev": "1"
+      }
+    },
+    "oauth-sign": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+      "optional": true
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "optimist": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+      "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+      "requires": {
+        "minimist": "~0.0.1",
+        "wordwrap": "~0.0.2"
+      }
+    },
+    "param-case": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
+      "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
+      "requires": {
+        "no-case": "^2.2.0"
+      }
+    },
+    "pend": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+      "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
+      "optional": true
+    },
+    "performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
+      "optional": true
+    },
+    "phantomjs-prebuilt": {
+      "version": "2.1.16",
+      "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz",
+      "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=",
+      "optional": true,
+      "requires": {
+        "es6-promise": "^4.0.3",
+        "extract-zip": "^1.6.5",
+        "fs-extra": "^1.0.0",
+        "hasha": "^2.2.0",
+        "kew": "^0.7.0",
+        "progress": "^1.1.8",
+        "request": "^2.81.0",
+        "request-progress": "^2.0.1",
+        "which": "^1.2.10"
+      }
+    },
+    "pinkie": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+      "optional": true
+    },
+    "pinkie-promise": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+      "optional": true,
+      "requires": {
+        "pinkie": "^2.0.0"
+      }
+    },
+    "process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+      "optional": true
+    },
+    "progress": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
+      "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
+      "optional": true
+    },
+    "psl": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz",
+      "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==",
+      "optional": true
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "optional": true
+    },
+    "qs": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+      "optional": true
+    },
+    "readable-stream": {
+      "version": "2.3.6",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+      "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+      "optional": true,
+      "requires": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "relateurl": {
+      "version": "0.2.7",
+      "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
+      "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk="
+    },
+    "request": {
+      "version": "2.88.0",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+      "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
+      "optional": true,
+      "requires": {
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.8.0",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "form-data": "~2.3.2",
+        "har-validator": "~5.1.0",
+        "http-signature": "~1.2.0",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "~2.1.19",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.2",
+        "safe-buffer": "^5.1.2",
+        "tough-cookie": "~2.4.3",
+        "tunnel-agent": "^0.6.0",
+        "uuid": "^3.3.2"
+      }
+    },
+    "request-progress": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz",
+      "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=",
+      "optional": true,
+      "requires": {
+        "throttleit": "^1.0.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "optional": true
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "optional": true
+    },
+    "source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+    },
+    "sshpk": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+      "optional": true,
+      "requires": {
+        "asn1": "~0.2.3",
+        "assert-plus": "^1.0.0",
+        "bcrypt-pbkdf": "^1.0.0",
+        "dashdash": "^1.12.0",
+        "ecc-jsbn": "~0.1.1",
+        "getpass": "^0.1.1",
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.0.2",
+        "tweetnacl": "~0.14.0"
+      }
+    },
+    "string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "optional": true,
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "throttleit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
+      "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=",
+      "optional": true
+    },
+    "tough-cookie": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+      "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
+      "optional": true,
+      "requires": {
+        "psl": "^1.1.24",
+        "punycode": "^1.4.1"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+          "optional": true
+        }
+      }
+    },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "optional": true,
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "tweetnacl": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+      "optional": true
+    },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+      "optional": true
+    },
+    "uglify-js": {
+      "version": "3.6.8",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz",
+      "integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==",
+      "optional": true,
+      "requires": {
+        "commander": "~2.20.3",
+        "source-map": "~0.6.1"
+      }
+    },
+    "upper-case": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
+      "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
+    },
+    "uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "optional": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "url-join": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
+      "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+      "optional": true
+    },
+    "uuid": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
+      "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
+      "optional": true
+    },
+    "verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "optional": true,
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    },
+    "which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "optional": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "wordwrap": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+      "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
+    },
+    "yauzl": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
+      "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
+      "optional": true,
+      "requires": {
+        "fd-slicer": "~1.0.1"
+      }
+    }
+  }
+}

+ 20 - 0
package.json

@@ -0,0 +1,20 @@
+{
+  "name": "CV",
+  "version": "0.0.1",
+  "description": "Craig Fletcher's CV",
+  "scripts": {
+    "build": "node .npm-scripts/build.js",
+    "serve": "ecstatic output/"
+  },
+  "author": "Craig Fletcher <hi@leakypixel.net>",
+  "dependencies": {
+    "careless-fs": "^1.0.0",
+    "ecstatic": "^4.1.2",
+    "grapefruit": "github:leakypixel/grapefruit",
+    "handlebars": "^4.0.12",
+    "html-minifier": "^3.5.21",
+    "html-pdf": "^2.2.0",
+    "markdown": "^0.5.0"
+  },
+  "homepage": "https://leakypixel.net/"
+}

+ 8 - 2
styles.css

@@ -36,7 +36,7 @@ section img {
 
 h1 {
   text-transform: uppercase;
-  display: inline;
+  display: inline-block;
   font-family: "Gidole", monospace;
   font-weight: bold;
   font-weight: 600;
@@ -45,7 +45,7 @@ h1 {
   line-height: 1.6em;
   border-bottom: 0.4em solid;
   border-color: var(--color-accent-2);
-  margin: 0 0 1em 0;
+  margin: 1em 0 2em 0;
 }
 
 h2 {
@@ -136,6 +136,12 @@ header {
     width: 100%;
     margin: 0;
   }
+  body {
+    max-width: 95%;
+    width: 95%;
+    margin: 1em;
+    font-size: 14px;
+  }
 }
 /* Fonts */
 @import url("https://fonts.googleapis.com/css?family=Noto+Sans");

+ 15 - 0
template.handlebars

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Craig Fletcher's CV</title>
+{{#if meta.styles}}
+<style type="text/css">
+{{{ meta.styles.content }}}
+</style> {{/if}}
+</head>
+<body>
+  <section>
+  {{{ item.content }}}
+  </section>
+</body>
+</html>