Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fda0455c1 | ||
|
|
0d344a4f57 | ||
|
|
84539ec40f | ||
|
|
97fc024a38 | ||
|
|
7215cd6db0 |
@@ -70,6 +70,12 @@ This is a Phoenix Liveview monorepo, the blogging library is in blogex, the appl
|
|||||||
|
|
||||||
Main make targets:
|
Main make targets:
|
||||||
|
|
||||||
|
- `make setup` - one-time clone setup (and after any mix.exs gains a dep):
|
||||||
|
fetches Hex deps for both apps, the app's JS packages, and compiles deps
|
||||||
|
(some deps fetch artifacts at compile time, e.g. the mdex_native NIF).
|
||||||
|
Needs network - run it yourself; agent sandboxes refuse installs.
|
||||||
|
check/test/verify never touch the network: they run a local `deps.check`
|
||||||
|
guard that fails with a pointer here when deps are missing.
|
||||||
- `make check` - cheap static gate for both apps (no DB): app `credo --strict` +
|
- `make check` - cheap static gate for both apps (no DB): app `credo --strict` +
|
||||||
format **check** (`mix format --check-formatted`), blogex compile (warnings
|
format **check** (`mix format --check-formatted`), blogex compile (warnings
|
||||||
fatal) + format **check**, then `scripts/check-pins.sh`. It never rewrites
|
fatal) + format **check**, then `scripts/check-pins.sh`. It never rewrites
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
MISE_BIN ?= mise
|
MISE_BIN ?= mise
|
||||||
|
|
||||||
.PHONY: check precommit verify hooks deps compile test test-elisp format
|
.PHONY: check precommit verify hooks deps setup compile test test-elisp format
|
||||||
|
|
||||||
# Cheap static gate over both apps, cheapest first. No database, no coverage:
|
# Cheap static gate over both apps, cheapest first. No database, no coverage:
|
||||||
# this is the loop you run freely while working and before every commit.
|
# this is the loop you run freely while working and before every commit.
|
||||||
@@ -47,9 +47,18 @@ hooks:
|
|||||||
echo "installed $$hooks_dir/$$name -> $$hook"; \
|
echo "installed $$hooks_dir/$$name -> $$hook"; \
|
||||||
done
|
done
|
||||||
|
|
||||||
# Sync dependencies
|
# One-time setup for this clone (and after any mix.exs gains a dependency):
|
||||||
deps:
|
# fetches Hex deps for both apps and the app's JS packages. Needs network, and
|
||||||
@make -C app deps
|
# is deliberately NOT wired into check/test/verify: `mix deps.get` contacts
|
||||||
|
# hex.pm even when everything is already installed (the advisory check), which
|
||||||
|
# fails offline/sandboxed. The everyday loops run the per-app `deps.check`
|
||||||
|
# guard instead and point here when deps are missing.
|
||||||
|
setup:
|
||||||
|
@$(MAKE) -C app MISE_BIN=$(MISE_BIN) deps
|
||||||
|
@$(MAKE) -C blogex MISE_BIN=$(MISE_BIN) deps
|
||||||
|
|
||||||
|
# Kept for muscle memory; `setup` is the canonical entry point.
|
||||||
|
deps: setup
|
||||||
|
|
||||||
# Compile the project
|
# Compile the project
|
||||||
compile:
|
compile:
|
||||||
|
|||||||
+31
-8
@@ -3,12 +3,13 @@
|
|||||||
MISE_BIN ?= mise
|
MISE_BIN ?= mise
|
||||||
MISE_EXEC = $(MISE_BIN) exec --
|
MISE_EXEC = $(MISE_BIN) exec --
|
||||||
|
|
||||||
.PHONY: check precommit deps assets compile test test.coverage sobelow coverage crap format format.check credo verify
|
.PHONY: check precommit deps deps.check assets compile test test.coverage sobelow coverage crap format format.check credo verify
|
||||||
|
|
||||||
# Run all static analysis checks (no database required). `format.check` only
|
# Run all static analysis checks (no database, no network required). The
|
||||||
# reports: a gate that rewrites the files it judges can never fail. Fix
|
# deps.check guard fails loudly with a pointer at `make setup` when Hex deps
|
||||||
# formatting with `make format`.
|
# are missing. `format.check` only reports: a gate that rewrites the files it
|
||||||
check: credo format.check
|
# judges can never fail. Fix formatting with `make format`.
|
||||||
|
check: deps.check credo format.check
|
||||||
|
|
||||||
# Precommit target for CI/pre-commit hooks
|
# Precommit target for CI/pre-commit hooks
|
||||||
precommit: check compile
|
precommit: check compile
|
||||||
@@ -23,9 +24,31 @@ assets:
|
|||||||
@test -f assets/node_modules/.package-lock.json || \
|
@test -f assets/node_modules/.package-lock.json || \
|
||||||
(echo "==> assets: npm install (first run)"; cd assets && npm install)
|
(echo "==> assets: npm install (first run)"; cd assets && npm install)
|
||||||
|
|
||||||
# Sync dependencies (Hex packages + JS packages)
|
# Sync dependencies (Hex packages + JS packages) and compile them. The compile
|
||||||
|
# step is part of setup on purpose: some deps download artifacts at compile
|
||||||
|
# time - rustler_precompiled fetches the mdex_native NIF from GitHub during
|
||||||
|
# its first compile, and NimblePublisher pulls mdex_native into this app. Keep
|
||||||
|
# that traffic in this explicit, user-run step so check/test/verify stay
|
||||||
|
# offline even on a cold clone. (rustler_precompiled caches artifacts globally
|
||||||
|
# in ~/Library/Caches/rustler_precompiled, so later test-env compiles find
|
||||||
|
# them without network.)
|
||||||
deps: assets
|
deps: assets
|
||||||
$(MISE_EXEC) mix deps.get
|
$(MISE_EXEC) mix deps.get
|
||||||
|
$(MISE_EXEC) mix deps.compile
|
||||||
|
|
||||||
|
# Local, network-free guard: every package pinned in mix.lock must be present
|
||||||
|
# in deps/. check, test and test.coverage depend on this instead of `deps`, so
|
||||||
|
# the everyday loops never touch the network: `mix deps.get` contacts hex.pm
|
||||||
|
# even when everything is already installed (the advisory check), which fails
|
||||||
|
# in offline/sandboxed environments. Fetching is `make setup`'s explicit job,
|
||||||
|
# run from the repo root.
|
||||||
|
deps.check:
|
||||||
|
@test -f mix.lock || { echo "deps.check: no mix.lock - run 'make setup' at the repo root" >&2; exit 1; }
|
||||||
|
@missing=""; \
|
||||||
|
for dep in $$(sed -n 's/^[[:space:]]*"\([^"]*\)".*/\1/p' mix.lock); do \
|
||||||
|
[ -d "deps/$$dep" ] || missing="$$missing $$dep"; \
|
||||||
|
done; \
|
||||||
|
test -z "$$missing" || { echo "deps.check: missing:$$missing - run 'make setup' at the repo root" >&2; exit 1; }
|
||||||
|
|
||||||
# Compile the project
|
# Compile the project
|
||||||
compile:
|
compile:
|
||||||
@@ -33,13 +56,13 @@ compile:
|
|||||||
|
|
||||||
# Run tests (requires PostgreSQL running on localhost:5432)
|
# Run tests (requires PostgreSQL running on localhost:5432)
|
||||||
# Note: If you don't have PostgreSQL, you can skip tests with `make check`
|
# Note: If you don't have PostgreSQL, you can skip tests with `make check`
|
||||||
test: deps compile
|
test: deps.check compile
|
||||||
$(MISE_EXEC) mix test
|
$(MISE_EXEC) mix test
|
||||||
|
|
||||||
# Run the suite once with coverage exported, so the coverage floor check (and,
|
# Run the suite once with coverage exported, so the coverage floor check (and,
|
||||||
# later, CRAP) reads the export instead of running the suite a second time.
|
# later, CRAP) reads the export instead of running the suite a second time.
|
||||||
# Stale exports are removed first: the floor must never report an older run.
|
# Stale exports are removed first: the floor must never report an older run.
|
||||||
test.coverage: deps compile
|
test.coverage: deps.check compile
|
||||||
rm -f cover/*.coverdata
|
rm -f cover/*.coverdata
|
||||||
$(MISE_EXEC) mix test --cover --export-coverage default
|
$(MISE_EXEC) mix test --cover --export-coverage default
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -2,8 +2,7 @@
|
|||||||
title: "The Intention, Location, Details communication Pattern",
|
title: "The Intention, Location, Details communication Pattern",
|
||||||
author: "Willem van den Ende",
|
author: "Willem van den Ende",
|
||||||
tags: ~w(),
|
tags: ~w(),
|
||||||
description: "Communicating our intent clearly is helpful in getting the results we want. I find the pattern “Intention, Location Details” as described by Emily Bache quite handy, It took me a while to find sources, I am collecting them here, before expanding on my own musings."
|
description: "Communicating our intent clearly is helpful in getting the results we want. I find the pattern Intention, Location Details as described by Emily Bache quite handy, It took me a while to find sources, I am collecting them here, before expanding on my own musings.",
|
||||||
",
|
|
||||||
image: nil,
|
image: nil,
|
||||||
published: true
|
published: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
defmodule FirehoseWeb.BlogControllerTest do
|
defmodule FirehoseWeb.BlogControllerTest do
|
||||||
use FirehoseWeb.ConnCase, async: false
|
use FirehoseWeb.ConnCase, async: false
|
||||||
|
|
||||||
|
# Content churns; the behavior under test does not. Pick deterministically
|
||||||
|
# from the compiled posts instead of pinning slugs - the pinned "hello-world"
|
||||||
|
# post was deleted in 1709cb7 and ten tests silently rotted with it.
|
||||||
|
defp a_draft_post do
|
||||||
|
Enum.find(Firehose.EngineeringBlog.all_posts(), &(&1.published == false)) ||
|
||||||
|
raise "no draft (published: false) engineering post to exercise status banners"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp a_live_post do
|
||||||
|
Enum.find(Firehose.EngineeringBlog.all_posts(), &(Blogex.Post.visibility(&1) == :live)) ||
|
||||||
|
raise "no live engineering post to exercise og meta tags"
|
||||||
|
end
|
||||||
|
|
||||||
describe "GET /blog/:blog_id (index) - date filtering" do
|
describe "GET /blog/:blog_id (index) - date filtering" do
|
||||||
test "does not show future-dated posts", %{conn: conn} do
|
test "does not show future-dated posts", %{conn: conn} do
|
||||||
html = conn |> get(~p"/blog/engineering") |> html_response(200)
|
html = conn |> get(~p"/blog/engineering") |> html_response(200)
|
||||||
@@ -26,7 +39,8 @@ defmodule FirehoseWeb.BlogControllerTest do
|
|||||||
setup :register_and_log_in_user
|
setup :register_and_log_in_user
|
||||||
|
|
||||||
test "authenticated user sees draft banner on draft post", %{conn: conn} do
|
test "authenticated user sees draft banner on draft post", %{conn: conn} do
|
||||||
response = conn |> get(~p"/blog/engineering/hello-world")
|
post = a_draft_post()
|
||||||
|
response = conn |> get(~p"/blog/engineering/#{post.id}")
|
||||||
|
|
||||||
assert html_response(response, 200) =~ "Draft"
|
assert html_response(response, 200) =~ "Draft"
|
||||||
assert response.resp_body =~ "not published"
|
assert response.resp_body =~ "not published"
|
||||||
@@ -49,9 +63,11 @@ defmodule FirehoseWeb.BlogControllerTest do
|
|||||||
|
|
||||||
describe "GET /blog/:blog_id/:slug - no banners for unauthenticated" do
|
describe "GET /blog/:blog_id/:slug - no banners for unauthenticated" do
|
||||||
test "unauthenticated user sees no banner on draft post", %{conn: conn} do
|
test "unauthenticated user sees no banner on draft post", %{conn: conn} do
|
||||||
|
post = a_draft_post()
|
||||||
|
|
||||||
response =
|
response =
|
||||||
conn
|
conn
|
||||||
|> get(~p"/blog/engineering/hello-world")
|
|> get(~p"/blog/engineering/#{post.id}")
|
||||||
|> html_response(200)
|
|> html_response(200)
|
||||||
|
|
||||||
refute response =~ "post-status-banner"
|
refute response =~ "post-status-banner"
|
||||||
@@ -69,26 +85,30 @@ defmodule FirehoseWeb.BlogControllerTest do
|
|||||||
|
|
||||||
describe "GET /blog/:blog_id/:slug - Open Graph meta tags" do
|
describe "GET /blog/:blog_id/:slug - Open Graph meta tags" do
|
||||||
test "meta tags include og_title", %{conn: conn} do
|
test "meta tags include og_title", %{conn: conn} do
|
||||||
response = conn |> get(~p"/blog/engineering/hello-world") |> html_response(200)
|
post = a_live_post()
|
||||||
|
response = conn |> get(~p"/blog/engineering/#{post.id}") |> html_response(200)
|
||||||
|
|
||||||
assert response =~ ~s(<meta property="og:title")
|
assert response =~ ~s(<meta property="og:title")
|
||||||
assert response =~ "Hello World"
|
assert response =~ post.title
|
||||||
end
|
end
|
||||||
|
|
||||||
test "meta tags include og_description", %{conn: conn} do
|
test "meta tags include og_description", %{conn: conn} do
|
||||||
response = conn |> get(~p"/blog/engineering/hello-world") |> html_response(200)
|
post = a_live_post()
|
||||||
|
response = conn |> get(~p"/blog/engineering/#{post.id}") |> html_response(200)
|
||||||
|
|
||||||
assert response =~ ~s(<meta property="og:description")
|
assert response =~ ~s(<meta property="og:description")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "meta tags include og_type article", %{conn: conn} do
|
test "meta tags include og_type article", %{conn: conn} do
|
||||||
response = conn |> get(~p"/blog/engineering/hello-world") |> html_response(200)
|
post = a_live_post()
|
||||||
|
response = conn |> get(~p"/blog/engineering/#{post.id}") |> html_response(200)
|
||||||
|
|
||||||
assert response =~ ~s(<meta property="og:type" content="article")
|
assert response =~ ~s(<meta property="og:type" content="article")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "meta tags include twitter:card", %{conn: conn} do
|
test "meta tags include twitter:card", %{conn: conn} do
|
||||||
response = conn |> get(~p"/blog/engineering/hello-world") |> html_response(200)
|
post = a_live_post()
|
||||||
|
response = conn |> get(~p"/blog/engineering/#{post.id}") |> html_response(200)
|
||||||
|
|
||||||
assert response =~ ~s(<meta name="twitter:card" content="summary_large_image")
|
assert response =~ ~s(<meta name="twitter:card" content="summary_large_image")
|
||||||
end
|
end
|
||||||
@@ -101,10 +121,11 @@ defmodule FirehoseWeb.BlogControllerTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "meta tags have correct og_url", %{conn: conn} do
|
test "meta tags have correct og_url", %{conn: conn} do
|
||||||
response = conn |> get(~p"/blog/engineering/hello-world") |> html_response(200)
|
post = a_live_post()
|
||||||
|
response = conn |> get(~p"/blog/engineering/#{post.id}") |> html_response(200)
|
||||||
|
|
||||||
assert response =~
|
assert response =~
|
||||||
~s(<meta property="og:url" content="http://localhost:4002/blog/engineering/hello-world")
|
~s(<meta property="og:url" content="http://localhost:4002/blog/engineering/#{post.id}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
defmodule FirehoseWeb.BlogTagsTest do
|
defmodule FirehoseWeb.BlogTagsTest do
|
||||||
use FirehoseWeb.ConnCase
|
use FirehoseWeb.ConnCase
|
||||||
|
|
||||||
|
# Content churns; the behavior under test does not. The newest live post
|
||||||
|
# with a tag is a deterministic stand-in, and tag pages sort date-descending,
|
||||||
|
# so it is on page 1 whatever else is published under that tag. (The pinned
|
||||||
|
# "Hello World" post was deleted in 1709cb7 and this test silently rotted.)
|
||||||
|
defp a_newest_live_post_with_tag do
|
||||||
|
Enum.find(Firehose.EngineeringBlog.all_posts(), fn post ->
|
||||||
|
Blogex.Post.visibility(post) == :live and post.tags != []
|
||||||
|
end) || raise "no live tagged engineering post to exercise the tag page"
|
||||||
|
end
|
||||||
|
|
||||||
describe "engineering blog tags" do
|
describe "engineering blog tags" do
|
||||||
test "GET /blog/engineering/tag/:tag shows tag page with filtered posts", %{conn: conn} do
|
test "GET /blog/engineering/tag/:tag shows tag page with filtered posts", %{conn: conn} do
|
||||||
body = conn |> get("/blog/engineering/tag/elixir") |> html_response(200)
|
post = a_newest_live_post_with_tag()
|
||||||
assert body =~ ~s(tagged "elixir")
|
tag = hd(post.tags)
|
||||||
|
|
||||||
|
body = conn |> get("/blog/engineering/tag/#{tag}") |> html_response(200)
|
||||||
|
assert body =~ ~s(tagged "#{tag}")
|
||||||
assert body =~ "Engineering Blog"
|
assert body =~ "Engineering Blog"
|
||||||
assert body =~ "Hello World"
|
assert body =~ post.title
|
||||||
end
|
end
|
||||||
|
|
||||||
test "GET /blog/engineering/tag/:tag shows empty list for nonexistent tag", %{conn: conn} do
|
test "GET /blog/engineering/tag/:tag shows empty list for nonexistent tag", %{conn: conn} do
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ defmodule FirehoseWeb.BlogTest do
|
|||||||
body
|
body
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Content churns; the behavior under test does not. The newest live post is
|
||||||
|
# a deterministic stand-in - the pinned "hello-world" post was deleted in
|
||||||
|
# 1709cb7 and the slug tests silently rotted with it.
|
||||||
|
defp a_live_post do
|
||||||
|
Enum.find(Firehose.EngineeringBlog.all_posts(), &(Blogex.Post.visibility(&1) == :live)) ||
|
||||||
|
raise "no live engineering post to exercise the post pages"
|
||||||
|
end
|
||||||
|
|
||||||
describe "engineering blog (HTML)" do
|
describe "engineering blog (HTML)" do
|
||||||
test "GET /blog/engineering returns HTML index with layout", %{conn: conn} do
|
test "GET /blog/engineering returns HTML index with layout", %{conn: conn} do
|
||||||
body = visit_blog_page(conn, "engineering")
|
body = visit_blog_page(conn, "engineering")
|
||||||
@@ -17,8 +25,9 @@ defmodule FirehoseWeb.BlogTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "GET /blog/engineering/:slug returns HTML post with layout", %{conn: conn} do
|
test "GET /blog/engineering/:slug returns HTML post with layout", %{conn: conn} do
|
||||||
body = visit_blog_page(conn, "engineering", "/hello-world")
|
post = a_live_post()
|
||||||
assert body =~ "Hello World"
|
body = visit_blog_page(conn, "engineering", "/#{post.id}")
|
||||||
|
assert body =~ post.title
|
||||||
end
|
end
|
||||||
|
|
||||||
test "GET /blog/engineering?page=2 shows the next page of older posts", %{conn: conn} do
|
test "GET /blog/engineering?page=2 shows the next page of older posts", %{conn: conn} do
|
||||||
@@ -191,11 +200,16 @@ defmodule FirehoseWeb.BlogTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "GET /api/blog/engineering/:slug returns a post", %{conn: conn} do
|
test "GET /api/blog/engineering/:slug returns a post", %{conn: conn} do
|
||||||
assert %{"id" => "hello-world", "title" => "Hello World"} =
|
post = a_live_post()
|
||||||
|
|
||||||
|
assert %{"id" => id, "title" => title} =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/json")
|
|> put_req_header("accept", "application/json")
|
||||||
|> get("/api/blog/engineering/hello-world")
|
|> get("/api/blog/engineering/#{post.id}")
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert id == post.id
|
||||||
|
assert title == post.title
|
||||||
end
|
end
|
||||||
|
|
||||||
test "GET /api/blog/engineering/:slug returns 404 for missing post", %{conn: conn} do
|
test "GET /api/blog/engineering/:slug returns 404 for missing post", %{conn: conn} do
|
||||||
|
|||||||
+27
-7
@@ -3,39 +3,59 @@
|
|||||||
MISE_BIN ?= mise
|
MISE_BIN ?= mise
|
||||||
MISE_EXEC = $(MISE_BIN) exec --
|
MISE_EXEC = $(MISE_BIN) exec --
|
||||||
|
|
||||||
.PHONY: check precommit deps compile test test.coverage coverage crap format format.check verify
|
.PHONY: check precommit deps deps.check compile test test.coverage coverage crap format format.check verify
|
||||||
|
|
||||||
# Cheap static gate for this library (no database, no coverage). Runs from the
|
# Cheap static gate for this library (no database, no network, no coverage).
|
||||||
# root `make check`, which loops app then blogex. `format.check` only reports:
|
# The deps.check guard fails loudly with a pointer at `make setup` when Hex
|
||||||
|
# deps are missing. Runs from the root `make check`, which loops app then
|
||||||
|
# blogex. `format.check` only reports:
|
||||||
# a gate that rewrites the files it judges can never fail. Fix formatting with
|
# a gate that rewrites the files it judges can never fail. Fix formatting with
|
||||||
# `make format`.
|
# `make format`.
|
||||||
#
|
#
|
||||||
# Sensor slot: add `credo` once the "credo strict, tuned config (both apps)"
|
# Sensor slot: add `credo` once the "credo strict, tuned config (both apps)"
|
||||||
# yak lands blogex's credo dep + .credo.exs. Do not list it before then — a
|
# yak lands blogex's credo dep + .credo.exs. Do not list it before then — a
|
||||||
# gate that claims a check it did not run is worse than no gate.
|
# gate that claims a check it did not run is worse than no gate.
|
||||||
check: compile format.check
|
check: deps.check compile format.check
|
||||||
|
|
||||||
# Per-app precommit loop: same target name and alias shape as app/, so both
|
# Per-app precommit loop: same target name and alias shape as app/, so both
|
||||||
# apps share one habit (compile --warning-as-errors, format, test).
|
# apps share one habit (compile --warning-as-errors, format, test).
|
||||||
precommit:
|
precommit:
|
||||||
$(MISE_EXEC) mix precommit
|
$(MISE_EXEC) mix precommit
|
||||||
|
|
||||||
# Sync dependencies
|
# Sync dependencies and compile them: NimblePublisher pulls in mdex_native,
|
||||||
|
# whose precompiled NIF downloads from GitHub at first compile. Keeping that
|
||||||
|
# in `deps` (called by root `make setup`) keeps check/test/verify offline on
|
||||||
|
# a cold clone.
|
||||||
deps:
|
deps:
|
||||||
$(MISE_EXEC) mix deps.get
|
$(MISE_EXEC) mix deps.get
|
||||||
|
$(MISE_EXEC) mix deps.compile
|
||||||
|
|
||||||
|
# Local, network-free guard: every package pinned in mix.lock must be present
|
||||||
|
# in deps/. check, test and test.coverage depend on this instead of `deps`, so
|
||||||
|
# the everyday loops never touch the network: `mix deps.get` contacts hex.pm
|
||||||
|
# even when everything is already installed (the advisory check), which fails
|
||||||
|
# in offline/sandboxed environments. Fetching is `make setup`'s explicit job,
|
||||||
|
# run from the repo root.
|
||||||
|
deps.check:
|
||||||
|
@test -f mix.lock || { echo "deps.check: no mix.lock - run 'make setup' at the repo root" >&2; exit 1; }
|
||||||
|
@missing=""; \
|
||||||
|
for dep in $$(sed -n 's/^[[:space:]]*"\([^"]*\)".*/\1/p' mix.lock); do \
|
||||||
|
[ -d "deps/$$dep" ] || missing="$$missing $$dep"; \
|
||||||
|
done; \
|
||||||
|
test -z "$$missing" || { echo "deps.check: missing:$$missing - run 'make setup' at the repo root" >&2; exit 1; }
|
||||||
|
|
||||||
# Compile the project (warnings are fatal)
|
# Compile the project (warnings are fatal)
|
||||||
compile:
|
compile:
|
||||||
$(MISE_EXEC) mix compile --warnings-as-errors
|
$(MISE_EXEC) mix compile --warnings-as-errors
|
||||||
|
|
||||||
# Run the test suite
|
# Run the test suite
|
||||||
test: deps
|
test: deps.check
|
||||||
$(MISE_EXEC) mix test
|
$(MISE_EXEC) mix test
|
||||||
|
|
||||||
# Run the suite once with coverage exported, so the coverage floor check (and,
|
# Run the suite once with coverage exported, so the coverage floor check (and,
|
||||||
# later, CRAP) reads the export instead of running the suite a second time.
|
# later, CRAP) reads the export instead of running the suite a second time.
|
||||||
# Stale exports are removed first: the floor must never report an older run.
|
# Stale exports are removed first: the floor must never report an older run.
|
||||||
test.coverage: deps
|
test.coverage: deps.check
|
||||||
rm -f cover/*.coverdata
|
rm -f cover/*.coverdata
|
||||||
$(MISE_EXEC) mix test --cover --export-coverage default
|
$(MISE_EXEC) mix test --cover --export-coverage default
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,9 @@
|
|||||||
# tags composed from the pins (catches hand-edited image references).
|
# tags composed from the pins (catches hand-edited image references).
|
||||||
#
|
#
|
||||||
# Pure shell + sed: no mise, docker or elixir needed, so it runs in CI and in
|
# Pure shell + sed: no mise, docker or elixir needed, so it runs in CI and in
|
||||||
# sandboxes before the toolchain is installed.
|
# sandboxes before the toolchain is installed. Keep regexes POSIX-BRE: GNU
|
||||||
|
# extensions like \+ silently break under /usr/bin/sed on macOS (it treats
|
||||||
|
# \+ as a literal plus), which false-fails every pin as "not found".
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||||
@@ -35,9 +37,9 @@ grab() { # grab <description> <key-regex> <file>
|
|||||||
|
|
||||||
mise_elixir=$(grab 'mise elixir' 'elixir' "$mise_toml")
|
mise_elixir=$(grab 'mise elixir' 'elixir' "$mise_toml")
|
||||||
mise_erlang=$(grab 'mise erlang' 'erlang' "$mise_toml")
|
mise_erlang=$(grab 'mise erlang' 'erlang' "$mise_toml")
|
||||||
d_elixir=$(grab 'Dockerfile ELIXIR_VERSION' 'ARG[[:space:]]\+ELIXIR_VERSION' "$dockerfile")
|
d_elixir=$(grab 'Dockerfile ELIXIR_VERSION' 'ARG[[:space:]]\{1,\}ELIXIR_VERSION' "$dockerfile")
|
||||||
d_otp=$(grab 'Dockerfile OTP_VERSION' 'ARG[[:space:]]\+OTP_VERSION' "$dockerfile")
|
d_otp=$(grab 'Dockerfile OTP_VERSION' 'ARG[[:space:]]\{1,\}OTP_VERSION' "$dockerfile")
|
||||||
d_debian=$(grab 'Dockerfile DEBIAN_VERSION' 'ARG[[:space:]]\+DEBIAN_VERSION' "$dockerfile")
|
d_debian=$(grab 'Dockerfile DEBIAN_VERSION' 'ARG[[:space:]]\{1,\}DEBIAN_VERSION' "$dockerfile")
|
||||||
|
|
||||||
if [ $status -eq 0 ]; then
|
if [ $status -eq 0 ]; then
|
||||||
mise_elixir_ver=${mise_elixir%%-otp-*}
|
mise_elixir_ver=${mise_elixir%%-otp-*}
|
||||||
@@ -59,7 +61,7 @@ if [ $status -eq 0 ]; then
|
|||||||
|
|
||||||
# BUILDER_IMAGE must expand to the tag composed from the pins.
|
# BUILDER_IMAGE must expand to the tag composed from the pins.
|
||||||
expected_builder="docker.io/hexpm/elixir:${d_elixir}-erlang-${d_otp}-debian-${d_debian}"
|
expected_builder="docker.io/hexpm/elixir:${d_elixir}-erlang-${d_otp}-debian-${d_debian}"
|
||||||
d_builder=$(grab 'Dockerfile BUILDER_IMAGE' 'ARG[[:space:]]\+BUILDER_IMAGE' "$dockerfile")
|
d_builder=$(grab 'Dockerfile BUILDER_IMAGE' 'ARG[[:space:]]\{1,\}BUILDER_IMAGE' "$dockerfile")
|
||||||
expanded_builder=$(printf '%s' "$d_builder" |
|
expanded_builder=$(printf '%s' "$d_builder" |
|
||||||
sed -e "s/\${ELIXIR_VERSION}/$d_elixir/" \
|
sed -e "s/\${ELIXIR_VERSION}/$d_elixir/" \
|
||||||
-e "s/\${OTP_VERSION}/$d_otp/" \
|
-e "s/\${OTP_VERSION}/$d_otp/" \
|
||||||
@@ -69,7 +71,7 @@ if [ $status -eq 0 ]; then
|
|||||||
|
|
||||||
# RUNNER_IMAGE must expand to the Debian pin.
|
# RUNNER_IMAGE must expand to the Debian pin.
|
||||||
expected_runner="docker.io/debian:${d_debian}"
|
expected_runner="docker.io/debian:${d_debian}"
|
||||||
d_runner=$(grab 'Dockerfile RUNNER_IMAGE' 'ARG[[:space:]]\+RUNNER_IMAGE' "$dockerfile")
|
d_runner=$(grab 'Dockerfile RUNNER_IMAGE' 'ARG[[:space:]]\{1,\}RUNNER_IMAGE' "$dockerfile")
|
||||||
expanded_runner=${d_runner/\$\{DEBIAN_VERSION\}/$d_debian}
|
expanded_runner=${d_runner/\$\{DEBIAN_VERSION\}/$d_debian}
|
||||||
[ "$expanded_runner" = "$expected_runner" ] ||
|
[ "$expanded_runner" = "$expected_runner" ] ||
|
||||||
die "RUNNER_IMAGE expands to $expanded_runner, expected $expected_runner"
|
die "RUNNER_IMAGE expands to $expanded_runner, expected $expected_runner"
|
||||||
|
|||||||
Reference in New Issue
Block a user