Skip to content

Repository files navigation

Web Project Template

Set Up: Project Template

Set Up: Constants

CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"
PROJ="$(basename -- "${CWD}")"
EXT="xhtml"
cd "${CWD}"

Add Package: github.com/ptb/autokeep

if [ ! -d "${CWD}/.git" ]; then
  curl -fsSL "https://api.github.com/repos/ptb/autokeep/tarball/master" \
    | tar -C "${CWD}" --strip-components 1 -xz
  sh "${CWD}/initialize.command"
fi

Create File: .rubocop.yml

cat > "${CWD}/.rubocop.yml" <<-EOF
Style/AlignParameters:
  EnforcedStyle: with_fixed_indentation

Metrics/LineLength:
  Max: 80
EOF

Create File: Gemfile

cat > "${CWD}/Gemfile" <<-EOF
ruby '2.3.3', patchlevel: '222'

source 'https://rubygems.org'
source "file:#{Dir.home}/.gem/cache"

gem 'builder', '~> 3.2'
gem 'bundler', '~> 1.13'
gem 'middleman', '~> 4.1'
gem 'middleman-blog', '~> 4.0'
gem 'middleman-minify-html', '~> 3.4'
gem 'nokogiri', '~> 1.6'
gem 'rubocop', '~> 0.46', require: false
gem 'slim', '~> 3.0'
gem 'slim_lint', '~> 0.8'
EOF

Install: RubyGems

1> /dev/null bundle update

Create: Working Directories

mkdir -p "${CWD}/.tmp" "${CWD}/data" "${CWD}/docs" "${CWD}/helpers" \
  "${CWD}/logs" "${CWD}/src/_example" "${CWD}/src/_layouts" \
  "${CWD}/src/_parts" "${CWD}/src/css" "${CWD}/src/fonts" "${CWD}/src/img" \
  "${CWD}/src/js" "${CWD}/src/js/example-tag.tag"

Create Files: .gitignore and .keep

printf "%s\n" '*' '!.gitignore' > "${CWD}/.tmp/.gitignore"
touch "${CWD}/data/.keep" "${CWD}/docs/.keep" "${CWD}/src/.keep"

Set Up: SSL Certificate

HOWTO: Create A Certificate Authority

openssl genrsa -out ca.key 4096
openssl req -days 365 -key ca.key -multivalue-rdn -new -out ca.crt -sha256 \
  -subj /C=""/ST=""/L=""/O=""/OU=""/CN=example.com/emailAddress=""/ -x509

openssl x509 -in ca.crt -noout -text
openssl pkcs12 -export -in ca.crt -inkey ca.key -out ca.p12 -passout pass:abc

open ca.p12

HOWTO: Create A Private Key, CSR, and Certificate

openssl genrsa -out localhost.key 4096
openssl req -key localhost.key -multivalue-rdn -new -out localhost.csr \
  -subj /C=""/ST=""/L=""/O=""/OU=""/CN=localhost/emailAddress=""/
openssl x509 -CA ca.crt -CAkey ca.key -days 365 -in localhost.csr \
  -out localhost.crt -req -set_serial 01 -sha256

openssl x509 -in localhost.crt -noout -text
openssl pkcs12 -export -in localhost.crt -inkey localhost.key \
  -out localhost.p12 -passout pass:abc

open localhost.p12

Create Example: localhost.crt

cat > "${CWD}/localhost.crt" <<-EOF
-----BEGIN CERTIFICATE-----
MIIEmjCCAoICAQEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UEAxMHcHRiMi5tZTAe
Fw0xNjExMDExNjAwMDBaFw0xNzExMDExNjAwMDBaMBQxEjAQBgNVBAMTCWxvY2Fs
aG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKtTUxxo6ZJAUCIx
YehDOpxXr/UhsAMSV0jW1qegVjIWoVNOJi90cqbZlKuCVVUpzRhqHRcMmAB7g7fA
Jhzt8qt56T+atNHYV6sMuE4B3APF74lIFqQVfxO4duQWnIKea4jguYoWNXB9LzA4
XkIn104FYjrW71LbDm+7IW+IxaZ3sHh2s4NwGtCUZiKuxkt5JFIUx/Z4HVp0dSXI
1eH7h+b0e6XuE1L4BKkywDPGIxinZq55hgpA5h3I3YfofJs9l8RsJDOh1YkRJ+3g
pGp/RvnM8BwTOZrPV4Ut8b1loIWdMYpzvbgVjM27ayQX37nTwT+8MqQeFUGKdoUJ
TuXb2TaARU5YGfNLM5FQycArOTFuAjHgIOpWjzNKozMX73s2G6X2xUapNAaUfYg2
6UUVEML1+IciYI0/WEQSCgjDs9VYCNpO3DndY1GCAnzs9R/soA8aUNQTQOfuTrRS
0g/HzHXl1F8vldmQVP7qckymFpeTsPQB6sl9yNaUQ/BhrWiJ1gkulw+2fvz00z3D
0o8fGwlbkPRBn+7eqKg6tSfkkbX+pSu6JowDYB4GsOvbEZ0Hf+Sq+zkjRLVehnie
KWIddgzuU/CyFLdxHxzdsUotXuPc6VD1o6LNAH65LmpMNOruBLiZBI0Yj6j6brhR
nDwdJkZsJ8SxTBlWzcaHcaEp/jDVAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAFUE
VbxYKLYPUGrWpx6ZUg3FbC5flGxt5VocAA6pIYDyhxDGVlbh5GS7dtUSw5aaZCtX
6IL/8sI5jGYEA0UiO97kExZ2RfJWw53plQAJM1V6tBAm278E2SwBi2XvIbTc5zNw
gGckRhPbgOtcR8Pgt02pvwJ66XGZn/Uwh3BSlG05mTubUYTEgFM6eU+Y8QLEm6+q
WRz1zfUVX0uQYcVAcUpj+WhXhSErgKvaYp5LHhpNXD8X9WsYODMWcEsgluo5SO/B
9+xR0PZMFzsGjbEbgIF61wtPqSdiWF+Ir9kPOoPbVcgyNbMHqAx2JNE/I7/ixJLw
Gt2Zxc1+GcZWSQcJiVCcJm80aLsCuQyK29n0JSnsBX8qwj7UQWzHHZvQGRHu8vev
VqMTyagmp7XHro+6CIjv7fCENQuxu8LG0V1ZdAVngowqMY/K4VG2Quwh1sHvAre4
59NFu9zP/qESdmU6yTN85gGXethtJaah9OG7V/smia1zSyfPBLk8QDPCPpBynDOC
oaqwf1iP8MH/WDZrJ329gDfnMagSnDUs34KwDICSc6lTX2hrLUguxWa62jFRSAK5
WJWjGd+TfzkxoqVTTXpfnBxWRMhhiMnbfgsuUt1MU2qsLBcdlfDEZSg3qG/fSon7
ARGGl/To+UnJ9SdV1dNtiJfemZ844gN/5c4Z26UR
-----END CERTIFICATE-----
EOF

Create Example: localhost.key

cat > "${CWD}/localhost.key" <<-EOF
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAq1NTHGjpkkBQIjFh6EM6nFev9SGwAxJXSNbWp6BWMhahU04m
L3RyptmUq4JVVSnNGGodFwyYAHuDt8AmHO3yq3npP5q00dhXqwy4TgHcA8XviUgW
pBV/E7h25Bacgp5riOC5ihY1cH0vMDheQifXTgViOtbvUtsOb7shb4jFpneweHaz
g3Aa0JRmIq7GS3kkUhTH9ngdWnR1JcjV4fuH5vR7pe4TUvgEqTLAM8YjGKdmrnmG
CkDmHcjdh+h8mz2XxGwkM6HViREn7eCkan9G+czwHBM5ms9XhS3xvWWghZ0xinO9
uBWMzbtrJBffudPBP7wypB4VQYp2hQlO5dvZNoBFTlgZ80szkVDJwCs5MW4CMeAg
6laPM0qjMxfvezYbpfbFRqk0BpR9iDbpRRUQwvX4hyJgjT9YRBIKCMOz1VgI2k7c
Od1jUYICfOz1H+ygDxpQ1BNA5+5OtFLSD8fMdeXUXy+V2ZBU/upyTKYWl5Ow9AHq
yX3I1pRD8GGtaInWCS6XD7Z+/PTTPcPSjx8bCVuQ9EGf7t6oqDq1J+SRtf6lK7om
jANgHgaw69sRnQd/5Kr7OSNEtV6GeJ4pYh12DO5T8LIUt3EfHN2xSi1e49zpUPWj
os0Afrkuakw06u4EuJkEjRiPqPpuuFGcPB0mRmwnxLFMGVbNxodxoSn+MNUCAwEA
AQKCAgAzhoXSX4MPpyPyhKOLdNyltIGI2a7T3ao+j85S75e8zK2MHk7BquXoZDug
MTx0tnOmShOhoPn9+yesFa/gt3dqNun9ZQvIqKYFHXg4jbqbr+XQhcu5YXWseqfX
BS3g4sA4lE14yCEeSafteqZugwjvwBLA229yncsDs2Xk419+JzT9pcVrXEXUQS1O
dP3SKQbRYMqax5kcYTXyPSqGIl3HCfQ9/RH2u/y63jaL26UmfwIUqxuy7b3Ha2ek
vjjrf2SrgFKK7LsJ1A0ML6mDFfBEpGv6JZYHhyAtP5oaefwC8zm5CAIg4OpN3XXl
jwCMJLFT98fLL/j4kyn97MS7wJjcm0U1GaPb/6P6h3oTsudqcssFQ1YMqJUElji4
FPDE8lkRHgTLKWT/pegorKFg2PXVDiPAcL28kowBrz0hVrqb8KSYQjtQ1UYNy4k2
FVomP+kRYDZUkKke1Q7VHaKR5YyZD4Ebjt7+LEZWb4YYsrLrQLGTVj3cEg4MEZzq
9caksYr8wetOZ+cmGpUzn4GIjNdgk/vHjHjU5yAaTCCK8ySTdA5Hs9qdZDGA0mvp
u2riDrjI01M3cMBUEYvq5mMvrw91yghU6/rDsCy7m3wwyRcGKtqVpGG5m9r0co3j
55ydIDAcAaW3UZZ/9LQarmCPoxMZ3CQ0zqspoTKLAiaHCDA5aQKCAQEA150S5HDD
mBP7GUpHO0ECtz+pwC/eKhZqCovy+BtCz2pV9Q6qybcwHQAvsyF7ZhlmDUIpBcwT
0F9RT9s/FugpYB/dtj2noN7pCt3e73Rn8FqI1yaFSsM+LBqhr/QfD3M7wLe+BdZN
Bk9qbooAVoJWAZlqkeHLVj88CYml+lBlzjQ9yhONoSjo5uJPscQCcARFdCeXVaI/
LDUWq2jFWcNTjI8wpoM0nC66JQWH5XJUWS9WXnDxx2jkm+tAI5HROlLqedCLCLDA
3RRb5arKyMv+AJXZbdLV7TJGpYZgbjVU6P5bk46RTRLetqMtOT5bwsRo0mP2xnhv
ECIphzx6Qb/2twKCAQEAy2qVta5cQZRm0QMbW1EH9DpB57ytvYeahzlT8u4lgGjK
6gYVBpSXxfe22gxY1iFXTGZ7Wt6licXZY538g10N76SAIZmR5BtsKM9x0pYaR8TE
dbg77+1ygQGftZmnXEfkjPA4vLePgo9TNNq1nCMnhWeSEHOSK6qRA0xtiop2t2BA
YTa+LXlut2wBNMXJxrPhMB2YWmmt7mR70HaBTXtAYc1NG/jxvvTZ8kj2gKQsoZnk
02kSFMN29eJqtroWiLKXuGopUFi4ONvDzCdG0zX9MHRmHRXzRfA+mnHlsCs3nCrB
D4R3TAW4EMaxlhqEykESKUj5cVw/Hh5ZenxdWFDo0wKCAQAmdMyO1BH+yOmLTDVC
O0kpuAAl2CPO6+qD8Qwn9mzHI9cq+y+5BKRfN85KK9mfjz4ldTxt3UVXb/jHvnoV
MtlWXLilrX99cOSt3H7LxL5ZHzyy6xrxB7vKEAHwqSD1F3970wngbqWUT4vTJWgE
BgEjDeL1HAmxW1vw840YBUjzK1wt86jaKrrHm2vNcAFjS0+79OfIDUUpNFrTvb8d
UHpRYqSxvkse5DKtMbxYTzj+IzRLaeelwm6r5Jyu+24O8gUCLxDtPuFXTS9K2liK
0d9+6Ts7nFQtlz8EfOAsJ/T6DYv12ILP9WKlvprtT9L7/R4ZEF1ObuKYRQ+VIFNJ
8NwnAoIBAQCGyyZRsizoOBK5xjlGlbeTm1c43Mq0oTtBCDgc7rpjEQ4FbepY5fZ8
9N1yOGRV4NocgnyThp5jYXvgizxdQDiP6I4Ptdf7NEDD+lMOnPDlRfp5l5AAOXR7
EDxo9lz0xZ8p1bWDOJAckCUvDQ7zXEhAGpWr2SmvTeOyL98WwxJQCbQft0rmj2VS
nnteIQLIbUJ3w+TFZOog4u05Ao5EbbwIEydZtelcBJy39KmELUZ1/6imfyXAJJab
8WGNnFp6Uv4+8fpWh7Pr2ZUxn4VLSKdyiG9yLq16VuzlIzx1NO4wyhQM2FbFRbTn
SQM82G3OKLiEKxtAh0Q8DRh2Fhs0/siJAoIBAQCTjmptTuCAjHUwtg9JSKOdZEBS
Ro8kMvIJWan88XuSgKyHiCSgxb5OVN/DTLKfEzwqiX6ZP30D44SqfHXTovKRej7T
N9a29erDIF3u05CteVQKCQ2f8sZxT5peDgAD6I4ScZICnYX5q8Z/FgPkcExqP+8I
vQR28on9+aRkF1fsUN0j/Wgy8I7vkoVuCdGbK0ZZoWrIFW5GymLebZQl/lfaXOp9
Gw7+8Ze/wC7Lsxeds7jpDxG2bajZTgDs53D7+KNks3itsd3sDU4PHj5XpvbcDxjO
eAfR7m1rtN2dKTJVuXXRnPNuzxLaAWbFkTyx82HyUkaYHskYX1fxb+TjGUAu
-----END RSA PRIVATE KEY-----
EOF

