Implement watch support for build system

This commit is contained in:
Mikko Ahlroth 2017-03-19 16:42:57 +02:00
parent 6e589dd5a9
commit 5af3350fc0
14 changed files with 236 additions and 112 deletions

View file

@ -6,12 +6,11 @@ defmodule Mix.Tasks.Frontend.Build.Css.Compile do
def bin(), do: node_bin("node-sass")
def out_path(:prod), do: "#{tmp_path()}/compiled/css"
def out_path(_), do: "#{dist_path()}/css"
def out_path(), do: "#{tmp_path()}/compiled/css"
def args(), do: [
"-o",
out_path(Mix.env()),
out_path(),
"--source-map",
"true",
"--include-path",

View file

@ -0,0 +1,20 @@
defmodule Mix.Tasks.Frontend.Build.Css.Copy do
use Mix.Task
import MebeWeb.{TaskUtils, FrontendConfs}
@shortdoc "Copy compiled CSS to target dir"
def do_copy() do
# Ensure target path exists
out_path = "#{dist_path()}/css"
File.mkdir_p!(out_path)
File.cp_r!(Mix.Tasks.Frontend.Build.Css.Compile.out_path(), out_path)
end
def run(_) do
run_task("frontend.build.css.compile")
do_copy()
end
end

View file

@ -7,7 +7,7 @@ defmodule Mix.Tasks.Frontend.Build.Css do
def run(_) do
task = case Mix.env() do
:prod -> "frontend.build.css.minify"
_ -> "frontend.build.css.compile"
_ -> "frontend.build.css.copy"
end
run_task(task)

View file

@ -8,7 +8,7 @@ defmodule Mix.Tasks.Frontend.Build.Css.Minify do
def run(_) do
run_task("frontend.build.css.compile")
in_path = "#{tmp_path()}/compiled/css"
in_path = Mix.Tasks.Frontend.Build.Css.Compile.out_path()
in_file = "#{in_path}/app.css"
out_path = "#{dist_path()}/css"
out_file = "#{out_path}/app.css"

View file

@ -6,17 +6,16 @@ defmodule Mix.Tasks.Frontend.Build.Js.Bundle do
def bin(), do: node_bin("rollup")
def out_path(:prod), do: "#{tmp_path()}/bundled/js"
def out_path(_), do: "#{dist_path()}/js"
def out_path(), do: "#{tmp_path()}/bundled/js"
def args() do
op = out_path(Mix.env())
op = out_path()
[
"--config",
"rollup.config.js",
"--input",
"#{tmp_path()}/transpiled/js/app.js",
"#{Mix.Tasks.Frontend.Build.Js.Transpile.out_path()}/app.js",
"--output",
"#{op}/app.js",
"--format",
@ -26,9 +25,13 @@ defmodule Mix.Tasks.Frontend.Build.Js.Bundle do
]
end
def do_bundle() do
bin() |> exec(args()) |> listen()
end
def run(_) do
run_task("frontend.build.js.transpile")
bin() |> exec(args()) |> listen()
do_bundle()
end
end

View file

@ -0,0 +1,20 @@
defmodule Mix.Tasks.Frontend.Build.Js.Copy do
use Mix.Task
import MebeWeb.{TaskUtils, FrontendConfs}
@shortdoc "Copy bundled JS to target dir"
def do_copy() do
# Ensure target path exists
out_path = "#{dist_path()}/js"
File.mkdir_p!(out_path)
File.cp_r!(Mix.Tasks.Frontend.Build.Js.Bundle.out_path(), out_path)
end
def run(_) do
run_task("frontend.build.js.bundle")
do_copy()
end
end

View file

@ -7,7 +7,7 @@ defmodule Mix.Tasks.Frontend.Build.Js do
def run(_) do
task = case Mix.env() do
:prod -> "frontend.build.js.minify"
_ -> "frontend.build.js.bundle"
_ -> "frontend.build.js.copy"
end
run_task(task)

View file

@ -8,7 +8,7 @@ defmodule Mix.Tasks.Frontend.Build.Js.Minify do
def run(_) do
run_task("frontend.build.js.bundle")
in_path = "#{tmp_path()}/bundled/js"
in_path = Mix.Tasks.Frontend.Build.Js.Bundle.out_path()
in_file = "#{in_path}/app.js"
out_path = "#{dist_path()}/js"
out_file = "#{out_path}/app.js"

View file

@ -6,10 +6,12 @@ defmodule Mix.Tasks.Frontend.Build.Js.Transpile do
def bin(), do: node_bin("babel")
def out_path(), do: "#{tmp_path()}/transpiled/js"
def args(), do: [
"#{src_path()}/js",
"--out-dir",
"#{tmp_path()}/transpiled/js",
out_path(),
"--source-maps",
"inline"
]

View file

@ -1,9 +1,11 @@
defmodule Mix.Tasks.Frontend.Watch do
use Mix.Task
import MebeWeb.{TaskUtils, FrontendConfs}
import MebeWeb.{TaskUtils}
alias Mix.Tasks.Frontend.Build.Js.Transpile, as: TranspileJS
alias Mix.Tasks.Frontend.Build.Js.Bundle, as: BundleJS
alias Mix.Tasks.Frontend.Build.Js.Copy, as: CopyJS
alias Mix.Tasks.Frontend.Build.Css.Compile, as: CompileCSS
alias Mix.Tasks.Frontend.Build.Css.Copy, as: CopyCSS
@shortdoc "Watch frontend and rebuild when necessary"
@ -16,9 +18,16 @@ defmodule Mix.Tasks.Frontend.Watch do
TranspileJS.args() ++ ["-w"]
),
exec(
BundleJS.bin(),
BundleJS.args() ++ ["-w"]
watch(
"JSBundle",
TranspileJS.out_path(),
fn _, _ -> Task.start_link(BundleJS, :do_bundle, []) end
),
watch(
"JSCopy",
BundleJS.out_path(),
fn _, _ -> CopyJS.do_copy() end
),
exec(
@ -27,7 +36,13 @@ defmodule Mix.Tasks.Frontend.Watch do
"-w",
CompileCSS.scss_file()
]
),
watch(
"CSSCopy",
CompileCSS.out_path(),
fn _, _ -> CopyCSS.do_copy() end
)
] |> watch()
] |> listen(watch: true)
end
end

