├── .babelrc
├── .editorconfig
├── .gitignore
├── LICENSE
├── README.md
├── examples
├── assets
│ ├── avocado-235px.jpg
│ ├── avocado-logo-sml.png
│ ├── facebook.png
│ ├── footer-bg.jpg
│ ├── github.png
│ ├── gplus.png
│ ├── guac-485px.jpg
│ ├── instagram.png
│ ├── tomatoes-235px.jpg
│ ├── twitter.png
│ └── white-avocado-logo-sml.png
└── avocado-industries-with-images.heml
├── gulpfile.js
├── lerna.json
├── package-lock.json
├── package.json
├── packages
├── heml-elements
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ ├── Base.js
│ │ ├── Block.js
│ │ ├── Body.js
│ │ ├── Button.js
│ │ ├── Column.js
│ │ ├── Container.js
│ │ ├── Font.js
│ │ ├── Head.js
│ │ ├── Heml.js
│ │ ├── Hr.js
│ │ ├── Img.js
│ │ ├── Meta.js
│ │ ├── Preview.js
│ │ ├── Row.js
│ │ ├── Style.js
│ │ ├── Subject.js
│ │ ├── Table.js
│ │ ├── Typography.js
│ │ └── index.js
├── heml-inline
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ ├── fixWidthsFor.js
│ │ ├── index.js
│ │ ├── inlineMargins.js
│ │ ├── preferMaxWidth.js
│ │ ├── removeProcessingIds.js
│ │ └── styleHelper.js
├── heml-parse
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ └── index.js
├── heml-render
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ ├── createHtmlElement.js
│ │ ├── index.js
│ │ ├── renderElement.js
│ │ └── stringifyAttributes.js
├── heml-styles
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ ├── index.js
│ │ └── plugins
│ │ ├── postcss-element-expander
│ │ ├── coerceElements.js
│ │ ├── expanders.js
│ │ ├── findDirectElementSelectors.js
│ │ ├── index.js
│ │ └── tagAliasSelectors.js
│ │ ├── postcss-expand-shorthand
│ │ └── index.js
│ │ ├── postcss-merge-adjacent-media
│ │ └── index.js
│ │ └── postcss-zero-out-margin
│ │ └── index.js
├── heml-utils
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ ├── HEMLError.js
│ │ ├── condition.js
│ │ ├── createElement.js
│ │ ├── index.js
│ │ └── transforms
│ │ ├── convertProp.js
│ │ ├── fallbackFor.js
│ │ ├── ieAlignFallback.js
│ │ ├── index.js
│ │ └── trueHide.js
├── heml-validate
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ ├── index.js
│ │ └── validators
│ │ ├── attrs.js
│ │ ├── children.js
│ │ ├── index.js
│ │ ├── parent.js
│ │ └── unique.js
└── heml
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ ├── bin
│ ├── commands
│ │ ├── build.js
│ │ └── develop.js
│ ├── heml.js
│ └── utils
│ │ ├── buildErrorPage.js
│ │ ├── isHemlFile.js
│ │ └── renderHemlFile.js
│ └── index.js
└── test
├── __fixtures__
├── index.js
└── simple.fixture.js
├── __snapshots__
└── snapshot.spec.js.snap
└── snapshot.spec.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "targets": {
5 | "node": 4
6 | },
7 | }]
8 | ],
9 | "plugins": [
10 | "transform-runtime",
11 | "transform-object-rest-spread",
12 | ["transform-react-jsx", {
13 | "pragma": "HEML.renderElement"
14 | }]
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = "utf-8"
8 |
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | build/
4 | .env
5 | *.log
6 | testing/
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | MIT License
3 |
4 | Copyright (c) 2017-present SparkPost
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7 |
8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9 |
10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
<heml>
2 |
3 |
4 |
5 | Guide •
6 | Documentation •
7 | Editor
8 |
9 |
10 |
11 | HEML is an open source markup language for building responsive email.
12 |
13 | - **Native Feel:** Do you know HTML and CSS? Check out our docs and you're off to the races! No special rules or styling paradigms to master.
14 |
15 | - **Forward Thinking:** HEML is designed to take advantage of all that email can do while still providing a solid experience for all clients.
16 |
17 | - **Extendable:** You can create your own powerful elements and style rules. Share them with the world, or keep them to yourself. Your choice.
18 |
19 |
20 | ## FAQ
21 |
22 | ### Why should I use HEML?
23 |
24 | It makes building emails easier.
25 |
26 | ### How do I use it?
27 |
28 | Check out our [usage guide](http://heml.io/docs/getting-started/usage).
29 |
30 | ### What do I do if I found a bug?
31 |
32 | Open up an [issue](https://github.com/SparkPost/heml/issues/new) on the repository. Thanks for catching it! 🙏
33 |
34 | ### Want to help?
35 |
36 | Awesome!! We welcome any and all help! Head over to the [issues](https://github.com/SparkPost/heml/issues) and see if anything catches your eye.
37 |
38 | ## License
39 |
40 | [MIT](https://github.com/SparkPost/heml/blob/master/LICENSE)
41 |
--------------------------------------------------------------------------------
/examples/assets/avocado-235px.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SparkPost/heml/9c600cb97b4d5f46a97a0c8af75851c15d5c681c/examples/assets/avocado-235px.jpg
--------------------------------------------------------------------------------
/examples/assets/avocado-logo-sml.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SparkPost/heml/9c600cb97b4d5f46a97a0c8af75851c15d5c681c/examples/assets/avocado-logo-sml.png
--------------------------------------------------------------------------------
/examples/assets/facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SparkPost/heml/9c600cb97b4d5f46a97a0c8af75851c15d5c681c/examples/assets/facebook.png
--------------------------------------------------------------------------------
/examples/assets/footer-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SparkPost/heml/9c600cb97b4d5f46a97a0c8af75851c15d5c681c/examples/assets/footer-bg.jpg
--------------------------------------------------------------------------------
/examples/assets/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SparkPost/heml/9c600cb97b4d5f46a97a0c8af75851c15d5c681c/examples/assets/github.png
--------------------------------------------------------------------------------
/examples/assets/gplus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SparkPost/heml/9c600cb97b4d5f46a97a0c8af75851c15d5c681c/examples/assets/gplus.png
--------------------------------------------------------------------------------
/examples/assets/guac-485px.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SparkPost/heml/9c600cb97b4d5f46a97a0c8af75851c15d5c681c/examples/assets/guac-485px.jpg
--------------------------------------------------------------------------------
/examples/assets/instagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SparkPost/heml/9c600cb97b4d5f46a97a0c8af75851c15d5c681c/examples/assets/instagram.png
--------------------------------------------------------------------------------
/examples/assets/tomatoes-235px.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SparkPost/heml/9c600cb97b4d5f46a97a0c8af75851c15d5c681c/examples/assets/tomatoes-235px.jpg
--------------------------------------------------------------------------------
/examples/assets/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SparkPost/heml/9c600cb97b4d5f46a97a0c8af75851c15d5c681c/examples/assets/twitter.png
--------------------------------------------------------------------------------
/examples/assets/white-avocado-logo-sml.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SparkPost/heml/9c600cb97b4d5f46a97a0c8af75851c15d5c681c/examples/assets/white-avocado-logo-sml.png
--------------------------------------------------------------------------------
/examples/avocado-industries-with-images.heml:
--------------------------------------------------------------------------------
1 |
2 |
3 | America's Finest Homemade Guacamole!
4 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | America's Finest Homemade Guacamole!
37 |
38 |
39 |
40 | Spicy heml tasty responsive jalapeno bacon ipsum dolor amet pariatur mollit fatback venison,
41 | cillum occaecat quis ut labore pork belly culpa ea bacon in spare ribs.
42 |
43 |
44 | Place an order
45 |
46 |
47 |
48 |
49 |
50 |
51 | Tasty Recipes!
52 | Velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.
53 |
54 |
55 |
56 | All Natural Ingredients
57 | Velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.
58 |
59 |
60 |
61 |
94 |
95 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Inspired by https://github.com/babel/minify/blob/master/gulpfile.babel.js
3 |
4 | const del = require('del')
5 | const through = require('through2')
6 | const newer = require('gulp-newer')
7 | const babel = require('gulp-babel')
8 | const util = require('gulp-util')
9 | const plumber = require('gulp-plumber')
10 | const gulp = require('gulp')
11 | const path = require('path')
12 | const { cyan } = util.colors
13 |
14 | const scripts = './packages/*/src/**/*.js'
15 | const builds = './packages/*/build'
16 | const dest = 'packages'
17 |
18 | let srcEx, libFragment
19 |
20 | if (path.win32 === path) {
21 | srcEx = /(packages\\[^\\]+)\\src\\/
22 | libFragment = '$1\\build\\'
23 | } else {
24 | srcEx = new RegExp('(packages/[^/]+)/src/')
25 | libFragment = '$1/build/'
26 | }
27 |
28 | function build () {
29 | return gulp
30 | .src(scripts)
31 | .pipe(plumber())
32 | .pipe(through.obj((file, enc, callback) => {
33 | file._path = file.path
34 | file.path = file.path.replace(srcEx, libFragment)
35 | callback(null, file)
36 | }))
37 | .pipe(newer(dest))
38 | .pipe(babel())
39 | .pipe(gulp.dest(dest))
40 | .on('end', () => {
41 | util.log(`Finished '${cyan('build')}'`)
42 | })
43 | }
44 |
45 | gulp.task('build', build)
46 |
47 | gulp.task('watch', ['build'], function () {
48 | gulp.watch(scripts, { debounceDelay: 200 }, build)
49 | })
50 |
51 | gulp.task('clean', () => {
52 | return del(builds)
53 | })
54 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "lerna": "2.4.0",
3 | "packages": [
4 | "packages/*"
5 | ],
6 | "version": "1.1.3"
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "lint": "standard 'packages/**/src/**/*.js' 'test/**/*.js' --fix",
5 | "test": "npm run bootstrap && npm run build && npm run lint && jest ./test/*.spec.js",
6 | "bootstrap": "npm install && lerna bootstrap",
7 | "clean": "gulp clean && lerna clean --yes",
8 | "build": "gulp build",
9 | "watch": "gulp watch",
10 | "publish": "npm run clean && npm test && lerna publish"
11 | },
12 | "devDependencies": {
13 | "babel-cli": "^6.26.0",
14 | "babel-plugin-transform-async-to-generator": "^6.24.1",
15 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
16 | "babel-plugin-transform-react-jsx": "^6.24.1",
17 | "babel-plugin-transform-runtime": "^6.23.0",
18 | "babel-preset-env": "^1.6.0",
19 | "del": "^3.0.0",
20 | "gulp": "^3.9.1",
21 | "gulp-babel": "^7.0.0",
22 | "gulp-newer": "^1.3.0",
23 | "gulp-plumber": "^1.1.0",
24 | "gulp-util": "^3.0.8",
25 | "husky": "^0.14.3",
26 | "jest": "^21.2.1",
27 | "lerna": "^2.4.0",
28 | "require-all": "^2.2.0",
29 | "standard": "^10.0.3",
30 | "through2": "^2.0.3"
31 | },
32 | "standard": {
33 | "globals": [
34 | "afterAll",
35 | "afterEach",
36 | "beforeAll",
37 | "beforeEach",
38 | "describe",
39 | "expect",
40 | "test"
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/heml-elements/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heml/elements",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "axios": {
8 | "version": "0.17.0",
9 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.17.0.tgz",
10 | "integrity": "sha1-fadHkW24A/dhZR1gkdcIeJuVPGo=",
11 | "requires": {
12 | "follow-redirects": "1.2.5",
13 | "is-buffer": "1.1.5"
14 | }
15 | },
16 | "debug": {
17 | "version": "2.6.9",
18 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
19 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
20 | "requires": {
21 | "ms": "2.0.0"
22 | }
23 | },
24 | "follow-redirects": {
25 | "version": "1.2.5",
26 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.2.5.tgz",
27 | "integrity": "sha512-lMhwQTryFbG+wYsAIEKC1Kf5IGDlVNnONRogIBllh7LLoV7pNIxW0z9fhjRar9NBql+hd2Y49KboVVNxf6GEfg==",
28 | "requires": {
29 | "debug": "2.6.9"
30 | }
31 | },
32 | "image-size": {
33 | "version": "0.6.1",
34 | "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.1.tgz",
35 | "integrity": "sha512-lHMlI2MykfeHAQdtydQh4fTcBQVf4zLTA91w1euBe9rbmAfJ/iyzMh8H3KD9u1RldlHaMS3tmMV5TEe9BkmW9g=="
36 | },
37 | "is-absolute-url": {
38 | "version": "2.1.0",
39 | "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz",
40 | "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY="
41 | },
42 | "is-buffer": {
43 | "version": "1.1.5",
44 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
45 | "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw="
46 | },
47 | "lodash": {
48 | "version": "4.17.4",
49 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
50 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
51 | },
52 | "ms": {
53 | "version": "2.0.0",
54 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
55 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/packages/heml-elements/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heml/elements",
3 | "version": "1.1.3",
4 | "description": "Core elements for HEML",
5 | "keywords": [
6 | "heml"
7 | ],
8 | "homepage": "https://heml.io",
9 | "bugs": "https://github.com/SparkPost/heml/issues",
10 | "license": "MIT",
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/SparkPost/heml.git"
14 | },
15 | "author": "SparkPost (https://sparkpost.com)",
16 | "files": [
17 | "build/"
18 | ],
19 | "main": "build/index.js",
20 | "publishConfig": {
21 | "access": "public"
22 | },
23 | "dependencies": {
24 | "@heml/styles": "^1.1.2",
25 | "@heml/utils": "^1.1.2",
26 | "axios": "^0.17.0",
27 | "image-size": "^0.6.1",
28 | "is-absolute-url": "^2.1.0",
29 | "lodash": "^4.17.4"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Base.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import Meta from './Meta'
3 | import isAbsoluteUrl from 'is-absolute-url'
4 | import { resolve } from 'url'
5 | import { has, first } from 'lodash'
6 |
7 | export default createElement('base', {
8 | parent: [ 'head' ],
9 | children: false,
10 | unique: true,
11 | defaultAttrs: { href: '' },
12 |
13 | render (attrs, contents) {
14 | Meta.set('base', attrs.href)
15 |
16 | return false
17 | },
18 |
19 | preRender ({ $ }) {
20 | const base = first($.findNodes('base'))
21 |
22 | if (base) {
23 | const baseUrl = base.attr('href')
24 |
25 | $('[href], [src]')
26 | .each((i, node) => {
27 | const attr = has(node.attribs, 'href') ? 'href' : 'src'
28 |
29 | if (has(node.attribs, attr) && !isAbsoluteUrl(node.attribs[attr])) {
30 | node.attribs[attr] = resolve(baseUrl, node.attribs[attr])
31 | }
32 | })
33 | }
34 | }
35 | })
36 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Block.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement, transforms, cssGroups, condition } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import Style from './Style'
3 |
4 | const {
5 | trueHide,
6 | ieAlignFallback } = transforms
7 |
8 | const {
9 | background,
10 | margin,
11 | padding,
12 | border,
13 | borderRadius,
14 | width,
15 | height,
16 | table,
17 | box } = cssGroups
18 |
19 | export default createElement('block', {
20 | containsText: true,
21 |
22 | rules: {
23 | '.block': [ { '@pseudo': 'root' }, { display: trueHide('block') }, margin, width ],
24 |
25 | '.block__table__ie': [ 'width', 'max-width', { [margin]: ieAlignFallback } ],
26 |
27 | '.block__table': [ { '@pseudo': 'table' }, table ],
28 |
29 | '.block__row': [ { '@pseudo': 'row' } ],
30 |
31 | '.block__cell': [ { '@pseudo': 'cell' }, height, background, box, padding, border, borderRadius, 'vertical-align' ]
32 | },
33 |
34 | render (attrs, contents) {
35 | attrs.class += ' block'
36 | return (
37 |
38 | {condition('mso | IE', `
`)}
39 |
40 |
41 | {contents}
42 |
43 |
44 | {condition('mso | IE', `
`)}
45 |
50 |
51 | )
52 | }
53 | })
54 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Body.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement, transforms, cssGroups } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import Style from './Style'
3 | import Preview from './Preview'
4 |
5 | const {
6 | background,
7 | padding,
8 | font,
9 | text } = cssGroups
10 |
11 | export default createElement('body', {
12 | unique: true,
13 | parent: [ 'heml' ],
14 | containsText: true,
15 |
16 | rules: {
17 | '.body': [ { '@pseudo': 'root' }, background ],
18 |
19 | '.bodyTable': [ { '@pseudo': 'table' }, '@default', background ],
20 |
21 | '.body__content': [ { '@pseudo': 'content' }, padding, font, text ],
22 |
23 | '.preview': [ { 'background-color': transforms.convertProp('color') } ]
24 | },
25 |
26 | async render (attrs, contents) {
27 | attrs.class += ' body'
28 |
29 | return (
30 |
31 | {Preview.flush()}
32 |
33 |
34 | {contents}
35 |
36 |
37 | {' '.repeat(30)}
38 |
48 | )
49 | }
50 | })
51 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Button.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement, transforms, cssGroups } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import { omit, pick } from 'lodash'
3 | import Style from './Style'
4 |
5 | const {
6 | background,
7 | margin,
8 | padding,
9 | border,
10 | borderRadius,
11 | width,
12 | height,
13 | table,
14 | text,
15 | font,
16 | box } = cssGroups
17 |
18 | export default createElement('button', {
19 | attrs: [ 'href', 'target' ],
20 | defaultAttrs: {
21 | href: '#'
22 | },
23 |
24 | rules: {
25 | '.button': [
26 | { '@pseudo': 'root' }, { display: transforms.trueHide('block') } ],
27 |
28 | '.button__table': [
29 | { '@pseudo': 'table' }, margin, table ],
30 |
31 | '.button__cell': [
32 | { '@pseudo': 'cell' }, background, padding, borderRadius, border, height, width, box ],
33 |
34 | '.button__link': [
35 | { '@pseudo': 'link' }, background, text, font ],
36 | '.button__text': [
37 | { '@pseudo': 'text' }, 'color', 'text-decoration' ]
38 | },
39 |
40 | render (attrs, contents) {
41 | attrs.class += ' button'
42 |
43 | return (
44 |
45 |
46 |
47 |
48 |
57 |
58 |
59 |
60 |
70 |
)
71 | }
72 | })
73 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Column.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement, transforms, cssGroups } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import Style from './Style'
3 |
4 | const {
5 | background,
6 | box,
7 | padding,
8 | border,
9 | borderRadius } = cssGroups
10 |
11 | const breakpoint = 600
12 |
13 | export default createElement('column', {
14 | attrs: [ 'small', 'large' ],
15 | parent: [ 'row' ],
16 | defaultAttrs: { small: 12, large: 12 },
17 | containsText: true,
18 |
19 | rules: {
20 | '.column': [ { '@pseudo': 'root' }, { display: transforms.trueHide(undefined, true) }, background, box, padding, border, borderRadius, 'vertical-align' ]
21 | },
22 |
23 | render (attrs, contents) {
24 | const small = parseInt(attrs.small, 10)
25 | const large = parseInt(attrs.large, 10)
26 | const largeWidth = `${Math.round((100 * large) / 12)}%`
27 | attrs.class += ` column col-sm-${small}`
28 |
29 | delete attrs.large
30 | delete attrs.small
31 |
32 | return ([
33 |
34 | {contents.length === 0 ? ' ' : contents}
35 | ,
36 | small === large ? '' : ()
45 | ])
46 | }
47 | })
48 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Container.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement, transforms, cssGroups, condition } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import Style from './Style'
3 |
4 | const {
5 | trueHide,
6 | ieAlignFallback } = transforms
7 |
8 | const {
9 | background,
10 | margin,
11 | padding,
12 | border,
13 | borderRadius,
14 | width,
15 | height,
16 | table,
17 | box } = cssGroups
18 |
19 | export default createElement('container', {
20 | containsText: true,
21 |
22 | rules: {
23 | '.container': [ { '@pseudo': 'root' }, { display: trueHide('block') }, margin, width ],
24 |
25 | '.container__table__ie': [ 'width', 'max-width', { [margin]: ieAlignFallback } ],
26 |
27 | '.container__table': [ { '@pseudo': 'table' }, table ],
28 |
29 | '.container__row': [ { '@pseudo': 'row' } ],
30 |
31 | '.container__cell': [ { '@pseudo': 'cell' }, height, background, box, padding, border, borderRadius ]
32 | },
33 |
34 | render (attrs, contents) {
35 | attrs.class += ' container'
36 | return (
37 |
38 | {condition('mso | IE', `
`)}
39 |
40 |
41 | {contents}
42 |
43 |
44 | {condition('mso | IE', `
`)}
45 |
52 |
53 | )
54 | }
55 | })
56 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Font.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars
2 |
3 | export default createElement('font', {
4 | parent: [ 'head' ],
5 | children: false,
6 | defaultAttrs: { href: '' },
7 |
8 | render (attrs, contents) {
9 | return ([
10 | ``,
11 | ,
12 | ,
15 | ``
16 | ])
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Head.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import Subject from './Subject'
3 | import Style from './Style'
4 |
5 | export default createElement('head', {
6 | unique: true,
7 | parent: [ 'heml' ],
8 | attrs: [],
9 |
10 | async render (attrs, contents) {
11 | return ([
12 | {/* Fake head for Yahoo */} ,
13 |
14 |
15 |
16 |
17 |
18 | {/* */
19 | ``}
20 |
21 | {``}
22 |
23 | {/* http://tabletrtd.com/opening-css-resets/ */}
24 |
34 |
44 | {``}
50 | {Subject.flush()}
51 | {await Style.flush()}
52 | {``}
53 | {/* drop in the contents */
54 | contents}
55 | {/* https://litmus.com/community/discussions/151-mystery-solved-dpi-scaling-in-outlook-2007-2013 */
56 | ``}
62 | ])
63 | }
64 | })
65 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Heml.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars
2 |
3 | export default createElement('heml', {
4 | unique: true,
5 | parent: [],
6 | children: [ 'head', 'body' ],
7 | defaultAttrs: {
8 | 'lang': 'en',
9 | 'xmlns': 'http://www.w3.org/1999/xhtml',
10 | 'xmlns:v': 'urn:schemas-microsoft-com:vml',
11 | 'xmlns:o': 'urn:schemas-microsoft-com:office:office'
12 | },
13 |
14 | render (attrs, contents) {
15 | return ([
16 | ``,
17 |
18 | {contents}
19 | ])
20 | }
21 | })
22 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Hr.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement, transforms, cssGroups, condition } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import Style from './Style'
3 |
4 | const {
5 | trueHide,
6 | ieAlignFallback } = transforms
7 |
8 | const {
9 | background,
10 | margin,
11 | padding,
12 | border,
13 | borderRadius,
14 | width,
15 | height,
16 | table,
17 | box } = cssGroups
18 |
19 | export default createElement('hr', {
20 | children: false,
21 |
22 | rules: {
23 | '.hr': [ { '@pseudo': 'root' }, { display: trueHide() }, margin, width ],
24 |
25 | '.hr__table__ie': [ 'width', 'max-width', { [margin]: ieAlignFallback } ],
26 |
27 | '.hr__table': [ { '@pseudo': 'table' }, table ],
28 |
29 | '.hr__row': [ { '@pseudo': 'row' } ],
30 |
31 | '.hr__cell': [ { '@pseudo': 'cell' }, height, background, box, padding, border, borderRadius, 'vertical-align' ]
32 | },
33 |
34 | render (attrs, contents) {
35 | attrs.class += ' hr'
36 | return (
37 |
38 | {condition('mso | IE', `
`)}
39 |
40 |
41 | {` `}
42 |
43 |
44 | {condition('mso | IE', `
`)}
45 |
52 |
53 | )
54 | }
55 | })
56 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Img.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement, transforms } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import Style from './Style'
3 | import { omit, has } from 'lodash'
4 | import fs from 'fs-extra'
5 | import isAbsoluteUrl from 'is-absolute-url'
6 | import axios from 'axios'
7 | import sizeOf from 'image-size'
8 |
9 | export default createElement('img', {
10 | attrs: [ 'src', 'width', 'height', 'alt', 'infer', 'inline', 'style' ],
11 | children: false,
12 | defaultAttrs: {
13 | border: '0',
14 | alt: ''
15 | },
16 |
17 | rules: {
18 | 'img': [ { '@pseudo': 'root' }, { display: transforms.trueHide() }, '@default' ]
19 | },
20 |
21 | async render (attrs, contents) {
22 | const isBlock = !attrs.inline
23 |
24 | if (!!attrs.infer && has(attrs, 'src') && !attrs.width) {
25 | attrs.width = await getWidth(attrs.src, attrs.infer === 'retina')
26 | }
27 |
28 | attrs.class += ` ${isBlock ? 'img__block' : 'img__inline'}`
29 | attrs.style = isBlock ? '' : 'display: inline-block;'
30 |
31 | return ([
32 | ,
33 | ])
39 | }
40 | })
41 |
42 | async function getWidth (path, isRetina) {
43 | try {
44 | const image = await (isAbsoluteUrl(path) ? getRemoteBuffer(path) : fs.readFile(path))
45 |
46 | const { width } = sizeOf(image)
47 | if (!width) { return 'auto' }
48 |
49 | return isRetina ? Math.round(width / 2) : width
50 | } catch (e) {
51 | return 'auto' // if we fail fall back to auto
52 | }
53 | }
54 |
55 | function getRemoteBuffer (path) {
56 | return axios({
57 | method: 'get',
58 | url: path,
59 | responseType: 'arraybuffer'
60 | })
61 | .then(({ data }) => {
62 | return Buffer.from(data, 'binary')
63 | })
64 | }
65 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Meta.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars
2 |
3 | let metaMap
4 |
5 | export default createElement('meta', {
6 | attrs: true,
7 | parent: [ 'head' ],
8 |
9 | preRender () { metaMap = new Map([ [ 'meta', [] ] ]) },
10 |
11 | render (attrs, contents) {
12 | metaMap.get('meta').push(attrs)
13 |
14 | return true
15 | },
16 |
17 | get (key) {
18 | return metaMap.get(key)
19 | },
20 |
21 | set (key, value) {
22 | return metaMap.set(key, value)
23 | },
24 |
25 | flush () {
26 | let metaObject = {}
27 |
28 | for (let [ key, value ] of metaMap) {
29 | metaObject[key] = value
30 | }
31 |
32 | metaMap = null
33 |
34 | return metaObject
35 | }
36 | })
37 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Preview.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import Meta from './Meta'
3 |
4 | export default createElement('preview', {
5 | parent: [ 'head' ],
6 | unique: true,
7 |
8 | render (attrs, contents) {
9 | Meta.set('preview', contents)
10 |
11 | return false
12 | },
13 |
14 | flush () {
15 | const preview = Meta.get('preview')
16 |
17 | return preview ? {preview}{' '.repeat(200 - preview.length)}
: ''
18 | }
19 | })
20 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Row.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement, transforms } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import { sum, max, isUndefined } from 'lodash'
3 |
4 | export default createElement('row', {
5 | children: [ 'column' ],
6 |
7 | rules: {
8 | '.row': [ { '@pseudo': 'root' }, { display: transforms.trueHide('block') } ],
9 |
10 | '.row__table': [ { '@pseudo': 'table' } ],
11 |
12 | '.row__row': [ { '@pseudo': 'row' } ]
13 | },
14 |
15 | render (attrs, contents) {
16 | attrs.class += ' row'
17 | return (
18 | )
23 | },
24 |
25 | preRender ({ $ }) {
26 | $.findNodes('row').forEach(($row) => {
27 | const $columns = $row.children().toNodes()
28 | const columnSizes = $columns.map(($column) => parseInt($column.attr('large') || 0, 10))
29 | const remainingSpace = 12 - sum(columnSizes)
30 | const remainingColumns = columnSizes.filter((size) => size === 0).length
31 | const spacePerColumn = max([Math.floor(remainingSpace / remainingColumns), 1])
32 | const overageSpace = remainingSpace - spacePerColumn * remainingColumns
33 |
34 | let filledColumns = 0
35 | $columns.forEach(($column) => {
36 | if (isUndefined($column.attr('large'))) {
37 | filledColumns++
38 | $column.attr('large', spacePerColumn + (filledColumns === remainingColumns ? overageSpace : 0))
39 | }
40 | })
41 |
42 | // if they don't add up to 12
43 | // and there are no specified columns
44 | if (remainingColumns === 0 && remainingSpace > 0) {
45 | $row.append(' ')
46 | }
47 | })
48 | }
49 | })
50 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Style.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import hemlstyles from '@heml/styles'
3 | import { castArray, isEqual, uniqWith, sortBy } from 'lodash'
4 |
5 | const START_EMBED_CSS = `/*!***START:EMBED_CSS*****/`
6 | const START_INLINE_CSS = `/*!***START:INLINE_CSS*****/`
7 |
8 | let styleMap
9 | let options
10 |
11 | export default createElement('style', {
12 | parent: [ 'head' ],
13 | attrs: [ 'for', 'heml-embed' ],
14 | defaultAttrs: {
15 | 'heml-embed': false,
16 | 'for': 'global'
17 | },
18 |
19 | preRender ({ $, elements }) {
20 | styleMap = new Map([ [ 'global', [] ] ])
21 | options = {
22 | plugins: [],
23 | elements: {},
24 | aliases: {}
25 | }
26 |
27 | for (let element of elements) {
28 | if (element.postcss) {
29 | options.plugins = options.plugins.concat(castArray(element.postcss))
30 | }
31 |
32 | if (element.rules) {
33 | options.elements[element.tagName] = element.rules
34 | }
35 |
36 | options.aliases[element.tagName] = $.findNodes(element.tagName)
37 | }
38 | },
39 |
40 | render (attrs, contents) {
41 | if (!styleMap.get(attrs.for)) {
42 | styleMap.set(attrs.for, [])
43 | }
44 |
45 | styleMap.get(attrs.for).push({
46 | embed: !!attrs['heml-embed'],
47 | ignore: !!attrs['heml-ignore'],
48 | css: contents
49 | })
50 |
51 | return false
52 | },
53 |
54 | async flush () {
55 | /**
56 | * reverse the styles so they fall in an order that mirrors their position
57 | * - they get rendered bottom to top - should be styled top to bottom
58 | *
59 | * the global styles should always be rendered last
60 | */
61 | const globalStyles = styleMap.get('global')
62 | styleMap.delete('global')
63 | styleMap = new Map([...styleMap].reverse())
64 | styleMap.set('global', globalStyles)
65 |
66 | let ignoredCSS = []
67 | let fullCSS = ''
68 |
69 | /** combine the non-ignored css to be combined */
70 | for (let [ element, styles ] of styleMap) {
71 | styles = uniqWith(styles, isEqual)
72 | styles = element === 'global' ? styles : sortBy(styles, ['embed', 'css'])
73 |
74 | styles.forEach(({ ignore, embed, css }) => {
75 | /** replace the ignored css with placeholders that will be swapped later */
76 | if (ignore) {
77 | ignoredCSS.push({ embed, css })
78 | fullCSS += ignoreComment(ignoredCSS.length - 1)
79 | } else if (embed) {
80 | fullCSS += `${START_EMBED_CSS}${css}`
81 | } else {
82 | fullCSS += `${START_INLINE_CSS}${css}`
83 | }
84 | })
85 | }
86 |
87 | let { css: processedCss } = await hemlstyles(fullCSS, options)
88 |
89 | /** put the ignored css back in */
90 | ignoredCSS.forEach(({ embed, css }, index) => {
91 | processedCss = processedCss.replace(ignoreComment(index), embed ? `${START_EMBED_CSS}${css}` : `${START_INLINE_CSS}${css}`)
92 | })
93 |
94 | /** split on the dividers and map it so each part starts with INLINE or EMBED */
95 | let processedCssParts = processedCss.split(/\/\*!\*\*\*START:/g).splice(1).map((css) => css.replace(/_CSS\*\*\*\*\*\//, ''))
96 |
97 | /** build the html */
98 | let html = ''
99 | let lastType = null
100 |
101 | for (let cssPart of processedCssParts) {
102 | const css = cssPart.replace(/^(EMBED|INLINE)/, '')
103 | const type = cssPart.startsWith('EMBED') ? 'EMBED' : 'INLINE'
104 |
105 | if (type === lastType) {
106 | html += css
107 | } else {
108 | lastType = type
109 | html += `${html === '' ? '' : ''}\n'
114 |
115 | /** reset the styles and options */
116 | styleMap = options = null
117 |
118 | return html
119 | }
120 | })
121 |
122 | function ignoreComment (index) {
123 | return `/*!***IGNORE_${index}*****/`
124 | }
125 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Subject.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import Meta from './Meta'
3 |
4 | export default createElement('subject', {
5 | parent: [ 'head' ],
6 | unique: true,
7 |
8 | render (attrs, contents) {
9 | Meta.set('subject', contents)
10 |
11 | return false
12 | },
13 |
14 | flush () {
15 | return Meta.get('subject') || ''
16 | }
17 | })
18 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Table.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement, transforms } from '@heml/utils' // eslint-disable-line no-unused-vars
2 |
3 | const Table = createElement('table', {
4 | attrs: true,
5 | containsText: true,
6 | rules: {
7 | '.table': [ { '@pseudo': 'root' }, '@default', { display: transforms.trueHide('table') } ]
8 | },
9 |
10 | render (attrs, contents) {
11 | attrs.class += ' table'
12 | return
13 | }
14 | })
15 |
16 | const Tr = createElement('tr', {
17 | attrs: true,
18 | containsText: true,
19 | rules: {
20 | '.tr': [ { '@pseudo': 'root' }, '@default' ]
21 | },
22 |
23 | render (attrs, contents) {
24 | attrs.class += ' tr'
25 | return {contents}
26 | }
27 | })
28 |
29 | const Td = createElement('td', {
30 | attrs: true,
31 | containsText: true,
32 | rules: {
33 | '.td': [ { '@pseudo': 'root' }, '@default' ]
34 | },
35 |
36 | render (attrs, contents) {
37 | attrs.class += ' td'
38 | return {contents}
39 | }
40 | })
41 |
42 | export { Table, Tr, Td }
43 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/Typography.js:
--------------------------------------------------------------------------------
1 | import HEML, { createElement, transforms, cssGroups } from '@heml/utils' // eslint-disable-line no-unused-vars
2 | import { merge } from 'lodash'
3 |
4 | const {
5 | margin, background, border, borderRadius, text, font
6 | } = cssGroups
7 |
8 | /**
9 | * create mergable text element
10 | * @param {String} name
11 | * @param {Object} element
12 | * @return {Object}
13 | */
14 | function createTextElement (name, element = {}) {
15 | let classToAdd = ''
16 | const Tag = name
17 |
18 | if (/^h\d$/i.test(name)) {
19 | classToAdd = 'header'
20 | } else {
21 | classToAdd = 'text'
22 | }
23 |
24 | return createElement(name, merge({
25 | attrs: true,
26 | rules: {
27 | [`.${name}.${classToAdd}`]: [ { '@pseudo': 'root' }, '@default', { display: transforms.trueHide() }, margin, background, border, borderRadius, text, font ]
28 | },
29 | render (attrs, contents) {
30 | attrs.class += ` ${classToAdd} ${name}`
31 |
32 | return {contents}
33 | }
34 | }, element))
35 | }
36 |
37 | const H1 = createTextElement('h1')
38 | const H2 = createTextElement('h2')
39 | const H3 = createTextElement('h3')
40 | const H4 = createTextElement('h4')
41 | const H5 = createTextElement('h5')
42 | const H6 = createTextElement('h6')
43 | const P = createTextElement('p')
44 | const Ol = createTextElement('ol')
45 | const Ul = createTextElement('ul')
46 | const Li = createTextElement('li')
47 |
48 | const A = createElement('a', {
49 | attrs: true,
50 | defaultAttrs: { href: '#' },
51 |
52 | rules: {
53 | '.a': [ { '@pseudo': 'root' }, { '@default': true }, { display: transforms.trueHide('inline') }, 'color', 'text-decoration' ],
54 | '.a__text': [ { '@pseudo': 'text' }, 'color', 'text-decoration' ]
55 | },
56 |
57 | render (attrs, contents) {
58 | attrs.class += ' a'
59 | return {contents}
60 | }
61 | })
62 |
63 | export { H1, H2, H3, H4, H5, H6, P, Ol, Ul, Li, A }
64 |
--------------------------------------------------------------------------------
/packages/heml-elements/src/index.js:
--------------------------------------------------------------------------------
1 | /* Document */
2 | import Heml from './Heml'
3 | import Head from './Head'
4 | import Body from './Body'
5 |
6 | /* Meta */
7 | import Meta from './Meta'
8 | import Subject from './Subject'
9 | import Preview from './Preview'
10 | import Base from './Base'
11 | import Font from './Font'
12 | import Style from './Style'
13 |
14 | /* Table */
15 | import { Table, Tr, Td } from './Table'
16 |
17 | /* Grid */
18 | import Block from './Block'
19 | import Container from './Container'
20 | import Row from './Row'
21 | import Column from './Column'
22 |
23 | /* Content */
24 | import { H1, H2, H3, H4, H5, H6, P, Ol, Ul, Li, A } from './Typography'
25 | import Hr from './Hr'
26 | import Button from './Button'
27 | import Img from './Img'
28 |
29 | export {
30 | /* Document */
31 | Heml,
32 | Head,
33 | Body,
34 |
35 | /* Meta */
36 | Meta,
37 | Subject,
38 | Preview,
39 | Base,
40 | Font,
41 | Style,
42 |
43 | /* Table */
44 | Table,
45 | Tr,
46 | Td,
47 |
48 | /* Grid */
49 | Block,
50 | Container,
51 | Row,
52 | Column,
53 |
54 | /* Content */
55 | H1,
56 | H2,
57 | H3,
58 | H4,
59 | H5,
60 | H6,
61 | P,
62 | Ol,
63 | Ul,
64 | Li,
65 | A,
66 | Hr,
67 | Button,
68 | Img
69 | }
70 |
--------------------------------------------------------------------------------
/packages/heml-inline/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "requires": true,
3 | "lockfileVersion": 1,
4 | "dependencies": {
5 | "ajv": {
6 | "version": "5.2.3",
7 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz",
8 | "integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=",
9 | "requires": {
10 | "co": "4.6.0",
11 | "fast-deep-equal": "1.0.0",
12 | "json-schema-traverse": "0.3.1",
13 | "json-stable-stringify": "1.0.1"
14 | }
15 | },
16 | "ansi-regex": {
17 | "version": "2.1.1",
18 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
19 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
20 | },
21 | "ansi-styles": {
22 | "version": "2.2.1",
23 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
24 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
25 | },
26 | "asn1": {
27 | "version": "0.2.3",
28 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
29 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
30 | },
31 | "assert-plus": {
32 | "version": "1.0.0",
33 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
34 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
35 | },
36 | "async": {
37 | "version": "2.5.0",
38 | "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz",
39 | "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==",
40 | "requires": {
41 | "lodash": "4.17.4"
42 | },
43 | "dependencies": {
44 | "lodash": {
45 | "version": "4.17.4",
46 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
47 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
48 | }
49 | }
50 | },
51 | "asynckit": {
52 | "version": "0.4.0",
53 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
54 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
55 | },
56 | "aws-sign2": {
57 | "version": "0.7.0",
58 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
59 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
60 | },
61 | "aws4": {
62 | "version": "1.6.0",
63 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
64 | "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
65 | },
66 | "bcrypt-pbkdf": {
67 | "version": "1.0.1",
68 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
69 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
70 | "optional": true,
71 | "requires": {
72 | "tweetnacl": "0.14.5"
73 | }
74 | },
75 | "boolbase": {
76 | "version": "1.0.0",
77 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
78 | "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
79 | },
80 | "boom": {
81 | "version": "4.3.1",
82 | "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
83 | "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
84 | "requires": {
85 | "hoek": "4.2.0"
86 | }
87 | },
88 | "caseless": {
89 | "version": "0.12.0",
90 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
91 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
92 | },
93 | "chalk": {
94 | "version": "1.1.3",
95 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
96 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
97 | "requires": {
98 | "ansi-styles": "2.2.1",
99 | "escape-string-regexp": "1.0.5",
100 | "has-ansi": "2.0.0",
101 | "strip-ansi": "3.0.1",
102 | "supports-color": "2.0.0"
103 | }
104 | },
105 | "cheerio": {
106 | "version": "0.22.0",
107 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz",
108 | "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=",
109 | "requires": {
110 | "css-select": "1.2.0",
111 | "dom-serializer": "0.1.0",
112 | "entities": "1.1.1",
113 | "htmlparser2": "3.9.2",
114 | "lodash.assignin": "4.2.0",
115 | "lodash.bind": "4.2.1",
116 | "lodash.defaults": "4.2.0",
117 | "lodash.filter": "4.6.0",
118 | "lodash.flatten": "4.4.0",
119 | "lodash.foreach": "4.5.0",
120 | "lodash.map": "4.6.0",
121 | "lodash.merge": "4.6.0",
122 | "lodash.pick": "4.4.0",
123 | "lodash.reduce": "4.6.0",
124 | "lodash.reject": "4.6.0",
125 | "lodash.some": "4.6.0"
126 | }
127 | },
128 | "co": {
129 | "version": "4.6.0",
130 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
131 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
132 | },
133 | "combined-stream": {
134 | "version": "1.0.5",
135 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
136 | "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
137 | "requires": {
138 | "delayed-stream": "1.0.0"
139 | }
140 | },
141 | "commander": {
142 | "version": "2.9.0",
143 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
144 | "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
145 | "requires": {
146 | "graceful-readlink": "1.0.1"
147 | }
148 | },
149 | "core-util-is": {
150 | "version": "1.0.2",
151 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
152 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
153 | },
154 | "cross-spawn": {
155 | "version": "5.1.0",
156 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
157 | "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
158 | "requires": {
159 | "lru-cache": "4.1.1",
160 | "shebang-command": "1.2.0",
161 | "which": "1.3.0"
162 | }
163 | },
164 | "cryptiles": {
165 | "version": "3.1.2",
166 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
167 | "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
168 | "requires": {
169 | "boom": "5.2.0"
170 | },
171 | "dependencies": {
172 | "boom": {
173 | "version": "5.2.0",
174 | "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
175 | "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
176 | "requires": {
177 | "hoek": "4.2.0"
178 | }
179 | }
180 | }
181 | },
182 | "css-select": {
183 | "version": "1.2.0",
184 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
185 | "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
186 | "requires": {
187 | "boolbase": "1.0.0",
188 | "css-what": "2.1.0",
189 | "domutils": "1.5.1",
190 | "nth-check": "1.0.1"
191 | }
192 | },
193 | "css-what": {
194 | "version": "2.1.0",
195 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz",
196 | "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0="
197 | },
198 | "dashdash": {
199 | "version": "1.14.1",
200 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
201 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
202 | "requires": {
203 | "assert-plus": "1.0.0"
204 | }
205 | },
206 | "datauri": {
207 | "version": "1.0.5",
208 | "resolved": "https://registry.npmjs.org/datauri/-/datauri-1.0.5.tgz",
209 | "integrity": "sha1-0JddGrbI8uDOPKQ7qkU5vhLSiaA=",
210 | "requires": {
211 | "image-size": "0.3.5",
212 | "mimer": "0.2.1",
213 | "semver": "5.4.1"
214 | }
215 | },
216 | "deep-extend": {
217 | "version": "0.5.0",
218 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.0.tgz",
219 | "integrity": "sha1-bvSgmwX5iw41jW2T1Mo8rsZnKAM="
220 | },
221 | "delayed-stream": {
222 | "version": "1.0.0",
223 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
224 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
225 | },
226 | "dom-serializer": {
227 | "version": "0.1.0",
228 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
229 | "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
230 | "requires": {
231 | "domelementtype": "1.1.3",
232 | "entities": "1.1.1"
233 | },
234 | "dependencies": {
235 | "domelementtype": {
236 | "version": "1.1.3",
237 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
238 | "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs="
239 | }
240 | }
241 | },
242 | "domelementtype": {
243 | "version": "1.3.0",
244 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
245 | "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI="
246 | },
247 | "domhandler": {
248 | "version": "2.4.1",
249 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz",
250 | "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=",
251 | "requires": {
252 | "domelementtype": "1.3.0"
253 | }
254 | },
255 | "domutils": {
256 | "version": "1.5.1",
257 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
258 | "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
259 | "requires": {
260 | "dom-serializer": "0.1.0",
261 | "domelementtype": "1.3.0"
262 | }
263 | },
264 | "ecc-jsbn": {
265 | "version": "0.1.1",
266 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
267 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
268 | "optional": true,
269 | "requires": {
270 | "jsbn": "0.1.1"
271 | }
272 | },
273 | "entities": {
274 | "version": "1.1.1",
275 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
276 | "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
277 | },
278 | "escape-string-regexp": {
279 | "version": "1.0.5",
280 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
281 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
282 | },
283 | "extend": {
284 | "version": "3.0.1",
285 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
286 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
287 | },
288 | "extsprintf": {
289 | "version": "1.3.0",
290 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
291 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
292 | },
293 | "fast-deep-equal": {
294 | "version": "1.0.0",
295 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz",
296 | "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8="
297 | },
298 | "forever-agent": {
299 | "version": "0.6.1",
300 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
301 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
302 | },
303 | "form-data": {
304 | "version": "2.3.1",
305 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
306 | "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=",
307 | "requires": {
308 | "asynckit": "0.4.0",
309 | "combined-stream": "1.0.5",
310 | "mime-types": "2.1.17"
311 | }
312 | },
313 | "getpass": {
314 | "version": "0.1.7",
315 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
316 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
317 | "requires": {
318 | "assert-plus": "1.0.0"
319 | }
320 | },
321 | "graceful-readlink": {
322 | "version": "1.0.1",
323 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
324 | "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
325 | },
326 | "har-schema": {
327 | "version": "2.0.0",
328 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
329 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
330 | },
331 | "har-validator": {
332 | "version": "5.0.3",
333 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
334 | "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
335 | "requires": {
336 | "ajv": "5.2.3",
337 | "har-schema": "2.0.0"
338 | }
339 | },
340 | "has-ansi": {
341 | "version": "2.0.0",
342 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
343 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
344 | "requires": {
345 | "ansi-regex": "2.1.1"
346 | }
347 | },
348 | "hawk": {
349 | "version": "6.0.2",
350 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
351 | "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
352 | "requires": {
353 | "boom": "4.3.1",
354 | "cryptiles": "3.1.2",
355 | "hoek": "4.2.0",
356 | "sntp": "2.0.2"
357 | }
358 | },
359 | "hoek": {
360 | "version": "4.2.0",
361 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
362 | "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ=="
363 | },
364 | "htmlparser2": {
365 | "version": "3.9.2",
366 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz",
367 | "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=",
368 | "requires": {
369 | "domelementtype": "1.3.0",
370 | "domhandler": "2.4.1",
371 | "domutils": "1.5.1",
372 | "entities": "1.1.1",
373 | "inherits": "2.0.3",
374 | "readable-stream": "2.3.3"
375 | }
376 | },
377 | "http-signature": {
378 | "version": "1.2.0",
379 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
380 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
381 | "requires": {
382 | "assert-plus": "1.0.0",
383 | "jsprim": "1.4.1",
384 | "sshpk": "1.13.1"
385 | }
386 | },
387 | "image-size": {
388 | "version": "0.3.5",
389 | "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.3.5.tgz",
390 | "integrity": "sha1-gyQOqy+1sAsEqrjHSwRx6cunrYw="
391 | },
392 | "inherits": {
393 | "version": "2.0.3",
394 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
395 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
396 | },
397 | "is-typedarray": {
398 | "version": "1.0.0",
399 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
400 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
401 | },
402 | "isarray": {
403 | "version": "1.0.0",
404 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
405 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
406 | },
407 | "isexe": {
408 | "version": "2.0.0",
409 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
410 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
411 | },
412 | "isstream": {
413 | "version": "0.1.2",
414 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
415 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
416 | },
417 | "jsbn": {
418 | "version": "0.1.1",
419 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
420 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
421 | "optional": true
422 | },
423 | "json-schema": {
424 | "version": "0.2.3",
425 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
426 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
427 | },
428 | "json-schema-traverse": {
429 | "version": "0.3.1",
430 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
431 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
432 | },
433 | "json-stable-stringify": {
434 | "version": "1.0.1",
435 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
436 | "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
437 | "requires": {
438 | "jsonify": "0.0.0"
439 | }
440 | },
441 | "json-stringify-safe": {
442 | "version": "5.0.1",
443 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
444 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
445 | },
446 | "jsonify": {
447 | "version": "0.0.0",
448 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
449 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM="
450 | },
451 | "jsprim": {
452 | "version": "1.4.1",
453 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
454 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
455 | "requires": {
456 | "assert-plus": "1.0.0",
457 | "extsprintf": "1.3.0",
458 | "json-schema": "0.2.3",
459 | "verror": "1.10.0"
460 | }
461 | },
462 | "juice": {
463 | "version": "4.2.0",
464 | "resolved": "https://registry.npmjs.org/juice/-/juice-4.2.0.tgz",
465 | "integrity": "sha512-JCOZtBxh/BnzYNeHYPs3EhFjD5uUKpmsaOXMO7aeZuDH4HrSTdIpBtoWE2mUCb+5F9KysImSUGmKHpIULSd5HQ==",
466 | "requires": {
467 | "cheerio": "0.22.0",
468 | "commander": "2.9.0",
469 | "cross-spawn": "5.1.0",
470 | "deep-extend": "0.5.0",
471 | "mensch": "0.3.3",
472 | "slick": "1.12.2",
473 | "web-resource-inliner": "4.2.0"
474 | }
475 | },
476 | "lodash": {
477 | "version": "4.17.4",
478 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
479 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
480 | },
481 | "lodash.assignin": {
482 | "version": "4.2.0",
483 | "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
484 | "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI="
485 | },
486 | "lodash.bind": {
487 | "version": "4.2.1",
488 | "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz",
489 | "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU="
490 | },
491 | "lodash.defaults": {
492 | "version": "4.2.0",
493 | "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
494 | "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
495 | },
496 | "lodash.filter": {
497 | "version": "4.6.0",
498 | "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz",
499 | "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4="
500 | },
501 | "lodash.flatten": {
502 | "version": "4.4.0",
503 | "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
504 | "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
505 | },
506 | "lodash.foreach": {
507 | "version": "4.5.0",
508 | "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
509 | "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM="
510 | },
511 | "lodash.map": {
512 | "version": "4.6.0",
513 | "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz",
514 | "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM="
515 | },
516 | "lodash.merge": {
517 | "version": "4.6.0",
518 | "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.0.tgz",
519 | "integrity": "sha1-aYhLoUSsM/5plzemCG3v+t0PicU="
520 | },
521 | "lodash.pick": {
522 | "version": "4.4.0",
523 | "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
524 | "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM="
525 | },
526 | "lodash.reduce": {
527 | "version": "4.6.0",
528 | "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz",
529 | "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs="
530 | },
531 | "lodash.reject": {
532 | "version": "4.6.0",
533 | "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz",
534 | "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU="
535 | },
536 | "lodash.some": {
537 | "version": "4.6.0",
538 | "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz",
539 | "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0="
540 | },
541 | "lodash.unescape": {
542 | "version": "4.0.1",
543 | "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
544 | "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw="
545 | },
546 | "lru-cache": {
547 | "version": "4.1.1",
548 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
549 | "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
550 | "requires": {
551 | "pseudomap": "1.0.2",
552 | "yallist": "2.1.2"
553 | }
554 | },
555 | "mensch": {
556 | "version": "0.3.3",
557 | "resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.3.tgz",
558 | "integrity": "sha1-4gD/TdgjcX+OBWOzLj9UgfyiYrI="
559 | },
560 | "mime-db": {
561 | "version": "1.30.0",
562 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
563 | "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE="
564 | },
565 | "mime-types": {
566 | "version": "2.1.17",
567 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
568 | "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
569 | "requires": {
570 | "mime-db": "1.30.0"
571 | }
572 | },
573 | "mimer": {
574 | "version": "0.2.1",
575 | "resolved": "https://registry.npmjs.org/mimer/-/mimer-0.2.1.tgz",
576 | "integrity": "sha1-xjxaF/6GQj9RYahdVcPtUYm6r/w="
577 | },
578 | "nth-check": {
579 | "version": "1.0.1",
580 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz",
581 | "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=",
582 | "requires": {
583 | "boolbase": "1.0.0"
584 | }
585 | },
586 | "oauth-sign": {
587 | "version": "0.8.2",
588 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
589 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
590 | },
591 | "performance-now": {
592 | "version": "2.1.0",
593 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
594 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
595 | },
596 | "process-nextick-args": {
597 | "version": "1.0.7",
598 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
599 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
600 | },
601 | "pseudomap": {
602 | "version": "1.0.2",
603 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
604 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
605 | },
606 | "punycode": {
607 | "version": "1.4.1",
608 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
609 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
610 | },
611 | "qs": {
612 | "version": "6.5.1",
613 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
614 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
615 | },
616 | "readable-stream": {
617 | "version": "2.3.3",
618 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
619 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
620 | "requires": {
621 | "core-util-is": "1.0.2",
622 | "inherits": "2.0.3",
623 | "isarray": "1.0.0",
624 | "process-nextick-args": "1.0.7",
625 | "safe-buffer": "5.1.1",
626 | "string_decoder": "1.0.3",
627 | "util-deprecate": "1.0.2"
628 | }
629 | },
630 | "request": {
631 | "version": "2.83.0",
632 | "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
633 | "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==",
634 | "requires": {
635 | "aws-sign2": "0.7.0",
636 | "aws4": "1.6.0",
637 | "caseless": "0.12.0",
638 | "combined-stream": "1.0.5",
639 | "extend": "3.0.1",
640 | "forever-agent": "0.6.1",
641 | "form-data": "2.3.1",
642 | "har-validator": "5.0.3",
643 | "hawk": "6.0.2",
644 | "http-signature": "1.2.0",
645 | "is-typedarray": "1.0.0",
646 | "isstream": "0.1.2",
647 | "json-stringify-safe": "5.0.1",
648 | "mime-types": "2.1.17",
649 | "oauth-sign": "0.8.2",
650 | "performance-now": "2.1.0",
651 | "qs": "6.5.1",
652 | "safe-buffer": "5.1.1",
653 | "stringstream": "0.0.5",
654 | "tough-cookie": "2.3.3",
655 | "tunnel-agent": "0.6.0",
656 | "uuid": "3.1.0"
657 | }
658 | },
659 | "safe-buffer": {
660 | "version": "5.1.1",
661 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
662 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
663 | },
664 | "semver": {
665 | "version": "5.4.1",
666 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
667 | "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
668 | },
669 | "shebang-command": {
670 | "version": "1.2.0",
671 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
672 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
673 | "requires": {
674 | "shebang-regex": "1.0.0"
675 | }
676 | },
677 | "shebang-regex": {
678 | "version": "1.0.0",
679 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
680 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
681 | },
682 | "slick": {
683 | "version": "1.12.2",
684 | "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz",
685 | "integrity": "sha1-vQSN23TefRymkV+qSldXCzVQwtc="
686 | },
687 | "sntp": {
688 | "version": "2.0.2",
689 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.0.2.tgz",
690 | "integrity": "sha1-UGQRDwr4X3z9t9a2ekACjOUrSys=",
691 | "requires": {
692 | "hoek": "4.2.0"
693 | }
694 | },
695 | "sshpk": {
696 | "version": "1.13.1",
697 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
698 | "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
699 | "requires": {
700 | "asn1": "0.2.3",
701 | "assert-plus": "1.0.0",
702 | "bcrypt-pbkdf": "1.0.1",
703 | "dashdash": "1.14.1",
704 | "ecc-jsbn": "0.1.1",
705 | "getpass": "0.1.7",
706 | "jsbn": "0.1.1",
707 | "tweetnacl": "0.14.5"
708 | }
709 | },
710 | "string_decoder": {
711 | "version": "1.0.3",
712 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
713 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
714 | "requires": {
715 | "safe-buffer": "5.1.1"
716 | }
717 | },
718 | "stringstream": {
719 | "version": "0.0.5",
720 | "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
721 | "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg="
722 | },
723 | "strip-ansi": {
724 | "version": "3.0.1",
725 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
726 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
727 | "requires": {
728 | "ansi-regex": "2.1.1"
729 | }
730 | },
731 | "supports-color": {
732 | "version": "2.0.0",
733 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
734 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
735 | },
736 | "tough-cookie": {
737 | "version": "2.3.3",
738 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
739 | "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=",
740 | "requires": {
741 | "punycode": "1.4.1"
742 | }
743 | },
744 | "tunnel-agent": {
745 | "version": "0.6.0",
746 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
747 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
748 | "requires": {
749 | "safe-buffer": "5.1.1"
750 | }
751 | },
752 | "tweetnacl": {
753 | "version": "0.14.5",
754 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
755 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
756 | "optional": true
757 | },
758 | "util-deprecate": {
759 | "version": "1.0.2",
760 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
761 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
762 | },
763 | "uuid": {
764 | "version": "3.1.0",
765 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
766 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
767 | },
768 | "valid-data-url": {
769 | "version": "0.1.4",
770 | "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.4.tgz",
771 | "integrity": "sha512-p3bCVl3Vrz42TV37a1OjagyLLd6qQAXBDWarIazuo7NQzCt8Kw8ZZwSAbUVPGlz5ubgbgJmgT0KRjLeCFNrfoQ=="
772 | },
773 | "verror": {
774 | "version": "1.10.0",
775 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
776 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
777 | "requires": {
778 | "assert-plus": "1.0.0",
779 | "core-util-is": "1.0.2",
780 | "extsprintf": "1.3.0"
781 | }
782 | },
783 | "web-resource-inliner": {
784 | "version": "4.2.0",
785 | "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-4.2.0.tgz",
786 | "integrity": "sha512-NvLvZzKvnNAB3LXG5c12WwUx5ZA7ZfNMYq82GnbhFyBLuu3jtamW4tQ40M02XiQzkFsyDuWG6Y2TOq9yywaxlg==",
787 | "requires": {
788 | "async": "2.5.0",
789 | "chalk": "1.1.3",
790 | "datauri": "1.0.5",
791 | "htmlparser2": "3.9.2",
792 | "lodash.unescape": "4.0.1",
793 | "request": "2.83.0",
794 | "valid-data-url": "0.1.4",
795 | "xtend": "4.0.1"
796 | }
797 | },
798 | "which": {
799 | "version": "1.3.0",
800 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
801 | "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
802 | "requires": {
803 | "isexe": "2.0.0"
804 | }
805 | },
806 | "xtend": {
807 | "version": "4.0.1",
808 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
809 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
810 | },
811 | "yallist": {
812 | "version": "2.1.2",
813 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
814 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
815 | }
816 | }
817 | }
818 |
--------------------------------------------------------------------------------
/packages/heml-inline/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heml/inline",
3 | "version": "1.1.2",
4 | "description": "CSS inliner for HEML",
5 | "keywords": [
6 | "heml"
7 | ],
8 | "homepage": "https://heml.io",
9 | "bugs": "https://github.com/SparkPost/heml/issues",
10 | "license": "MIT",
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/SparkPost/heml.git"
14 | },
15 | "author": "SparkPost (https://sparkpost.com)",
16 | "files": [
17 | "build/"
18 | ],
19 | "main": "build/index.js",
20 | "publishConfig": {
21 | "access": "public"
22 | },
23 | "dependencies": {
24 | "juice": "^4.2.0",
25 | "lodash": "^4.17.4"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/heml-inline/src/fixWidthsFor.js:
--------------------------------------------------------------------------------
1 | import { setProp, getProp } from './styleHelper'
2 |
3 | /**
4 | * Converts all width properties on the given tag to be a fixed value
5 | * when any given image has an ancestor with a fixed width
6 | * (fix for outlook)
7 | * @param {Cheerio} $
8 | * @param {String} selector
9 | */
10 | export default function fixWidthsFor ($, selector) {
11 | // get all relative widths and set them to fixed values by default
12 | $(`${selector}`).filter(`[width*="%"]`).toNodes().forEach(($node) => {
13 | const nodeWidth = $node.attr('width')
14 | /**
15 | * Gather all the parent percents and multiply them against
16 | * the image and fixed parent width
17 | */
18 | let parentPercent = 1
19 |
20 | for (let $el of $node.parents().toNodes()) {
21 | const parentWidth = $el.attr('width') || getProp($el.attr('style'), 'width')
22 |
23 | if (parentWidth && !parentWidth.endsWith('%')) {
24 | const currentStyles = $node.attr('style')
25 |
26 | $node.attr('style', setProp(currentStyles, 'width', nodeWidth))
27 | $node.attr('width', parseFloat(parentWidth, 10) * parentPercent * parseFloat(nodeWidth, 10) / 100)
28 |
29 | break
30 | } else if (parentWidth && parentWidth.endsWith('%')) {
31 | parentPercent = parentPercent * parseFloat(parentWidth, 10) / 100
32 | }
33 | }
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/packages/heml-inline/src/index.js:
--------------------------------------------------------------------------------
1 | import juice from 'juice'
2 | import inlineMargins from './inlineMargins'
3 | import fixWidthsFor from './fixWidthsFor'
4 | import removeProcessingIds from './removeProcessingIds'
5 | import preferMaxWidth from './preferMaxWidth'
6 |
7 | function inline ($, options = {}) {
8 | const { juice: juiceOptions = {} } = options
9 |
10 | juice.juiceDocument($, {
11 | ...juiceOptions
12 | })
13 |
14 | inlineMargins($)
15 | preferMaxWidth($, '[class$="__ie"]')
16 | fixWidthsFor($, 'img, .block__table__ie, .column')
17 | removeProcessingIds($)
18 |
19 | return $
20 | }
21 |
22 | export default inline
23 |
--------------------------------------------------------------------------------
/packages/heml-inline/src/inlineMargins.js:
--------------------------------------------------------------------------------
1 | import { compact, first, last, nth } from 'lodash'
2 |
3 | /**
4 | * finds all the tables that are centered with margins
5 | * and centers them with the align attribute
6 | * @param {Cheerio} $
7 | */
8 | function inlineMargins ($) {
9 | $('table[style*=margin]').toNodes().forEach(($el) => {
10 | const { left, right } = getSideMargins($el.attr('style'))
11 |
12 | if (left === 'auto' && right === 'auto') {
13 | $el.attr('align', 'center')
14 | } else if (left === 'auto' && right !== 'auto') {
15 | $el.attr('align', 'right')
16 | } else if (left !== 'auto') {
17 | $el.attr('align', 'left')
18 | }
19 | })
20 | }
21 |
22 | /**
23 | * pulls the left and right margins from the given inline styles
24 | * @param {String} style the inline styles
25 | * @return {Object} object with left and right margins
26 | */
27 | function getSideMargins (style) {
28 | const margins = compact(style.split(';')).map((decl) => {
29 | const split = decl.split(':')
30 |
31 | return {
32 | prop: first(split).trim().toLowerCase(),
33 | value: last(split).trim().toLowerCase()
34 | }
35 | }).filter(({ prop, value }) => prop.indexOf('margin') === 0)
36 |
37 | let left = 0
38 | let right = 0
39 | margins.forEach(({ prop, value }) => {
40 | if (prop === 'margin-left') {
41 | left = value
42 | return
43 | }
44 |
45 | if (prop === 'margin-right') {
46 | right = value
47 | return
48 | }
49 |
50 | if (prop === 'margin') {
51 | const values = value.split(' ').map((i) => i.trim())
52 |
53 | switch (values.length) {
54 | case 1:
55 | right = first(values)
56 | left = first(values)
57 | break
58 |
59 | case 2:
60 | right = last(values)
61 | left = last(values)
62 | break
63 |
64 | case 3:
65 | right = nth(values, 1)
66 | left = nth(values, 1)
67 | break
68 |
69 | default:
70 | right = nth(values, 1)
71 | left = nth(values, 3)
72 | break
73 | }
74 | }
75 | })
76 |
77 | return { left, right }
78 | }
79 |
80 | export default inlineMargins
81 |
--------------------------------------------------------------------------------
/packages/heml-inline/src/preferMaxWidth.js:
--------------------------------------------------------------------------------
1 | import { setProp, getProp, removeProp } from './styleHelper'
2 |
3 | export default function preferMaxWidth ($, selector) {
4 | $(selector).toNodes().forEach(($node) => {
5 | const maxWidth = getProp($node.attr('style'), 'max-width')
6 | const width = $node.attr('width') || ''
7 |
8 | if (!maxWidth) { return }
9 |
10 | const maxWidthIsPxValue = maxWidth && maxWidth.endsWith('px')
11 |
12 | const maxWidthIsSmallerThenWidth = maxWidth.endsWith('%') && width.endsWith('%') && (parseInt(maxWidth, 10) < parseInt(width, 10))
13 |
14 | if (maxWidthIsPxValue || maxWidthIsSmallerThenWidth) {
15 | let styles = removeProp($node.attr('style'), 'max-width')
16 | styles = removeProp(styles, 'width')
17 |
18 | $node.attr('width', maxWidth.replace('px', ''))
19 | $node.attr('style', setProp(styles, 'width', maxWidth))
20 | }
21 | })
22 | }
23 |
--------------------------------------------------------------------------------
/packages/heml-inline/src/removeProcessingIds.js:
--------------------------------------------------------------------------------
1 | /**
2 | * remove all ids used for processing only
3 | * @param {Cheerio} $
4 | */
5 | function removeProcessingIds ($) {
6 | $('[id^="heml-"]').removeAttr('id')
7 | }
8 |
9 | export default removeProcessingIds
10 |
--------------------------------------------------------------------------------
/packages/heml-inline/src/styleHelper.js:
--------------------------------------------------------------------------------
1 | import { compact } from 'lodash'
2 |
3 | /**
4 | * Gets the value of a prop in a given inline style string
5 | * @param {String} style inline styles
6 | * @param {String} prop prop to get
7 | *
8 | * @return {String} style
9 | */
10 | function getProp (style = '', prop) {
11 | prop = prop.trim().toLowerCase()
12 | const decls = style.split(';')
13 |
14 | let value = false
15 |
16 | decls.forEach((decl) => {
17 | if (decl.trim().toLowerCase().startsWith(`${prop}:`)) {
18 | value = decl.split(':')[1].trim()
19 | }
20 | })
21 |
22 | return value
23 | }
24 |
25 | /**
26 | * Sets the value of a prop in a given inline style string
27 | * @param {String} style inline styles
28 | * @param {String} prop prop to update/add
29 | * @param {String} value new value
30 | *
31 | * @return {String} style
32 | */
33 | function setProp (style = '', prop, value) {
34 | prop = prop.trim().toLowerCase()
35 | const decls = style.split(';')
36 |
37 | let updated = false
38 |
39 | const updatedDecls = decls.map((decl) => {
40 | if (decl.trim().toLowerCase().startsWith(`${prop}:`)) {
41 | updated = true
42 | return `${prop}: ${value}`
43 | }
44 |
45 | return decl
46 | })
47 |
48 | if (!updated) { updatedDecls.push(`${prop}: ${value}`) }
49 |
50 | return compact(updatedDecls).join(';')
51 | }
52 |
53 | /**
54 | * removes a prop in a given inline style string
55 | * @param {String} style inline styles
56 | * @param {String} prop prop to remove
57 | *
58 | * @return {String} style
59 | */
60 | function removeProp (style = '', prop) {
61 | prop = prop.trim().toLowerCase()
62 | const decls = style.split(';')
63 |
64 | const updatedDecls = decls.map((decl) => {
65 | if (decl.trim().toLowerCase().startsWith(`${prop}:`)) {
66 | return false
67 | }
68 |
69 | return decl
70 | })
71 |
72 | return compact(updatedDecls).join(';')
73 | }
74 |
75 | export { getProp, setProp, removeProp }
76 |
--------------------------------------------------------------------------------
/packages/heml-parse/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heml/parse",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@types/node": {
8 | "version": "6.0.89",
9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.89.tgz",
10 | "integrity": "sha512-Z/67L97+6H1qJiEEHSN1SQapkWjDss1D90rAnFcQ6UxKkah9juzotK5UNEP1bDv/0lJ3NAQTnVfc/JWdgCGruA=="
11 | },
12 | "boolbase": {
13 | "version": "1.0.0",
14 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
15 | "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
16 | },
17 | "cheerio": {
18 | "version": "1.0.0-rc.2",
19 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz",
20 | "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=",
21 | "requires": {
22 | "css-select": "1.2.0",
23 | "dom-serializer": "0.1.0",
24 | "entities": "1.1.1",
25 | "htmlparser2": "3.9.2",
26 | "lodash": "4.17.4",
27 | "parse5": "3.0.2"
28 | }
29 | },
30 | "core-util-is": {
31 | "version": "1.0.2",
32 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
33 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
34 | },
35 | "crypto-random-string": {
36 | "version": "1.0.0",
37 | "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
38 | "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4="
39 | },
40 | "css-select": {
41 | "version": "1.2.0",
42 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
43 | "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
44 | "requires": {
45 | "boolbase": "1.0.0",
46 | "css-what": "2.1.0",
47 | "domutils": "1.5.1",
48 | "nth-check": "1.0.1"
49 | }
50 | },
51 | "css-what": {
52 | "version": "2.1.0",
53 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz",
54 | "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0="
55 | },
56 | "dom-serializer": {
57 | "version": "0.1.0",
58 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
59 | "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
60 | "requires": {
61 | "domelementtype": "1.1.3",
62 | "entities": "1.1.1"
63 | },
64 | "dependencies": {
65 | "domelementtype": {
66 | "version": "1.1.3",
67 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
68 | "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs="
69 | }
70 | }
71 | },
72 | "domelementtype": {
73 | "version": "1.3.0",
74 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
75 | "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI="
76 | },
77 | "domhandler": {
78 | "version": "2.4.1",
79 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz",
80 | "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=",
81 | "requires": {
82 | "domelementtype": "1.3.0"
83 | }
84 | },
85 | "domutils": {
86 | "version": "1.5.1",
87 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
88 | "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
89 | "requires": {
90 | "dom-serializer": "0.1.0",
91 | "domelementtype": "1.3.0"
92 | }
93 | },
94 | "entities": {
95 | "version": "1.1.1",
96 | "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
97 | "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
98 | },
99 | "html-tags": {
100 | "version": "2.0.0",
101 | "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz",
102 | "integrity": "sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos="
103 | },
104 | "htmlparser2": {
105 | "version": "3.9.2",
106 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz",
107 | "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=",
108 | "requires": {
109 | "domelementtype": "1.3.0",
110 | "domhandler": "2.4.1",
111 | "domutils": "1.5.1",
112 | "entities": "1.1.1",
113 | "inherits": "2.0.3",
114 | "readable-stream": "2.3.3"
115 | }
116 | },
117 | "inherits": {
118 | "version": "2.0.3",
119 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
120 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
121 | },
122 | "isarray": {
123 | "version": "1.0.0",
124 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
125 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
126 | },
127 | "lodash": {
128 | "version": "4.17.4",
129 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
130 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
131 | },
132 | "nth-check": {
133 | "version": "1.0.1",
134 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz",
135 | "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=",
136 | "requires": {
137 | "boolbase": "1.0.0"
138 | }
139 | },
140 | "parse5": {
141 | "version": "3.0.2",
142 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.2.tgz",
143 | "integrity": "sha1-Be/1fw70V3+xRKefi5qWemzERRA=",
144 | "requires": {
145 | "@types/node": "6.0.89"
146 | }
147 | },
148 | "process-nextick-args": {
149 | "version": "1.0.7",
150 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
151 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
152 | },
153 | "readable-stream": {
154 | "version": "2.3.3",
155 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
156 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
157 | "requires": {
158 | "core-util-is": "1.0.2",
159 | "inherits": "2.0.3",
160 | "isarray": "1.0.0",
161 | "process-nextick-args": "1.0.7",
162 | "safe-buffer": "5.1.1",
163 | "string_decoder": "1.0.3",
164 | "util-deprecate": "1.0.2"
165 | }
166 | },
167 | "safe-buffer": {
168 | "version": "5.1.1",
169 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
170 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
171 | },
172 | "string_decoder": {
173 | "version": "1.0.3",
174 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
175 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
176 | "requires": {
177 | "safe-buffer": "5.1.1"
178 | }
179 | },
180 | "util-deprecate": {
181 | "version": "1.0.2",
182 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
183 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/packages/heml-parse/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heml/parse",
3 | "version": "1.1.2",
4 | "description": "Parser for HEML",
5 | "keywords": [
6 | "heml"
7 | ],
8 | "homepage": "https://heml.io",
9 | "bugs": "https://github.com/SparkPost/heml/issues",
10 | "license": "MIT",
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/SparkPost/heml.git"
14 | },
15 | "author": "SparkPost (https://sparkpost.com)",
16 | "files": [
17 | "build/"
18 | ],
19 | "main": "build/index.js",
20 | "publishConfig": {
21 | "access": "public"
22 | },
23 | "dependencies": {
24 | "cheerio": "^1.0.0-rc.2",
25 | "crypto-random-string": "^1.0.0",
26 | "html-tags": "^2.0.0",
27 | "lodash": "^4.17.4"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/heml-parse/src/index.js:
--------------------------------------------------------------------------------
1 | import { load } from 'cheerio'
2 | import { difference, compact, first } from 'lodash'
3 | import randomString from 'crypto-random-string'
4 | import htmlTags from 'html-tags'
5 | import selfClosingHtmlTags from 'html-tags/void'
6 |
7 | const wrappingHtmlTags = difference(htmlTags, selfClosingHtmlTags)
8 |
9 | function parse (contents, options = {}) {
10 | const {
11 | elements = [],
12 | cheerio: cheerioOptions = {}
13 | } = options
14 |
15 | const $ = load(contents, {
16 | xmlMode: true,
17 | lowerCaseTags: true,
18 | decodeEntities: false,
19 | ...cheerioOptions
20 | })
21 |
22 | $.findNodes = function (q) {
23 | return $(Array.isArray(q) ? q.join(',') : q)
24 | .not('[heml-ignore]')
25 | .toNodes()
26 | }
27 |
28 | $.prototype.toNodes = function () {
29 | return this
30 | .toArray()
31 | .map((node) => $(node))
32 | }
33 |
34 | const selfClosingTags = [
35 | ...selfClosingHtmlTags,
36 | ...elements.filter((element) => element.children === false).map(({ tagName }) => tagName) ]
37 | const wrappingTags = [
38 | ...wrappingHtmlTags,
39 | ...elements.filter((element) => element.children !== false).map(({ tagName }) => tagName) ]
40 |
41 | const $selfClosingNodes = $.findNodes(selfClosingTags).reverse()
42 | const $wrappingNodes = $.findNodes(wrappingTags).reverse()
43 |
44 | /** Move contents from self wrapping tags outside of itself */
45 | $selfClosingNodes.forEach(($node) => {
46 | $node.after($node.html())
47 | $node.html('')
48 | })
49 |
50 | /** ensure that all wrapping tags have at least a zero-width, non-joining character */
51 | $wrappingNodes.forEach(($node) => {
52 | if ($node.html().length === 0) {
53 | $node.html(' ')
54 | }
55 | })
56 |
57 | /** try for head, fallback to body, then heml */
58 | const $head = first(compact([...$('head').toNodes(), ...$('body').toNodes(), ...$('heml').toNodes()]))
59 |
60 | /** move inline styles to a style tag with unique ids so they can be hit by the css processor */
61 | if ($head) {
62 | const $inlineStyleNodes = $.findNodes(elements.map(({ tagName }) => tagName)).filter($node => !!$node.attr('style'))
63 |
64 | const inlineCSS = $inlineStyleNodes.map(($node) => {
65 | let id = $node.attr('id')
66 | const css = $node.attr('style')
67 | $node.removeAttr('style')
68 |
69 | if (!id) {
70 | id = `heml-${randomString(5)}`
71 | $node.attr('id', id)
72 | }
73 |
74 | return `#${id} {${css}}`
75 | }).join('\n')
76 |
77 | $head.append(``)
78 | }
79 |
80 | return $
81 | }
82 |
83 | export default parse
84 |
--------------------------------------------------------------------------------
/packages/heml-render/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "requires": true,
3 | "lockfileVersion": 1,
4 | "dependencies": {
5 | "escape-goat": {
6 | "version": "1.3.0",
7 | "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-1.3.0.tgz",
8 | "integrity": "sha512-E2nU1Y39N5UgfLU8qwMlK0vZrZprIwWLeVmDYN8wd/e37hMtGzu2w1DBiREts0XHfgyZEQlj/hYr0H0izF0HDQ=="
9 | },
10 | "html-tags": {
11 | "version": "2.0.0",
12 | "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz",
13 | "integrity": "sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos="
14 | },
15 | "is-promise": {
16 | "version": "2.1.0",
17 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
18 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
19 | },
20 | "lodash": {
21 | "version": "4.17.4",
22 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
23 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
24 | },
25 | "stringify-attributes": {
26 | "version": "1.0.0",
27 | "resolved": "https://registry.npmjs.org/stringify-attributes/-/stringify-attributes-1.0.0.tgz",
28 | "integrity": "sha1-nosvmpRn57SAk8shJOvBwX5jgsU=",
29 | "requires": {
30 | "escape-goat": "1.3.0"
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/heml-render/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heml/render",
3 | "version": "1.1.2",
4 | "description": "Renderer for HEML",
5 | "keywords": [
6 | "heml"
7 | ],
8 | "homepage": "https://heml.io",
9 | "bugs": "https://github.com/SparkPost/heml/issues",
10 | "license": "MIT",
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/SparkPost/heml.git"
14 | },
15 | "author": "SparkPost (https://sparkpost.com)",
16 | "files": [
17 | "build/"
18 | ],
19 | "main": "build/index.js",
20 | "publishConfig": {
21 | "access": "public"
22 | },
23 | "dependencies": {
24 | "html-tags": "^2.0.0",
25 | "is-promise": "^2.1.0",
26 | "lodash": "^4.17.4"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/heml-render/src/createHtmlElement.js:
--------------------------------------------------------------------------------
1 | import stringifyAttributes from './stringifyAttributes'
2 | import selfClosingHtmlTags from 'html-tags/void'
3 |
4 | export default function createHtmlElement ({ name, attrs, contents = ' ' }) {
5 | if (selfClosingHtmlTags.includes(name)) {
6 | return `<${name}${attrs ? stringifyAttributes(attrs) : ''} />`
7 | }
8 |
9 | return `<${name}${attrs ? stringifyAttributes(attrs) : ''}>${contents || ' '}${name}>`
10 | }
11 |
--------------------------------------------------------------------------------
/packages/heml-render/src/index.js:
--------------------------------------------------------------------------------
1 | import { filter, difference, keyBy, first } from 'lodash'
2 | import renderElement from './renderElement'
3 |
4 | export { renderElement }
5 |
6 | /**
7 | * preRender, render, and postRender all elements
8 | * @param {Array} elements List of element definitons
9 | * @param {Object} globals
10 | * @return {Promise} Returns an object with the cheerio object and metadata
11 | */
12 | export default async function render ($, options = {}) {
13 | const {
14 | elements = []
15 | } = options
16 |
17 | const globals = { $, elements, options }
18 | const Meta = first(elements.filter(({ tagName }) => tagName === 'meta'))
19 |
20 | await preRenderElements(elements, globals)
21 | await renderElements(elements, globals)
22 | await postRenderElements(elements, globals)
23 |
24 | return { $, metadata: Meta ? Meta.flush() : {} }
25 | }
26 |
27 | /**
28 | * Run the async preRender functions for each element
29 | * @param {Array} elements List of element definitons
30 | * @param {Object} globals
31 | * @return {Promise}
32 | */
33 | async function preRenderElements (elements, globals) {
34 | for (let element of elements) {
35 | await element.preRender(globals)
36 | }
37 | }
38 |
39 | /**
40 | * Run the async postRender functions for each element
41 | * @param {Array} elements List of element definitons
42 | * @param {Object} globals
43 | * @return {Promise}
44 | */
45 | async function postRenderElements (elements, globals) {
46 | for (let element of elements) {
47 | await element.postRender(globals)
48 | }
49 | }
50 |
51 | /**
52 | * Renders all HEML elements
53 | * @param {Array} elements List of element definitons
54 | * @param {Object} globals
55 | * @return {Promise}
56 | */
57 | async function renderElements (elements, globals) {
58 | const { $ } = globals
59 | const elementMap = keyBy(elements, 'tagName')
60 | const metaTagNames = filter(elements, { parent: [ 'head' ] }).map(({ tagName }) => tagName)
61 | const nonMetaTagNames = difference(elements.map(({ tagName }) => tagName), metaTagNames)
62 |
63 | const $nodes = [
64 | ...$.findNodes(metaTagNames), /** Render the meta elements first to last */
65 | ...$.findNodes(nonMetaTagNames).reverse() /** Render the elements last to first/outside to inside */
66 | ]
67 |
68 | for (let $node of $nodes) {
69 | const element = elementMap[$node.prop('tagName').toLowerCase()]
70 | const contents = $node.html()
71 | const attrs = $node[0].attribs
72 |
73 | const renderedValue = await Promise.resolve(renderElement(element, attrs, contents))
74 |
75 | $node.replaceWith(renderedValue.trim())
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/packages/heml-render/src/renderElement.js:
--------------------------------------------------------------------------------
1 | import isPromise from 'is-promise'
2 | import { isPlainObject, defaults, mapValues, castArray, compact, flattenDeep } from 'lodash'
3 | import createHtmlElement from './createHtmlElement'
4 |
5 | export default function (name, attrs, ...contents) {
6 | /** catch all promises in this content and wait for them to finish */
7 | if (contents.filter(isPromise).length > 0) { return Promise.all(contents).then((contents) => render(name, attrs, contents.join(''))) }
8 |
9 | return render(name, attrs, contents.join(''))
10 | }
11 |
12 | function render (name, attrs, contents) {
13 | if (!name || (isPlainObject(name) && !name.render)) {
14 | throw new Error(`name must be a HEML element or HTML tag name (.e.g 'td'). Received: ${JSON.stringify(name)}`)
15 | }
16 |
17 | if (isPlainObject(name) && name.render) {
18 | /** set the defaults and massage attribute values */
19 | attrs = defaults({}, attrs, name.defaultAttrs || {})
20 | attrs = mapValues(attrs, (value, name) => {
21 | if ((value === '' && name !== 'class') || value === 'true' || value === 'on') { return true }
22 |
23 | if (value === 'false' || value === 'off') { return false }
24 |
25 | return value
26 | })
27 |
28 | /**
29 | * custom elements can return promises, arrays, or strings
30 | *
31 | * we will:
32 | * 1. check for the shorthands and render on that
33 | * 2. return a string synchronously if we can
34 | * 3. return a string in a promise
35 | */
36 | const renderResults = castArray(name.render(attrs, contents))
37 |
38 | /** 1. catch shorthands for rerendering the element */
39 | if (renderResults.length === 1 && renderResults[0] === true) {
40 | return render(name.tagName, attrs, contents)
41 | }
42 |
43 | /** 2. we want to return synchronously if we can */
44 | if (renderResults.filter(isPromise).length === 0) {
45 | return compact(renderResults).join('')
46 | }
47 |
48 | /** otherwise, combine the array of promises/strings into a single string */
49 | return Promise.all(renderResults).then((results) => {
50 | return compact(flattenDeep(results)).join('')
51 | })
52 | }
53 |
54 | /** if we have a regular ol element go ahead and convert it to a string */
55 | if (attrs && attrs.class === '') { delete attrs.class }
56 | if (attrs && attrs.class) { attrs.class = attrs.class.trim() }
57 |
58 | return createHtmlElement({ name, attrs, contents })
59 | }
60 |
--------------------------------------------------------------------------------
/packages/heml-render/src/stringifyAttributes.js:
--------------------------------------------------------------------------------
1 | /** escapeless version of npmjs.com/stringify-attributes */
2 | export default function stringifyAttributes (attrsObj) {
3 | const attributes = []
4 |
5 | for (let [ key, value ] of Object.entries(attrsObj)) {
6 | if (value === false) { continue }
7 |
8 | if (Array.isArray(value)) { value = value.join(' ') }
9 |
10 | value = value === true ? '' : `="${String(value)}"`
11 |
12 | attributes.push(`${key}${value}`)
13 | }
14 |
15 | return attributes.length > 0 ? ' ' + attributes.join(' ') : ''
16 | }
17 |
--------------------------------------------------------------------------------
/packages/heml-styles/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heml/styles",
3 | "version": "1.1.2",
4 | "description": "CSS processor for HEML",
5 | "keywords": [
6 | "heml"
7 | ],
8 | "homepage": "https://heml.io",
9 | "bugs": "https://github.com/SparkPost/heml/issues",
10 | "license": "MIT",
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/SparkPost/heml.git"
14 | },
15 | "author": "SparkPost (https://sparkpost.com)",
16 | "files": [
17 | "build/"
18 | ],
19 | "main": "build/index.js",
20 | "publishConfig": {
21 | "access": "public"
22 | },
23 | "dependencies": {
24 | "css-declaration-sorter": "^2.1.0",
25 | "css-shorthand-expand": "^1.1.0",
26 | "lodash": "^4.17.4",
27 | "postcss": "^6.0.13",
28 | "postcss-calc": "^6.0.1",
29 | "postcss-color-rgba-fallback": "^3.0.0",
30 | "postcss-colornames-to-hex": "^1.0.1",
31 | "postcss-convert-values": "^2.6.1",
32 | "postcss-discard-comments": "^2.0.4",
33 | "postcss-discard-duplicates": "^2.1.0",
34 | "postcss-discard-empty": "^2.1.0",
35 | "postcss-discard-overridden": "^0.1.1",
36 | "postcss-email-important": "^1.0.0",
37 | "postcss-hex-format": "^1.0.0",
38 | "postcss-merge-longhand": "^3.0.0",
39 | "postcss-merge-rules": "^2.1.2",
40 | "postcss-minify-font-values": "^1.0.5",
41 | "postcss-minify-gradients": "^1.0.5",
42 | "postcss-minify-params": "^1.2.2",
43 | "postcss-minify-selectors": "^2.1.1",
44 | "postcss-normalize-display-values": "^4.0.0-rc.2",
45 | "postcss-normalize-positions": "^4.0.0-rc.2",
46 | "postcss-normalize-repeat-style": "^4.0.0-rc.2",
47 | "postcss-normalize-string": "^4.0.0-rc.2",
48 | "postcss-normalize-timing-functions": "^4.0.0-rc.2",
49 | "postcss-ordered-values": "^2.2.3",
50 | "postcss-rgba-hex": "^0.3.7",
51 | "postcss-safe-parser": "^3.0.1",
52 | "postcss-shorthand-expand": "^1.0.1",
53 | "postcss-unique-selectors": "^2.0.2"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/heml-styles/src/index.js:
--------------------------------------------------------------------------------
1 | import postcss from 'postcss'
2 | import safeParser from 'postcss-safe-parser'
3 |
4 | /** optimize css - credz to cssnano */
5 | import discardComments from 'postcss-discard-comments'
6 | import minifyGradients from 'postcss-minify-gradients'
7 | import normalizeDisplayValues from 'postcss-normalize-display-values'
8 | import normalizeTimingFunctions from 'postcss-normalize-timing-functions'
9 | import convertValues from 'postcss-convert-values'
10 | import reduceCalc from 'postcss-calc'
11 | import orderedValues from 'postcss-ordered-values'
12 | import minifySelectors from 'postcss-minify-selectors'
13 | import minifyParams from 'postcss-minify-params'
14 | import discardOverridden from 'postcss-discard-overridden'
15 | import normalizeString from 'postcss-normalize-string'
16 | import minifyFontValues from 'postcss-minify-font-values'
17 | import normalizeRepeatStyle from 'postcss-normalize-repeat-style'
18 | import normalizePositions from 'postcss-normalize-positions'
19 | import discardEmpty from 'postcss-discard-empty'
20 | import uniqueSelectors from 'postcss-unique-selectors'
21 | import declarationSorter from 'css-declaration-sorter'
22 | import mergeAdjacentMedia from './plugins/postcss-merge-adjacent-media'
23 | import discardDuplicates from 'postcss-discard-duplicates'
24 | import mergeRules from 'postcss-merge-rules'
25 |
26 | /** format colors */
27 | import rgbToHex from 'postcss-rgba-hex'
28 | import colorNamesToHex from 'postcss-colornames-to-hex'
29 | import rgbaFallback from 'postcss-color-rgba-fallback'
30 | import formatHexColors from 'postcss-hex-format'
31 |
32 | /** email fixes */
33 | import shorthandExpand from './plugins/postcss-expand-shorthand'
34 | import emailImportant from 'postcss-email-important'
35 | import zeroOutMargin from './plugins/postcss-zero-out-margin'
36 |
37 | /** custom element expander */
38 | import elementExpander from './plugins/postcss-element-expander'
39 |
40 | import mergeLonghand from 'postcss-merge-longhand'
41 |
42 | async function hemlstyles (contents, options = {}) {
43 | const {
44 | elements = {},
45 | aliases = {},
46 | plugins = []
47 | } = options
48 |
49 | return postcss([
50 | ...plugins,
51 |
52 | // /** optimize css */
53 | discardComments({ removeAll: false }),
54 | minifyGradients(),
55 | normalizeDisplayValues(),
56 | normalizeTimingFunctions(),
57 | convertValues({ length: false }),
58 | reduceCalc(),
59 | orderedValues(),
60 | minifySelectors(),
61 | minifyParams(),
62 | discardOverridden(),
63 | normalizeString(),
64 | minifyFontValues({ removeQuotes: false }),
65 | normalizeRepeatStyle(),
66 | normalizePositions(),
67 | discardEmpty(),
68 | uniqueSelectors(),
69 | declarationSorter(),
70 | mergeAdjacentMedia(),
71 | discardDuplicates(),
72 | mergeRules(),
73 |
74 | /** color handling */
75 | colorNamesToHex(),
76 | rgbToHex({ rgbOnly: true, silent: true }),
77 | rgbaFallback(),
78 | formatHexColors(),
79 |
80 | /** email fixes */
81 | emailImportant(),
82 | shorthandExpand(), // so we can match for margin-top/margin-left etc.
83 | zeroOutMargin(),
84 |
85 | /** expanding to match heml elements */
86 | elementExpander({ elements, aliases }),
87 | mergeLonghand(),
88 | discardEmpty()
89 | ])
90 | .process(contents, { parser: safeParser })
91 | }
92 |
93 | export default hemlstyles
94 |
--------------------------------------------------------------------------------
/packages/heml-styles/src/plugins/postcss-element-expander/coerceElements.js:
--------------------------------------------------------------------------------
1 | import { isPlainObject, escapeRegExp, isString, compact } from 'lodash'
2 |
3 | /**
4 | * remap the elements var to looks like this
5 | * [
6 | * {
7 | * tag: 'button',
8 | * pseudos: { root: '.button', text: '.text' },
9 | * defaults: [ '.button' ],
10 | * rules: {
11 | * '.button': [ { prop: /^background-color$/, tranform: () => {} } ],
12 | * '.text': [ { prop: /^color$/, transform: function() { tranform here } } ],
13 | * }
14 | * }
15 | * ...
16 | * ]
17 | */
18 |
19 | /**
20 | * coerce the elements for use in the plugin
21 | * @param {Object} elements the given elements
22 | * @return {Array} elements in a more usable format
23 | */
24 | export default function (originalElements) {
25 | let elements = []
26 |
27 | for (const [ tag, originalRules ] of Object.entries(originalElements)) {
28 | let defaults = []
29 | let pseudos = {}
30 | let rules = {}
31 |
32 | for (const [ selector, decls ] of Object.entries(originalRules)) {
33 | /** gather all the default values */
34 | if (findAtDecl(decls, 'default')) defaults.push(selector)
35 |
36 | /** gather all the pseudo selectors */
37 | let pseudo = findAtDecl(decls, 'pseudo')
38 | if (pseudo) pseudos[pseudo] = selector
39 |
40 | /** remap the rules to always be { prop: RegExp, transform: Function } */
41 | rules[selector] = compact(decls.map((decl) => {
42 | if (isPlainObject(decl) && Object.keys(decl).length === 0) return
43 |
44 | const prop = isPlainObject(decl) ? Object.keys(decl)[0] : decl
45 | const transform = isPlainObject(decl) ? Object.values(decl)[0] : () => {}
46 |
47 | if (isString(prop) && prop.startsWith('@')) return
48 |
49 | return { prop: toRegExp(prop), transform }
50 | }))
51 | }
52 |
53 | elements.push({ tag, defaults, pseudos, rules })
54 | }
55 |
56 | return elements
57 | }
58 |
59 | /**
60 | * finds the given at declaration value
61 | * @param {Array[Object]} decls the decls from an element
62 | * @param {String} the prop
63 | * @return {Any} the found value
64 | */
65 | function findAtDecl (decls, prop) {
66 | const foundDecls = decls.filter((decl) => {
67 | return (isPlainObject(decl) &&
68 | Object.keys(decl).length > 0 &&
69 | Object.keys(decl)[0] === `@${prop}`) || decl === `@${prop}`
70 | })
71 |
72 | if (foundDecls.length === 0) { return }
73 |
74 | const decl = foundDecls[0]
75 |
76 | return isPlainObject(decl) ? Object.values(decl)[0] : true
77 | }
78 |
79 | /**
80 | * convert the given string to a regular expression
81 | * @param {String|RegExp} prop the string to convert
82 | * @return {RegExp} the regular expression
83 | */
84 | function toRegExp (string) {
85 | if (isString(string) && string.startsWith('/') && string.lastIndexOf('/') !== 0) {
86 | const pattern = string.substr(1, string.lastIndexOf('/') - 1)
87 | const opts = string.substr(string.lastIndexOf('/') + 1).toLowerCase()
88 |
89 | return new RegExp(pattern, opts.includes('i') ? opts : `${opts}i`)
90 | }
91 |
92 | if (isString(string)) {
93 | return new RegExp(`^${escapeRegExp(string)}$`, 'i')
94 | }
95 |
96 | return string
97 | }
98 |
--------------------------------------------------------------------------------
/packages/heml-styles/src/plugins/postcss-element-expander/expanders.js:
--------------------------------------------------------------------------------
1 | import selectorParser from 'postcss-selector-parser'
2 |
3 | /**
4 | * replace all custom element tag selectors
5 | * @param {Object} element the element definition
6 | * @param {String} selector the selector
7 | * @return {String} the replaced selector
8 | */
9 | function replaceElementTagMentions (element, selector) {
10 | const processor = selectorParser((selectors) => {
11 | let nodesToReplace = []
12 |
13 | /**
14 | * looping breaks if we replace dynamically
15 | * so instead collect an array of nodes to swap and do it at the end
16 | */
17 | selectors.walk((node) => {
18 | if (node.value === element.tag && node.type === 'tag') { nodesToReplace.push(node) }
19 | })
20 |
21 | nodesToReplace.forEach((node) => node.replaceWith(element.pseudos.root))
22 | })
23 |
24 | return processor.process(selector).result
25 | }
26 |
27 | /**
28 | * replace all custom element pseudo selectors
29 | * @param {Object} element the element definiton
30 | * @param {String} selector the selector
31 | * @return {String} the replaced selector
32 | */
33 | function replaceElementPseudoMentions (element, selector) {
34 | const processor = selectorParser((selectors) => {
35 | let nodesToReplace = []
36 |
37 | /**
38 | * looping breaks if we replace dynamically
39 | * so instead collect an array of nodes to swap and do it at the end
40 | */
41 | selectors.each((selector) => {
42 | let onElementTag = false
43 |
44 | selector.each((node) => {
45 | if (node.type === 'tag' && node.value === element.tag) {
46 | onElementTag = true
47 | } else if (node.type === 'combinator') {
48 | onElementTag = false
49 | } else if (node.type === 'pseudo' && onElementTag) {
50 | const matchedPseudos = Object.entries(element.pseudos).filter(([ pseudo ]) => {
51 | return node.value.replace(/::?/, '') === pseudo
52 | })
53 |
54 | if (matchedPseudos.length > 0) {
55 | const [, value] = matchedPseudos[0]
56 | nodesToReplace.push({ node, value })
57 | }
58 | }
59 | })
60 | })
61 |
62 | nodesToReplace.forEach(({ node, value }) => node.replaceWith(` ${value}`))
63 | })
64 |
65 | return processor.process(selector).result
66 | }
67 |
68 | /**
69 | * expand the given rule to correctly the style the element
70 | * @param {Object} element element The element definition
71 | * @param {Array} selectors the matched selectors to for
72 | * @return {Array[Rule]} an array of the expanded rules
73 | */
74 | function expandElementRule (element, selectors = [], originalRule) {
75 | /** early return if we don't have any selectors */
76 | if (selectors.length === 0) return []
77 |
78 | let usedProps = []
79 | let expandedRules = []
80 | let defaultRules = []
81 |
82 | /** create the base rule */
83 | const baseRule = originalRule.clone()
84 | baseRule.selectors = selectors
85 | baseRule.selector = replaceElementTagMentions(element, baseRule.selector)
86 |
87 | /** create postcss rules for each element rule */
88 | for (const [ ruleSelector, ruleDecls ] of Object.entries(element.rules)) {
89 | const isRoot = element.pseudos.root === ruleSelector
90 | const isDefault = element.defaults.includes(ruleSelector)
91 | const expandedRule = baseRule.clone()
92 |
93 | /** gather all rules that get decls be default */
94 | if (isDefault) { defaultRules.push(expandedRule) }
95 |
96 | /** map all the selectors to target this rule selector */
97 | if (!isRoot) { expandedRule.selectors = expandedRule.selectors.map((selector) => `${selector} ${ruleSelector}`) }
98 |
99 | /** strip any non whitelisted props, run tranforms, gather used props */
100 | expandedRule.walkDecls((decl) => {
101 | const matchedRuleDecls = ruleDecls.filter(({ prop }) => prop.test(decl.prop))
102 |
103 | if (matchedRuleDecls.length === 0) { return decl.remove() }
104 |
105 | usedProps.push(decl.prop)
106 | matchedRuleDecls.forEach(({ transform }) => transform(decl, originalRule))
107 | })
108 |
109 | expandedRules.push(expandedRule)
110 | }
111 |
112 | baseRule.walkDecls((decl) => {
113 | if (!usedProps.includes(decl.prop)) {
114 | defaultRules.forEach((defaultRule) => defaultRule.prepend(decl.clone()))
115 | }
116 | })
117 |
118 | return expandedRules
119 | }
120 |
121 | export { replaceElementTagMentions, replaceElementPseudoMentions, expandElementRule }
122 |
--------------------------------------------------------------------------------
/packages/heml-styles/src/plugins/postcss-element-expander/findDirectElementSelectors.js:
--------------------------------------------------------------------------------
1 | import selectorParser from 'postcss-selector-parser'
2 | const simpleSelectorParser = selectorParser()
3 |
4 | /**
5 | * find all selectors that target the give element
6 | * @param {Object} element the element definition
7 | * @param {String} selector the selector
8 | * @return {Array} the matched selectors
9 | */
10 | export default function (element, selector) {
11 | const selectors = simpleSelectorParser.process(selector).res
12 |
13 | return selectors.filter((selector) => {
14 | let selectorNodes = selector.nodes.concat([]).reverse() // clone the array
15 |
16 | for (const node of selectorNodes) {
17 | if (node.type === 'combinator') { return false }
18 |
19 | if (node.type === 'pseudo' && node.value.replace(/::?/, '') in element.pseudos) { return false }
20 |
21 | if (node.type === 'tag' && node.value === element.tag) { return true }
22 | }
23 | }).map((selector) => String(selector).trim())
24 | }
25 |
--------------------------------------------------------------------------------
/packages/heml-styles/src/plugins/postcss-element-expander/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | import postcss from 'postcss'
4 | import coerceElements from './coerceElements'
5 | import tagAliasSelectors from './tagAliasSelectors'
6 | import findDirectElementSelectors from './findDirectElementSelectors'
7 | import { replaceElementTagMentions, replaceElementPseudoMentions, expandElementRule } from './expanders'
8 |
9 | /**
10 | * elements var looks like this before being coerced
11 | *
12 | * {
13 | * button: {
14 | * '.button': [ { '@pseudo': 'root' }, { '@default': true }, 'background-color' ],
15 | * '.text': [ { '@pseudo': 'text' }, { color: function() { tranform here } } ],
16 | * },
17 | * ...
18 | * }
19 | */
20 |
21 | /**
22 | * aliases var looks like this
23 | *
24 | * {
25 | * button: [ $node, $node, $node, ... ]
26 | * ...
27 | * }
28 | */
29 |
30 | /**
31 | * Convert CSS to match custom elements
32 | * @param {Object} options.elements An object of elements that define how to
33 | * split up the css for each element
34 | * @param {[type]} options.$ A cheerio instance
35 | */
36 | export default postcss.plugin('postcss-element-expander', ({ elements, aliases }) => {
37 | elements = coerceElements(elements)
38 |
39 | return (root, result) => {
40 | for (let element of elements) {
41 | /**
42 | * add the element tag to any css selectors that implicitly target an element
43 | * .i.e. #my-button that selects click me
44 | */
45 | root.walkRules((rule) => {
46 | tagAliasSelectors(element, aliases[element.tag], rule)
47 | })
48 |
49 | /**
50 | * There are 3 (non-mutually exclusive) possibilities when it contains the element tag
51 | *
52 | * 1. it directly targets the element - i.e. button { background: blue; }
53 | * in this case we need generate entirely new rules, prepend before the original rule, and strip the used selectors
54 | *
55 | * 2. it uses an element tag as an ancestor/sibling - .i.e. button span { color: black; }
56 | *
57 | * 3. it uses an element pseudo element - .i.e. button::text { color: blue }
58 | */
59 | root.walkRules(new RegExp(element.tag, 'i'), (rule) => {
60 | /** CASE 1 */
61 | /** grab all the selectors that target this element */
62 | const elementSelectors = findDirectElementSelectors(element, rule.selector)
63 |
64 | /** Create new rules to properly target the elements */
65 | const expandedRules = expandElementRule(element, elementSelectors, rule)
66 | expandedRules.forEach((expandedRule) => rule.before(expandedRule))
67 |
68 | /** remove the directly targeting selectors from the original rule */
69 | rule.selectors = rule.selectors.filter((selector) => !elementSelectors.includes(selector))
70 |
71 | /** remove the rule if has no selectors */
72 | if (rule.selector.trim() === '') return rule.remove()
73 |
74 | /** CASE 2 */
75 | /** Replace all mentions of the element pseudo elements */
76 | rule.selector = replaceElementPseudoMentions(element, rule.selector)
77 |
78 | /** CASE 3 */
79 | /** Replace all mentions of the element tag */
80 | rule.selector = replaceElementTagMentions(element, rule.selector)
81 | })
82 | }
83 | }
84 | })
85 |
--------------------------------------------------------------------------------
/packages/heml-styles/src/plugins/postcss-element-expander/tagAliasSelectors.js:
--------------------------------------------------------------------------------
1 | import selectorParser from 'postcss-selector-parser'
2 |
3 | const simpleSelectorParser = selectorParser()
4 |
5 | /**
6 | * Add the element tag to selectors from the rule that match the element alias
7 | * @param {Object} element element definition
8 | * @param {Array[$node]} aliases array of cheerio nodes
9 | * @param {Rule} rule postcss node
10 | */
11 | export default function (element, aliases, rule) {
12 | if (!aliases) return
13 |
14 | let selectors = []
15 |
16 | rule.selectors.forEach((selector) => {
17 | const matchedAliases = aliases.filter((alias) => alias.is(selector.replace(/::?\S*/g, ''))).length > 0
18 |
19 | /** the selector in an alias that doesn't target the tag already */
20 | if (matchedAliases && !targetsTag(selector)) {
21 | selectors.push(appendElementSelector(element, selector))
22 | }
23 |
24 | /** dont add the original selector back in if it targets a pseudo selector */
25 | if (!targetsElementPseudo(element, selector)) { selectors.push(selector) }
26 | })
27 |
28 | rule.selectors = selectors
29 | }
30 |
31 | /**
32 | * checks if selector targets a tag
33 | * @param {String} selector the selector
34 | * @return {Boolean} if the selector targets a tag
35 | */
36 | function targetsTag (selector) {
37 | const selectors = simpleSelectorParser.process(selector).res
38 |
39 | return selectors.filter((selector) => {
40 | let selectorNodes = selector.nodes.concat([]).reverse() // clone the array
41 |
42 | for (const node of selectorNodes) {
43 | if (node.type === 'cominator') { break }
44 |
45 | if (node.type === 'tag') { return true }
46 | }
47 |
48 | return false
49 | }).length > 0
50 | }
51 |
52 | /**
53 | * find all selectors that target the give element
54 | * @param {Object} element the element definition
55 | * @param {String} selector the selector
56 | * @return {Array} the matched selectors
57 | */
58 | function targetsElementPseudo (element, selector) {
59 | const selectors = simpleSelectorParser.process(selector).res
60 |
61 | return selectors.filter((selector) => {
62 | let selectorNodes = selector.nodes.concat([]).reverse() // clone the array
63 |
64 | for (const node of selectorNodes) {
65 | if (node.type === 'cominator') { break }
66 |
67 | if (node.type === 'pseudo' && node.value.replace(/::?/, '') in element.pseudos) {
68 | return true
69 | }
70 |
71 | if (node.type === 'tag' && node.value === element.tag) { break }
72 | }
73 |
74 | return false
75 | }).length > 0
76 | }
77 |
78 | /**
79 | * Add the element tag to the end of the selector
80 | * @param {Object} element element definition
81 | * @param {String} selector the selector
82 | * @return {String} the modified selector
83 | */
84 | function appendElementSelector (element, selector) {
85 | const processor = selectorParser((selectors) => {
86 | let combinatorNode = null
87 |
88 | /**
89 | * looping breaks if we insert dynamically
90 | */
91 | selectors.each((selector) => {
92 | const elementNode = selectorParser.tag({ value: element.tag })
93 | selector.walk((node) => {
94 | if (node.type === 'combinator') { combinatorNode = node }
95 | })
96 |
97 | if (combinatorNode) {
98 | selector.insertAfter(combinatorNode, elementNode)
99 | } else {
100 | selector.prepend(elementNode)
101 | }
102 | })
103 | })
104 |
105 | return processor.process(selector).result
106 | }
107 |
--------------------------------------------------------------------------------
/packages/heml-styles/src/plugins/postcss-expand-shorthand/index.js:
--------------------------------------------------------------------------------
1 | import postcss from 'postcss'
2 | import shorthandExpand from 'css-shorthand-expand'
3 |
4 | export default postcss.plugin('postcss-expand-shorthand', () => (root) => {
5 | root.walkDecls((decl) => {
6 | if (shouldExpand(decl.prop) && !!decl.value) {
7 | const expandedDecls = shorthandExpand(decl.prop, decl.value)
8 |
9 | if (!expandedDecls) { return }
10 |
11 | for (const [ prop, value ] of Object.entries(expandedDecls)) {
12 | decl.before(postcss.decl({ prop, value }))
13 | }
14 |
15 | decl.remove()
16 | }
17 | })
18 | })
19 |
20 | function shouldExpand (prop) {
21 | return ['background', 'font', 'margin'].includes(prop)
22 | }
23 |
--------------------------------------------------------------------------------
/packages/heml-styles/src/plugins/postcss-merge-adjacent-media/index.js:
--------------------------------------------------------------------------------
1 | import postcss from 'postcss'
2 |
3 | export default postcss.plugin('postcss-merge-adjacent-media', () => (root) => {
4 | root.walkAtRules((rule) => {
5 | if (rule.name !== 'media') { return }
6 |
7 | const nextRule = getNextRule(rule)
8 |
9 | if (!nextRule || nextRule.type !== 'atrule') { return }
10 |
11 | if (nextRule.params === rule.params) {
12 | nextRule.prepend(rule.nodes)
13 | rule.remove()
14 | }
15 | })
16 | })
17 |
18 | function getNextRule (rule) {
19 | const nextNode = rule.next()
20 | if (!nextNode) { return }
21 |
22 | if (nextNode.type === 'atrule' || nextNode.type === 'rule') { return nextNode }
23 |
24 | return getNextRule(nextNode)
25 | }
26 |
--------------------------------------------------------------------------------
/packages/heml-styles/src/plugins/postcss-zero-out-margin/index.js:
--------------------------------------------------------------------------------
1 | import postcss from 'postcss'
2 |
3 | /**
4 | * convert margin-top/margin-bottom to 0 when they are margin auto
5 | */
6 | export default postcss.plugin('postcss-zero-out-margin', () => (root) => {
7 | root.walkDecls(/margin-top|margin-bottom/i, (decl) => {
8 | decl.value = decl.value.toLowerCase() === 'auto' ? '0' : decl.value
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/packages/heml-utils/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "requires": true,
3 | "lockfileVersion": 1,
4 | "dependencies": {
5 | "css-groups": {
6 | "version": "0.1.1",
7 | "resolved": "https://registry.npmjs.org/css-groups/-/css-groups-0.1.1.tgz",
8 | "integrity": "sha1-UyvG3LId3Mps1BCdAkmPyY/7BO8="
9 | },
10 | "lodash": {
11 | "version": "4.17.4",
12 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
13 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/heml-utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heml/utils",
3 | "version": "1.1.2",
4 | "description": "A collection of utilities for HEML",
5 | "keywords": [
6 | "heml"
7 | ],
8 | "homepage": "https://heml.io",
9 | "bugs": "https://github.com/SparkPost/heml/issues",
10 | "license": "MIT",
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/SparkPost/heml.git"
14 | },
15 | "author": "SparkPost (https://sparkpost.com)",
16 | "files": [
17 | "build/"
18 | ],
19 | "main": "build/index.js",
20 | "publishConfig": {
21 | "access": "public"
22 | },
23 | "dependencies": {
24 | "@heml/render": "^1.1.2",
25 | "css-groups": "^0.1.1",
26 | "lodash": "^4.17.4"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/heml-utils/src/HEMLError.js:
--------------------------------------------------------------------------------
1 | import { min, max } from 'lodash'
2 |
3 | export default class HEMLError extends Error {
4 | constructor (message, $node) {
5 | super(message)
6 | this.name = 'HEMLError'
7 |
8 | if ($node) {
9 | this.$node = $node
10 | this.selector = buildExactSelector($node)
11 | }
12 |
13 | Error.captureStackTrace(this, HEMLError)
14 | }
15 | }
16 |
17 | function buildExactSelector ($node) {
18 | const nodeSelector = buildSelector($node[0])
19 | const strSelector = $node.parents()
20 | .map((index, node) => buildSelector(node))
21 | .toArray()
22 | .reverse()
23 | .concat([nodeSelector])
24 | .join(' > ')
25 |
26 | const chopAfter = min(max(0, strSelector.lastIndexOf('#')),
27 | max(0, strSelector.lastIndexOf('html')),
28 | max(0, strSelector.lastIndexOf('heml')))
29 |
30 | return strSelector.substr(chopAfter)
31 | }
32 |
33 | function buildSelector (node) {
34 | if (node.id) {
35 | return `#${node.id}`
36 | }
37 |
38 | const tag = node.tagName.toLowerCase()
39 | const siblingsBefore = findSiblingsBefore(node)
40 | const siblingsAfter = findSiblingsAfter(node)
41 | const siblings = siblingsBefore.concat(siblingsAfter)
42 |
43 | const sameTag = siblings.filter((s) => s.tagName.toLowerCase() === tag)
44 |
45 | if (siblings.length === 0 || sameTag.length === 0) {
46 | return tag
47 | }
48 |
49 | const sameTagAndClass = siblings.filter((s) => s.className === node.className && s.tagName.toLowerCase() === tag)
50 |
51 | if (node.className && sameTagAndClass.length === 0) {
52 | return `${tag}.${node.className.split(' ').join('.')}`
53 | }
54 |
55 | return `${tag}:nth-child(${siblingsBefore.length + 1})`
56 | }
57 |
58 | function findSiblingsBefore (node, siblings = []) {
59 | if (!node.previousSibling) { return siblings }
60 |
61 | if (node.previousSibling.tagName) { siblings = siblings.concat([node.previousSibling]) }
62 |
63 | return findSiblingsBefore(node.previousSibling, siblings)
64 | }
65 |
66 | function findSiblingsAfter (node, siblings = []) {
67 | if (!node.nextSibling) { return siblings }
68 |
69 | if (node.nextSibling.tagName) { siblings = siblings.concat([node.nextSibling]) }
70 |
71 | return findSiblingsAfter(node.nextSibling, siblings)
72 | }
73 |
--------------------------------------------------------------------------------
/packages/heml-utils/src/condition.js:
--------------------------------------------------------------------------------
1 | const parts = {
2 | 'START_CONDITION': ''
5 | }
6 |
7 | function condition (condition, content) {
8 | return `
9 | START_CONDITION${condition}END_CONDITION
10 | ${content.trim()}
11 | END_COMMENT_CONDITIONAL
12 | `
13 | }
14 |
15 | condition.replace = function (html) {
16 | for (let [search, replace] of Object.entries(parts)) {
17 | html = html.replace(new RegExp(search, 'g'), replace)
18 | }
19 |
20 | return html
21 | }
22 |
23 | export default condition
24 |
--------------------------------------------------------------------------------
/packages/heml-utils/src/createElement.js:
--------------------------------------------------------------------------------
1 | import { defaults, isFunction } from 'lodash'
2 |
3 | const textRegex = /^(text(-([^-\s]+))?(-([^-\s]+))?|word-(break|spacing|wrap)|line-break|hanging-punctuation|hyphens|letter-spacing|overflow-wrap|tab-size|white-space|font-family|font-weight|font-style|font-variant|color)$/i
4 |
5 | export default function (name, element) {
6 | if (!name || name.trim().length === 0) {
7 | throw new Error(`When creating an element, you must set the name. ${name.trim().length === 0 ? 'An empty string' : `"${name}"`} was given.`)
8 | }
9 |
10 | if (isFunction(element)) {
11 | element = { render: element }
12 | }
13 |
14 | if (element.containsText) {
15 | element.rules = element.rules || {}
16 | element.rules['.header'] = [ textRegex ]
17 | element.rules['.text'] = [ textRegex, 'font-size', 'line-height' ]
18 | }
19 |
20 | element = defaults({}, element || {}, {
21 | tagName: name.trim().toLowerCase(),
22 | attrs: [],
23 | children: true,
24 | defaultAttrs: {},
25 | preRender () {},
26 | render () { return false },
27 | postRender () {}
28 | })
29 |
30 | element.defaultAttrs.class = element.defaultAttrs.class || ''
31 |
32 | return element
33 | }
34 |
--------------------------------------------------------------------------------
/packages/heml-utils/src/index.js:
--------------------------------------------------------------------------------
1 | import { renderElement } from '@heml/render'
2 | import cssGroups from 'css-groups'
3 | import createElement from './createElement'
4 | import HEMLError from './HEMLError'
5 | import transforms from './transforms'
6 | import condition from './condition'
7 |
8 | module.exports = { createElement, renderElement, HEMLError, cssGroups, transforms, condition }
9 |
--------------------------------------------------------------------------------
/packages/heml-utils/src/transforms/convertProp.js:
--------------------------------------------------------------------------------
1 | /**
2 | * convert a decleration to different properity
3 | * .i.e. max-width -> width
4 | * @param {String} prop
5 | * @return {Function}
6 | */
7 | export default function convertProp (prop) {
8 | return (decl) => { decl.prop = prop }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/heml-utils/src/transforms/fallbackFor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Adds the property this tranform is attached to, if the desired property wasn't given
3 | * @param {String} prop
4 | * @return {Function}
5 | */
6 | export default function fallbackFor (desiredProp) {
7 | return (prop, rule) => {
8 | let hasDesiredProp = false
9 | rule.walkDecls(desiredProp, () => { hasDesiredProp = true })
10 |
11 | /** remove the fallback property if we already have the desired properity */
12 | if (hasDesiredProp) {
13 | prop.remove()
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/heml-utils/src/transforms/ieAlignFallback.js:
--------------------------------------------------------------------------------
1 | /**
2 | * inline margin-left: auto; and margin-right: auto; otherwise, through it to 0
3 | */
4 | export default function ieAlignFallback (decl) {
5 | if (decl.prop === 'margin-top' || decl.prop === 'margin-bottom') {
6 | return decl.remove()
7 | }
8 |
9 | if ((decl.prop === 'margin-left' || decl.prop === 'margin-right') && decl.value !== 'auto') {
10 | decl.value = '0'
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/heml-utils/src/transforms/index.js:
--------------------------------------------------------------------------------
1 | import trueHide from './trueHide'
2 | import convertProp from './convertProp'
3 | import ieAlignFallback from './ieAlignFallback'
4 | import fallbackFor from './fallbackFor'
5 |
6 | export default { trueHide, convertProp, ieAlignFallback, fallbackFor }
7 |
--------------------------------------------------------------------------------
/packages/heml-utils/src/transforms/trueHide.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | import { isUndefined } from 'lodash'
4 |
5 | export default (type, containsTables = false) => (decl, originalRule) => {
6 | if (decl.value.trim().toLowerCase() === 'none') {
7 | decl.after(decl.clone({ prop: 'mso-hide', value: 'all' }))
8 | decl.after(decl.clone({ prop: 'max-height', value: '0px' }))
9 | decl.after(decl.clone({ prop: 'overflow', value: 'hidden' }))
10 |
11 | if (type === 'block' || type === 'table' || containsTables) {
12 | const hideTableRule = decl.parent.clone()
13 | hideTableRule.selectors = hideTableRule.selectors.map((s) => `${s} table`)
14 | hideTableRule.removeAll()
15 | hideTableRule.append(decl.clone({ prop: 'mso-hide', value: 'all' }))
16 | originalRule.after(hideTableRule)
17 | }
18 | } else if (decl.value.trim().toLowerCase() === type || isUndefined(type)) {
19 | decl.after(decl.clone({ prop: 'mso-hide', value: 'none' }))
20 | decl.after(decl.clone({ prop: 'max-height', value: 'initial' }))
21 | decl.after(decl.clone({ prop: 'overflow', value: 'auto' }))
22 |
23 | if (type === 'block' || type === 'table' || containsTables) {
24 | const showTableRule = decl.parent.clone()
25 | showTableRule.selectors = showTableRule.selectors.map((s) => `${s} table`)
26 | showTableRule.removeAll()
27 | showTableRule.append(decl.clone({ prop: 'mso-hide', value: 'none' }))
28 | originalRule.after(showTableRule)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/heml-validate/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "requires": true,
3 | "lockfileVersion": 1,
4 | "dependencies": {
5 | "lodash": {
6 | "version": "4.17.4",
7 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
8 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/heml-validate/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@heml/validate",
3 | "version": "1.1.2",
4 | "description": "Validator for HEML",
5 | "keywords": [
6 | "heml"
7 | ],
8 | "homepage": "https://heml.io",
9 | "bugs": "https://github.com/SparkPost/heml/issues",
10 | "license": "MIT",
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/SparkPost/heml.git"
14 | },
15 | "author": "SparkPost (https://sparkpost.com)",
16 | "files": [
17 | "build/"
18 | ],
19 | "main": "build/index.js",
20 | "publishConfig": {
21 | "access": "public"
22 | },
23 | "dependencies": {
24 | "@heml/utils": "^1.1.2",
25 | "lodash": "^4.17.4"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/heml-validate/src/index.js:
--------------------------------------------------------------------------------
1 | import * as validatorsObject from './validators'
2 |
3 | const validators = Object.values(validatorsObject)
4 |
5 | /**
6 | * Validate that a cheerio instance contains valid HEML
7 | * @param {Cheero} $ the heml cheerio
8 | * @param {Object} options
9 | * @return {Array[HEMLError]} an array of heml errors
10 | */
11 | export default function validate ($, options = {}) {
12 | const {
13 | elements = []
14 | } = options
15 |
16 | let errors = []
17 |
18 | for (let element of elements) {
19 | const matchedValidators = validators.filter((validator) => validator.name in element)
20 |
21 | if (matchedValidators.length === 0) { return }
22 |
23 | const $nodes = $.findNodes(element.tagName)
24 |
25 | $nodes.forEach(($node) => matchedValidators.forEach((validator) => {
26 | try {
27 | validator($node, element, $)
28 | } catch (e) {
29 | errors.push(e)
30 | }
31 | }))
32 | }
33 |
34 | return errors
35 | }
36 |
--------------------------------------------------------------------------------
/packages/heml-validate/src/validators/attrs.js:
--------------------------------------------------------------------------------
1 | import { HEMLError } from '@heml/utils'
2 | import { difference } from 'lodash'
3 |
4 | const nativeAttrs = [ 'id', 'class', 'dir', 'lang', 'accesskey', 'tabindex', 'title', 'translate' ]
5 |
6 | export default function attrs ($node, { tagName, attrs: allowedAttrs, defaultAttrs }) {
7 | /** allow any attributes through */
8 | if (allowedAttrs === true) { return }
9 |
10 | allowedAttrs = allowedAttrs
11 | .concat(Object.keys(defaultAttrs))
12 | .concat(nativeAttrs)
13 |
14 | const usedAttrs = Object.keys($node.get(0).attribs)
15 |
16 | const foundNotAllowedAttrs = difference(usedAttrs, allowedAttrs)
17 |
18 | if (foundNotAllowedAttrs.length > 0) {
19 | /** remove non-whitelisted attributes */
20 | foundNotAllowedAttrs.forEach((attr) => $node.removeAttr(attr))
21 |
22 | const plural = foundNotAllowedAttrs.length > 1
23 | throw new HEMLError(`Attribute${plural ? 's' : ''} ${foundNotAllowedAttrs.join(', ')} ${plural ? 'are' : 'is'} not allowed on ${tagName}.`, $node)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/heml-validate/src/validators/children.js:
--------------------------------------------------------------------------------
1 | import { HEMLError } from '@heml/utils'
2 | import { isArray, intersection, difference } from 'lodash'
3 |
4 | export default function children ($node, { tagName, children: requiredChildren }) {
5 | if (isArray(requiredChildren)) {
6 | const children = $node.children().toArray().map((c) => c.name)
7 |
8 | const foundRequiredChildren = intersection(requiredChildren, children)
9 |
10 | if (foundRequiredChildren.length < requiredChildren.length) {
11 | const missingRequiredChildren = difference(requiredChildren, foundRequiredChildren)
12 |
13 | throw new HEMLError(`${tagName} is missing required children: ${missingRequiredChildren}`, $node)
14 | }
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/packages/heml-validate/src/validators/index.js:
--------------------------------------------------------------------------------
1 | import attrs from './attrs'
2 | import children from './children'
3 | import parent from './parent'
4 | import unique from './unique'
5 |
6 | export { attrs, children, parent, unique }
7 |
--------------------------------------------------------------------------------
/packages/heml-validate/src/validators/parent.js:
--------------------------------------------------------------------------------
1 | import { HEMLError } from '@heml/utils'
2 |
3 | export default function parent ($node, { tagName, parent: allowedParents }) {
4 | const parentTag = $node.parent().get(0)
5 |
6 | if (!parentTag) { return }
7 |
8 | if (allowedParents.includes(parentTag.name)) {
9 | return
10 | }
11 |
12 | let message = `${tagName} is inside of ${parentTag.name}.`
13 |
14 | if (allowedParents.length === 0) {
15 | message = `${message} It may not have any parents.`
16 | } else {
17 | message = `${message} It should only be used in: ${allowedParents.join(', ')}`
18 | }
19 |
20 | throw new HEMLError(message, $node)
21 | }
22 |
--------------------------------------------------------------------------------
/packages/heml-validate/src/validators/unique.js:
--------------------------------------------------------------------------------
1 | import { HEMLError } from '@heml/utils'
2 |
3 | export default function unique ($node, { tagName, unique: shouldBeUnique }, $) {
4 | const $nodes = $.findNodes(tagName)
5 |
6 | if ($nodes.length > 1 && shouldBeUnique) {
7 | /** remove all but the first $node */
8 | $nodes.slice(1).forEach(($node) => $node.remove())
9 |
10 | throw new HEMLError(`${tagName} should be unique. ${$nodes.length} were found.`, $node)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/heml/README.md:
--------------------------------------------------------------------------------
1 | <heml>
2 |
3 |
4 |
5 | Guide •
6 | Documentation •
7 | Editor
8 |
9 |
10 |
11 | HEML is an open source markup language for building responsive email.
12 |
13 | - **Native Feel:** Do you know HTML and CSS? Check out our docs and you're off to the races! No special rules or styling paradigms to master.
14 |
15 | - **Forward Thinking:** HEML is designed to take advantage of all that email can do while still providing a solid experience for all clients.
16 |
17 | - **Extendable:** You can create your own powerful elements and style rules. Share them with the world, or keep em to yourself. Your choice.
18 |
19 |
20 | ## FAQ
21 |
22 | ### Why should I use HEML?
23 |
24 | It makes building emails easier.
25 |
26 | ### How do I use it?
27 |
28 | Check out our [usage guide](http://heml.io/docs/getting-started/usage).
29 |
30 | ### What do I do if I found a bug?
31 |
32 | Open up an [issue](https://github.com/SparkPost/heml/issues) on the repository. Thanks for catching it! 🙏
33 |
34 | ### Want to help?
35 |
36 | Awesome!! We welcome any and all help! Head over to the [issues](https://github.com/SparkPost/heml/issues) and see if anything catches your eye.
37 |
38 | ## License
39 |
40 | [MIT](https://github.com/SparkPost/heml/blob/master/LICENSE)
41 |
--------------------------------------------------------------------------------
/packages/heml/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "heml",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "abbrev": {
8 | "version": "1.1.1",
9 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
10 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
11 | },
12 | "accepts": {
13 | "version": "1.3.4",
14 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz",
15 | "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=",
16 | "requires": {
17 | "mime-types": "2.1.17",
18 | "negotiator": "0.6.1"
19 | }
20 | },
21 | "ansi-align": {
22 | "version": "2.0.0",
23 | "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz",
24 | "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=",
25 | "requires": {
26 | "string-width": "2.1.1"
27 | }
28 | },
29 | "ansi-escapes": {
30 | "version": "2.0.0",
31 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz",
32 | "integrity": "sha1-W65SvkJIeN2Xg+iRDj/Cki6DyBs="
33 | },
34 | "ansi-regex": {
35 | "version": "3.0.0",
36 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
37 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
38 | },
39 | "ansi-styles": {
40 | "version": "3.2.0",
41 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
42 | "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
43 | "requires": {
44 | "color-convert": "1.9.0"
45 | }
46 | },
47 | "array-flatten": {
48 | "version": "1.1.1",
49 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
50 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
51 | },
52 | "babel-runtime": {
53 | "version": "6.26.0",
54 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
55 | "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
56 | "requires": {
57 | "core-js": "2.5.1",
58 | "regenerator-runtime": "0.11.0"
59 | }
60 | },
61 | "balanced-match": {
62 | "version": "1.0.0",
63 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
64 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
65 | },
66 | "bluebird": {
67 | "version": "3.5.1",
68 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
69 | "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA=="
70 | },
71 | "body-parser": {
72 | "version": "1.18.2",
73 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
74 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
75 | "requires": {
76 | "bytes": "3.0.0",
77 | "content-type": "1.0.4",
78 | "debug": "2.6.9",
79 | "depd": "1.1.1",
80 | "http-errors": "1.6.2",
81 | "iconv-lite": "0.4.19",
82 | "on-finished": "2.3.0",
83 | "qs": "6.5.1",
84 | "raw-body": "2.3.2",
85 | "type-is": "1.6.15"
86 | }
87 | },
88 | "boxen": {
89 | "version": "1.2.2",
90 | "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.2.2.tgz",
91 | "integrity": "sha1-Px1AMsMP/qnUsCwyLq8up0HcvOU=",
92 | "requires": {
93 | "ansi-align": "2.0.0",
94 | "camelcase": "4.1.0",
95 | "chalk": "2.2.0",
96 | "cli-boxes": "1.0.0",
97 | "string-width": "2.1.1",
98 | "term-size": "1.2.0",
99 | "widest-line": "1.0.0"
100 | }
101 | },
102 | "brace-expansion": {
103 | "version": "1.1.8",
104 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
105 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
106 | "requires": {
107 | "balanced-match": "1.0.0",
108 | "concat-map": "0.0.1"
109 | }
110 | },
111 | "byte-length": {
112 | "version": "0.1.1",
113 | "resolved": "https://registry.npmjs.org/byte-length/-/byte-length-0.1.1.tgz",
114 | "integrity": "sha1-6bR3TbznxZdkv1vofDAniaiHOMM="
115 | },
116 | "bytes": {
117 | "version": "3.0.0",
118 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
119 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
120 | },
121 | "camelcase": {
122 | "version": "4.1.0",
123 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
124 | "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
125 | },
126 | "chalk": {
127 | "version": "2.2.0",
128 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.2.0.tgz",
129 | "integrity": "sha512-0BMM/2hG3ZaoPfR6F+h/oWpZtsh3b/s62TjSM6MGCJWEbJDN1acqCXvyhhZsDSVFklpebUoQ5O1kKC7lOzrn9g==",
130 | "requires": {
131 | "ansi-styles": "3.2.0",
132 | "escape-string-regexp": "1.0.5",
133 | "supports-color": "4.5.0"
134 | }
135 | },
136 | "cli-boxes": {
137 | "version": "1.0.0",
138 | "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz",
139 | "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM="
140 | },
141 | "cli-color": {
142 | "version": "1.2.0",
143 | "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.2.0.tgz",
144 | "integrity": "sha1-OlrnT9drYmevZm5p4q+70B3vNNE=",
145 | "requires": {
146 | "ansi-regex": "2.1.1",
147 | "d": "1.0.0",
148 | "es5-ext": "0.10.35",
149 | "es6-iterator": "2.0.3",
150 | "memoizee": "0.4.11",
151 | "timers-ext": "0.1.2"
152 | },
153 | "dependencies": {
154 | "ansi-regex": {
155 | "version": "2.1.1",
156 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
157 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
158 | }
159 | }
160 | },
161 | "cli-cursor": {
162 | "version": "2.1.0",
163 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
164 | "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
165 | "requires": {
166 | "restore-cursor": "2.0.0"
167 | }
168 | },
169 | "code-point-at": {
170 | "version": "1.1.0",
171 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
172 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
173 | },
174 | "color-convert": {
175 | "version": "1.9.0",
176 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz",
177 | "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=",
178 | "requires": {
179 | "color-name": "1.1.3"
180 | }
181 | },
182 | "color-name": {
183 | "version": "1.1.3",
184 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
185 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
186 | },
187 | "commander": {
188 | "version": "2.11.0",
189 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
190 | "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ=="
191 | },
192 | "concat-map": {
193 | "version": "0.0.1",
194 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
195 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
196 | },
197 | "config-chain": {
198 | "version": "1.1.11",
199 | "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz",
200 | "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=",
201 | "requires": {
202 | "ini": "1.3.4",
203 | "proto-list": "1.2.4"
204 | }
205 | },
206 | "content-disposition": {
207 | "version": "0.5.2",
208 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
209 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
210 | },
211 | "content-type": {
212 | "version": "1.0.4",
213 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
214 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
215 | },
216 | "cookie": {
217 | "version": "0.3.1",
218 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
219 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
220 | },
221 | "cookie-signature": {
222 | "version": "1.0.6",
223 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
224 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
225 | },
226 | "core-js": {
227 | "version": "2.5.1",
228 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz",
229 | "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs="
230 | },
231 | "cross-spawn": {
232 | "version": "5.1.0",
233 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
234 | "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
235 | "requires": {
236 | "lru-cache": "4.1.1",
237 | "shebang-command": "1.2.0",
238 | "which": "1.3.0"
239 | }
240 | },
241 | "d": {
242 | "version": "1.0.0",
243 | "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
244 | "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
245 | "requires": {
246 | "es5-ext": "0.10.35"
247 | }
248 | },
249 | "debug": {
250 | "version": "2.6.9",
251 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
252 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
253 | "requires": {
254 | "ms": "2.0.0"
255 | }
256 | },
257 | "depd": {
258 | "version": "1.1.1",
259 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
260 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
261 | },
262 | "destroy": {
263 | "version": "1.0.4",
264 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
265 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
266 | },
267 | "editorconfig": {
268 | "version": "0.13.3",
269 | "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.13.3.tgz",
270 | "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==",
271 | "requires": {
272 | "bluebird": "3.5.1",
273 | "commander": "2.11.0",
274 | "lru-cache": "3.2.0",
275 | "semver": "5.4.1",
276 | "sigmund": "1.0.1"
277 | },
278 | "dependencies": {
279 | "lru-cache": {
280 | "version": "3.2.0",
281 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz",
282 | "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=",
283 | "requires": {
284 | "pseudomap": "1.0.2"
285 | }
286 | }
287 | }
288 | },
289 | "ee-first": {
290 | "version": "1.1.1",
291 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
292 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
293 | },
294 | "encodeurl": {
295 | "version": "1.0.1",
296 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz",
297 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA="
298 | },
299 | "es5-ext": {
300 | "version": "0.10.35",
301 | "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.35.tgz",
302 | "integrity": "sha1-GO6FjOajxFx9eekcFfzKnsVoSU8=",
303 | "requires": {
304 | "es6-iterator": "2.0.3",
305 | "es6-symbol": "3.1.1"
306 | }
307 | },
308 | "es6-iterator": {
309 | "version": "2.0.3",
310 | "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
311 | "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
312 | "requires": {
313 | "d": "1.0.0",
314 | "es5-ext": "0.10.35",
315 | "es6-symbol": "3.1.1"
316 | }
317 | },
318 | "es6-symbol": {
319 | "version": "3.1.1",
320 | "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
321 | "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
322 | "requires": {
323 | "d": "1.0.0",
324 | "es5-ext": "0.10.35"
325 | }
326 | },
327 | "es6-weak-map": {
328 | "version": "2.0.2",
329 | "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
330 | "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
331 | "requires": {
332 | "d": "1.0.0",
333 | "es5-ext": "0.10.35",
334 | "es6-iterator": "2.0.3",
335 | "es6-symbol": "3.1.1"
336 | }
337 | },
338 | "escape-html": {
339 | "version": "1.0.3",
340 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
341 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
342 | },
343 | "escape-string-regexp": {
344 | "version": "1.0.5",
345 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
346 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
347 | },
348 | "etag": {
349 | "version": "1.8.1",
350 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
351 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
352 | },
353 | "event-emitter": {
354 | "version": "0.3.5",
355 | "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
356 | "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
357 | "requires": {
358 | "d": "1.0.0",
359 | "es5-ext": "0.10.35"
360 | }
361 | },
362 | "execa": {
363 | "version": "0.7.0",
364 | "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
365 | "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
366 | "requires": {
367 | "cross-spawn": "5.1.0",
368 | "get-stream": "3.0.0",
369 | "is-stream": "1.1.0",
370 | "npm-run-path": "2.0.2",
371 | "p-finally": "1.0.0",
372 | "signal-exit": "3.0.2",
373 | "strip-eof": "1.0.0"
374 | }
375 | },
376 | "express": {
377 | "version": "4.16.2",
378 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz",
379 | "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=",
380 | "requires": {
381 | "accepts": "1.3.4",
382 | "array-flatten": "1.1.1",
383 | "body-parser": "1.18.2",
384 | "content-disposition": "0.5.2",
385 | "content-type": "1.0.4",
386 | "cookie": "0.3.1",
387 | "cookie-signature": "1.0.6",
388 | "debug": "2.6.9",
389 | "depd": "1.1.1",
390 | "encodeurl": "1.0.1",
391 | "escape-html": "1.0.3",
392 | "etag": "1.8.1",
393 | "finalhandler": "1.1.0",
394 | "fresh": "0.5.2",
395 | "merge-descriptors": "1.0.1",
396 | "methods": "1.1.2",
397 | "on-finished": "2.3.0",
398 | "parseurl": "1.3.2",
399 | "path-to-regexp": "0.1.7",
400 | "proxy-addr": "2.0.2",
401 | "qs": "6.5.1",
402 | "range-parser": "1.2.0",
403 | "safe-buffer": "5.1.1",
404 | "send": "0.16.1",
405 | "serve-static": "1.13.1",
406 | "setprototypeof": "1.1.0",
407 | "statuses": "1.3.1",
408 | "type-is": "1.6.15",
409 | "utils-merge": "1.0.1",
410 | "vary": "1.1.2"
411 | }
412 | },
413 | "finalhandler": {
414 | "version": "1.1.0",
415 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz",
416 | "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=",
417 | "requires": {
418 | "debug": "2.6.9",
419 | "encodeurl": "1.0.1",
420 | "escape-html": "1.0.3",
421 | "on-finished": "2.3.0",
422 | "parseurl": "1.3.2",
423 | "statuses": "1.3.1",
424 | "unpipe": "1.0.0"
425 | }
426 | },
427 | "forwarded": {
428 | "version": "0.1.2",
429 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
430 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
431 | },
432 | "fresh": {
433 | "version": "0.5.2",
434 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
435 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
436 | },
437 | "fs-extra": {
438 | "version": "4.0.2",
439 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.2.tgz",
440 | "integrity": "sha1-+RcExT0bRh+JNFKwwwfZmXZHq2s=",
441 | "requires": {
442 | "graceful-fs": "4.1.11",
443 | "jsonfile": "4.0.0",
444 | "universalify": "0.1.1"
445 | }
446 | },
447 | "fs.realpath": {
448 | "version": "1.0.0",
449 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
450 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
451 | },
452 | "gaze": {
453 | "version": "1.1.2",
454 | "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.2.tgz",
455 | "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=",
456 | "requires": {
457 | "globule": "1.2.0"
458 | }
459 | },
460 | "get-stream": {
461 | "version": "3.0.0",
462 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
463 | "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
464 | },
465 | "glob": {
466 | "version": "7.1.2",
467 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
468 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
469 | "requires": {
470 | "fs.realpath": "1.0.0",
471 | "inflight": "1.0.6",
472 | "inherits": "2.0.3",
473 | "minimatch": "3.0.4",
474 | "once": "1.4.0",
475 | "path-is-absolute": "1.0.1"
476 | }
477 | },
478 | "globule": {
479 | "version": "1.2.0",
480 | "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz",
481 | "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=",
482 | "requires": {
483 | "glob": "7.1.2",
484 | "lodash": "4.17.4",
485 | "minimatch": "3.0.4"
486 | }
487 | },
488 | "graceful-fs": {
489 | "version": "4.1.11",
490 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
491 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
492 | },
493 | "graceful-readlink": {
494 | "version": "1.0.1",
495 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
496 | "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
497 | },
498 | "has-flag": {
499 | "version": "2.0.0",
500 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
501 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE="
502 | },
503 | "http-errors": {
504 | "version": "1.6.2",
505 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
506 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
507 | "requires": {
508 | "depd": "1.1.1",
509 | "inherits": "2.0.3",
510 | "setprototypeof": "1.0.3",
511 | "statuses": "1.3.1"
512 | },
513 | "dependencies": {
514 | "setprototypeof": {
515 | "version": "1.0.3",
516 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
517 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
518 | }
519 | }
520 | },
521 | "iconv-lite": {
522 | "version": "0.4.19",
523 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
524 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
525 | },
526 | "inflight": {
527 | "version": "1.0.6",
528 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
529 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
530 | "requires": {
531 | "once": "1.4.0",
532 | "wrappy": "1.0.2"
533 | }
534 | },
535 | "inherits": {
536 | "version": "2.0.3",
537 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
538 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
539 | },
540 | "ini": {
541 | "version": "1.3.4",
542 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
543 | "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4="
544 | },
545 | "ipaddr.js": {
546 | "version": "1.5.2",
547 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz",
548 | "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A="
549 | },
550 | "is-fullwidth-code-point": {
551 | "version": "2.0.0",
552 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
553 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
554 | },
555 | "is-promise": {
556 | "version": "2.1.0",
557 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
558 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
559 | },
560 | "is-stream": {
561 | "version": "1.1.0",
562 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
563 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
564 | },
565 | "isexe": {
566 | "version": "2.0.0",
567 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
568 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
569 | },
570 | "js-beautify": {
571 | "version": "1.7.4",
572 | "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.4.tgz",
573 | "integrity": "sha512-6YX1g+lIl0/JDxjFFbgj7fz6i0bWFa2Hdc7PfGqFhynaEiYe1NJ3R1nda0VGaRiGU82OllR+EGDoWFpGr3k5Kg==",
574 | "requires": {
575 | "config-chain": "1.1.11",
576 | "editorconfig": "0.13.3",
577 | "mkdirp": "0.5.1",
578 | "nopt": "3.0.6"
579 | }
580 | },
581 | "jsonfile": {
582 | "version": "4.0.0",
583 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
584 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
585 | "requires": {
586 | "graceful-fs": "4.1.11"
587 | }
588 | },
589 | "lodash": {
590 | "version": "4.17.4",
591 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
592 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
593 | },
594 | "log-update": {
595 | "version": "2.1.0",
596 | "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.1.0.tgz",
597 | "integrity": "sha1-6jcli1NU7bAuc7KRkAFsh9HIcUE=",
598 | "requires": {
599 | "ansi-escapes": "2.0.0",
600 | "cli-cursor": "2.1.0",
601 | "wrap-ansi": "3.0.1"
602 | }
603 | },
604 | "lru-cache": {
605 | "version": "4.1.1",
606 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
607 | "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
608 | "requires": {
609 | "pseudomap": "1.0.2",
610 | "yallist": "2.1.2"
611 | }
612 | },
613 | "lru-queue": {
614 | "version": "0.1.0",
615 | "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
616 | "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=",
617 | "requires": {
618 | "es5-ext": "0.10.35"
619 | }
620 | },
621 | "media-typer": {
622 | "version": "0.3.0",
623 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
624 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
625 | },
626 | "memoizee": {
627 | "version": "0.4.11",
628 | "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.11.tgz",
629 | "integrity": "sha1-vemBdmPJ5A/bKk6hw2cpYIeujI8=",
630 | "requires": {
631 | "d": "1.0.0",
632 | "es5-ext": "0.10.35",
633 | "es6-weak-map": "2.0.2",
634 | "event-emitter": "0.3.5",
635 | "is-promise": "2.1.0",
636 | "lru-queue": "0.1.0",
637 | "next-tick": "1.0.0",
638 | "timers-ext": "0.1.2"
639 | }
640 | },
641 | "merge-descriptors": {
642 | "version": "1.0.1",
643 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
644 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
645 | },
646 | "methods": {
647 | "version": "1.1.2",
648 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
649 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
650 | },
651 | "mime": {
652 | "version": "1.4.1",
653 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
654 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
655 | },
656 | "mime-db": {
657 | "version": "1.30.0",
658 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
659 | "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE="
660 | },
661 | "mime-types": {
662 | "version": "2.1.17",
663 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
664 | "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
665 | "requires": {
666 | "mime-db": "1.30.0"
667 | }
668 | },
669 | "mimic-fn": {
670 | "version": "1.1.0",
671 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz",
672 | "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg="
673 | },
674 | "minimatch": {
675 | "version": "3.0.4",
676 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
677 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
678 | "requires": {
679 | "brace-expansion": "1.1.8"
680 | }
681 | },
682 | "minimist": {
683 | "version": "0.0.8",
684 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
685 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
686 | },
687 | "mkdirp": {
688 | "version": "0.5.1",
689 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
690 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
691 | "requires": {
692 | "minimist": "0.0.8"
693 | }
694 | },
695 | "ms": {
696 | "version": "2.0.0",
697 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
698 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
699 | },
700 | "negotiator": {
701 | "version": "0.6.1",
702 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
703 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
704 | },
705 | "next-tick": {
706 | "version": "1.0.0",
707 | "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
708 | "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
709 | },
710 | "nopt": {
711 | "version": "3.0.6",
712 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
713 | "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
714 | "requires": {
715 | "abbrev": "1.1.1"
716 | }
717 | },
718 | "npm-run-path": {
719 | "version": "2.0.2",
720 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
721 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
722 | "requires": {
723 | "path-key": "2.0.1"
724 | }
725 | },
726 | "number-is-nan": {
727 | "version": "1.0.1",
728 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
729 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
730 | },
731 | "on-finished": {
732 | "version": "2.3.0",
733 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
734 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
735 | "requires": {
736 | "ee-first": "1.1.1"
737 | }
738 | },
739 | "once": {
740 | "version": "1.4.0",
741 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
742 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
743 | "requires": {
744 | "wrappy": "1.0.2"
745 | }
746 | },
747 | "onetime": {
748 | "version": "2.0.1",
749 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
750 | "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
751 | "requires": {
752 | "mimic-fn": "1.1.0"
753 | }
754 | },
755 | "open": {
756 | "version": "0.0.5",
757 | "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz",
758 | "integrity": "sha1-QsPhjslUZra/DcQvOilFw/DK2Pw="
759 | },
760 | "p-finally": {
761 | "version": "1.0.0",
762 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
763 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
764 | },
765 | "parseurl": {
766 | "version": "1.3.2",
767 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
768 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
769 | },
770 | "path-is-absolute": {
771 | "version": "1.0.1",
772 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
773 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
774 | },
775 | "path-key": {
776 | "version": "2.0.1",
777 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
778 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
779 | },
780 | "path-to-regexp": {
781 | "version": "0.1.7",
782 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
783 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
784 | },
785 | "proto-list": {
786 | "version": "1.2.4",
787 | "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
788 | "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk="
789 | },
790 | "proxy-addr": {
791 | "version": "2.0.2",
792 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz",
793 | "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=",
794 | "requires": {
795 | "forwarded": "0.1.2",
796 | "ipaddr.js": "1.5.2"
797 | }
798 | },
799 | "pseudomap": {
800 | "version": "1.0.2",
801 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
802 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
803 | },
804 | "qs": {
805 | "version": "6.5.1",
806 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
807 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
808 | },
809 | "querystringify": {
810 | "version": "1.0.0",
811 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz",
812 | "integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs="
813 | },
814 | "range-parser": {
815 | "version": "1.2.0",
816 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
817 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
818 | },
819 | "raw-body": {
820 | "version": "2.3.2",
821 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
822 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
823 | "requires": {
824 | "bytes": "3.0.0",
825 | "http-errors": "1.6.2",
826 | "iconv-lite": "0.4.19",
827 | "unpipe": "1.0.0"
828 | }
829 | },
830 | "regenerator-runtime": {
831 | "version": "0.11.0",
832 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz",
833 | "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A=="
834 | },
835 | "reload": {
836 | "version": "2.2.2",
837 | "resolved": "https://registry.npmjs.org/reload/-/reload-2.2.2.tgz",
838 | "integrity": "sha512-su5O0Db0LSxAw4XHl7FeSlu79PYaFt+OFxEIdc2/kb+pechWOnlQxV2LB+kHVdjjghQ7/2J1G/AWH20qUAywQQ==",
839 | "requires": {
840 | "cli-color": "1.2.0",
841 | "commander": "2.9.0",
842 | "finalhandler": "1.0.6",
843 | "minimist": "1.2.0",
844 | "open": "0.0.5",
845 | "serve-static": "1.12.6",
846 | "supervisor": "0.12.0",
847 | "url-parse": "1.1.9",
848 | "ws": "3.0.0"
849 | },
850 | "dependencies": {
851 | "commander": {
852 | "version": "2.9.0",
853 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
854 | "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
855 | "requires": {
856 | "graceful-readlink": "1.0.1"
857 | }
858 | },
859 | "finalhandler": {
860 | "version": "1.0.6",
861 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz",
862 | "integrity": "sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8=",
863 | "requires": {
864 | "debug": "2.6.9",
865 | "encodeurl": "1.0.1",
866 | "escape-html": "1.0.3",
867 | "on-finished": "2.3.0",
868 | "parseurl": "1.3.2",
869 | "statuses": "1.3.1",
870 | "unpipe": "1.0.0"
871 | }
872 | },
873 | "mime": {
874 | "version": "1.3.4",
875 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz",
876 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM="
877 | },
878 | "minimist": {
879 | "version": "1.2.0",
880 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
881 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
882 | },
883 | "send": {
884 | "version": "0.15.6",
885 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.6.tgz",
886 | "integrity": "sha1-IPI6nJJbdiq4JwX+L52yUqzkfjQ=",
887 | "requires": {
888 | "debug": "2.6.9",
889 | "depd": "1.1.1",
890 | "destroy": "1.0.4",
891 | "encodeurl": "1.0.1",
892 | "escape-html": "1.0.3",
893 | "etag": "1.8.1",
894 | "fresh": "0.5.2",
895 | "http-errors": "1.6.2",
896 | "mime": "1.3.4",
897 | "ms": "2.0.0",
898 | "on-finished": "2.3.0",
899 | "range-parser": "1.2.0",
900 | "statuses": "1.3.1"
901 | }
902 | },
903 | "serve-static": {
904 | "version": "1.12.6",
905 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.6.tgz",
906 | "integrity": "sha1-uXN3P2NEmTTaVOW+ul4x2fQhFXc=",
907 | "requires": {
908 | "encodeurl": "1.0.1",
909 | "escape-html": "1.0.3",
910 | "parseurl": "1.3.2",
911 | "send": "0.15.6"
912 | }
913 | }
914 | }
915 | },
916 | "requires-port": {
917 | "version": "1.0.0",
918 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
919 | "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
920 | },
921 | "restore-cursor": {
922 | "version": "2.0.0",
923 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
924 | "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
925 | "requires": {
926 | "onetime": "2.0.1",
927 | "signal-exit": "3.0.2"
928 | }
929 | },
930 | "safe-buffer": {
931 | "version": "5.1.1",
932 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
933 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
934 | },
935 | "semver": {
936 | "version": "5.4.1",
937 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
938 | "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
939 | },
940 | "send": {
941 | "version": "0.16.1",
942 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz",
943 | "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==",
944 | "requires": {
945 | "debug": "2.6.9",
946 | "depd": "1.1.1",
947 | "destroy": "1.0.4",
948 | "encodeurl": "1.0.1",
949 | "escape-html": "1.0.3",
950 | "etag": "1.8.1",
951 | "fresh": "0.5.2",
952 | "http-errors": "1.6.2",
953 | "mime": "1.4.1",
954 | "ms": "2.0.0",
955 | "on-finished": "2.3.0",
956 | "range-parser": "1.2.0",
957 | "statuses": "1.3.1"
958 | }
959 | },
960 | "serve-static": {
961 | "version": "1.13.1",
962 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz",
963 | "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==",
964 | "requires": {
965 | "encodeurl": "1.0.1",
966 | "escape-html": "1.0.3",
967 | "parseurl": "1.3.2",
968 | "send": "0.16.1"
969 | }
970 | },
971 | "setprototypeof": {
972 | "version": "1.1.0",
973 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
974 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
975 | },
976 | "shebang-command": {
977 | "version": "1.2.0",
978 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
979 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
980 | "requires": {
981 | "shebang-regex": "1.0.0"
982 | }
983 | },
984 | "shebang-regex": {
985 | "version": "1.0.0",
986 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
987 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
988 | },
989 | "sigmund": {
990 | "version": "1.0.1",
991 | "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
992 | "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA="
993 | },
994 | "signal-exit": {
995 | "version": "3.0.2",
996 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
997 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
998 | },
999 | "statuses": {
1000 | "version": "1.3.1",
1001 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
1002 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4="
1003 | },
1004 | "string-width": {
1005 | "version": "2.1.1",
1006 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
1007 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
1008 | "requires": {
1009 | "is-fullwidth-code-point": "2.0.0",
1010 | "strip-ansi": "4.0.0"
1011 | }
1012 | },
1013 | "strip-ansi": {
1014 | "version": "4.0.0",
1015 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
1016 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
1017 | "requires": {
1018 | "ansi-regex": "3.0.0"
1019 | }
1020 | },
1021 | "strip-eof": {
1022 | "version": "1.0.0",
1023 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
1024 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
1025 | },
1026 | "supervisor": {
1027 | "version": "0.12.0",
1028 | "resolved": "https://registry.npmjs.org/supervisor/-/supervisor-0.12.0.tgz",
1029 | "integrity": "sha1-3n5jNwFbKRhRwQ81OMSn8EkX7ME="
1030 | },
1031 | "supports-color": {
1032 | "version": "4.5.0",
1033 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
1034 | "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
1035 | "requires": {
1036 | "has-flag": "2.0.0"
1037 | }
1038 | },
1039 | "term-size": {
1040 | "version": "1.2.0",
1041 | "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz",
1042 | "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=",
1043 | "requires": {
1044 | "execa": "0.7.0"
1045 | }
1046 | },
1047 | "timers-ext": {
1048 | "version": "0.1.2",
1049 | "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.2.tgz",
1050 | "integrity": "sha1-YcxHp2wavTGV8UUn+XjViulMUgQ=",
1051 | "requires": {
1052 | "es5-ext": "0.10.35",
1053 | "next-tick": "1.0.0"
1054 | }
1055 | },
1056 | "type-is": {
1057 | "version": "1.6.15",
1058 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz",
1059 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=",
1060 | "requires": {
1061 | "media-typer": "0.3.0",
1062 | "mime-types": "2.1.17"
1063 | }
1064 | },
1065 | "ultron": {
1066 | "version": "1.1.0",
1067 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.0.tgz",
1068 | "integrity": "sha1-sHoualQagV/Go0zNRTO67DB8qGQ="
1069 | },
1070 | "universalify": {
1071 | "version": "0.1.1",
1072 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz",
1073 | "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc="
1074 | },
1075 | "unpipe": {
1076 | "version": "1.0.0",
1077 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1078 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
1079 | },
1080 | "url-parse": {
1081 | "version": "1.1.9",
1082 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.1.9.tgz",
1083 | "integrity": "sha1-xn8dd11R8KGJEd17P/rSe7nlvRk=",
1084 | "requires": {
1085 | "querystringify": "1.0.0",
1086 | "requires-port": "1.0.0"
1087 | }
1088 | },
1089 | "utils-merge": {
1090 | "version": "1.0.1",
1091 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1092 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
1093 | },
1094 | "vary": {
1095 | "version": "1.1.2",
1096 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1097 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
1098 | },
1099 | "which": {
1100 | "version": "1.3.0",
1101 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz",
1102 | "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==",
1103 | "requires": {
1104 | "isexe": "2.0.0"
1105 | }
1106 | },
1107 | "widest-line": {
1108 | "version": "1.0.0",
1109 | "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz",
1110 | "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=",
1111 | "requires": {
1112 | "string-width": "1.0.2"
1113 | },
1114 | "dependencies": {
1115 | "ansi-regex": {
1116 | "version": "2.1.1",
1117 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
1118 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
1119 | },
1120 | "is-fullwidth-code-point": {
1121 | "version": "1.0.0",
1122 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
1123 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
1124 | "requires": {
1125 | "number-is-nan": "1.0.1"
1126 | }
1127 | },
1128 | "string-width": {
1129 | "version": "1.0.2",
1130 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
1131 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
1132 | "requires": {
1133 | "code-point-at": "1.1.0",
1134 | "is-fullwidth-code-point": "1.0.0",
1135 | "strip-ansi": "3.0.1"
1136 | }
1137 | },
1138 | "strip-ansi": {
1139 | "version": "3.0.1",
1140 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
1141 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
1142 | "requires": {
1143 | "ansi-regex": "2.1.1"
1144 | }
1145 | }
1146 | }
1147 | },
1148 | "wrap-ansi": {
1149 | "version": "3.0.1",
1150 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz",
1151 | "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=",
1152 | "requires": {
1153 | "string-width": "2.1.1",
1154 | "strip-ansi": "4.0.0"
1155 | }
1156 | },
1157 | "wrappy": {
1158 | "version": "1.0.2",
1159 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1160 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
1161 | },
1162 | "ws": {
1163 | "version": "3.0.0",
1164 | "resolved": "https://registry.npmjs.org/ws/-/ws-3.0.0.tgz",
1165 | "integrity": "sha1-mN2wAFbIOQy3Ued4h4hJf5kQO2w=",
1166 | "requires": {
1167 | "safe-buffer": "5.0.1",
1168 | "ultron": "1.1.0"
1169 | },
1170 | "dependencies": {
1171 | "safe-buffer": {
1172 | "version": "5.0.1",
1173 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz",
1174 | "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c="
1175 | }
1176 | }
1177 | },
1178 | "yallist": {
1179 | "version": "2.1.2",
1180 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
1181 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
1182 | }
1183 | }
1184 | }
1185 |
--------------------------------------------------------------------------------
/packages/heml/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "heml",
3 | "version": "1.1.3",
4 | "description": "HEML is an open source markup language for building responsive email",
5 | "keywords": [
6 | "heml",
7 | "email"
8 | ],
9 | "homepage": "https://heml.io",
10 | "bugs": "https://github.com/SparkPost/heml/issues",
11 | "license": "MIT",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/SparkPost/heml.git"
15 | },
16 | "author": "SparkPost (https://sparkpost.com)",
17 | "files": [
18 | "build/"
19 | ],
20 | "main": "build/index.js",
21 | "bin": "build/bin/heml.js",
22 | "dependencies": {
23 | "@heml/elements": "^1.1.3",
24 | "@heml/inline": "^1.1.2",
25 | "@heml/parse": "^1.1.2",
26 | "@heml/render": "^1.1.2",
27 | "@heml/utils": "^1.1.2",
28 | "@heml/validate": "^1.1.2",
29 | "babel-runtime": "^6.26.0",
30 | "boxen": "^1.2.1",
31 | "byte-length": "^0.1.1",
32 | "chalk": "^2.1.0",
33 | "commander": "^2.11.0",
34 | "express": "^4.16.2",
35 | "fs-extra": "^4.0.2",
36 | "gaze": "^1.1.2",
37 | "get-port": "^3.2.0",
38 | "js-beautify": "^1.7.4",
39 | "lodash": "^4.17.4",
40 | "log-update": "^2.1.0",
41 | "open": "0.0.5",
42 | "reload": "^2.2.2"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/heml/src/bin/commands/build.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import { writeFile } from 'fs-extra'
3 | import chalk, { red as error, yellow as code, blue, dim } from 'chalk'
4 | import isHemlFile from '../utils/isHemlFile'
5 | import renderHemlFile from '../utils/renderHemlFile'
6 |
7 | const errorBlock = chalk.bgRed.black
8 | const successBlock = chalk.bgGreen.black
9 | const { log } = console
10 |
11 | export default async function build (file, options) {
12 | const filepath = path.resolve(file)
13 | const outputpath = path.resolve(options.output || file.replace(/\.heml$/, '.html'))
14 |
15 | /** require .heml extention */
16 | if (!isHemlFile(file)) {
17 | log(`${error('ERROR')} ${file} must have ${code('.heml')} extention`)
18 | process.exit(1)
19 | }
20 |
21 | try {
22 | log(`${chalk.bgBlue.black(' COMPILING ')}`)
23 | log(`${blue(' -')} Reading ${file}`)
24 | log(`${blue(' -')} Building HEML`)
25 | const { html, metadata, errors } = await renderHemlFile(filepath, options)
26 |
27 | log(`${blue(' -')} Writing ${metadata.size}`)
28 | await writeFile(outputpath, html)
29 |
30 | const relativePath = code(path.relative(process.cwd(), outputpath))
31 |
32 | log(errors.length
33 | ? `\n${errorBlock(' DONE ')} Compiled with errors to ${code(relativePath)} in ${metadata.time}ms\n`
34 | : `\n${successBlock(' DONE ')} Compiled successfully to ${code(relativePath)} in ${metadata.time}ms\n`)
35 |
36 | if (errors.length) {
37 | log(error(`${errors.length} ${errors.length > 1 ? 'errors' : 'error'} `))
38 | errors.forEach((err) => log(`> ${code(err.selector)}\n ${err.message}`))
39 | }
40 | } catch (err) {
41 | log(`\n${errorBlock(' ERROR ')} ${err.message}\n${dim(err.stack)}`)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/heml/src/bin/commands/develop.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import express from 'express'
3 | import reloadServer from 'reload'
4 | import openUrl from 'open'
5 | import logUpdate from 'log-update'
6 | import boxen from 'boxen'
7 | import gaze from 'gaze'
8 | import getPort from 'get-port'
9 | import chalk, { red as error, yellow as code } from 'chalk'
10 | import isHemlFile from '../utils/isHemlFile'
11 | import renderHemlFile from '../utils/renderHemlFile'
12 | import buildErrorPage from '../utils/buildErrorPage'
13 |
14 | const errorBlock = chalk.bgRed.white
15 | const { log } = console
16 |
17 | export default async function develop (file, options) {
18 | const filepath = path.resolve(file)
19 | const {
20 | port = 3000,
21 | open = false
22 | } = options
23 |
24 | /** require .heml extention */
25 | if (!isHemlFile(file)) {
26 | log(`${error('ERROR')} ${file} must have ${code('.heml')} extention`)
27 | process.exit(1)
28 | }
29 |
30 | try {
31 | const { update, url } = await startDevServer(path.dirname(filepath), port)
32 | const { html, errors, metadata } = await renderHemlFile(filepath)
33 |
34 | update({ html, errors, metadata })
35 |
36 | if (open) openUrl(url)
37 |
38 | /** watch for file changes */
39 | gaze(filepath, function (err) {
40 | if (err) throw err
41 |
42 | this.on('changed', async (changedFile) => {
43 | const { html, errors, metadata } = await renderHemlFile(filepath)
44 | update({ html, errors, metadata })
45 | })
46 |
47 | this.on('deleted', async (changedFile) => {
48 | log(`${errorBlock(' Error ')} ${code(file)} was deleted. Shutting down.`)
49 | process.exit()
50 | })
51 | })
52 | } catch (err) {
53 | if (err.code === 'ENOENT') {
54 | log(`${errorBlock(' Error ')} ${code(file)} doesn't exist`)
55 | } else {
56 | log(`${errorBlock(' Error ')} ${err.message}`)
57 | }
58 | process.exit()
59 | }
60 | }
61 |
62 | /**
63 | * update the cli UI
64 | * @param {String} params.url URL for preview server
65 | * @param {String} params.status the current status
66 | * @param {String} params.time time to compile the heml
67 | * @param {String} params.size size of the HTML in mb
68 | */
69 | function renderCLI ({ url, status, time, size }) {
70 | return logUpdate(boxen(
71 | `${chalk.bgBlue.black(' HEML ')}\n\n` +
72 | `- ${chalk.bold('Preview:')} ${url}\n` +
73 | `- ${chalk.bold('Status:')} ${status}\n` +
74 | `- ${chalk.bold('Compile time:')} ${time}ms\n` +
75 | `- ${chalk.bold('Total size:')} ${size}`,
76 | { padding: 1, margin: 1 }))
77 | }
78 |
79 | /**
80 | * Launches a server that reloads when the update function is called
81 | * @param {String} defaultPreview the default content for when the sever loads
82 | * @return {Object} { server, port, update }
83 | */
84 | function startDevServer (directory, port = 3000) {
85 | let url
86 | const app = express()
87 | const { reload } = reloadServer(app)
88 | let preview = ''
89 |
90 | app.get('/', (req, res) => res.send(preview))
91 | app.use(express.static(directory))
92 |
93 | function update ({ html, errors, metadata }) {
94 | let status = errors.length ? chalk.red('failed') : chalk.green('success')
95 | preview = errors.length
96 | ? buildErrorPage(errors)
97 | : html.replace('