Set Up: Middleman Static Site Generator

TODO: Notes

configure :build do
  after_build do
    puts 'hi'
  end
end

Create File: config.rb

cat > "${CWD}/config.rb" <<-EOF
MIN = config[:environment] == :production
EXT = '${EXT}'.freeze

activate :blog do |blog|
  Time.zone = 'America/New_York'

  blog.sources = "{title}/index.#{EXT}"
  blog.default_extension = '.slim'

  # blog.layout = 'blog'
  blog.permalink = '{title}'

  # blog.generate_tag_pages = true
  blog.tag_template = "articles.#{EXT}"
  blog.taglink = "{tag}/index.#{EXT}"

  blog.calendar_template = "articles.#{EXT}"
  blog.year_link = "{year}/index.#{EXT}"
  blog.month_link = "{year}/{month}/index.#{EXT}"
  blog.day_link = "{year}/{month}/{day}/index.#{EXT}"

  blog.generate_year_pages = false
  blog.generate_month_pages = false
  blog.generate_day_pages = false

  blog.paginate = true
  blog.per_page = 3
  blog.page_link = 'page/{num}'
end

activate :directory_indexes

activate :external_pipeline,
  command: "node_modules/.bin/gulp build#{MIN ? ' --min' : nil} --silent",
  name: :gulp,
  source: '.tmp'

configure :development do
  if build?
    # url_for('/blog/file.xhtml') or url_for(sitemap.resources[0])
    # Example: link(href="#{url_for('/css/style.css')}" rel='stylesheet')

    activate :relative_assets
    set :relative_links, true
    set :strip_index_file, false
  end
end

configure :production do
  activate :asset_hash
  activate :minify_html, remove_quotes: false, simple_boolean_attributes: false
end

ignore(/.*\.keep/)
ignore(%r{\.tag/.*})

set :build_dir, 'docs'
set :css_dir, 'css' if File.directory? 'src/css/'
set :fonts_dir, 'fonts' if File.directory? 'src/fonts/'
set :images_dir, 'img' if File.directory? 'src/img/'
set :js_dir, 'js' if File.directory? 'src/js/'
set :layouts_dir, '_layouts' if File.directory? 'src/_layouts/'
set :source, 'src' if File.directory? 'src/'

set :https, true
set :ssl_certificate, 'localhost.crt'
set :ssl_private_key, 'localhost.key'

set :index_file, "index.#{EXT}"
set :layout, 'layout'

set :slim,
  attr_quote: "'",
  format: EXT.to_sym,
  pretty: !MIN,
  sort_attrs: true,
  shortcut: {
    '@' => { attr: 'role' },
    '#' => { attr: 'id' },
    '.' => { attr: 'class' },
    '%' => { attr: 'itemprop' },
    '^' => { attr: 'data-is' },
    '&' => { attr: 'type', tag: 'input' }
  }
EOF

Create File: helpers/custom_helpers.rb

cat > "${CWD}/helpers/custom_helpers.rb" <<-EOF
module CustomHelpers
  def article(article, content)
    partial '_parts/article', locals: {
      article: article,
      content: content,
      single: is_blog_article?
    }
  end

  def inline_tag(tag, *files)
    content_tag tag.to_sym do
      content = '/*<![CDATA[*/ '
      files.map do |file|
        content << sitemap.find_resource_by_path(file).render
      end
      content << ' /*]]>*/'
      content
    end
  end

  def page_intro
    if current_page.methods.include? :slug
      if File.exist?("src/_parts/_#{current_page.slug}.slim")
        partial "_parts/#{current_page.slug}"
      end
    elsif !!current_page.locals['tagname']
      if File.exist?("src/_parts/_#{current_page.locals['tagname']}.slim")
        partial "_parts/#{current_page.locals['tagname']}"
      end
    end
  end

  def page_title
    site_name = 'ptb2.me'
    if is_blog_article?
      "#{current_page.title} - #{site_name}"
    else
      d = Date.new(current_page.locals['year'] || 1, current_page.locals['month'] || 1, current_page.locals['day'] || 1)
      case current_page.locals['page_type']
      when 'day'
        "#{site_name} for #{d.strftime('%B')} #{d.strftime('%e').to_i.ordinalize}, #{d.strftime('%Y')}"
      when 'month'
        "#{site_name} for #{d.strftime('%B')} #{d.strftime('%Y')}"
      when 'year'
        "#{site_name} for #{d.strftime('%Y')}"
      when 'tag'
        "#{current_page.locals['tagname'].titleize} - #{site_name}"
      else
        "Welcome to #{site_name}"
      end
    end
  end

  def pagination
    if is_blog_article?
      partial '_parts/pagination', locals: {
        prev_pg: current_page.next_article,
        next_pg: current_page.previous_article,
        page_num: nil,
        total_pg: nil,
        single: true
      }
    else
      partial '_parts/pagination', locals: {
        prev_pg: current_page.locals['prev_page'],
        next_pg: current_page.locals['next_page'],
        page_num: current_page.locals['page_number'],
        total_pg: current_page.locals['num_pages'],
        single: false
      }
    end
  end
end
EOF

Create Example: src/_layouts/layout.slim

cat > "${CWD}/src/_layouts/layout.slim" <<-EOF
doctype 5
html.no-js(lang='en' xml:lang='en' xmlns='http://www.w3.org/1999/xhtml')
  head
    meta(charset='utf-8')/

    title = page_title

    meta(content='initial-scale=1, width=device-width' name='viewport')/

    / link(href="#{url_for('/css/style.css')}" rel='stylesheet')/

    - if content_for? :head
      == yield_content :head

  body(itemscope itemtype='http://www.schema.org/Blog')

    #main@main(class="#{is_blog_article? ? nil : 'hfeed'}")
      == page_intro

      - if is_blog_article?
        == article(current_article, yield)
      - else
        - page_articles.each do |article|
          == article(article, article.summary)

      == pagination

    - if content_for? :foot
      == yield_content :foot

    / script(src='/browser-sync/browser-sync-client.js')
EOF

Set Up: Example File Placeholders

Create Example: src/css/style.css.sass

# touch "${CWD}/src/css/style.css.sass"

Create Example: src/favicon.ico

base64 -D > "${CWD}/src/favicon.ico" <<-EOF
  AAABAAEAEBACAAEAAQCwAAAAFgAAACgAAAAQAAAAIAAAAAEAAQAAAAAAAAAAAAAA
  AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA//8AAP//
  AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//
  AAD//wAA
EOF

Create Example: src/apple-touch-icon-precomposed.png

base64 -D > "${CWD}/src/apple-touch-icon-precomposed.png" <<-EOF
  iVBORw0KGgoAAAANSUhEUgAAALQAAAC0AQMAAAAHA5RxAAAAA1BMVEUmRcn0DMbc
  AAAAAXRSTlMAQObYZgAAABtJREFUeF7twAEJAAAAwjD7pzbHYVscAAAAwAEQ4AAB
  d61H3AAAAABJRU5ErkJggg==
EOF

Create: Middleman Blog Templates

TODO: Notes

- Reference: https://github.com/middleman/middleman-templates-default
- Reference: https://github.com/middleman/middleman-templates-blog

Create File: src/index.${EXT}.slim

cat > "${CWD}/src/index.${EXT}.slim" <<-EOF
---
pageable: true
per_page: 3
---
EOF

Link File: src/index.${EXT}.slim to src/articles.${EXT}.slim

cd "${CWD}/src" && ln -s index.${EXT}.slim articles.${EXT}.slim && cd "${CWD}"

Create Example: src/_parts/_article.slim

cat > "${CWD}/src/_parts/_article.slim" <<-EOF
article.hentry%blogPost<>(itemscope itemtype='http://schema.org/BlogPosting')
  header
    - unless article.tags.empty?
      ul.tags@navigation
        - article.tags.each do |tag, articles|
          li
            a%keywords(href="#{tag_path tag}" rel='tag') = tag

  h2.entry-title%headline
    - if single
      = article.title
    - else
      a.permalink%url(href="#{article.url}" rel='bookmark') = article.title

  - if single
    div.entry-content%articleBody
      == content
  - else
    div.entry-summary%description
      == content
EOF

Create Example: src/_parts/_pagination.slim

cat > "${CWD}/src/_parts/_pagination.slim" <<-EOF
- if prev_pg || next_pg
  nav.pages@navigation(aria-labelledby='pagination')
    h3#pagination Page Navigation
    div
  - if prev_pg
    span.prev>
      a(href="#{prev_pg.url}" rel='prev')
        = single ? prev_pg.title : 'Newer'
  - if page_num && total_pg
    span.page
      = page_num
      span &#160;of&#160;
      = total_pg
  - if next_pg
    span.next<
      a(href="#{next_pg.url}" rel='next')
        = single ? next_pg.title : 'Older'
EOF

Create Example: src/_example/index.${EXT}.slim

cat > "${CWD}/src/_example/index.${EXT}.slim" <<-EOF
---
title: Example
date: 2016-11-01
tags: examples
---
p
  |
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
    tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
    veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
    commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
    velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
    cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
    est laborum.

example-tag
EOF

Create: Riot Example Tag

Create Example: src/js/example-tag.tag/example-tag.slim

cat > "${CWD}/src/js/example-tag.tag/example-tag.slim" <<-EOF
p
  |
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
    tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
    veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
    commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
    velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
    cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
    est laborum.

div
  p
    |
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
      tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
      veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
      commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
      velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
      occaecat cupidatat non proident, sunt in culpa qui officia deserunt
      mollit anim id est laborum.
EOF

Create Example: src/js/example-tag.tag/example-tag.svg

cat > "${CWD}/src/js/example-tag.tag/example-tag.svg" <<-EOF
<svg xmlns="http://www.w3.org/2000/svg">
  <rect height="100" width="100" />
</svg>
EOF

Create Example: src/js/example-tag.tag/example-tag.sass

cat > "${CWD}/src/js/example-tag.tag/example-tag.sass" <<-EOF
\\:scope
  display: block

p
  width: 80em
EOF

Create Example: src/js/example-tag.tag/example-tag.es6

cat > "${CWD}/src/js/example-tag.tag/example-tag.es6" <<-EOF
/* eslint no-console: 0, no-magic-numbers: 0 */

/**
 * Add two numbers.
 * @param {number} a The first number.
 * @param {number} b The second number.
 * @returns {number} The sum of the two numbers.
 */
const add = (a, b) => a + b

console.log(add(4, 5))
EOF

Set Up: NPM Defaults

Create File: package.json

cat > "${CWD}/package.json" <<-EOF
{
  "author": "Peter T Bosse II <ptb@ioutime.com> (http://ptb2.me)",
  "bugs": {
    "url": "https://github.com/ptb/${PROJ}/issues"
  },
  "dependencies": {},
  "description": "web project template",
  "devDependencies": {},
  "homepage": "https://github.com/ptb/${PROJ}#readme",
  "license": "Apache-2.0",
  "name": "${PROJ}",
  "repository": {
    "type": "git",
    "url": "git://github.com/ptb/${PROJ}.git"
  },
  "scripts": {},
  "version": "$(date '+%Y.%-m.%-e')"
}
EOF

Set Up: Gulp

Add Package: gulp

1> /dev/null yarn add --dev \
  github:gulpjs/gulp#4.0 \
  gulp-cli \
  gulp-load-plugins \
  gulp-util \
  kexec

Create File: gulpfile.js

cat > "${CWD}/gulpfile.js" <<-EOF
// -- require ---------------------------------------------------------------