View file

@ -9,9 +9,9 @@ defmodule MebeWeb.TaskUtils do
@default_task_timeout 60000
defmodule Program do
defmodule ProgramSpec do
@moduledoc """
Program to execute with arguments. Name is used for prefixing logs.
Program that is executed with arguments. Name is used for prefixing logs.
"""
defstruct [
name: "",
@ -20,6 +20,22 @@ defmodule MebeWeb.TaskUtils do
]
end
defmodule WatchSpec do
@moduledoc """
Watch specification, target to watch and callback to execute on events.
Name is used for prefixing logs.
Callback must have arity 2, gets filename/path and list of events as arguments.
"""
defstruct [
name: "",
path: "",
callback: nil,
pid: nil,
name_atom: nil
]
end
@doc """
Get configuration value.
"""
@ -56,19 +72,13 @@ defmodule MebeWeb.TaskUtils do
Tasks should be tuples {task_name, args} or binaries (no args).
"""
def run_tasks(tasks, timeout \\ 60000) do
run_and_log = fn task, args ->
Logger.info("[Started] #{task}")
Mix.Task.run(task, args)
Logger.info("[Finished] #{task}")
end
def run_tasks(tasks, timeout \\ @default_task_timeout) do
tasks
|> Enum.map(fn
task when is_binary(task) ->
fn -> run_and_log.(task, []) end
fn -> Mix.Task.run(task, []) end
{task, args} ->
fn -> run_and_log.(task, args) end
fn -> Mix.Task.run(task, args) end
end)
|> run_funs(timeout)
end
@ -80,7 +90,7 @@ defmodule MebeWeb.TaskUtils do
Functions can either be anonymous functions or tuples of
{module, fun, args}.
"""
def run_funs(funs, timeout \\ 60000) when is_list(funs) do
def run_funs(funs, timeout \\ @default_task_timeout) when is_list(funs) do
funs
|> Enum.map(fn
fun when is_function(fun) ->
@ -103,6 +113,8 @@ defmodule MebeWeb.TaskUtils do
- name: Use as name for logging, otherwise name of binary is used.
- cd: Directory to change to before executing.
Returns ProgramSpec for the started program.
"""
def exec(executable, args, opts \\ []) do
name = Keyword.get(
@ -122,9 +134,9 @@ defmodule MebeWeb.TaskUtils do
cd -> Keyword.put(options, :cd, cd)
end
Logger.debug("[Spawned] #{name}")
Logger.debug("[Spawned] Program #{name}")
%Program{
%ProgramSpec{
name: name,
pending_output: "",
port: Port.open(
@ -135,84 +147,117 @@ defmodule MebeWeb.TaskUtils do
end
@doc """
Listen to messages from programs and print them to the screen.
Start watching a path. Name is used for prefixing logs.
Also listens to input from user and returns if input is given.
Path can point to a file or a directory in which case all subdirs will be watched.
Returns a WatchSpec.
"""
def listen(programs, task \\ nil, opts \\ [])
def watch(name, path, fun) do
name_atom = String.to_atom(name)
{:ok, pid} = :fs.start_link(name_atom, String.to_charlist(path))
:fs.subscribe(name_atom)
def listen([], _, _), do: nil
Logger.debug("[Spawned] Watch #{name}")
def listen(%Program{} = program, task, opts) do
listen([program], task, opts)
%WatchSpec{
name: name,
path: path,
callback: fun,
pid: pid,
name_atom: name_atom
}
end
def listen(programs, task, opts) do
@doc """
Listen to messages from specs and print them to the screen.
If watch: true is given in the options, will listen for user's enter key and
kill programs if enter is pressed.
"""
def listen(specs, opts \\ [])
# If there are no specs, stop running
def listen([], _), do: :ok
def listen(spec, opts) when not is_list(spec) do
listen([spec], opts)
end
def listen(specs, opts) when is_list(specs) do
# Start another task to ask for user input if we are in watch mode
task = with \
true <- Keyword.get(opts, :watch, false),
nil <- task,
nil <- Keyword.get(opts, :task),
{:ok, task} <- Task.start_link(__MODULE__, :wait_for_input, [self()])
do
Logger.info("Programs/watches started, press ENTER to exit.")
task
end
programs = receive do
specs = receive do
# User pressed enter
:user_input_received ->
Logger.info("ENTER received, killing tasks.")
Enum.each(specs, &kill/1)
[]
# Program sent output with end of line
{port, {:data, {:eol, msg}}} ->
program = Enum.find(programs, get_port_checker(port))
program = Enum.find(specs, program_checker(port))
msg = :unicode.characters_to_binary(msg, :unicode)
prefix = "[#{program.name}] #{program.pending_output}"
Logger.debug(prefix <> msg)
programs
|> Enum.reject(get_port_checker(port))
specs
|> Enum.reject(program_checker(port))
|> Enum.concat([
%{program | pending_output: ""}
])
# Program sent output without end of line
{port, {:data, {:noeol, msg}}} ->
program = Enum.find(programs, get_port_checker(port))
program = Enum.find(specs, program_checker(port))
msg = :unicode.characters_to_binary(msg, :unicode)
programs
|> Enum.reject(get_port_checker(port))
specs
|> Enum.reject(program_checker(port))
|> Enum.concat([
%{program | pending_output: "#{program.pending_output}#{msg}"}
])
# Port was closed normally after being told to close
{port, :closed} ->
handle_closed(programs, port)
handle_closed(specs, port)
# Port closed because the program closed by itself
{port, {:exit_status, 0}} ->
handle_closed(programs, port)
handle_closed(specs, port)
# Program closed with error status
{port, {:exit_status, status}} ->
program = Enum.find(programs, get_port_checker(port))
program = Enum.find(specs, program_checker(port))
Logger.error("Program #{program.name} returned status #{status}.")
raise "Failed status #{status} from #{program.name}!"
# Port crashed
{:EXIT, port, _} ->
handle_closed(programs, port)
handle_closed(specs, port)
# FS watch sent file event
{_, {:fs, :file_event}, {file, events}} ->
handle_events(specs, file, events)
end
if not Enum.empty?(programs) do
listen(programs, task, opts)
end
listen(specs, Keyword.put(opts, :task, task))
end
@doc """
Kill a running program returned by exec().
"""
def kill(%Program{name: name, port: port}) do
def kill(%ProgramSpec{name: name, port: port}) do
if name != nil do
Logger.debug("[Killing] #{name}")
end
@ -221,21 +266,11 @@ defmodule MebeWeb.TaskUtils do
end
@doc """
Print output from given programs to screen until user input is given.
When user input is given, kill programs and return.
Kill a running watch.
"""
def watch(%Program{} = program), do: watch([program])
def watch(programs) when is_list(programs) do
Logger.info("Programs started, press ENTER to exit.")
listen(programs, nil, watch: true)
Logger.info("ENTER received, killing tasks.")
Enum.each(programs, &kill/1)
:ok
def kill(%WatchSpec{name: name, pid: pid}) do
Logger.debug("[Killing] #{name}")
Process.exit(pid, :kill)
end
@doc """
@ -291,23 +326,57 @@ defmodule MebeWeb.TaskUtils do
defp size_with_unit(size, unit), do: "#{size} #{unit}"
defp get_port_checker(port) do
fn %Program{port: program_port} ->
program_port == port
# Utility to find program in spec list based on port
defp program_checker(port) do
fn
%ProgramSpec{port: program_port} ->
program_port == port
%WatchSpec{} ->
false
end
end
defp handle_closed(programs, port) do
case Enum.find(programs, get_port_checker(port)) do
%Program{} = program ->
# Utility to find watch in spec list based on path
defp watch_checker(path) do
fn
%ProgramSpec{} ->
false
%WatchSpec{path: watch_path} ->
# If given path is relative to (under) the watch path or is the same
# path completely, it's a match.
path != watch_path and Path.relative_to(path, watch_path) != path
end
end
defp handle_closed(specs, port) do
case Enum.find(specs, program_checker(port)) do
%ProgramSpec{} = program ->
Logger.debug("[Stopped] #{program.name}")
programs
|> Enum.reject(get_port_checker(port))
specs
|> Enum.reject(program_checker(port))
nil ->
# Program was already removed
programs
specs
end
end
defp handle_events(specs, file, events) do
file = to_string(file)
case Enum.find(specs, watch_checker(file)) do
%WatchSpec{name: name, callback: callback} ->
Logger.debug("[#{name}] Changed #{inspect(events)}: #{file}")
callback.(file, events)
nil ->
# Watch was maybe removed for some reason
Logger.debug("[Error] Watch sent event but path was not in specs list: #{inspect(events)} #{file}")
end
specs
end
end

17
mix.exs
View file

@ -1,7 +1,7 @@
defmodule MebeWeb.Mixfile do
use Mix.Project
def project do
def project() do
[app: :mebe_web,
version: "1.1.0",
elixir: "~> 1.4.0",
@ -15,9 +15,13 @@ defmodule MebeWeb.Mixfile do
# Configuration for the OTP application
#
# Type `mix help compile.app` for more information
def application do
[mod: {MebeWeb, []},
applications: [:phoenix, :cowboy, :logger, :phoenix_html, :earmark, :calendar, :tzdata]]
def application() do
[
mod: {MebeWeb, []},
extra_applications: [
:logger
]
]
end
# Specifies which paths to compile per environment
@ -27,7 +31,7 @@ defmodule MebeWeb.Mixfile do
# Specifies your project dependencies
#
# Type `mix help deps` for examples and options
defp deps do
defp deps() do
[
{:phoenix, "~> 1.2.1"},
{:phoenix_live_reload, "~> 1.0.7", only: :dev},
@ -35,7 +39,8 @@ defmodule MebeWeb.Mixfile do
{:cowboy, "~> 1.0.4"},
{:earmark, "~> 1.0.3"},
{:slugger, github: "h4cc/slugger", ref: "ef864669cdaae18d475600589c19c74e92ef67b4"},
{:calendar, "~> 0.17.1"}
{:calendar, "~> 0.17.1"},
{:fs, "~> 2.12.0", override: true, only: :dev}
]
end
end

View file

@ -1,31 +1,22 @@
%{"bbmustache": {:hex, :bbmustache, "1.0.4"},
"calendar": {:hex, :calendar, "0.17.1", "5c7dfffde2b68011c2d6832ff1a15496292de965a3b57b3fad32405f1176f024", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, optional: false]}]},
"certifi": {:hex, :certifi, "0.7.0", "861a57f3808f7eb0c2d1802afeaae0fa5de813b0df0979153cbafcd853ababaf", [:rebar3], []},
"cf": {:hex, :cf, "0.2.1"},
"conform": {:hex, :conform, "1.0.0-rc8"},
%{"calendar": {:hex, :calendar, "0.17.2", "d6b7bccc29c72203b076d4e488d967780bf2d123a96fafdbf45746fdc2fa342c", [:mix], [{:tzdata, "~> 0.5.8 or ~> 0.1.201603", [hex: :tzdata, optional: false]}]},
"certifi": {:hex, :certifi, "1.0.0", "1c787a85b1855ba354f0b8920392c19aa1d06b0ee1362f9141279620a5be2039", [:rebar3], []},
"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:make, :rebar], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []},
"earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []},
"erlware_commons": {:hex, :erlware_commons, "0.18.0"},
"exrm": {:hex, :exrm, "1.0.0-rc8"},
"fs": {:hex, :fs, "2.12.0", "ad631efacc9a5683c8eaa1b274e24fa64a1b8eb30747e9595b93bec7e492e25e", [:rebar3], []},
"getopt": {:hex, :getopt, "0.8.2"},
"hackney": {:hex, :hackney, "1.6.5", "8c025ee397ac94a184b0743c73b33b96465e85f90a02e210e86df6cbafaa5065", [:rebar3], [{:certifi, "0.7.0", [hex: :certifi, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
"idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], []},
"fwatch": {:hex, :fwatch, "0.5.0", "c61d9c1653ef5958770165315552e560c01202edd21ec05be9ba737ed44166e0", [:mix], [{:earmark, ">= 0.0.0", [hex: :earmark, optional: false]}, {:fs, "~> 0.9.1", [hex: :fs, optional: false]}]},
"hackney": {:hex, :hackney, "1.7.1", "e238c52c5df3c3b16ce613d3a51c7220a784d734879b1e231c9babd433ac1cb4", [:rebar3], [{:certifi, "1.0.0", [hex: :certifi, optional: false]}, {:idna, "4.0.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
"idna": {:hex, :idna, "4.0.0", "10aaa9f79d0b12cf0def53038547855b91144f1bfcc0ec73494f38bb7b9c4961", [:rebar3], []},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
"mime": {:hex, :mime, "1.0.1", "05c393850524767d13a53627df71beeebb016205eb43bfbd92d14d24ec7a1b51", [:mix], []},
"mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], []},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
"neotoma": {:hex, :neotoma, "1.7.3"},
"phoenix": {:hex, :phoenix, "1.2.1", "6dc592249ab73c67575769765b66ad164ad25d83defa3492dc6ae269bd2a68ab", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]},
"phoenix_html": {:hex, :phoenix_html, "2.9.2", "371160b30cf4e10443b015efce6f03e1f19aae98ff6487620477b13a5b2ef660", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.7", "167ab0942e88d1d4a597996cf7dd8d2b014cc14d3f9472b58858cde8dd9ac2e4", [:mix], [{:fs, "~> 2.12.0", [hex: :fs, optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, optional: false]}]},
"phoenix": {:hex, :phoenix, "1.2.3", "b68dd6a7e6ff3eef38ad59771007d2f3f344988ea6e658e9b2c6ffb2ef494810", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.4 or ~> 1.3.3 or ~> 1.2.4 or ~> 1.1.8 or ~> 1.0.5", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]},
"phoenix_html": {:hex, :phoenix_html, "2.9.3", "1b5a2122cbf743aa242f54dced8a4f1cc778b8bd304f4b4c0043a6250c58e258", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.0.8", "4333f9c74190f485a74866beff2f9304f069d53f047f5fbb0fb8d1ee4c495f73", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2-rc", [hex: :phoenix, optional: false]}]},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.1", "c10ddf6237007c804bf2b8f3c4d5b99009b42eca3a0dfac04ea2d8001186056a", [:mix], []},
"plug": {:hex, :plug, "1.3.0", "6e2b01afc5db3fd011ca4a16efd9cb424528c157c30a44a0186bcc92c7b2e8f3", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]},
"plug": {:hex, :plug, "1.3.4", "b4ef3a383f991bfa594552ded44934f2a9853407899d47ecc0481777fb1906f6", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]},
"poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []},
"providers": {:hex, :providers, "1.6.0"},
"ranch": {:hex, :ranch, "1.2.1", "a6fb992c10f2187b46ffd17ce398ddf8a54f691b81768f9ef5f461ea7e28c762", [:make], []},
"relx": {:hex, :relx, "3.17.0"},
"ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], []},
"slugger": {:git, "https://github.com/h4cc/slugger.git", "ef864669cdaae18d475600589c19c74e92ef67b4", [ref: "ef864669cdaae18d475600589c19c74e92ef67b4"]},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []},
"ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"},
"tzdata": {:hex, :tzdata, "0.5.10", "087e8dfe8c0283473115ad8ca6974b898ecb55ca5c725427a142a79593391e90", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, optional: false]}]}}
"tzdata": {:hex, :tzdata, "0.5.11", "3d5469a9f46bdf4a8760333dbdabdcc4751325035c454b10521f71e7c611ae50", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, optional: false]}]}}

View file

@ -6,19 +6,19 @@
"description": "Minimalistic Elixir Blog Engine",
"private": true,
"devDependencies": {
"babel-cli": "~6.22.2",
"babel-core": "~6.22.1",
"babel-polyfill": "~6.22.0",
"babel-preset-es2015": "~6.22.0",
"babel-cli": "~6.24.0",
"babel-core": "~6.24.0",
"babel-polyfill": "~6.23.0",
"babel-preset-es2015": "~6.24.0",
"babel-preset-es2016": "~6.22.0",
"babel-preset-es2017": "~6.22.0",
"bootstrap-sass": "~3.3.7",
"cssnano": "~3.10.0",
"cssnano-cli": "~1.0.5",
"node-sass": "~4.3.0",
"rollup": "~0.41.4",
"node-sass": "~4.5.0",
"rollup": "~0.41.6",
"rollup-plugin-sourcemaps": "~0.4.1",
"rollup-watch": "~3.2.2",
"uglify-js": "~2.7.5"
"uglify-js": "~2.8.13"
}
}