Merge branch 'upgrade-credo' into 'master'
feat: Add linters to CI/CD config See merge request mythic-insight/legendary!92
This commit is contained in:
commit
83b9879145
36 changed files with 354 additions and 173 deletions
|
@ -38,6 +38,36 @@ test_1.12.1:
|
||||||
<<: *test_template
|
<<: *test_template
|
||||||
image: "elixir:1.12.1-alpine"
|
image: "elixir:1.12.1-alpine"
|
||||||
|
|
||||||
|
credo:
|
||||||
|
stage: test
|
||||||
|
image: "elixir:1.12.1-alpine"
|
||||||
|
cache:
|
||||||
|
key: "test_1.12.1"
|
||||||
|
paths:
|
||||||
|
- _build/
|
||||||
|
- deps/
|
||||||
|
script:
|
||||||
|
- mix local.hex --force
|
||||||
|
- mix local.rebar --force
|
||||||
|
- mix deps.get
|
||||||
|
- mix credo --all
|
||||||
|
|
||||||
|
stylelint:
|
||||||
|
stage: test
|
||||||
|
image: "node:16"
|
||||||
|
script:
|
||||||
|
- cd apps/app/assets/
|
||||||
|
- npm install
|
||||||
|
- npx stylelint **/*.css
|
||||||
|
|
||||||
|
prettier:
|
||||||
|
stage: test
|
||||||
|
image: "node:16"
|
||||||
|
script:
|
||||||
|
- cd apps/app/assets/
|
||||||
|
- npm install
|
||||||
|
- npx prettier --check **/*.js
|
||||||
|
|
||||||
build_image_for_commit:
|
build_image_for_commit:
|
||||||
stage: test
|
stage: test
|
||||||
image: "docker:20.10"
|
image: "docker:20.10"
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
defmodule Legendary.Admin.Routes do
|
defmodule Legendary.Admin.Routes do
|
||||||
|
@moduledoc """
|
||||||
|
Routes from the admin app. Use like:
|
||||||
|
|
||||||
|
use Legendary.Admin.Routes
|
||||||
|
"""
|
||||||
defmacro __using__(_opts \\ []) do
|
defmacro __using__(_opts \\ []) do
|
||||||
quote do
|
quote do
|
||||||
use Kaffy.Routes, scope: "/admin", pipe_through: [:require_admin]
|
use Kaffy.Routes, scope: "/admin", pipe_through: [:require_admin]
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
defmodule Legendary.Admin.Telemetry do
|
defmodule Legendary.Admin.Telemetry do
|
||||||
|
@moduledoc """
|
||||||
|
Collect metrics from the admin app.
|
||||||
|
"""
|
||||||
|
|
||||||
use Supervisor
|
use Supervisor
|
||||||
import Telemetry.Metrics
|
import Telemetry.Metrics
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
defmodule Legendary.Admin.Kaffy.Config do
|
defmodule Legendary.Admin.Kaffy.Config do
|
||||||
|
@moduledoc """
|
||||||
|
Pull in the resource list for the admin from the application config.
|
||||||
|
"""
|
||||||
|
|
||||||
def create_resources(_conn) do
|
def create_resources(_conn) do
|
||||||
config = Application.get_env(:admin, Legendary.Admin)
|
config = Application.get_env(:admin, Legendary.Admin)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
defmodule Legendary.Admin.Kaffy.EditorExtension do
|
defmodule Legendary.Admin.Kaffy.EditorExtension do
|
||||||
|
@moduledoc """
|
||||||
|
Bring in additional CSS and JS for the admin interface e.g. the
|
||||||
|
markdown editor library.
|
||||||
|
"""
|
||||||
|
|
||||||
def stylesheets(_conn) do
|
def stylesheets(_conn) do
|
||||||
[
|
[
|
||||||
{:safe, ~s(<link rel="stylesheet" href="/css/content-editor.css" />)},
|
{:safe, ~s(<link rel="stylesheet" href="/css/content-editor.css" />)},
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
{
|
{
|
||||||
"presets": [
|
"presets": ["@babel/preset-env"]
|
||||||
"@babel/preset-env"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
node_modules
|
node_modules
|
||||||
|
css/phoenix.css
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
@import "tailwindcss/base";
|
@import "tailwindcss/base";
|
||||||
|
|
||||||
@import "tailwindcss/components";
|
@import "tailwindcss/components";
|
||||||
|
|
||||||
@import "tailwindcss/utilities";
|
@import "tailwindcss/utilities";
|
||||||
|
|
||||||
@import "@fortawesome/fontawesome-free/css/all.css";
|
@import "@fortawesome/fontawesome-free/css/all.css";
|
||||||
|
|
||||||
@import "blog";
|
@import "blog";
|
||||||
@import "code";
|
@import "code";
|
||||||
|
|
||||||
|
@ -14,27 +10,28 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"]::after {
|
input[type="checkbox"]::after {
|
||||||
content: "";
|
display: inline-flex;
|
||||||
color: currentColor;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: inline-flex;
|
color: currentColor;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"]:checked::after {
|
input[type="checkbox"]:checked::after {
|
||||||
content: "✓";
|
content: "✓";
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden-options-toggle:checked+.hidden {
|
.hidden-options-toggle:checked + .hidden {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-revealer {
|
.password-revealer {
|
||||||
@apply absolute;
|
@apply absolute;
|
||||||
height: 45px;
|
|
||||||
width: 45px;
|
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
|
|
||||||
.hljs {
|
.hljs {
|
||||||
@apply mt-6 mb-12 border-l-4 border-indigo-900 font-mono;
|
@apply mt-6 mb-12 border-l-4 border-indigo-900 font-mono;
|
||||||
--background-color: theme("colors.indigo.100");
|
--background-color: theme("colors.indigo.100"); /* stylelint-disable-line custom-property-empty-line-before */
|
||||||
--attribute-color: theme("colors.orange.700");
|
--attribute-color: theme("colors.orange.700");
|
||||||
--section-color: theme("colors.teal.700");
|
--section-color: theme("colors.teal.700");
|
||||||
--string-color: theme("colors.green.700");
|
--string-color: theme("colors.green.700");
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// We need to import the CSS so that webpack will load it.
|
// We need to import the CSS so that webpack will load it.
|
||||||
// The MiniCssExtractPlugin is used to separate it out into
|
// The MiniCssExtractPlugin is used to separate it out into
|
||||||
// its own CSS file.
|
// its own CSS file.
|
||||||
import "../css/app.css"
|
import "../css/app.css";
|
||||||
|
|
||||||
// webpack automatically bundles all modules in your
|
// webpack automatically bundles all modules in your
|
||||||
// entry points. Those entry points can be configured
|
// entry points. Those entry points can be configured
|
||||||
|
@ -12,48 +12,46 @@ import "../css/app.css"
|
||||||
// import {Socket} from "phoenix"
|
// import {Socket} from "phoenix"
|
||||||
// import socket from "./socket"
|
// import socket from "./socket"
|
||||||
//
|
//
|
||||||
import "phoenix_html"
|
import "phoenix_html";
|
||||||
import "alpinejs"
|
import "alpinejs";
|
||||||
import "./live"
|
import "./live";
|
||||||
import { ready } from "./utils"
|
import { ready } from "./utils";
|
||||||
|
|
||||||
function togglePasswordFieldVisibility()
|
var bloop;
|
||||||
{
|
|
||||||
const passwordFields = document.querySelectorAll('[name="user[password]"]')
|
function togglePasswordFieldVisibility() {
|
||||||
|
const passwordFields = document.querySelectorAll('[name="user[password]"]');
|
||||||
passwordFields.forEach((el) => {
|
passwordFields.forEach((el) => {
|
||||||
if (el.type == 'password')
|
if (el.type == "password") {
|
||||||
{
|
el.type = "text";
|
||||||
el.type = 'text'
|
} else {
|
||||||
|
el.type = "password";
|
||||||
}
|
}
|
||||||
else
|
});
|
||||||
{
|
|
||||||
el.type = 'password'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleSidebar = (event) => {
|
const toggleSidebar = (event) => {
|
||||||
document.querySelectorAll('.sidebar').forEach((el) => {
|
document.querySelectorAll(".sidebar").forEach((el) => {
|
||||||
el.classList.toggle('visible')
|
el.classList.toggle("visible");
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
ready(() => {
|
ready(() => {
|
||||||
(document.getElementById('nav-toggle') ||{}).onclick = function(){
|
(document.getElementById("nav-toggle") || {}).onclick = function () {
|
||||||
document.getElementById("nav-content").classList.toggle("hidden");
|
document.getElementById("nav-content").classList.toggle("hidden");
|
||||||
}
|
};
|
||||||
|
|
||||||
document.querySelectorAll('.js-passwordRevealer').forEach((el) => {
|
document.querySelectorAll(".js-passwordRevealer").forEach((el) => {
|
||||||
el.addEventListener('click', togglePasswordFieldVisibility)
|
el.addEventListener("click", togglePasswordFieldVisibility);
|
||||||
})
|
});
|
||||||
|
|
||||||
document.querySelectorAll('.js-SidebarOpener').forEach((el) => {
|
document.querySelectorAll(".js-SidebarOpener").forEach((el) => {
|
||||||
el.addEventListener('click', toggleSidebar)
|
el.addEventListener("click", toggleSidebar);
|
||||||
})
|
});
|
||||||
|
|
||||||
document.querySelectorAll('.js-flash-closer').forEach((el) => {
|
document.querySelectorAll(".js-flash-closer").forEach((el) => {
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener("click", () => {
|
||||||
el.closest('.js-flash').remove()
|
el.closest(".js-flash").remove();
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,34 +1,33 @@
|
||||||
import { ready } from "./utils"
|
import { ready } from "./utils";
|
||||||
import SimpleMDE from "simplemde"
|
import SimpleMDE from "simplemde";
|
||||||
import "simplemde/dist/simplemde.min.css"
|
import "simplemde/dist/simplemde.min.css";
|
||||||
import "../css/content-editor-overrides.css"
|
import "../css/content-editor-overrides.css";
|
||||||
|
|
||||||
|
|
||||||
const requestPreview = (plainText, previewContainer) => {
|
const requestPreview = (plainText, previewContainer) => {
|
||||||
let request = new XMLHttpRequest()
|
let request = new XMLHttpRequest();
|
||||||
const postForm = previewContainer.closest('form')
|
const postForm = previewContainer.closest("form");
|
||||||
let formData = new FormData(postForm)
|
let formData = new FormData(postForm);
|
||||||
|
|
||||||
formData.set('post[content]', plainText)
|
formData.set("post[content]", plainText);
|
||||||
|
|
||||||
request.addEventListener('load', function(event) {
|
request.addEventListener("load", function (event) {
|
||||||
previewContainer.innerHTML = event.target.responseText
|
previewContainer.innerHTML = event.target.responseText;
|
||||||
})
|
});
|
||||||
|
|
||||||
request.open('POST', '/pages/posts/preview', true)
|
request.open("POST", "/pages/posts/preview", true);
|
||||||
|
|
||||||
request.send(formData)
|
request.send(formData);
|
||||||
}
|
};
|
||||||
|
|
||||||
ready(() => {
|
ready(() => {
|
||||||
document.querySelectorAll('[data-simplemde]').forEach(el => {
|
document.querySelectorAll("[data-simplemde]").forEach((el) => {
|
||||||
new SimpleMDE({
|
new SimpleMDE({
|
||||||
element: el,
|
element: el,
|
||||||
previewRender: (plainText, previewContainer) => {
|
previewRender: (plainText, previewContainer) => {
|
||||||
requestPreview(plainText, previewContainer)
|
requestPreview(plainText, previewContainer);
|
||||||
|
|
||||||
return previewContainer.innerHTML
|
return previewContainer.innerHTML;
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,18 +1,25 @@
|
||||||
import {Socket} from "phoenix"
|
import { Socket } from "phoenix";
|
||||||
import LiveSocket from "phoenix_live_view"
|
import LiveSocket from "phoenix_live_view";
|
||||||
import topbar from "topbar"
|
import topbar from "topbar";
|
||||||
|
|
||||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
let csrfToken = document
|
||||||
|
.querySelector("meta[name='csrf-token']")
|
||||||
|
.getAttribute("content");
|
||||||
|
|
||||||
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
|
let liveSocket = new LiveSocket("/live", Socket, {
|
||||||
|
params: { _csrf_token: csrfToken },
|
||||||
|
});
|
||||||
|
|
||||||
// Show progress bar on live navigation and form submits
|
// Show progress bar on live navigation and form submits
|
||||||
topbar.config({barColors: {0: "#3B82F6"}, shadowColor: "rgba(0, 0, 0, .3)"})
|
topbar.config({
|
||||||
window.addEventListener("phx:page-loading-start", info => topbar.show())
|
barColors: { 0: "#3B82F6" },
|
||||||
window.addEventListener("phx:page-loading-stop", info => topbar.hide())
|
shadowColor: "rgba(0, 0, 0, .3)",
|
||||||
|
});
|
||||||
|
window.addEventListener("phx:page-loading-start", (info) => topbar.show());
|
||||||
|
window.addEventListener("phx:page-loading-stop", (info) => topbar.hide());
|
||||||
|
|
||||||
// Connect if there are any LiveViews on the page
|
// Connect if there are any LiveViews on the page
|
||||||
liveSocket.connect()
|
liveSocket.connect();
|
||||||
|
|
||||||
// Expose liveSocket on window for web console debug logs and latency simulation:
|
// Expose liveSocket on window for web console debug logs and latency simulation:
|
||||||
// >> liveSocket.enableDebug()
|
// >> liveSocket.enableDebug()
|
||||||
|
@ -20,4 +27,4 @@ liveSocket.connect()
|
||||||
// The latency simulator is enabled for the duration of the browser session.
|
// The latency simulator is enabled for the duration of the browser session.
|
||||||
// Call disableLatencySim() to disable:
|
// Call disableLatencySim() to disable:
|
||||||
// >> liveSocket.disableLatencySim()
|
// >> liveSocket.disableLatencySim()
|
||||||
window.liveSocket = liveSocket
|
window.liveSocket = liveSocket;
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
//
|
//
|
||||||
// Pass the token on params as below. Or remove it
|
// Pass the token on params as below. Or remove it
|
||||||
// from the params if you are not using authentication.
|
// from the params if you are not using authentication.
|
||||||
import {Socket} from "phoenix"
|
import { Socket } from "phoenix";
|
||||||
|
|
||||||
let socket = new Socket("/socket", {params: {token: window.userToken}})
|
let socket = new Socket("/socket", { params: { token: window.userToken } });
|
||||||
|
|
||||||
// When you connect, you'll often need to authenticate the client.
|
// When you connect, you'll often need to authenticate the client.
|
||||||
// For example, imagine you have an authentication plug, `MyAuth`,
|
// For example, imagine you have an authentication plug, `MyAuth`,
|
||||||
|
@ -52,12 +52,17 @@ let socket = new Socket("/socket", {params: {token: window.userToken}})
|
||||||
// end
|
// end
|
||||||
//
|
//
|
||||||
// Finally, connect to the socket:
|
// Finally, connect to the socket:
|
||||||
socket.connect()
|
socket.connect();
|
||||||
|
|
||||||
// Now that you are connected, you can join channels with a topic:
|
// Now that you are connected, you can join channels with a topic:
|
||||||
let channel = socket.channel("topic:subtopic", {})
|
let channel = socket.channel("topic:subtopic", {});
|
||||||
channel.join()
|
channel
|
||||||
.receive("ok", resp => { console.log("Joined successfully", resp) })
|
.join()
|
||||||
.receive("error", resp => { console.log("Unable to join", resp) })
|
.receive("ok", (resp) => {
|
||||||
|
console.log("Joined successfully", resp);
|
||||||
|
})
|
||||||
|
.receive("error", (resp) => {
|
||||||
|
console.log("Unable to join", resp);
|
||||||
|
});
|
||||||
|
|
||||||
export default socket
|
export default socket;
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
|
|
||||||
const ready = (fn) => {
|
const ready = (fn) => {
|
||||||
if (document.readyState != 'loading') {
|
if (document.readyState != "loading") {
|
||||||
fn()
|
fn();
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener('DOMContentLoaded', fn)
|
document.addEventListener("DOMContentLoaded", fn);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export { ready }
|
export { ready };
|
||||||
|
|
19
apps/app/assets/package-lock.json
generated
19
apps/app/assets/package-lock.json
generated
|
@ -34,6 +34,7 @@
|
||||||
"postcss-css-variables": "^0.17.0",
|
"postcss-css-variables": "^0.17.0",
|
||||||
"postcss-import": "^12.0.1",
|
"postcss-import": "^12.0.1",
|
||||||
"postcss-loader": "^6.1.0",
|
"postcss-loader": "^6.1.0",
|
||||||
|
"prettier": "2.3.2",
|
||||||
"sass": "^1.35.1",
|
"sass": "^1.35.1",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"style-loader": "^1.2.1",
|
"style-loader": "^1.2.1",
|
||||||
|
@ -9749,6 +9750,18 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin-prettier.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pretty-hrtime": {
|
"node_modules/pretty-hrtime": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
|
||||||
|
@ -20384,6 +20397,12 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"prettier": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"pretty-hrtime": {
|
"pretty-hrtime": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"postcss-css-variables": "^0.17.0",
|
"postcss-css-variables": "^0.17.0",
|
||||||
"postcss-import": "^12.0.1",
|
"postcss-import": "^12.0.1",
|
||||||
"postcss-loader": "^6.1.0",
|
"postcss-loader": "^6.1.0",
|
||||||
|
"prettier": "2.3.2",
|
||||||
"sass": "^1.35.1",
|
"sass": "^1.35.1",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"style-loader": "^1.2.1",
|
"style-loader": "^1.2.1",
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
require('postcss-import')({
|
require("postcss-import")({
|
||||||
plugins: [
|
plugins: [require("stylelint")()],
|
||||||
require('stylelint')(),
|
|
||||||
]
|
|
||||||
}),
|
}),
|
||||||
require('tailwindcss'),
|
require("tailwindcss"),
|
||||||
require('autoprefixer'),
|
require("autoprefixer"),
|
||||||
require('csswring')(),
|
require("csswring")(),
|
||||||
require('postcss-color-function')()
|
require("postcss-color-function")(),
|
||||||
],
|
],
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const yargsParser = require('yargs-parser');
|
const yargsParser = require("yargs-parser");
|
||||||
const cliArgs = yargsParser(process.argv);
|
const cliArgs = yargsParser(process.argv);
|
||||||
|
|
||||||
const mode = process.env.NODE_ENV || cliArgs.mode || 'development';
|
const mode = process.env.NODE_ENV || cliArgs.mode || "development";
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
future: {
|
future: {
|
||||||
|
@ -9,21 +9,21 @@ module.exports = {
|
||||||
purgeLayersByDefault: true,
|
purgeLayersByDefault: true,
|
||||||
},
|
},
|
||||||
purge: {
|
purge: {
|
||||||
enabled: mode == 'production',
|
enabled: mode == "production",
|
||||||
layers: ['base', 'components', 'utilities'],
|
layers: ["base", "components", "utilities"],
|
||||||
content: [
|
content: [
|
||||||
'../../../**/views/*.ex',
|
"../../../**/views/*.ex",
|
||||||
'../../../**/*.html.eex',
|
"../../../**/*.html.eex",
|
||||||
'../../../**/*.html.leex',
|
"../../../**/*.html.leex",
|
||||||
'../../../**/*.html.heex',
|
"../../../**/*.html.heex",
|
||||||
'./js/**/*.js'
|
"./js/**/*.js",
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
backgroundColor: ['responsive', 'hover', 'focus', 'checked'],
|
backgroundColor: ["responsive", "hover", "focus", "checked"],
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,31 +1,31 @@
|
||||||
const path = require('path');
|
const path = require("path");
|
||||||
const glob = require('glob');
|
const glob = require("glob");
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
|
|
||||||
const nodeModulesPath = path.resolve(__dirname, 'node_modules')
|
const nodeModulesPath = path.resolve(__dirname, "node_modules");
|
||||||
|
|
||||||
module.exports = (env, options) => {
|
module.exports = (env, options) => {
|
||||||
const devMode = options.mode !== 'production';
|
const devMode = options.mode !== "production";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
optimization: {
|
optimization: {
|
||||||
minimizer: [
|
minimizer: [
|
||||||
new TerserPlugin({ cache: true, parallel: true, sourceMap: devMode }),
|
new TerserPlugin({ cache: true, parallel: true, sourceMap: devMode }),
|
||||||
new CssMinimizerPlugin(),
|
new CssMinimizerPlugin(),
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
mode: options.mode,
|
mode: options.mode,
|
||||||
devtool: devMode ? 'source-map' : undefined,
|
devtool: devMode ? "source-map" : undefined,
|
||||||
entry: {
|
entry: {
|
||||||
'app': glob.sync('./vendor/**/*.js').concat(['./js/app.js']),
|
app: glob.sync("./vendor/**/*.js").concat(["./js/app.js"]),
|
||||||
'content-editor': ['./js/content-editor.js'],
|
"content-editor": ["./js/content-editor.js"],
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: 'js/[name].js',
|
filename: "js/[name].js",
|
||||||
path: path.resolve(__dirname, '../priv/static/')
|
path: path.resolve(__dirname, "../priv/static/"),
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
|
@ -33,9 +33,9 @@ module.exports = (env, options) => {
|
||||||
{
|
{
|
||||||
test: /\.(jpg|jpeg|gif|png)$/,
|
test: /\.(jpg|jpeg|gif|png)$/,
|
||||||
use: [
|
use: [
|
||||||
'file-loader',
|
"file-loader",
|
||||||
{
|
{
|
||||||
loader: 'image-webpack-loader',
|
loader: "image-webpack-loader",
|
||||||
options: {
|
options: {
|
||||||
disable: devMode,
|
disable: devMode,
|
||||||
},
|
},
|
||||||
|
@ -44,61 +44,108 @@ module.exports = (env, options) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(woff2?|ttf|eot|svg)(\?[a-z0-9\=\.]+)?$/,
|
test: /\.(woff2?|ttf|eot|svg)(\?[a-z0-9\=\.]+)?$/,
|
||||||
loader: 'file-loader',
|
loader: "file-loader",
|
||||||
options: {
|
options: {
|
||||||
publicPath: '/fonts',
|
publicPath: "/fonts",
|
||||||
outputPath: (url, resourcePath, context) => {
|
outputPath: (url, resourcePath, context) => {
|
||||||
return `/fonts/${url}`;
|
return `/fonts/${url}`;
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: {
|
use: {
|
||||||
loader: 'babel-loader'
|
loader: "babel-loader",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
use: [
|
use: [
|
||||||
{loader: MiniCssExtractPlugin.loader},
|
{ loader: MiniCssExtractPlugin.loader },
|
||||||
{loader: 'css-loader', options: {sourceMap: true}},
|
{ loader: "css-loader", options: { sourceMap: true } },
|
||||||
{loader: 'postcss-loader', options: {sourceMap: true}},
|
{ loader: "postcss-loader", options: { sourceMap: true } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: 'css/[name].css',
|
filename: "css/[name].css",
|
||||||
chunkFilename: '[id].css',
|
chunkFilename: "[id].css",
|
||||||
|
}),
|
||||||
|
new CopyWebpackPlugin({
|
||||||
|
patterns: [
|
||||||
|
{
|
||||||
|
from: path.resolve(__dirname, "static"),
|
||||||
|
to: path.resolve(__dirname, "../priv/static"),
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
new CopyWebpackPlugin({patterns: [
|
|
||||||
{
|
|
||||||
from: path.resolve(__dirname, 'static'),
|
|
||||||
to: path.resolve(__dirname, '../priv/static'),
|
|
||||||
},
|
|
||||||
]}),
|
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"../webfonts/fa-brands-400.eot": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.eot"),
|
"../webfonts/fa-brands-400.eot": path.resolve(
|
||||||
"../webfonts/fa-brands-400.woff2": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2"),
|
__dirname,
|
||||||
"../webfonts/fa-brands-400.woff": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff"),
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.eot"
|
||||||
"../webfonts/fa-brands-400.ttf": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf"),
|
),
|
||||||
"../webfonts/fa-brands-400.svg": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.svg"),
|
"../webfonts/fa-brands-400.woff2": path.resolve(
|
||||||
"../webfonts/fa-regular-400.eot": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.eot"),
|
__dirname,
|
||||||
"../webfonts/fa-regular-400.woff2": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2"),
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2"
|
||||||
"../webfonts/fa-regular-400.woff": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff"),
|
),
|
||||||
"../webfonts/fa-regular-400.ttf": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf"),
|
"../webfonts/fa-brands-400.woff": path.resolve(
|
||||||
"../webfonts/fa-regular-400.svg": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.svg"),
|
__dirname,
|
||||||
"../webfonts/fa-solid-900.eot": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.eot"),
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff"
|
||||||
"../webfonts/fa-solid-900.woff2": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2"),
|
),
|
||||||
"../webfonts/fa-solid-900.woff": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff"),
|
"../webfonts/fa-brands-400.ttf": path.resolve(
|
||||||
"../webfonts/fa-solid-900.ttf": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf"),
|
__dirname,
|
||||||
"../webfonts/fa-solid-900.svg": path.resolve(__dirname, "node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.svg"),
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf"
|
||||||
}
|
),
|
||||||
|
"../webfonts/fa-brands-400.svg": path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.svg"
|
||||||
|
),
|
||||||
|
"../webfonts/fa-regular-400.eot": path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.eot"
|
||||||
|
),
|
||||||
|
"../webfonts/fa-regular-400.woff2": path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2"
|
||||||
|
),
|
||||||
|
"../webfonts/fa-regular-400.woff": path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff"
|
||||||
|
),
|
||||||
|
"../webfonts/fa-regular-400.ttf": path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf"
|
||||||
|
),
|
||||||
|
"../webfonts/fa-regular-400.svg": path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.svg"
|
||||||
|
),
|
||||||
|
"../webfonts/fa-solid-900.eot": path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.eot"
|
||||||
|
),
|
||||||
|
"../webfonts/fa-solid-900.woff2": path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2"
|
||||||
|
),
|
||||||
|
"../webfonts/fa-solid-900.woff": path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff"
|
||||||
|
),
|
||||||
|
"../webfonts/fa-solid-900.ttf": path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf"
|
||||||
|
),
|
||||||
|
"../webfonts/fa-solid-900.svg": path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.svg"
|
||||||
|
),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
defmodule AppWeb.Telemetry do
|
defmodule AppWeb.Telemetry do
|
||||||
|
@moduledoc """
|
||||||
|
Collect metrics on your app.
|
||||||
|
"""
|
||||||
|
|
||||||
use Supervisor
|
use Supervisor
|
||||||
import Telemetry.Metrics
|
import Telemetry.Metrics
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
defmodule Legendary.Content.CommentAdmin do
|
defmodule Legendary.Content.CommentAdmin do
|
||||||
|
@moduledoc """
|
||||||
|
Custom admin logic for blog post comments.
|
||||||
|
"""
|
||||||
|
|
||||||
def index(_) do
|
def index(_) do
|
||||||
[
|
[
|
||||||
id: nil,
|
id: nil,
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
defmodule Legendary.Content.MarkupField do
|
defmodule Legendary.Content.MarkupField do
|
||||||
|
@moduledoc """
|
||||||
|
Custom field type definition for markdown fields. Currently uses simplemde
|
||||||
|
to provide a markdown editing GUI.
|
||||||
|
"""
|
||||||
use Ecto.Type
|
use Ecto.Type
|
||||||
def type, do: :string
|
def type, do: :string
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
defmodule Legendary.Content.PostAdmin do
|
defmodule Legendary.Content.PostAdmin do
|
||||||
|
@moduledoc """
|
||||||
|
Custom admin logic for content posts and pages.
|
||||||
|
"""
|
||||||
|
|
||||||
import Ecto.Query, only: [from: 2]
|
import Ecto.Query, only: [from: 2]
|
||||||
|
|
||||||
def singular_name(_) do
|
def singular_name(_) do
|
||||||
|
|
|
@ -87,11 +87,12 @@ defmodule Legendary.Content.PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
# The static page we're looking for is missing, so this is just a 404
|
# The static page we're looking for is missing, so this is just a 404
|
||||||
|
# credo:disable-for-next-line
|
||||||
raise Phoenix.Router.NoRouteError.exception(conn: conn, router: router)
|
raise Phoenix.Router.NoRouteError.exception(conn: conn, router: router)
|
||||||
_ ->
|
_ ->
|
||||||
# We aren't missing the static page, we're missing a partial. This is probably
|
# We aren't missing the static page, we're missing a partial. This is probably
|
||||||
# a developer error, so bubble it up
|
# a developer error, so bubble it up
|
||||||
raise e
|
reraise e, System.stacktrace
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
defmodule Legendary.Content.Routes do
|
defmodule Legendary.Content.Routes do
|
||||||
|
@moduledoc """
|
||||||
|
Routes for the content engine, including blog posts, feeds, and pages.
|
||||||
|
"""
|
||||||
|
|
||||||
defmacro __using__(_opts \\ []) do
|
defmacro __using__(_opts \\ []) do
|
||||||
quote do
|
quote do
|
||||||
pipeline :feed do
|
pipeline :feed do
|
||||||
|
|
|
@ -52,7 +52,7 @@ defmodule Legendary.Content.PostsTest do
|
||||||
test "delete_posts/1", %{public_post: post} do
|
test "delete_posts/1", %{public_post: post} do
|
||||||
assert Enum.count(Posts.list_posts()) == 1
|
assert Enum.count(Posts.list_posts()) == 1
|
||||||
assert {:ok, _} = Posts.delete_posts(post)
|
assert {:ok, _} = Posts.delete_posts(post)
|
||||||
assert Enum.count(Posts.list_posts()) == 0
|
assert Enum.empty?(Posts.list_posts())
|
||||||
end
|
end
|
||||||
|
|
||||||
test "change_posts/1", %{public_post: post} do
|
test "change_posts/1", %{public_post: post} do
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
defmodule Legendary.Auth.MnesiaClusterSupervisor do
|
defmodule Legendary.Auth.MnesiaClusterSupervisor do
|
||||||
|
@moduledoc """
|
||||||
|
Manages the cache in Mnesia for Pow. This allows users to remain logged in
|
||||||
|
even if their traffic is hitting different nodes in the cluster.
|
||||||
|
"""
|
||||||
|
|
||||||
use Supervisor
|
use Supervisor
|
||||||
|
|
||||||
def start_link(init_arg) do
|
def start_link(init_arg) do
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
defmodule Legendary.Auth.Roles do
|
defmodule Legendary.Auth.Roles do
|
||||||
|
@moduledoc """
|
||||||
|
Functions for working with roles on users, such as testing whether a user has
|
||||||
|
a role.
|
||||||
|
"""
|
||||||
|
|
||||||
def has_role?(userlike, role) when is_atom(role), do: has_role?(userlike, Atom.to_string(role))
|
def has_role?(userlike, role) when is_atom(role), do: has_role?(userlike, Atom.to_string(role))
|
||||||
def has_role?(nil, _), do: false
|
def has_role?(nil, _), do: false
|
||||||
def has_role?(user = %Legendary.Auth.User{}, role) do
|
def has_role?(%Legendary.Auth.User{} = user, role) do
|
||||||
Enum.any?(user.roles || [], & &1 == role)
|
Enum.any?(user.roles || [], & &1 == role)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,7 +40,7 @@ defmodule Legendary.Auth.User do
|
||||||
|> pow_extension_changeset(attrs)
|
|> pow_extension_changeset(attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_password_changeset(user = %Legendary.Auth.User{}, params) do
|
def reset_password_changeset(%Legendary.Auth.User{} = user, params) do
|
||||||
user
|
user
|
||||||
|> new_password_changeset(params, @pow_config)
|
|> new_password_changeset(params, @pow_config)
|
||||||
|> Changeset.validate_required([:password])
|
|> Changeset.validate_required([:password])
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
defmodule Legendary.Auth.UserAdmin do
|
defmodule Legendary.Auth.UserAdmin do
|
||||||
|
@moduledoc """
|
||||||
|
Custom admin login for user records.
|
||||||
|
"""
|
||||||
import Ecto.Query, only: [from: 2]
|
import Ecto.Query, only: [from: 2]
|
||||||
alias Legendary.Auth.User
|
alias Legendary.Auth.User
|
||||||
alias Legendary.Core.Repo
|
alias Legendary.Core.Repo
|
||||||
|
|
||||||
def custom_links(_schema) do
|
def custom_links(_schema) do
|
||||||
|
# We add the funwithflags admin URL under this custom admin because kaffy
|
||||||
|
# doesn't have global custom links that work in this way and user is the
|
||||||
|
# closest fit.
|
||||||
[
|
[
|
||||||
%{name: "Feature Flags", url: "/admin/feature-flags", order: 2, location: :top, icon: "flag"},
|
%{name: "Feature Flags", url: "/admin/feature-flags", order: 2, location: :top, icon: "flag"},
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
defmodule Legendary.AuthWeb.Helpers do
|
defmodule Legendary.AuthWeb.Helpers do
|
||||||
def current_user(socket = %Phoenix.LiveView.Socket{assigns: %{current_user: user}}), do: user
|
@moduledoc """
|
||||||
def current_user(socket = %Phoenix.LiveView.Socket{assigns: %{__assigns__: %{current_user: user}}}), do: user
|
Utility functions for working with users and roles.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def current_user(%Phoenix.LiveView.Socket{assigns: %{current_user: user}}), do: user
|
||||||
|
def current_user(%Phoenix.LiveView.Socket{assigns: %{__assigns__: %{current_user: user}}}), do: user
|
||||||
def current_user(%Phoenix.LiveView.Socket{}), do: nil
|
def current_user(%Phoenix.LiveView.Socket{}), do: nil
|
||||||
def current_user(conn), do: Pow.Plug.current_user(conn)
|
def current_user(conn), do: Pow.Plug.current_user(conn)
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule Legendary.Core.MapUtils do
|
||||||
Map.merge(base, override, &deep_value/3)
|
Map.merge(base, override, &deep_value/3)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp deep_value(_key, base = %{}, override = %{}) do
|
defp deep_value(_key, %{} = base, %{} = override) do
|
||||||
deep_merge(base, override)
|
deep_merge(base, override)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
defmodule Legendary.Core.Routes do
|
defmodule Legendary.Core.Routes do
|
||||||
|
@moduledoc """
|
||||||
|
Router module that brings in core framework routes, such as the feature flag
|
||||||
|
admin interface. Can be included like:
|
||||||
|
|
||||||
|
use Legendary.Core.Routes
|
||||||
|
"""
|
||||||
defmacro __using__(_opts \\ []) do
|
defmacro __using__(_opts \\ []) do
|
||||||
quote do
|
quote do
|
||||||
scope path: "/admin/feature-flags" do
|
scope path: "/admin/feature-flags" do
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
defmodule Mix.Legendary do
|
defmodule Mix.Legendary do
|
||||||
|
@moduledoc """
|
||||||
|
Parent module for all Legendary framework mix tasks. Provides some helpers
|
||||||
|
used by tasks and generators.
|
||||||
|
"""
|
||||||
alias Mix.Phoenix.{Schema}
|
alias Mix.Phoenix.{Schema}
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
|
13
lefthook.yml
Normal file
13
lefthook.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
pre-commit:
|
||||||
|
parallel: true
|
||||||
|
commands:
|
||||||
|
credo:
|
||||||
|
runner: mix credo diff master
|
||||||
|
stylelint:
|
||||||
|
root: "apps/app/assets/"
|
||||||
|
glob: "*.{css}"
|
||||||
|
runner: npx stylelint {staged_files}
|
||||||
|
prettier:
|
||||||
|
root: "apps/app/assets/"
|
||||||
|
glob: "*.{js}"
|
||||||
|
runner: npx prettier --check {staged_files}
|
2
mix.lock
2
mix.lock
|
@ -11,7 +11,7 @@
|
||||||
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
|
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
|
||||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
|
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
|
||||||
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
|
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
|
||||||
"credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"},
|
"credo": {:hex, :credo, "1.5.6", "e04cc0fdc236fefbb578e0c04bd01a471081616e741d386909e527ac146016c6", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4b52a3e558bd64e30de62a648518a5ea2b6e3e5d2b164ef5296244753fc7eb17"},
|
||||||
"crontab": {:hex, :crontab, "1.1.10", "dc9bb1f4299138d47bce38341f5dcbee0aa6c205e864fba7bc847f3b5cb48241", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "1347d889d1a0eda997990876b4894359e34bfbbd688acbb0ba28a2795ca40685"},
|
"crontab": {:hex, :crontab, "1.1.10", "dc9bb1f4299138d47bce38341f5dcbee0aa6c205e864fba7bc847f3b5cb48241", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "1347d889d1a0eda997990876b4894359e34bfbbd688acbb0ba28a2795ca40685"},
|
||||||
"db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
|
"db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
|
||||||
"decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"},
|
"decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"},
|
||||||
|
|
Loading…
Reference in a new issue