const gulp = require("gulp")
const plug = require("gulp-load-plugins")({
  "pattern": "*"
})
const proc = require("child_process")

// -- const -----------------------------------------------------------------

const MIN = typeof plug.util.env.min != "undefined"

// -- opts ------------------------------------------------------------------

const opts = new function () {
  return {
    "restart": {
      "files": ["config.rb", "Gemfile.lock", "gulpfile.js", "package.json",
        "yarn.lock"]
    }
  }
}()

// -- task ------------------------------------------------------------------

const task = {
  "restart": function () {
    if (process.platform === "darwin") {
      proc.spawn("osascript", ["-e", 'activate app "Terminal"', "-e",
        'tell app "System Events" to keystroke "k" using command down'])
    }
    plug.kexec("npm", ["run", MIN ? "build" : "start"])
  }
}

// -- gulp ------------------------------------------------------------------

gulp.task("build", function build (done) {
  done()
})

gulp.task("default", function watch (done) {
  gulp.watch(opts.restart.files, task.restart)
  done()
})
EOF

Patch File: package.json

cat <<-EOF | patch 1> /dev/null
--- package.json
+++ package.json
@@ -22 +22,5 @@
-  "scripts": {},
+  "scripts": {
+    "prestart": "bundle install 1> /dev/null && yarn install 1> /dev/null",
+    "start": "gulp --silent",
+    "build": "gulp --min --silent"
+  },
EOF

gulp ▸ task ▸ check ▸ slim

Add Package: lazypipe

1> /dev/null yarn add --dev \
  gulp-changed-in-place \
  gulp-if \
  gulp-trimlines \
  lazypipe

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -3,0 +4 @@
+const path = require("path")
@@ -5 +6,4 @@
-  "pattern": "*"
+  "pattern": "*",
+  "rename": {
+    "gulp-if": "gulpIf"
+  }
@@ -10,0 +15,3 @@
+const CWD = process.cwd()
+const SRC = path.join(CWD, "src")
+
@@ -16,0 +24,9 @@
+    "changedInPlace": {
+      "firstPass": true
+    },
+    "ext": {
+      "slim": "*.sl?(i)m"
+    },
+    "path": {
+      "src": path.join(SRC, "**")
+    },
@@ -19,0 +36,3 @@
+    },
+    "trimlines": {
+      "leading": false
@@ -32,0 +52,19 @@
+  },
+  "save": {
+    "src": function () {
+      return plug.gulpIf(!MIN, gulp.dest(SRC))
+    }
+  },
+  "tidy": {
+    "lines": function () {
+      return plug.trimlines(opts.trimlines)
+    }
+  }
+}
+
+// -- pipe ------------------------------------------------------------------
+
+const pipe = {
+  "slim": function () {
+    return plug.lazypipe()
+      .pipe(task.tidy.lines)
@@ -42 +80,13 @@
-gulp.task("default", function watch (done) {
+gulp.task("check", gulp.series(
+  function slim (done) {
+    gulp.src(path.join(opts.path.src, opts.ext.slim), {
+      "since": gulp.lastRun("check")
+    })
+      .pipe(plug.changedInPlace(opts.changedInPlace))
+      .pipe(pipe.slim()())
+      .pipe(task.save.src())
+    done()
+  }
+))
+
+gulp.task("default", gulp.series("check", function watch (done) {
@@ -43,0 +94 @@
+  gulp.watch(opts.path.src, gulp.series("check"))
@@ -45 +96 @@
-})
+}))
EOF

task ▸ lint ▸ slim

Create File: .slim-lint.yml

cat > "${CWD}/.slim-lint.yml" <<-EOF
linters:
  TagCase:
    enabled: false

skip_frontmatter: true
EOF

Add Package: gulp-flatmap

1> /dev/null yarn add --dev \
  gulp-flatmap

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -45,0 +46,10 @@
+  "lint": {
+    "slim": function () {
+      return plug.flatmap(function (stream, file) {
+        proc.spawn("slim-lint", [file.path], {
+          "stdio": "inherit"
+        })
+        return stream
+      })
+    }
+  },
@@ -70,0 +81 @@
+      .pipe(task.lint.slim)
EOF

gulp ▸ task ▸ check ▸ sass

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -27,0 +28 @@
+      "sass": "*.s@(a|c)ss",
@@ -77,0 +79,4 @@
+  "sass": function () {
+    return plug.lazypipe()
+      .pipe(task.tidy.lines)
+  },
@@ -99,0 +105,9 @@
+  },
+  function sass (done) {
+    gulp.src(path.join(opts.path.src, opts.ext.sass), {
+      "since": gulp.lastRun("check")
+    })
+      .pipe(plug.changedInPlace(opts.changedInPlace))
+      .pipe(pipe.sass()())
+      .pipe(task.save.src())
+    done()
EOF

task ▸ tidy ▸ sass

Create File: .csscomb.json

cat > "${CWD}/.csscomb.json" <<-EOF
{
  "always-semicolon": true,
  "block-indent": "  ",
  "color-case": "lower",
  "color-shorthand": true,
  "element-case": "lower",
  "eof-newline": false,
  "exclude": [
    ".bundle/**",
    ".git/**",
    "node_modules/**"
  ],
  "leading-zero": true,
  "quotes": "double",
  "remove-empty-rulesets": true,
  "sort-order": [
    [
      "-webkit-rtl-ordering",
      "direction",
      "unicode-bidi",
      "writing-mode",
      "text-orientation",
      "glyph-orientation-vertical",
      "text-combine-upright",
      "text-transform",
      "white-space",
      "tab-size",
      "line-break",
      "word-break",
      "hyphens",
      "word-wrap",
      "overflow-wrap",
      "text-align",
      "text-align-last",
      "text-justify",
      "word-spacing",
      "letter-spacing",
      "text-indent",
      "hanging-punctuation",
      "-webkit-nbsp-mode",
      "text-decoration",
      "text-decoration-line",
      "text-decoration-style",
      "text-decoration-color",
      "text-decoration-skip",
      "text-underline-position",
      "text-emphasis",
      "text-emphasis-style",
      "text-emphasis-color",
      "text-emphasis-position",
      "text-shadow",
      "-webkit-text-fill-color",
      "-webkit-text-stroke",
      "-webkit-text-stroke-width",
      "-webkit-text-stroke-color",
      "-webkit-text-security",
      "font",
      "font-style",
      "font-variant",
      "font-weight",
      "font-stretch",
      "font-size",
      "line-height",
      "font-family",
      "src",
      "unicode-range",
      "-webkit-text-size-adjust",
      "font-size-adjust",
      "font-synthesis",
      "font-kerning",
      "font-variant-ligatures",
      "font-variant-position",
      "font-variant-caps",
      "font-variant-numeric",
      "font-variant-alternates",
      "font-variant-east-asian",
      "font-feature-settings",
      "font-language-override",
      "list-style",
      "list-style-type",
      "list-style-position",
      "list-style-image",
      "marker-side",
      "counter-set",
      "counter-increment",
      "caption-side",
      "table-layout",
      "border-collapse",
      "-webkit-border-horizontal-spacing",
      "-webkit-border-vertical-spacing",
      "border-spacing",
      "empty-cells",
      "move-to",
      "quotes",
      "counter-increment",
      "counter-reset",
      "page-policy",
      "content",
      "crop",
      "box-sizing",
      "outline",
      "outline-color",
      "outline-style",
      "outline-width",
      "outline-offset",
      "resize",
      "text-overflow",
      "cursor",
      "caret-color",
      "nav-up",
      "nav-right",
      "nav-down",
      "nav-left",
      "-webkit-appearance",
      "-webkit-user-drag",
      "-webkit-user-modify",
      "-webkit-user-select",
      "-moz-user-select",
      "-ms-user-select",
      "pointer-events",
      "-webkit-dashboard-region",
      "-apple-dashboard-region",
      "-webkit-touch-callout",
      "position",
      "top",
      "right",
      "bottom",
      "left",
      "offset-before",
      "offset-end",
      "offset-after",
      "offset-start",
      "z-index",
      "display",
      "-webkit-margin-collapse",
      "-webkit-margin-top-collapse",
      "-webkit-margin-bottom-collapse",
      "-webkit-margin-start",
      "margin",
      "margin-top",
      "margin-right",
      "margin-bottom",
      "margin-left",
      "-webkit-padding-start",
      "padding",
      "padding-top",
      "padding-right",
      "padding-bottom",
      "padding-left",
      "width",
      "min-width",
      "max-width",
      "height",
      "min-height",
      "max-height",
      "float",
      "clear",
      "overflow",
      "overflow-x",
      "overflow-y",
      "-webkit-overflow-scrolling",
      "overflow-style",
      "marquee-style",
      "marquee-loop",
      "marquee-direction",
      "marquee-speed",
      "visibility",
      "rotation",
      "rotation-point",
      "flex-flow",
      "flex-direction",
      "flex-wrap",
      "order",
      "flex",
      "flex-grow",
      "flex-shrink",
      "flex-basis",
      "justify-content",
      "align-items",
      "align-self",
      "align-content",
      "columns",
      "column-width",
      "column-count",
      "column-gap",
      "column-rule",
      "column-rule-width",
      "column-rule-style",
      "column-rule-color",
      "break-before",
      "break-after",
      "break-inside",
      "column-span",
      "column-fill",
      "grid",
      "grid-template",
      "grid-template-columns",
      "grid-template-rows",
      "grid-template-areas",
      "grid-auto-flow",
      "grid-auto-columns",
      "grid-auto-rows",
      "grid-column",
      "grid-row",
      "grid-area",
      "grid-row-start",
      "grid-column-start",
      "grid-row-end",
      "grid-column-end",
      "grid-gap",
      "grid-column-gap",
      "grid-row-gap",
      "orphans",
      "widows",
      "box-decoration-break",
      "background",
      "background-image",
      "background-position",
      "background-size",
      "background-repeat",
      "background-attachment",
      "background-origin",
      "background-clip",
      "background-color",
      "border",
      "border-width",
      "border-style",
      "border-color",
      "border-top",
      "border-top-width",
      "border-top-style",
      "border-top-color",
      "border-right",
      "border-right-width",
      "border-right-style",
      "border-right-color",
      "border-bottom",
      "border-bottom-width",
      "border-bottom-style",
      "border-bottom-color",
      "border-left",
      "border-left-width",
      "border-left-style",
      "border-left-color",
      "border-radius",
      "border-top-left-radius",
      "border-top-right-radius",
      "border-bottom-right-radius",
      "border-bottom-left-radius",
      "border-image",
      "border-image-source",
      "border-image-slice",
      "border-image-width",
      "border-image-outset",
      "border-image-repeat",
      "box-shadow",
      "color",
      "opacity",
      "-webkit-tap-highlight-color",
      "object-fit",
      "object-position",
      "image-resolution",
      "image-orientation",
      "clip-path",
      "mask",
      "mask-image",
      "mask-mode",
      "mask-repeat",
      "mask-position",
      "mask-clip",
      "mask-origin",
      "mask-size",
      "mask-composite",
      "mask-border",
      "mask-border-source",
      "mask-border-slice",
      "mask-border-width",
      "mask-border-outset",
      "mask-border-repeat",
      "mask-border-mode",
      "mask-type",
      "clip",
      "filter",
      "transition",
      "transition-property",
      "transition-duration",
      "transition-timing-function",
      "transition-delay",
      "transform",
      "transform-origin",
      "transform-style",
      "perspective",
      "perspective-origin",
      "backface-visibility",
      "animation",
      "animation-name",
      "animation-duration",
      "animation-timing-function",
      "animation-delay",
      "animation-iteration-count",
      "animation-direction",
      "animation-fill-mode",
      "animation-play-state",
      "voice-volume",
      "voice-balance",
      "speak",
      "speak-as",
      "pause",
      "pause-before",
      "pause-after",
      "rest",
      "rest-before",
      "rest-after",
      "cue",
      "cue-before",
      "cue-after",
      "voice-family",
      "voice-rate",
      "voice-pitch",
      "voice-range",
      "voice-stress",
      "voice-duration",
      "size",
      "page",
      "zoom",
      "min-zoom",
      "max-zoom",
      "user-zoom",
      "orientation"
    ]
  ],
  "sort-order-fallback": "abc",
  "space-after-colon": " ",
  "space-after-combinator": " ",
  "space-after-opening-brace": "\n",
  "space-after-selector-delimiter": " ",
  "space-before-closing-brace": " ",
  "space-before-colon": "",
  "space-before-combinator": " ",
  "space-before-opening-brace": " ",
  "space-before-selector-delimiter": "",
  "space-between-declarations": "\n",
  "strip-spaces": true,
  "tab-size": true,
  "unitless-zero": true,
  "vendor-prefix-align": false
}
EOF

Add Package: gulp-csscomb

1> /dev/null yarn add --dev \
  gulp-csscomb

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -71,0 +72,3 @@
+    },
+    "sass": function () {
+      return plug.csscomb()
@@ -81,0 +85 @@
+      .pipe(task.tidy.sass)
EOF

task ▸ lint ▸ sass

Create File: .sass-lint.yml

cat > "${CWD}/.sass-lint.yml" <<-EOF
rules:
  bem-depth: 0
  border-zero:
    - 1
    -
      convention: 0
  brace-style: 0
  class-name-format:
    - 1
    -
      allow-leading-underscore: false
      convention: hyphenatedlowercase
  clean-import-paths:
    - 1
    -
      leading-underscore: true
      filename-extension: true
  empty-args:
    - 1
    -
      include: true
  empty-line-between-blocks: 0
  extends-before-declarations: 1
  extends-before-mixins: 1
  final-newline: 0
  force-attribute-nesting: 1
  force-element-nesting: 1
  force-pseudo-nesting: 1
  function-name-format:
    - 1
    -
      allow-leading-underscore: false
      convention: hyphenatedlowercase
  hex-length:
    - 1
    -
      style: short
  hex-notation:
    - 1
    -
      style: lowercase
  id-name-format:
    - 1
    -
      allow-leading-underscore: false
      convention: hyphenatedlowercase
  indentation: 0
  leading-zero:
    - 1
    -
      include: true
  mixin-name-format:
    - 1
    -
      allow-leading-underscore: false
      convention: hyphenatedlowercase
  mixins-before-declarations: 1
  nesting-depth:
    - 1
    -
      max-depth: 3
  no-color-keywords: 1
  no-color-literals: 1
  no-css-comments: 1
  no-debug: 1
  no-duplicate-properties: 0
  no-empty-rulesets: 1
  no-extends: 0
  no-ids: 1
  no-important: 1
  no-invalid-hex: 1
  no-mergeable-selectors: 1
  no-misspelled-properties: 1
  no-qualifying-elements:
    - 1
    -
      allow-element-with-attribute: true
      allow-element-with-class: false
      allow-element-with-id: false
  no-trailing-zero: 1
  no-transition-all: 1
  no-url-protocols: 1
  no-vendor-prefixes: 0
  no-warn: 1
  one-declaration-per-line: 1
  placeholder-in-extend: 0
  placeholder-name-format:
    - 1
    -
      allow-leading-underscore: false
      convention: hyphenatedlowercase
  property-sort-order:
    - 1
    -
      order:
        - -webkit-rtl-ordering
        - direction
        - unicode-bidi
        - writing-mode
        - text-orientation
        - glyph-orientation-vertical
        - text-combine-upright
        - text-transform
        - white-space
        - tab-size
        - line-break
        - word-break
        - hyphens
        - word-wrap
        - overflow-wrap
        - text-align
        - text-align-last
        - text-justify
        - word-spacing
        - letter-spacing
        - text-indent
        - hanging-punctuation
        - -webkit-nbsp-mode
        - text-decoration
        - text-decoration-line
        - text-decoration-style
        - text-decoration-color
        - text-decoration-skip
        - text-underline-position
        - text-emphasis
        - text-emphasis-style
        - text-emphasis-color
        - text-emphasis-position
        - text-shadow
        - -webkit-text-fill-color
        - -webkit-text-stroke
        - -webkit-text-stroke-width
        - -webkit-text-stroke-color
        - -webkit-text-security
        - font
        - font-style
        - font-variant
        - font-weight
        - font-stretch
        - font-size
        - line-height
        - font-family
        - src
        - unicode-range
        - -webkit-text-size-adjust
        - font-size-adjust
        - font-synthesis
        - font-kerning
        - font-variant-ligatures
        - font-variant-position
        - font-variant-caps
        - font-variant-numeric
        - font-variant-alternates
        - font-variant-east-asian
        - font-feature-settings
        - font-language-override
        - list-style
        - list-style-type
        - list-style-position
        - list-style-image
        - marker-side
        - counter-set
        - counter-increment
        - caption-side
        - table-layout
        - border-collapse
        - -webkit-border-horizontal-spacing
        - -webkit-border-vertical-spacing
        - border-spacing
        - empty-cells
        - move-to
        - quotes
        - counter-increment
        - counter-reset
        - page-policy
        - content
        - crop
        - box-sizing
        - outline
        - outline-color
        - outline-style
        - outline-width
        - outline-offset
        - resize
        - text-overflow
        - cursor
        - caret-color
        - nav-up
        - nav-right
        - nav-down
        - nav-left
        - -webkit-appearance
        - -webkit-user-drag
        - -webkit-user-modify
        - -webkit-user-select
        - -moz-user-select
        - -ms-user-select
        - pointer-events
        - -webkit-dashboard-region
        - -apple-dashboard-region
        - -webkit-touch-callout
        - position
        - top
        - right
        - bottom
        - left
        - offset-before
        - offset-end
        - offset-after
        - offset-start
        - z-index
        - display
        - -webkit-margin-collapse
        - -webkit-margin-top-collapse
        - -webkit-margin-bottom-collapse
        - -webkit-margin-start
        - margin
        - margin-top
        - margin-right
        - margin-bottom
        - margin-left
        - -webkit-padding-start
        - padding
        - padding-top
        - padding-right
        - padding-bottom
        - padding-left
        - width
        - min-width
        - max-width
        - height
        - min-height
        - max-height
        - float
        - clear
        - overflow
        - overflow-x
        - overflow-y
        - -webkit-overflow-scrolling
        - overflow-style
        - marquee-style
        - marquee-loop
        - marquee-direction
        - marquee-speed
        - visibility
        - rotation
        - rotation-point
        - flex-flow
        - flex-direction
        - flex-wrap
        - order
        - flex
        - flex-grow
        - flex-shrink
        - flex-basis
        - justify-content
        - align-items
        - align-self
        - align-content
        - columns
        - column-width
        - column-count
        - column-gap
        - column-rule
        - column-rule-width
        - column-rule-style
        - column-rule-color
        - break-before
        - break-after
        - break-inside
        - column-span
        - column-fill
        - grid
        - grid-template
        - grid-template-columns
        - grid-template-rows
        - grid-template-areas
        - grid-auto-flow
        - grid-auto-columns
        - grid-auto-rows
        - grid-column
        - grid-row
        - grid-area
        - grid-row-start
        - grid-column-start
        - grid-row-end
        - grid-column-end
        - grid-gap
        - grid-column-gap
        - grid-row-gap
        - orphans
        - widows
        - box-decoration-break
        - background
        - background-image
        - background-position
        - background-size
        - background-repeat
        - background-attachment
        - background-origin
        - background-clip
        - background-color
        - border
        - border-width
        - border-style
        - border-color
        - border-top
        - border-top-width
        - border-top-style
        - border-top-color
        - border-right
        - border-right-width
        - border-right-style
        - border-right-color
        - border-bottom
        - border-bottom-width
        - border-bottom-style
        - border-bottom-color
        - border-left
        - border-left-width
        - border-left-style
        - border-left-color
        - border-radius
        - border-top-left-radius
        - border-top-right-radius
        - border-bottom-right-radius
        - border-bottom-left-radius
        - border-image
        - border-image-source
        - border-image-slice
        - border-image-width
        - border-image-outset
        - border-image-repeat
        - box-shadow
        - color
        - opacity
        - -webkit-tap-highlight-color
        - object-fit
        - object-position
        - image-resolution
        - image-orientation
        - clip-path
        - mask
        - mask-image
        - mask-mode
        - mask-repeat
        - mask-position
        - mask-clip
        - mask-origin
        - mask-size
        - mask-composite
        - mask-border
        - mask-border-source
        - mask-border-slice
        - mask-border-width
        - mask-border-outset
        - mask-border-repeat
        - mask-border-mode
        - mask-type
        - clip
        - filter
        - transition
        - transition-property
        - transition-duration
        - transition-timing-function
        - transition-delay
        - transform
        - transform-origin
        - transform-style
        - perspective
        - perspective-origin
        - backface-visibility
        - animation
        - animation-name
        - animation-duration
        - animation-timing-function
        - animation-delay
        - animation-iteration-count
        - animation-direction
        - animation-fill-mode
        - animation-play-state
        - voice-volume
        - voice-balance
        - speak
        - speak-as
        - pause
        - pause-before
        - pause-after
        - rest
        - rest-before
        - rest-after
        - cue
        - cue-before
        - cue-after
        - voice-family
        - voice-rate
        - voice-pitch
        - voice-range
        - voice-stress
        - voice-duration
        - size
        - page
        - zoom
        - min-zoom
        - max-zoom
        - user-zoom
        - orientation
  property-units: 1
  quotes:
    - 1
    -
      style: double
  shorthand-values: 1
  single-line-per-selector: 0
  space-after-bang: 1
  space-after-colon: 1
  space-after-comma: 1
  space-around-operator: 1
  space-before-bang: 1
  space-before-brace: 1
  space-before-colon: 1
  space-between-parens: 1
  trailing-semicolon: 0
  url-quotes: 1
  variable-for-property: 0
  variable-name-format:
    - 1
    -
      allow-leading-underscore: false
      convention: hyphenatedlowercase
  zero-unit: 1
EOF

Add Package: gulp-sass-lint

1> /dev/null yarn add --dev \
  gulp-sass-lint

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -47,0 +48,3 @@
+    "sass": plug.lazypipe()
+      .pipe(plug.sassLint)
+      .pipe(plug.sassLint.format),
@@ -85,0 +89 @@
+      .pipe(task.lint.sass)
EOF

task ▸ compile ▸ sass

Create File: .caniuse.json

cat > "${CWD}/.caniuse.json" <<-EOF
{
  "dataByBrowser": {
    "and_chr": {
      "54": 0.83604
    },
    "and_ff": {
      "50": 0
    },
    "and_uc": {
      "11": 0
    },
    "android": {
      "3": 0,
      "4": 0,
      "53": 0,
      "2.1": 0,
      "2.2": 0,
      "2.3": 0,
      "4.1": 0,
      "4.2-4.3": 0,
      "4.4": 0,
      "4.4.3-4.4.4": 0
    },
    "bb": {
      "7": 0,
      "10": 0
    },
    "chrome": {
      "4": 0,
      "5": 0,
      "6": 0,
      "7": 0,
      "8": 0,
      "9": 0,
      "10": 0,
      "11": 0,
      "12": 0,
      "13": 0,
      "14": 0,
      "15": 0,
      "16": 0,
      "17": 0,
      "18": 0,
      "19": 0,
      "20": 0,
      "21": 0,
      "22": 0,
      "23": 0,
      "24": 0.02322,
      "25": 0,
      "26": 0,
      "27": 0.02322,
      "28": 0,
      "29": 0,
      "30": 0.06967,
      "31": 0.30190,
      "32": 0.85926,
      "33": 0,
      "34": 0.09289,
      "35": 0.27868,
      "36": 1.57919,
      "37": 0,
      "38": 0.04644,
      "39": 0.06967,
      "40": 1.20761,
      "41": 0.04644,
      "42": 0.16256,
      "43": 0.09289,
      "44": 0.13934,
      "45": 0.25545,
      "46": 0.06967,
      "47": 1.34695,
      "48": 0.55736,
      "49": 2.50812,
      "50": 15.86158,
      "51": 17.67301,
      "52": 10.19507,
      "53": 11.89038,
      "54": 11.49558,
      "55": 0.34835,
      "56": 0.11611,
      "57": 0
    },
    "edge": {
      "12": 0.02322,
      "13": 0.48769,
      "14": 0.27868
    },
    "firefox": {
      "2": 0,
      "3": 0,
      "4": 0,
      "5": 0,
      "6": 0.09289,
      "7": 0,
      "8": 0,
      "9": 0,
      "10": 0,
      "11": 0,
      "12": 0,
      "13": 0,
      "14": 0.04644,
      "15": 0,
      "16": 0,
      "17": 0,
      "18": 0,
      "19": 0,
      "20": 0,
      "21": 0.06967,
      "22": 0,
      "23": 0,
      "24": 0,
      "25": 0.32512,
      "26": 0,
      "27": 0,
      "28": 0.02322,
      "29": 0.06967,
      "30": 0,
      "31": 0,
      "32": 0,
      "33": 0,
      "34": 0.06967,
      "35": 0,
      "36": 0.02322,
      "37": 0,
      "38": 0.23223,
      "39": 0.02322,
      "40": 0,
      "41": 0,
      "42": 0.18578,
      "43": 0.23223,
      "44": 0.25545,
      "45": 0.69670,
      "46": 2.57779,
      "47": 3.22805,
      "48": 1.60241,
      "49": 2.06688,
      "50": 0.58058,
      "51": 0.25545,
      "52": 0.02322,
      "53": 0,
      "3.5": 0,
      "3.6": 0
    },
    "ie": {
      "6": 0.06967,
      "7": 0.02322,
      "8": 0.02322,
      "9": 0.11611,
      "10": 0.23223,
      "11": 0.51091
    },
    "ie_mob": {
      "10": 0,
      "11": 0
    },
    "ios_saf": {
      "8": 0.32512,
      "10": 0.27868,
      "3.2": 0,
      "4.0-4.1": 0,
      "4.2-4.3": 0,
      "5.0-5.1": 0.04644,
      "6.0-6.1": 0.04644,
      "7.0-7.1": 0.09289,
      "8.1-8.4": 0,
      "9.0-9.2": 0.02322,
      "9.3": 0.99860
    },
    "op_mini": {
      "all": 0
    },
    "op_mob": {
      "12": 0,
      "37": 0,
      "12.1": 0
    },
    "opera": {
      "15": 0,
      "16": 0,
      "17": 0,
      "18": 0.02322,
      "19": 0.02322,
      "20": 0,
      "21": 0,
      "22": 0,
      "23": 0,
      "24": 0,
      "25": 0,
      "26": 0,
      "27": 0,
      "28": 0,
      "29": 0,
      "30": 0,
      "31": 0,
      "32": 0,
      "33": 0,
      "34": 0,
      "35": 0,
      "36": 0.04644,
      "37": 0.02322,
      "38": 0.25545,
      "39": 0.09289,
      "40": 0,
      "41": 0.04644,
      "42": 0,
      "43": 0,
      "10.0-10.1": 0,
      "11.5": 0,
      "12.1": 0.18578
    },
    "safari": {
      "4": 0,
      "5": 0.02322,
      "6": 0,
      "7": 0.06967,
      "8": 0.06967,
      "9": 0.23223,
      "10": 0.76637,
      "3.1": 0,
      "3.2": 0,
      "5.1": 0.09289,
      "6.1": 0,
      "7.1": 0,
      "9.1": 1.88109,
      "TP": 0
    },
    "samsung": {
      "4": 0
    }
  },
  "id": "71568934|undefined",
  "meta": {
    "end_date": "2016-12-01",
    "start_date": "2016-05-01"
  },
  "name": "ptb2.me",
  "source": "google_analytics",
  "type": "custom",
  "uid": "custom.71568934|undefined"
}
EOF

Add Package: gulp-sass

1> /dev/null yarn add --dev \
  browserslist \
  gulp-autoprefixer \
  gulp-sass

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -23,0 +24,7 @@
+    "autoprefixer": {
+      "browsers": plug.browserslist([">0.25% in my stats"], {
+        "stats": ".caniuse.json"
+      }),
+      "cascade": false,
+      "remove": true
+    },
@@ -37,0 +45,3 @@
+    "sass": {
+      "outputStyle": MIN ? "compressed" : "expanded"
+    },
@@ -46,0 +57,5 @@
+  "compile": {
+    "sass": plug.lazypipe()
+      .pipe(plug.sass, opts.sass)
+      .pipe(plug.autoprefixer, opts.autoprefixer)
+  },
@@ -120,0 +136 @@
+      .pipe(task.compile.sass())
EOF

task ▸ tidy ▸ css

Add Package: gulp-cssbeautify

1> /dev/null yarn add --dev \
  gulp-cssbeautify \

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -33,0 +34,4 @@
+    "cssbeautify": {
+      "autosemicolon": true,
+      "indent": "  "
+    },
@@ -87,0 +92,3 @@
+    "css": function () {
+      return plug.gulpIf(!MIN, plug.cssbeautify(opts.cssbeautify))
+    },
@@ -99,0 +107,4 @@
+  "css": function () {
+    return plug.lazypipe()
+      .pipe(task.tidy.css)
+  },
@@ -136,0 +148 @@
+      .pipe(pipe.css()())
EOF

task ▸ lint ▸ css

Add Package: gulp-csslint

1> /dev/null yarn add --dev \
  gulp-csslint

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -37,0 +38,34 @@
+    "csslint": {
+      "adjoining-classes": false,
+      "box-model": true,
+      "box-sizing": false,
+      "bulletproof-font-face": true,
+      "compatible-vendor-prefixes": false,
+      "display-property-grouping": true,
+      "duplicate-background-images": true,
+      "duplicate-properties": true,
+      "empty-rules": true,
+      "fallback-colors": true,
+      "floats": true,
+      "font-faces": true,
+      "font-sizes": true,
+      "gradients": true,
+      "ids": true,
+      "import": true,
+      "important": true,
+      "known-properties": true,
+      "order-alphabetical": false,
+      "outline-none": true,
+      "overqualified-elements": true,
+      "qualified-headings": true,
+      "regex-selectors": true,
+      "shorthand": true,
+      "star-property-hack": true,
+      "text-indent": true,
+      "underscore-property-hack": true,
+      "unique-headings": true,
+      "universal-selector": true,
+      "unqualified-attributes": true,
+      "vendor-prefix": true,
+      "zero-units": true
+    },
@@ -66,0 +101,3 @@
+    "css": plug.lazypipe()
+      .pipe(plug.csslint, opts.csslint)
+      .pipe(plug.csslint.formatter, "compact"),
@@ -109,0 +147 @@
+      .pipe(task.lint.css)
EOF

gulp ▸ task ▸ check ▸ es6

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -72,0 +73 @@
+      "es6": "*.@(e|j)s?(6|x)",
@@ -148,0 +150,4 @@
+  "es6": function () {
+    return plug.lazypipe()
+      .pipe(task.tidy.lines)
+  },
@@ -187,0 +193,9 @@
+  },
+  function es6 (done) {
+    gulp.src(path.join(opts.path.src, opts.ext.es6), {
+      "since": gulp.lastRun("check")
+    })
+      .pipe(plug.changedInPlace(opts.changedInPlace))
+      .pipe(pipe.es6()())
+      .pipe(task.save.src())
+    done()
EOF

task ▸ tidy ▸ es6

Add Package: gulp-jsbeautifier

1> /dev/null yarn add --dev \
  gulp-jsbeautifier

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -76,0 +77,18 @@
+    "jsbeautifier": {
+      "js": {
+        "file_types": [
+          ".es6",
+          ".js",
+          ".json"
+        ],
+        "break_chained_methods": true,
+        "end_with_newline": true,
+        "indent_size": 2,
+        "jslint_happy": true,
+        "keep_array_indentation": true,
+        "keep_function_indentation": true,
+        "max_preserve_newlines": 2,
+        "space_after_anon_function": true,
+        "wrap_line_length": 78
+      }
+    },
@@ -132,0 +151,7 @@
+    "es6": plug.lazypipe()
+      .pipe(function () {
+        return plug.gulpIf(!MIN, plug.jsbeautifier(opts.jsbeautifier))
+      })
+      .pipe(function () {
+        return plug.gulpIf(!MIN, plug.jsbeautifier.reporter())
+      }),
@@ -152,0 +178 @@
+      .pipe(task.tidy.es6)
EOF

task ▸ lint ▸ es6

Create File: .eslintignore

cat > "${CWD}/.eslintignore" <<-EOF
!.eslintrc.js
!*.json
*.min.js
/docs/**/*.js
EOF

Create File: .eslintrc.js

cat > "${CWD}/.eslintrc.js" <<-EOF
const INDENT_SIZE = 2

module.exports = {
  "env": {
    "amd": true,
    "browser": true,
    "commonjs": true,
    "es6": true,
    "mocha": true,
    "node": true,
    "shared-node-browser": true
  },
  "globals": {
    "document": false,
    "navigator": false,
    "window": false
  },
  "parserOptions": {
    "ecmaFeatures": {
      "experimentalObjectRestSpread": true,
      "jsx": false
    },
    "ecmaVersion": 6,
    "sourceType": "module"
  },
  "plugins": [
    "json",
    "promise",
    "standard"
  ],
  "rules": {
    "accessor-pairs": "error",
    "array-bracket-spacing": [
      "error",
      "never"
    ],
    "array-callback-return": "error",
    "arrow-body-style": [
      "error",
      "as-needed"
    ],
    "arrow-parens": [
      "error",
      "always"
    ],
    "arrow-spacing": [
      "error",
      {
        "after": true,
        "before": true
      }
    ],
    "block-scoped-var": "error",
    "block-spacing": [
      "error",
      "always"
    ],
    "brace-style": [
      "error",
      "1tbs",
      {
        "allowSingleLine": true
      }
    ],
    "callback-return": "error",
    "camelcase": [
      "error",
      {
        "properties": "always"
      }
    ],
    "comma-dangle": [
      "error",
      "never"
    ],
    "comma-spacing": [
      "error",
      {
        "after": true,
        "before": false
      }
    ],
    "comma-style": [
      "error",
      "last"
    ],
    "complexity": "off",
    "computed-property-spacing": [
      "error",
      "never"
    ],
    "consistent-return": "error",
    "consistent-this": [
      "warn",
      "self"
    ],
    "constructor-super": "error",
    "curly": [
      "error",
      "all"
    ],
    "default-case": "error",
    "dot-location": [
      "error",
      "property"
    ],
    "dot-notation": [
      "error",
      {
        "allowKeywords": false
      }
    ],
    "eol-last": [
      "error",
      "unix"
    ],
    "eqeqeq": [
      "error",
      "smart"
    ],
    "func-names": "off",
    "func-style": [
      "error",
      "expression"
    ],
    "generator-star-spacing": [
      "error",
      {
        "after": true,
        "before": true
      }
    ],
    "global-require": "error",
    "guard-for-in": "error",
    "handle-callback-err": [
      "error",
      "^(err|error)$"
    ],
    "id-blacklist": "off",
    "id-length": "off",
    "id-match": "off",
    "indent": [
      "error",
      INDENT_SIZE,
      {
        "SwitchCase": 1,
        "VariableDeclarator": 1
      }
    ],
    "init-declarations": "off",
    "jsx-quotes": [
      "error",
      "prefer-double"
    ],
    "key-spacing": [
      "error",
      {
        "afterColon": true,
        "beforeColon": false,
        "mode": "strict"
      }
    ],
    "keyword-spacing": [
      "error",
      {
        "after": true,
        "before": true
      }
    ],
    "linebreak-style": [
      "error",
      "unix"
    ],
    "lines-around-comment": [
      "error",
      {
        "afterBlockComment": false,
        "afterLineComment": false,
        "allowArrayEnd": true,
        "allowArrayStart": true,
        "allowBlockEnd": true,
        "allowBlockStart": true,
        "allowObjectEnd": true,
        "allowObjectStart": true,
        "beforeBlockComment": true,
        "beforeLineComment": true
      }
    ],
    "max-depth": "off",
    "max-len": [
      "warn",
      {
        "code": 78,
        "ignoreUrls": true
      }
    ],
    "max-nested-callbacks": "off",
    "max-params": "off",
    "max-statements": [
      "warn",
      {
        "max": 10
      }
    ],
    "max-statements-per-line": [
      "error",
      {
        "max": 1
      }
    ],
    "new-cap": [
      "error",
      {
        "capIsNew": true,
        "newIsCap": true
      }
    ],
    "new-parens": "error",
    "newline-after-var": [
      "error",
      "always"
    ],
    "newline-before-return": "off",
    "newline-per-chained-call": "error",
    "no-alert": "error",
    "no-array-constructor": "error",
    "no-bitwise": "error",
    "no-caller": "error",
    "no-case-declarations": "error",
    "no-catch-shadow": "off",
    "no-class-assign": "error",
    "no-cond-assign": "error",
    "no-confusing-arrow": [
      "error",
      {
        "allowParens": true
      }
    ],
    "no-console": "warn",
    "no-const-assign": "error",
    "no-constant-condition": "error",
    "no-continue": "error",
    "no-control-regex": "error",
    "no-debugger": "error",
    "no-delete-var": "error",
    "no-div-regex": "error",
    "no-dupe-args": "error",
    "no-dupe-class-members": "error",
    "no-dupe-keys": "error",
    "no-duplicate-case": "error",
    "no-duplicate-imports": [
      "error",
      {
        "includeExports": true
      }
    ],
    "no-else-return": "error",
    "no-empty": [
      "error",
      {
        "allowEmptyCatch": true
      }
    ],
    "no-empty-character-class": "error",
    "no-empty-function": "warn",
    "no-empty-pattern": "error",
    "no-eq-null": "error",
    "no-eval": "error",
    "no-ex-assign": "error",
    "no-extend-native": "error",
    "no-extra-bind": "error",
    "no-extra-boolean-cast": "error",
    "no-extra-label": "error",
    "no-extra-parens": [
      "error",
      "all",
      {
        "returnAssign": false
      }
    ],
    "no-extra-semi": "error",
    "no-fallthrough": "error",
    "no-floating-decimal": "error",
    "no-func-assign": "error",
    "no-implicit-coercion": "error",
    "no-implicit-globals": "error",
    "no-implied-eval": "error",
    "no-inline-comments": "error",
    "no-inner-declarations": [
      "error",
      "both"
    ],
    "no-invalid-regexp": "error",
    "no-invalid-this": "error",
    "no-irregular-whitespace": "error",
    "no-iterator": "error",
    "no-label-var": "error",
    "no-labels": [
      "error",
      {
        "allowLoop": false,
        "allowSwitch": false
      }
    ],
    "no-lone-blocks": "error",
    "no-lonely-if": "error",
    "no-loop-func": "error",
    "no-magic-numbers": [
      "warn",
      {
        "enforceConst": true,
        "ignoreArrayIndexes": true
      }
    ],
    "no-mixed-requires": [
      "error",
      {
        "allowCall": true,
        "grouping": true
      }
    ],
    "no-mixed-spaces-and-tabs": "error",
    "no-multi-spaces": "error",
    "no-multi-str": "error",
    "no-multiple-empty-lines": [
      "error",
      {
        "max": 1
      }
    ],
    "no-native-reassign": "error",
    "no-negated-condition": "error",
    "no-negated-in-lhs": "error",
    "no-nested-ternary": "error",
    "no-new": "error",
    "no-new-func": "error",
    "no-new-object": "error",
    "no-new-require": "error",
    "no-new-symbol": "error",
    "no-new-wrappers": "error",
    "no-obj-calls": "error",
    "no-octal": "error",
    "no-octal-escape": "error",
    "no-param-reassign": "error",
    "no-path-concat": "error",
    "no-plusplus": [
      "error",
      {
        "allowForLoopAfterthoughts": true
      }
    ],
    "no-process-env": "error",
    "no-process-exit": "error",
    "no-proto": "error",
    "no-redeclare": [
      "error",
      {
        "builtinGlobals": true
      }
    ],
    "no-regex-spaces": "error",
    "no-restricted-globals": "off",
    "no-restricted-imports": "off",
    "no-restricted-modules": "off",
    "no-restricted-syntax": "off",
    "no-return-assign": [
      "error",
      "always"
    ],
    "no-script-url": "error",
    "no-self-assign": "warn",
    "no-self-compare": "error",
    "no-sequences": "error",
    "no-shadow": [
      "error",
      {
        "builtinGlobals": true,
        "hoist": "all"
      }
    ],
    "no-shadow-restricted-names": "error",
    "no-spaced-func": "error",
    "no-sparse-arrays": "error",
    "no-sync": "off",
    "no-ternary": "off",
    "no-this-before-super": "error",
    "no-throw-literal": "error",
    "no-trailing-spaces": "error",
    "no-undef": "error",
    "no-undef-init": "error",
    "no-undefined": "error",
    "no-underscore-dangle": "off",
    "no-unexpected-multiline": "error",
    "no-unmodified-loop-condition": "error",
    "no-unneeded-ternary": [
      "error",
      {
        "defaultAssignment": false
      }
    ],
    "no-unreachable": "error",
    "no-unsafe-finally": "error",
    "no-unused-expressions": [
      "error",
      {
        "allowShortCircuit": true,
        "allowTernary": true
      }
    ],
    "no-unused-labels": "error",
    "no-unused-vars": [
      "error",
      {
        "args": "all",
        "argsIgnorePattern": "^_",
        "vars": "all"
      }
    ],
    "no-use-before-define": "error",
    "no-useless-call": "error",
    "no-useless-computed-key": "error",
    "no-useless-concat": "error",
    "no-useless-constructor": "error",
    "no-useless-escape": "error",
    "no-var": "off",
    "no-void": "error",
    "no-warning-comments": "warn",
    "no-whitespace-before-property": "error",
    "no-with": "error",
    "object-curly-spacing": [
      "error",
      "always",
      {
        "arraysInObjects": true,
        "objectsInObjects": true
      }
    ],
    "object-property-newline": "off",
    "object-shorthand": [
      "error",
      "always",
      {
        "avoidQuotes": true
      }
    ],
    "one-var": [
      "error",
      {
        "initialized": "never",
        "uninitialized": "always"
      }
    ],
    "one-var-declaration-per-line": "off",
    "operator-assignment": [
      "error",
      "always"
    ],
    "operator-linebreak": [
      "error",
      "after",
      {
        "overrides": {
          ":": "before",
          "?": "before"
        }
      }
    ],
    "padded-blocks": [
      "error",
      "never"
    ],
    "prefer-arrow-callback": "off",
    "prefer-const": "warn",
    "prefer-reflect": "off",
    "prefer-rest-params": "warn",
    "prefer-spread": "warn",
    "prefer-template": "error",
    "promise/param-names": "error",
    "quote-props": [
      "error",
      "always"
    ],
    "quotes": [
      "error",
      "double",
      {
        "allowTemplateLiterals": true,
        "avoidEscape": true
      }
    ],
    "radix": [
      "error",
      "always"
    ],
    "require-jsdoc": "warn",
    "require-yield": "off",
    "semi": [
      "error",
      "never"
    ],
    "semi-spacing": [
      "error",
      {
        "after": true,
        "before": false
      }
    ],
    "sort-imports": "error",
    "sort-vars": [
      "warn",
      {
        "ignoreCase": true
      }
    ],
    "space-before-blocks": [
      "error",
      "always"
    ],
    "space-before-function-paren": [
      "error",
      "always"
    ],
    "space-in-parens": [
      "error",
      "never"
    ],
    "space-infix-ops": "error",
    "space-unary-ops": [
      "error",
      {
        "nonwords": false,
        "words": true
      }
    ],
    "spaced-comment": [
      "error",
      "always",
      {
        "markers": [
          "global",
          "globals",
          "eslint",
          "eslint-disable",
          "*package",
          "!",
          ","
        ]
      }
    ],
    "standard/array-bracket-even-spacing": [
      "error",
      "either"
    ],
    "standard/computed-property-even-spacing": [
      "error",
      "even"
    ],
    "standard/object-curly-even-spacing": [
      "error",
      "either"
    ],
    "strict": [
      "error",
      "safe"
    ],
    "template-curly-spacing": [
      "error",
      "never"
    ],
    "use-isnan": "error",
    "valid-jsdoc": "warn",
    "valid-typeof": "error",
    "vars-on-top": "error",
    "wrap-iife": [
      "error",
      "any"
    ],
    "wrap-regex": "error",
    "yield-star-spacing": [
      "error",
      "both"
    ],
    "yoda": [
      "error",
      "never"
    ]
  }
}
EOF

Add Package: eslint

1> /dev/null yarn add --dev \
  eslint \
  eslint-plugin-json \
  eslint-plugin-promise \
  gulp-eslint
1> /dev/null yarn add --dev \
  eslint-plugin-standard

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -7,0 +8 @@
+    "eslint": "Eslint",
@@ -71,0 +73,3 @@
+    "eslint": {
+      "fix": true
+    },
@@ -122,0 +127,3 @@
+    "es6": plug.lazypipe()
+      .pipe(plug.eslint, opts.eslint)
+      .pipe(plug.eslint.format),
@@ -178,0 +186 @@
+      .pipe(task.lint.es6)
EOF

task ▸ build

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -72,0 +73 @@
+    "env": MIN ? "production" : "development",
@@ -117,0 +119,6 @@
+  "build": function (done) {
+    proc.execSync(\`bundle exec middleman build -e \${opts.env}\`, {
+      "stdio": "inherit"
+    })
+    done()
+  },
@@ -241 +248 @@
-  gulp.watch(opts.path.src, gulp.series("check"))
+  gulp.watch(opts.path.src, gulp.series("check", task.build))
EOF

gulp ▸ task ▸ default

Add Package: browser-sync

1> /dev/null yarn add --dev \
  ptb/browser-sync

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -17,0 +18 @@
+const OUT = path.join(CWD, "docs")
@@ -18,0 +20 @@
+const EXT = "xhtml"
@@ -100,0 +103 @@
+      "out": path.join(OUT, "**"),
@@ -247,3 +250,26 @@
-  gulp.watch(opts.restart.files, task.restart)
-  gulp.watch(opts.path.src, gulp.series("check", task.build))
-  done()
+  plug.browserSync.init({
+    "files": opts.path.out,
+    "https": {
+      "cert": "localhost.crt",
+      "key": "localhost.key"
+    },
+    "logConnections": true,
+    "notify": false,
+    "open": false,
+    "reloadDebounce": 100,
+    "reloadOnRestart": true,
+    "server": {
+      "baseDir": OUT,
+      "index": \`index.\${EXT}\`
+    },
+    "snippetOptions": {
+      "rule": {
+        "match": /qqq/
+      }
+    },
+    "ui": false
+  }, function () {
+    gulp.watch(opts.restart.files, task.restart)
+    gulp.watch(opts.path.src, gulp.series("check", task.build))
+    done()
+  })
EOF

task ▸ tail ▸ log

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -163,0 +164,6 @@
+  "tail": {
+    "log": function () {
+      proc.exec(\`tail -f -n0 "\${path.join(CWD, "logs", "access.log")}"\`)
+        .stdout.pipe(process.stdout)
+    }
+  },
@@ -273,0 +280 @@
+    task.tail.log()
EOF

gulp ▸ task ▸ build ▸ del

Add Package: del

1> /dev/null yarn add --dev \
  del

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -17,0 +18 @@
+const TMP = path.join(CWD, ".tmp")
@@ -219,3 +220,6 @@
-gulp.task("build", function build (done) {
-  done()
-})
+gulp.task("build", gulp.series(
+  function del (done) {
+    plug.del.sync(path.join(TMP, "*"))
+    done()
+  }
+))
EOF

gulp ▸ task ▸ build ▸ riot

Add Package: streamqueue

1> /dev/null yarn add --dev \
  streamqueue

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -2,0 +3 @@
+const fs = require("fs")
@@ -81,0 +83 @@
+      "riot": /\.tag$/,
@@ -133,0 +136,18 @@
+  "each": {
+    "folder": function (a, b, c) {
+      const d = function (e, f, g) {
+        return fs.readdirSync(f)
+          .reduce(function (h, i) {
+            const j = [path.join(f, i), e, path.relative(e, f), i]
+
+            if (fs.statSync(j[0])
+              .isDirectory()) {
+              return h.concat(g.test(i) ? [j] : [], d(e, j[0], g))
+            }
+            return h
+          }, [])
+      }
+
+      return d(a, b, c)
+    }
+  },
@@ -223,0 +244,11 @@
+  },
+  function riot (done) {
+    return task.each.folder(SRC, SRC, opts.ext.riot)
+      .map(function () {
+        return plug.streamqueue({
+          "objectMode": true
+        })
+          .on("end", function () {
+            done()
+          })
+      })
EOF

task ▸ compile ▸ slim

Add Package: gulp-slim

1> /dev/null yarn add --dev \
  gulp-slim

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -115,0 +116,10 @@
+    "slim": {
+      "chdir": true,
+      "options": ["attr_quote='\"'", \`format=:\${EXT}\`, "shortcut={ " +
+        "'@' => { attr: 'role' }, '#' => { attr: 'id' }, " +
+        "'.' => { attr: 'class' }, '%' => { attr: 'itemprop' }, " +
+        "'^' => { attr: 'data-is' }, '&' => { attr: 'type', tag: 'input' } }",
+        "sort_attrs=true"],
+      "pretty": !MIN,
+      "require": "slim/include"
+    },
@@ -134 +144,4 @@
-      .pipe(plug.autoprefixer, opts.autoprefixer)
+      .pipe(plug.autoprefixer, opts.autoprefixer),
+    "slim": function () {
+      return plug.slim(opts.slim)
+    }
@@ -247 +260 @@
-      .map(function () {
+      .map(function (dir) {
@@ -250 +263,4 @@
-        })
+        },
+            gulp.src(path.join(dir[0], opts.ext.slim))
+            .pipe(task.compile.slim())
+          )
EOF

task ▸ rename ▸ html

Add Package: gulp-rename

1> /dev/null yarn add --dev \
  gulp-rename

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -108,0 +109,5 @@
+    "rename": {
+      "html": {
+        "extname": \`.\${EXT}\`
+      }
+    },
@@ -185,0 +191,5 @@
+  "rename": {
+    "html": function () {
+      return plug.rename(opts.rename.html)
+    }
+  },
@@ -195,0 +206,3 @@
+    },
+    "tmp": function () {
+      return gulp.dest(TMP)
@@ -237,0 +251,4 @@
+  "html": function () {
+    return plug.lazypipe()
+      .pipe(task.rename.html)
+  },
@@ -265,0 +283 @@
+            .pipe(pipe.html()())
@@ -266,0 +285 @@
+          .pipe(task.save.tmp())
EOF

task ▸ tidy ▸ html

Add Package: gulp-htmltidy

1> /dev/null yarn add --dev \
  gulp-htmltidy

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -86,0 +87,13 @@
+    "htmltidy": {
+      "doctype": "html5",
+      "indent": true,
+      "indent-spaces": 2,
+      "input-xml": true,
+      "logical-emphasis": true,
+      "new-blocklevel-tags": "",
+      "output-xhtml": true,
+      "quiet": true,
+      "sort-attributes": "alpha",
+      "tidy-mark": false,
+      "wrap": 78
+    },
@@ -227,0 +241,3 @@
+    "html": function () {
+      return plug.gulpIf(!MIN, plug.htmltidy(opts.htmltidy))
+    },
@@ -253,0 +270 @@
+      .pipe(task.tidy.html)
EOF

task ▸ lint ▸ html

Add Package: gulp-w3cjs

1> /dev/null yarn add --dev \
  gulp-w3cjs

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -191,0 +192,3 @@
+    "html": function (lint) {
+      return plug.gulpIf(lint, plug.w3cjs())
+    },
@@ -267 +270 @@
-  "html": function () {
+  "html": function (tag) {
@@ -270,0 +274 @@
+      .pipe(task.lint.html, !tag)
@@ -300 +304 @@
-            .pipe(pipe.html()())
+            .pipe(pipe.html(true)())
EOF

task ▸ minify ▸ html

Add Package: gulp-htmlmin

1> /dev/null yarn add --dev \
  gulp-htmlmin

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -86,0 +87,9 @@
+    "htmlmin": {
+      "collapseWhitespace": MIN,
+      "keepClosingSlash": true,
+      "minifyURLs": true,
+      "removeComments": true,
+      "removeScriptTypeAttributes": true,
+      "removeStyleLinkTypeAttributes": true,
+      "useShortDoctype": true
+    },
@@ -206,0 +216,5 @@
+  "minify": {
+    "html": function () {
+      return plug.htmlmin(opts.htmlmin)
+    }
+  },
@@ -274,0 +289 @@
+      .pipe(task.minify.html)
EOF

task ▸ indent

Add Package: gulp-indent

1> /dev/null yarn add --dev \
  gulp-indent

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -193,0 +194,3 @@
+  "indent": function (tag) {
+    return plug.gulpIf(tag, plug.indent())
+  },
@@ -289,0 +293 @@
+      .pipe(task.indent, tag)
EOF

task ▸ tidy ▸ svg

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -85 +85,2 @@
-      "slim": "*.sl?(i)m"
+      "slim": "*.sl?(i)m",
+      "svg": "*.svg"
@@ -304,0 +306,4 @@
+  },
+  "svg": function () {
+    return plug.lazypipe()
+      .pipe(task.tidy.html)
@@ -323 +328,3 @@
-            .pipe(pipe.html(true)())
+            .pipe(pipe.html(true)()),
+            gulp.src(path.join(dir[0], opts.ext.svg))
+            .pipe(pipe.svg()())
EOF

task ▸ minify ▸ svg

Add Package: gulp-svgmin

1> /dev/null yarn add --dev \
  gulp-svgmin

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -222,0 +223,3 @@
+    },
+    "svg": function () {
+      return plug.gulpIf(MIN, plug.svgmin())
@@ -307 +310 @@
-  "svg": function () {
+  "svg": function (tag) {
@@ -309,0 +313,2 @@
+      .pipe(task.minify.svg)
+      .pipe(task.indent, tag)
@@ -330 +335 @@
-            .pipe(pipe.svg()())
+            .pipe(pipe.svg(true)())
EOF

task ▸ minify ▸ css

Add Package: gulp-cssnano

1> /dev/null yarn add --dev \
  gulp-cssnano

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -76,0 +77,8 @@
+    "cssnano": {
+      "autoprefixer": {
+        "add": true,
+        "browsers": plug.browserslist([">0.25% in my stats"], {
+          "stats": ".caniuse.json"
+        })
+      }
+    },
@@ -220,0 +229,3 @@
+    "css": function (min) {
+      return plug.gulpIf(min, plug.cssnano(opts.cssnano))
+    },
@@ -280 +291 @@
-  "css": function () {
+  "css": function (tag) {
@@ -283,0 +295,2 @@
+      .pipe(task.indent, tag)
+      .pipe(task.minify.css, MIN)
@@ -335 +348,4 @@
-            .pipe(pipe.svg(true)())
+            .pipe(pipe.svg(true)()),
+            gulp.src(path.join(dir[0], opts.ext.sass))
+            .pipe(task.compile.sass())
+            .pipe(pipe.css(true)())
EOF

task ▸ wrap ▸ css

Add Package: gulp-inject-string

1> /dev/null yarn add --dev \
  gulp-inject-string

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -117,0 +118,6 @@
+    "inject": {
+      "css": {
+        "above": "<style>",
+        "below": "</style>"
+      }
+    },
@@ -284,0 +291,11 @@
+  },
+  "wrap": {
+    "above": function (min) {
+      return plug.gulpIf(min, plug.injectString.prepend("\n"))
+    },
+    "below": function (min) {
+      return plug.gulpIf(min, plug.injectString.append("\n"))
+    },
+    "css": plug.lazypipe()
+      .pipe(plug.injectString.prepend, opts.inject.css.above)
+      .pipe(plug.injectString.append, opts.inject.css.below)
@@ -296,0 +314,10 @@
+      .pipe(function () {
+        return plug.gulpIf(tag, task.wrap.above(!MIN))
+      })
+      .pipe(function () {
+        return plug.gulpIf(tag, task.wrap.css())
+      })
+      .pipe(function () {
+        return plug.gulpIf(tag, task.wrap.below(!MIN))
+      })
+      .pipe(task.indent, tag)
EOF

task ▸ compile ▸ es6

Add Package: gulp-babel

1> /dev/null yarn add --dev \
  babel-preset-es2015 \
  gulp-babel

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -35,0 +36,16 @@
+    "babel": {
+      "plugins": ["check-es2015-constants",
+        "transform-es2015-arrow-functions",
+        "transform-es2015-block-scoped-functions",
+        "transform-es2015-block-scoping", "transform-es2015-classes",
+        "transform-es2015-computed-properties",
+        "transform-es2015-destructuring", "transform-es2015-duplicate-keys",
+        "transform-es2015-for-of", "transform-es2015-function-name",
+        "transform-es2015-literals", "transform-es2015-object-super",
+        "transform-es2015-parameters",
+        "transform-es2015-shorthand-properties", "transform-es2015-spread",
+        "transform-es2015-sticky-regex",
+        "transform-es2015-template-literals",
+        "transform-es2015-typeof-symbol", "transform-es2015-unicode-regex",
+        "transform-regenerator"]
+    },
@@ -183,0 +200,3 @@
+    "es6": function () {
+      return plug.babel(opts.babel)
+    },
@@ -378 +397,3 @@
-            .pipe(pipe.css(true)())
+            .pipe(pipe.css(true)()),
+            gulp.src(path.join(dir[0], opts.ext.es6))
+            .pipe(task.compile.es6())
EOF

task ▸ tidy ▸ js

Add Package: gulp-jsbeautifier

1> /dev/null yarn add --dev \
  gulp-jsbeautifier

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -357,0 +358,4 @@
+  "js": function () {
+    return plug.lazypipe()
+      .pipe(task.tidy.es6)
+  },
@@ -399,0 +404 @@
+            .pipe(pipe.js()())
EOF

task ▸ rename ▸ js

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -164,0 +165,3 @@
+      },
+      "js": {
+        "extname": ".js"
@@ -266,0 +270,3 @@
+    },
+    "js": function () {
+      return plug.rename(opts.rename.js)
@@ -358 +364 @@
-  "js": function () {
+  "js": function (tag) {
@@ -360,0 +367,2 @@
+      .pipe(task.rename.js)
+      .pipe(task.indent, tag)
@@ -404 +412 @@
-            .pipe(pipe.js()())
+            .pipe(pipe.js(true)())
EOF

task ▸ minify ▸ js

Add Package: gulp-uglify

1> /dev/null yarn add --dev \
  gulp-uglify

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -262,0 +263,3 @@
+    "js": function (min) {
+      return plug.gulpIf(min, plug.uglify())
+    },
@@ -368,0 +372 @@
+      .pipe(task.minify.js, MIN)
EOF

task ▸ wrap ▸ js

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -137,0 +138,4 @@
+      },
+      "js": {
+        "above": "<script>",
+        "below": "</script>"
@@ -329 +333,4 @@
-      .pipe(plug.injectString.append, opts.inject.css.below)
+      .pipe(plug.injectString.append, opts.inject.css.below),
+    "js": plug.lazypipe()
+      .pipe(plug.injectString.prepend, opts.inject.js.above)
+      .pipe(plug.injectString.append, opts.inject.js.below)
@@ -372,0 +380,10 @@
+      .pipe(function () {
+        return plug.gulpIf(tag, task.wrap.above(!MIN))
+      })
+      .pipe(function () {
+        return plug.gulpIf(tag, task.wrap.js())
+      })
+      .pipe(function () {
+        return plug.gulpIf(tag, task.wrap.below(!MIN))
+      })
+      .pipe(task.indent, tag)
EOF

task ▸ concat

Add Package: gulp-concat

1> /dev/null yarn add --dev \
  gulp-concat

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -216,0 +217,3 @@
+  "concat": function (folder) {
+    return plug.concat(folder)
+  },
@@ -274,0 +278,5 @@
+    "dir": function (dir) {
+      return plug.rename({
+        "dirname": dir
+      })
+    },
@@ -336 +344,10 @@
-      .pipe(plug.injectString.append, opts.inject.js.below)
+      .pipe(plug.injectString.append, opts.inject.js.below),
+    "tag": {
+      "above": function (folder) {
+        return plug.injectString.prepend(
+          \`<\${folder.split(".").shift()}>\n\`)
+      },
+      "below": function (folder) {
+        return plug.injectString.append(\`</\${folder.split(".").shift()}>\`)
+      }
+    }
@@ -390,0 +408,8 @@
+  "riot": function (dir) {
+    return plug.lazypipe()
+      .pipe(task.concat, dir[3])
+      .pipe(task.rename.dir, dir[2])
+      .pipe(task.wrap.below, MIN)
+      .pipe(task.wrap.tag.above, dir[3])
+      .pipe(task.wrap.tag.below, dir[3])
+  },
@@ -434,0 +460 @@
+          .pipe(pipe.riot(dir)())
EOF

task ▸ compile ▸ riot

Add Package: riot

1> /dev/null yarn add --dev \
  gulp-riot
1> /dev/null yarn add \
  riot

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -10 +10,2 @@
-    "gulp-if": "gulpIf"
+    "gulp-if": "gulpIf",
+    "riot": "Riot"
@@ -177,0 +179,3 @@
+    "riot": {
+      "compact": MIN
+    },
@@ -209,0 +214,3 @@
+    "riot": function () {
+      return plug.riot(opts.riot)
+    },
@@ -414,0 +422,2 @@
+      .pipe(task.compile.riot)
+      .pipe(task.minify.js, true)
EOF

task ▸ wrap ▸ license

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -143 +143,3 @@
-      }
+      },
+      "license": "/*! github.com/ptb, @license Apache-2.0 */\n",
+      "riot": \`const riot = require("riot")\${MIN ? ";" : "\n"}\`
@@ -351,0 +354,6 @@
+    "license": function () {
+      return plug.injectString.prepend(opts.inject.license)
+    },
+    "riot": function () {
+      return plug.injectString.prepend(opts.inject.riot)
+    },
@@ -423,0 +432,2 @@
+      .pipe(task.wrap.riot)
+      .pipe(task.wrap.license)
EOF

gulp ▸ task ▸ build ▸ assets

Patch File: config.rb

cat <<-EOF | patch 1> /dev/null
--- config.rb
+++ config.rb
@@ -54,0 +55,2 @@
+ignore(/\.es6/)
+ignore(/\.sass/)
EOF

Rename: src/css/style.css.sass to src/css/style.sass

# mv "${CWD}/src/css/style.css.sass" "${CWD}/src/css/style.sass"

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -165,0 +166 @@
+      "exclude": \`!\${path.join("**", "@(_*|*.tag)", "*")}\`,
@@ -484,0 +486,15 @@
+  },
+  function assets () {
+    return plug.streamqueue({
+      "objectMode": true
+    },
+        gulp.src([path.join(opts.path.src, opts.ext.svg), opts.path.exclude])
+        .pipe(pipe.svg(false)()),
+        gulp.src([path.join(opts.path.src, opts.ext.sass), opts.path.exclude])
+        .pipe(task.compile.sass())
+        .pipe(pipe.css(false)()),
+        gulp.src([path.join(opts.path.src, opts.ext.es6), opts.path.exclude])
+        .pipe(task.compile.es6())
+        .pipe(pipe.js(false)())
+      )
+      .pipe(task.save.tmp())
EOF

gulp ▸ task ▸ build ▸ webpack

TODO: Notes

https://github.com/rogueg/riot/raw/hot/lib/riot-hot-reload.js

npm install --save-dev \
  connect-modrewrite \
  webpack-dev-middleware \
  webpack-hot-middleware
  extract-text-webpack-plugin
  style-loader
  sass-loader
  deep-assign
  css-loader
  babel-loader
  babel-core
  webpack-dev-server@beta

const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin")

  "module": {
    "loaders": [
      {
        // "loader": ["style", "css", "sass"],
        "loader": ExtractTextWebpackPlugin.extract({
          "fallbackLoader": "style",
          "loader": ["css?sourceMap", "sass?sourceMap"]
        }),
        "test": /\.s[ac]ss$/
      },
      {
        "exclude": /node_modules/,
        "loader": "babel",
        "test": /\.jsx?$/
      }
    ]
  },

new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),

new ExtractTextWebpackPlugin({
  "allChunks": true,
  "disable": false,
  "filename": "[contenthash].css"
})

"middleware": [
  plug.connectModrewrite([`!\\.\\w+$ /index${EXT} [L]`]),
  plug.webpackDevMiddleware(webpack, {
    "publicPath": config.output.publicPath,
    "stats": {
      "colors": true
    }
  }),
  plug.webpackHotMiddleware(webpack)
],
npm install --save-dev extract-text-webpack-plugin@2.0.0-beta.4

Add Package: webpack

1> /dev/null yarn add --dev \
  webpack-stream \
  webpack@beta

Create File: src/js/index.es6

cat > "${CWD}/src/js/index.es6" <<-EOF
const riot = require("riot")

require("./example-tag")

riot.mount("*")
EOF

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -5,0 +6 @@
+const pkg = require("./package.json")
@@ -199,0 +201,40 @@
+    },
+    "webpack": {
+      "context": path.join(TMP, "js"),
+      "entry": {
+        "index": "./index.js",
+        "share": Object.keys(pkg.dependencies)
+      },
+      "output": {
+        "filename": path.join("js", "[name].js")
+      },
+      "plugins": [
+        new plug.webpack.SourceMapDevToolPlugin({
+          "filename": path.join("js", "[name].map"),
+          "moduleFilenameTemplate": function (info) {
+            if (!MIN && fs.existsSync(info.absoluteResourcePath)) {
+              return \`file://\${encodeURI(info.absoluteResourcePath)}\`
+            }
+            return \`\${path.basename(info.resourcePath)}?\${info.hash}\`
+          }
+        }),
+        new plug.webpack.optimize.AggressiveMergingPlugin(),
+        new plug.webpack.optimize.CommonsChunkPlugin({
+          "name": "share"
+        }),
+        new plug.webpack.optimize.UglifyJsPlugin({
+          "compress": {
+            "warnings": false
+          },
+          "mangle": MIN,
+          "output": {
+            "beautify": !MIN,
+            "comments": false,
+            "indent_level": 2
+          },
+          "sourceMap": true
+        })
+      ],
+      "resolve": {
+        "extensions": [".js", ".json"]
+      }
@@ -224,0 +266,3 @@
+    },
+    "webpack": function () {
+      return plug.webpackStream(opts.webpack, plug.webpack)
@@ -451,0 +496,4 @@
+  },
+  "webpack": function () {
+    return plug.lazypipe()
+      .pipe(task.compile.webpack)
@@ -500,0 +549,5 @@
+  },
+  function webpack () {
+    return gulp.src(path.join(TMP, "js", "index.js"))
+      .pipe(pipe.webpack()())
+      .pipe(task.save.tmp())
EOF

Example Options: webpack

  "compress": {
    "angular": false,
    "booleans": true,
    "cascade": true,
    "collapse_vars": false,
    "comparisons": true,
    "conditionals": true,
    "dead_code": true,
    "drop_console": false,
    "drop_debugger": true,
    "evaluate": true,
    "global_defs": {},
    "hoist_funs": true,
    "hoist_vars": false,
    "if_return": true,
    "join_vars": true,
    "keep_fargs": true,
    "keep_fnames": false,
    "loops": true,
    "negate_iife": true,
    "passes": 1,
    "properties": true,
    "pure_funcs": null,
    "pure_getters": false,
    "reduce_vars": false,
    "screw_ie8": true,
    "sequences": true,
    "side_effects": true,
    "unsafe_comps": false,
    "unsafe": false,
    "unused": true,
    "warnings": false // eslint-disable // default: true //
  },
  "fromString": false,
  "inSourceMap": null,
  "mangle": {},
  "mangleProperties": false,
  "nameCache": null,
  "outFileName": null,
  "output": {
    "ascii_only": false,
    "beautify": false,
    "bracketize": false,
    "comments": false,
    "indent_level": 2, // eslint-disable // default: 4 //
    "indent_start": 0,
    "inline_script": false,
    "keep_quoted_props": false,
    "max_line_len": 32000,
    "preamble": null,
    "preserve_line": false,
    "quote_keys": false,
    "quote_style": 0,
    "screw_ie8": true,
    "semicolons": true,
    "shebang": true,
    "source_map": null,
    "space_colon": true,
    "unescape_regexps": false,
    "width": 80,
    "wrap_iife": false
  },
  "outSourceMap": null,
  "parse": {},
  "sourceMapInline": false,
  "sourceMapUrl": null,
  "sourceRoot": null,
  "spidermonkey": false,
  "warnings": false
})

gulp ▸ task ▸ build ▸ webpack ▸ riot

Add Package: tag-loader

1> /dev/null yarn add --dev \
  tag-loader

Patch File: config.rb

cat <<-EOF | patch 1> /dev/null
--- config.rb
+++ config.rb
@@ -34 +34 @@
-  command: "node_modules/.bin/gulp build#{MIN ? ' --min' : nil} --silent",
+  command: "node_modules/.bin/gulp build#{MIN ? ' --min' : nil} --silent --wpk",
EOF

Patch File: gulpfile.js

cat <<-EOF | patch 1> /dev/null
--- gulpfile.js
+++ gulpfile.js
@@ -25,0 +26 @@
+const WPK = typeof plug.util.env.wpk != "undefined"
@@ -207,0 +209,11 @@
+      "module": {
+        "rules": [
+          {
+            "loader": "tag-loader",
+            "options": {
+              "compact": true
+            },
+            "test": /\.tag$/
+          }
+        ]
+      },
@@ -239 +251 @@
-        "extensions": [".js", ".json"]
+        "extensions": [".js", ".json", ".tag"]
@@ -475,4 +487,4 @@
-      .pipe(task.compile.riot)
-      .pipe(task.minify.js, true)
-      .pipe(task.wrap.riot)
-      .pipe(task.wrap.license)
+      .pipe(plug.gulpIf, !WPK, task.compile.riot())
+      .pipe(plug.gulpIf, !WPK, task.minify.js(true))
+      .pipe(plug.gulpIf, !WPK, task.wrap.riot())
+      .pipe(plug.gulpIf, !WPK, task.wrap.license())
EOF

Add Scripts to Layout

Patch File: src/_layouts/layout.slim

cat <<-EOF | patch -p0 1> /dev/null
--- src/_layouts/layout.slim
+++ src/_layouts/layout.slim
@@ -30,0 +31,3 @@
+    script(src="#{url_for('/js/share.js')}")
+    script(src="#{url_for('/js/index.js')}")
+    / = inline_tag 'script', '/js/index.js'
+
EOF

Configure: nginx

TODO: Notes

https://gist.github.com/vjt/804654
http://recipes.sinatrarb.com/p/deployment/nginx_proxied_to_unicorn
http://stackoverflow.com/questions/13030149/how-to-deploy-ruby-rack-app-with-nginx

Create File: nginx.conf

cat > "${CWD}/nginx.conf" <<-EOF
daemon off;

events {
  worker_connections 8000;
}

http {
  charset utf-8;
  charset_types
    application/javascript
    application/json
    application/rss+xml
    application/xhtml+xml
    application/xml
    text/css
    text/plain
    text/vnd.wap.wml;

  default_type application/octet-stream;

  gzip on;
  gzip_comp_level 9;
  gzip_min_length 256;
  gzip_proxied any;
  gzip_static on;
  gzip_vary on;

  gzip_types
    application/atom+xml
    application/javascript
    application/json
    application/ld+json
    application/manifest+json
    application/rss+xml
    application/vnd.geo+json
    application/vnd.ms-fontobject
    application/x-font-ttf
    application/x-web-app-manifest+json
    application/xhtml+xml
    application/xml
    font/opentype
    image/bmp
    image/svg+xml
    image/x-icon
    text/cache-manifest
    text/css
    text/plain
    text/vcard
    text/vnd.rim.location.xloc
    text/vtt
    text/x-component
    text/x-cross-domain-policy;

  keepalive_timeout 20s;

  log_format default '"\$request" \$status \$body_bytes_sent "\$http_referer" '
    '\$remote_addr';

  sendfile on;
  server_tokens off;

  server {
    listen 80;
    listen [::]:80;
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    location / {
      access_log /dev/stdout default;
      error_log stderr;
      # index index.html index.xhtml;
      proxy_http_version 1.1;
      proxy_pass https://localhost:3000;
      proxy_set_header Connection "Upgrade";
      proxy_set_header Upgrade \$http_upgrade;
      # root "${CWD}/docs";
    }

    server_name localhost;

    ssl_certificate "${CWD}/localhost.crt";
    ssl_certificate_key "${CWD}/localhost.key";

    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1.2;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 24h;
  }

  server {
    listen 80 default_server;
    listen [::]:80 default_server;
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2;

    location / {
      access_log off;
      error_log off;
      return 444;
    }
  }

  tcp_nopush on;

  types {
    application/atom+xml atom;
    application/font-woff woff;
    application/font-woff2 woff2;
    application/java-archive ear jar war;
    application/javascript js;
    application/json json map topojson;
    application/ld+json jsonld;
    application/mac-binhex40 hqx;
    application/manifest+json webmanifest;
    application/msword doc;
    application/octet-stream bin deb dll dmg exe img iso msi msm msp safariextz;
    application/pdf pdf;
    application/postscript ai eps ps;
    application/rss+xml rss;
    application/rtf rtf;
    application/vnd.geo+json geojson;
    application/vnd.google-earth.kml+xml kml;
    application/vnd.google-earth.kmz kmz;
    application/vnd.ms-excel xls;
    application/vnd.ms-fontobject eot;
    application/vnd.ms-powerpoint ppt;
    application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
    application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
    application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
    application/vnd.wap.wmlc wmlc;
    application/x-7z-compressed 7z;
    application/x-bb-appworld bbaw;
    application/x-bittorrent torrent;
    application/x-chrome-extension crx;
    application/x-cocoa cco;
    application/x-font-ttf ttc ttf;
    application/x-java-archive-diff jardiff;
    application/x-java-jnlp-file jnlp;
    application/x-makeself run;
    application/x-opera-extension oex;
    application/x-perl pl pm;
    application/x-pilot pdb prc;
    application/x-rar-compressed rar;
    application/x-redhat-package-manager rpm;
    application/x-sea sea;
    application/x-shockwave-flash swf;
    application/x-stuffit sit;
    application/x-tcl tcl tk;
    application/x-web-app-manifest+json webapp;
    application/x-x509-ca-cert crt der pem;
    application/x-xpinstall xpi;
    application/xhtml+xml xhtml;
    application/xml rdf xml;
    application/xslt+xml xsl;
    application/zip zip;
    audio/midi mid midi kar;
    audio/mp4 aac f4a f4b m4a;
    audio/mpeg mp3;
    audio/ogg oga ogg opus;
    audio/x-realaudio ra;
    audio/x-wav wav;
    font/opentype otf;
    image/bmp bmp;
    image/gif gif;
    image/jpeg jpeg jpg;
    image/png png;
    image/svg+xml svg svgz;
    image/tiff tif tiff;
    image/vnd.wap.wbmp wbmp;
    image/webp webp;
    image/x-icon cur ico;
    image/x-jng jng;
    text/cache-manifest appcache;
    text/css css;
    text/html htm html shtml;
    text/mathml mml;
    text/plain txt;
    text/vcard vcard vcf;
    text/vnd.rim.location.xloc xloc;
    text/vnd.sun.j2me.app-descriptor jad;
    text/vnd.wap.wml wml;
    text/vtt vtt;
    text/x-component htc;
    video/3gpp 3gp 3gpp;
    video/mp4 f4p f4v m4v mp4;
    video/mpeg mpeg mpg;
    video/ogg ogv;
    video/quicktime mov;
    video/webm webm;
    video/x-flv flv;
    video/x-mng mng;
    video/x-ms-asf asf asx;
    video/x-ms-wmv wmv;
    video/x-msvideo avi;
  }
}

worker_processes auto;
worker_rlimit_nofile 8192;
EOF

Git Commit: Initial

ln -s template.org readme.org
git add --all && git reset -- docs/* && git add docs/.keep

Stop Process: nginx

LABEL="localhost.nginx"
PLIST="/Library/LaunchDaemons/${LABEL}.plist"

/bin/echo
sudo launchctl unload "${PLIST}" &> /dev/null

Create File: /Library/LaunchDaemons/localhost.nginx.plist

sudo tee "${PLIST}" > /dev/null <<-EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>${LABEL}</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/nginx</string>
    <string>-c</string>
    <string>${CWD}/nginx.conf</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>StandardErrorPath</key>
  <string>${CWD}/logs/error.log</string>
  <key>StandardOutPath</key>
  <string>${CWD}/logs/access.log</string>
  <key>WatchPaths</key>
  <array>
    <string>${CWD}/nginx.conf</string>
  </array>
</dict>
</plist>
EOF

Start Process: nginx

sudo plutil -convert xml1 "${PLIST}"
sudo launchctl load "${PLIST}"

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages