slides: frame a 'How to implement a TAPPaaS module' deck
Adds a second Marp source root. Decks under slides/ build to an output path mirroring their repo path, so slides/TAPPaaS/HOW-TO/NewModule/index.md is served at slides.makerfloss.eu/TAPPaaS/HOW-TO/NewModule/. Decks in docs/presentations/ keep their flat URLs. The deck itself is a frame: structure and headings from the real src/apps/00-Template anatomy, content still to be written. Unfinished spots are written as inline `TODO: ...` and rendered in red so a half-finished deck cannot be presented by accident. Also fixes the Docker fallback, which could not write to a mktemp directory on macOS (not shared with Docker Desktop) and must not be passed --user.
This commit is contained in:
parent
4abc634443
commit
91f53f1f62
5 changed files with 342 additions and 53 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -2,8 +2,9 @@
|
||||||
*.retry
|
*.retry
|
||||||
|
|
||||||
# ---> Generated slides (built by marp on server or locally via build-slides.sh)
|
# ---> Generated slides (built by marp on server or locally via build-slides.sh)
|
||||||
slides/*.html
|
# Deck *sources* also live under slides/, so ignore only the build output.
|
||||||
slides/*.pdf
|
slides/**/*.html
|
||||||
|
slides/**/*.pdf
|
||||||
|
|
||||||
# ---> MkDocs build output
|
# ---> MkDocs build output
|
||||||
site/
|
site/
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,15 @@ docs/ # everything here is built and shipped to docs.makerfloss.eu
|
||||||
hardware/ # auto-indexed per-host frontmatter (srv01..srv05, makerfloss.eu)
|
hardware/ # auto-indexed per-host frontmatter (srv01..srv05, makerfloss.eu)
|
||||||
services/ # auto-indexed per-service frontmatter (docs, forgejo, …)
|
services/ # auto-indexed per-service frontmatter (docs, forgejo, …)
|
||||||
infrastructure/ # labdesign, VPS/DNS, etc.
|
infrastructure/ # labdesign, VPS/DNS, etc.
|
||||||
presentations/ # Marp decks (build-slides.sh)
|
presentations/ # Marp decks, flat URLs (build-slides.sh)
|
||||||
notes/ # repo-only working material, not built
|
notes/ # repo-only working material, not built
|
||||||
meetings/ # meeting notes (Danish allowed)
|
meetings/ # meeting notes (Danish allowed)
|
||||||
todo/ # task lists, working norms, wishlist, services
|
todo/ # task lists, working norms, wishlist, services
|
||||||
dev/ # internal plans/ and specs/
|
dev/ # internal plans/ and specs/
|
||||||
communications/ # community comms artifacts (Facebook posts, etc.)
|
communications/ # community comms artifacts (Facebook posts, etc.)
|
||||||
sandbox/ # scratch / pipeline fixtures (e.g. test-mermaid.md)
|
sandbox/ # scratch / pipeline fixtures (e.g. test-mermaid.md)
|
||||||
|
slides/ # build output — AND Marp deck sources whose URL is a path,
|
||||||
|
# e.g. slides/tappaas/how-to/new-module/index.md
|
||||||
```
|
```
|
||||||
|
|
||||||
## Infrastructure
|
## Infrastructure
|
||||||
|
|
|
||||||
138
build-slides.sh
138
build-slides.sh
|
|
@ -1,4 +1,14 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
# Build every Marp deck in the repository into static HTML.
|
||||||
|
#
|
||||||
|
# A deck is any *.md carrying `marp: true` in its front matter. Two source
|
||||||
|
# roots, two output conventions:
|
||||||
|
#
|
||||||
|
# docs/presentations/<name>.md -> <name>.html (flat, historical)
|
||||||
|
# slides/<path>/<name>.md -> <path>/<name>.html (path mirrors URL)
|
||||||
|
#
|
||||||
|
# So slides/tappaas/how-to/new-module/index.md is served at
|
||||||
|
# https://slides.makerfloss.eu/tappaas/how-to/new-module/ .
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
@ -8,53 +18,74 @@ REPO_WEB_URL="${REPO_WEB_URL:-https://forgejo.makerfloss.eu/sjat/MakerFLOSS}"
|
||||||
|
|
||||||
mkdir -p "$OUTPUT_DIR"
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
|
||||||
# Wipe previously built slides so deleted/de-tagged .md files disappear.
|
# Collect decks as "<repo-relative source>|<output-relative target>" pairs.
|
||||||
find "$OUTPUT_DIR" -maxdepth 1 -name "*.html" -delete
|
DECKS=()
|
||||||
|
|
||||||
# Find all markdown files with marp: true frontmatter
|
|
||||||
SLIDES=()
|
|
||||||
while IFS= read -r f; do
|
while IFS= read -r f; do
|
||||||
SLIDES+=("$f")
|
rel="${f#"$REPO_ROOT"/}"
|
||||||
done < <(grep -rl "^marp: true" "$REPO_ROOT/docs" --include="*.md" 2>/dev/null || true)
|
DECKS+=("$rel|$(basename "${rel%.md}").html")
|
||||||
|
done < <(grep -rl "^marp: true" "$REPO_ROOT/docs" --include="*.md" 2>/dev/null | sort || true)
|
||||||
|
|
||||||
if [ ${#SLIDES[@]} -eq 0 ]; then
|
if [ -d "$REPO_ROOT/slides" ]; then
|
||||||
echo "No marp presentations found in docs/."
|
while IFS= read -r f; do
|
||||||
|
rel="${f#"$REPO_ROOT"/}"
|
||||||
|
out="${rel#slides/}"
|
||||||
|
DECKS+=("$rel|${out%.md}.html")
|
||||||
|
done < <(grep -rl "^marp: true" "$REPO_ROOT/slides" --include="*.md" 2>/dev/null | sort || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#DECKS[@]} -eq 0 ]; then
|
||||||
|
echo "No marp presentations found."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Found ${#SLIDES[@]} presentation(s):"
|
echo "Found ${#DECKS[@]} presentation(s):"
|
||||||
printf ' %s\n' "${SLIDES[@]}"
|
printf ' %s\n' "${DECKS[@]%%|*}"
|
||||||
|
|
||||||
# Create temp output directory for Docker fallback
|
# Wipe previously built HTML so deleted/de-tagged decks disappear. Only *.html
|
||||||
TEMP_OUTPUT=$(mktemp -d)
|
# is removed, so deck sources living inside slides/ are never touched.
|
||||||
chmod 777 "$TEMP_OUTPUT"
|
find "$OUTPUT_DIR" -name "*.html" -type f -delete
|
||||||
trap "rm -rf '$TEMP_OUTPUT'" EXIT
|
|
||||||
|
|
||||||
if command -v marp &>/dev/null; then
|
render() { # render <source-rel> <target-rel>
|
||||||
for slide in "${SLIDES[@]}"; do
|
local src="$1" dst="$2"
|
||||||
BASENAME=$(basename "${slide%.*}")
|
mkdir -p "$OUTPUT_DIR/$(dirname "$dst")"
|
||||||
marp --html --output "$OUTPUT_DIR/${BASENAME}.html" "$slide"
|
if command -v marp &>/dev/null; then
|
||||||
done
|
marp --html --output "$OUTPUT_DIR/$dst" "$REPO_ROOT/$src"
|
||||||
else
|
elif command -v npx &>/dev/null; then
|
||||||
echo "marp not found locally — using Docker (marpteam/marp-cli)..."
|
npx --yes @marp-team/marp-cli --html --output "$OUTPUT_DIR/$dst" "$REPO_ROOT/$src"
|
||||||
for slide in "${SLIDES[@]}"; do
|
else
|
||||||
REL_SLIDE="${slide#${REPO_ROOT}/}"
|
# marpteam/marp-cli drops privileges to its own "marp" user, so the mounted
|
||||||
BASENAME=$(basename "${slide%.*}")
|
# output tree has to be writable by anyone — and --user must NOT be passed.
|
||||||
|
chmod -R a+rwX "$OUTPUT_DIR"
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
-v "$REPO_ROOT":/home/marp/app:ro \
|
-v "$REPO_ROOT":/home/marp/app:ro \
|
||||||
-v "$TEMP_OUTPUT":/home/marp/output \
|
-v "$OUTPUT_DIR":/home/marp/out \
|
||||||
marpteam/marp-cli --html --output "/home/marp/output/${BASENAME}.html" "$REL_SLIDE"
|
marpteam/marp-cli --html --output "/home/marp/out/$dst" "$src"
|
||||||
done
|
fi
|
||||||
cp "$TEMP_OUTPUT"/*.html "$OUTPUT_DIR/" 2>/dev/null || true
|
}
|
||||||
|
|
||||||
|
if ! command -v marp &>/dev/null && ! command -v npx &>/dev/null; then
|
||||||
|
if command -v docker &>/dev/null; then
|
||||||
|
echo "marp not found locally — using Docker (marpteam/marp-cli)..."
|
||||||
|
else
|
||||||
|
echo "error: need one of marp, npx or docker on PATH" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Inject mermaid.js into any HTML that contains mermaid code blocks.
|
for deck in "${DECKS[@]}"; do
|
||||||
|
render "${deck%%|*}" "${deck#*|}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Post-process each built page: highlight unfinished `TODO ...` markers, and
|
||||||
|
# turn mermaid code blocks into live diagrams.
|
||||||
|
#
|
||||||
# Marp emits fenced mermaid blocks as <pre><code class="language-mermaid">.
|
# Marp emits fenced mermaid blocks as <pre><code class="language-mermaid">.
|
||||||
# The script finds those elements, replaces them with <div class="mermaid">,
|
# The script replaces those elements with <div class="mermaid">, then loads and
|
||||||
# then loads and runs mermaid.js from CDN.
|
# runs mermaid.js from CDN.
|
||||||
inject_mermaid() {
|
postprocess() {
|
||||||
local html_file="$1"
|
local html_file="$1"
|
||||||
python3 - "$html_file" << 'PYEOF'
|
python3 - "$html_file" << 'PYEOF'
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
path = sys.argv[1]
|
path = sys.argv[1]
|
||||||
|
|
@ -73,25 +104,33 @@ await mermaid.run();
|
||||||
|
|
||||||
with open(path, encoding='utf-8') as f:
|
with open(path, encoding='utf-8') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
new_content = content.replace('</body>', snippet + '\n</body>', 1)
|
|
||||||
if new_content == content:
|
# Inline <code>TODO…</code> -> <code class="todo">…</code>; decks style that class.
|
||||||
print(f"Warning: </body> not found in {path}", file=sys.stderr)
|
content, todos = re.subn(r'<code>(TODO\b[^<]*)</code>', r'<code class="todo">\1</code>', content)
|
||||||
sys.exit(1)
|
|
||||||
|
if 'class="language-mermaid"' in content:
|
||||||
|
if '</body>' not in content:
|
||||||
|
print(f"Warning: </body> not found in {path}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
content = content.replace('</body>', snippet + '\n</body>', 1)
|
||||||
|
print(f" Injected mermaid.js into {path}")
|
||||||
|
|
||||||
with open(path, 'w', encoding='utf-8') as f:
|
with open(path, 'w', encoding='utf-8') as f:
|
||||||
f.write(new_content)
|
f.write(content)
|
||||||
|
|
||||||
|
if todos:
|
||||||
|
print(f" {todos} unfinished TODO marker(s) in {path}")
|
||||||
PYEOF
|
PYEOF
|
||||||
}
|
}
|
||||||
|
|
||||||
if command -v python3 &>/dev/null; then
|
if command -v python3 &>/dev/null; then
|
||||||
for html_file in "$OUTPUT_DIR"/*.html; do
|
# The landing index.html is generated further down, so every *.html present
|
||||||
[ -f "$html_file" ] || continue
|
# right now is a built deck.
|
||||||
if grep -q 'class="language-mermaid"' "$html_file"; then
|
while IFS= read -r html_file; do
|
||||||
inject_mermaid "$html_file"
|
postprocess "$html_file"
|
||||||
echo " Injected mermaid.js into $(basename "$html_file")"
|
done < <(find "$OUTPUT_DIR" -name "*.html" -type f)
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
else
|
||||||
echo "Warning: python3 not found — skipping mermaid injection"
|
echo "Warning: python3 not found — skipping post-processing"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Regenerate index.html listing every built deck.
|
# Regenerate index.html listing every built deck.
|
||||||
|
|
@ -113,9 +152,10 @@ INDEX="$OUTPUT_DIR/index.html"
|
||||||
printf '<p class="sub">Built from <a href="%s">%s</a></p>' \
|
printf '<p class="sub">Built from <a href="%s">%s</a></p>' \
|
||||||
"$REPO_WEB_URL" "${REPO_WEB_URL#https://}"
|
"$REPO_WEB_URL" "${REPO_WEB_URL#https://}"
|
||||||
printf '<ul>'
|
printf '<ul>'
|
||||||
find "$OUTPUT_DIR" -maxdepth 1 -name "*.html" ! -name "index.html" | sort | while IFS= read -r html; do
|
for deck in "${DECKS[@]}"; do
|
||||||
name=$(basename "$html" .html)
|
href="${deck#*|}"
|
||||||
printf '<li><a href="%s">%s</a></li>' "$(basename "$html")" "$name"
|
label="${href%/index.html}"; label="${label%.html}"
|
||||||
|
printf '<li><a href="%s">%s</a></li>' "$href" "$label"
|
||||||
done
|
done
|
||||||
printf '</ul>'
|
printf '</ul>'
|
||||||
printf '<footer>Last built: %s · <a href="https://marp.app">Marp</a></footer>' "$(date -Iseconds)"
|
printf '<footer>Last built: %s · <a href="https://marp.app">Marp</a></footer>' "$(date -Iseconds)"
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,18 @@ tls: letsencrypt
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
Slide-deck site. Decks are authored as Marp markdown in `docs/presentations/` and compiled to HTML by `build-slides.sh` (CI invokes it via the [marp](marp.md) toolchain). Built output is rsynced to the VPS and served alongside [docs](docs.md).
|
Slide-deck site. Decks are authored as Marp markdown and compiled to HTML by
|
||||||
|
`build-slides.sh` (CI invokes it via the [marp](marp.md) toolchain). Built output is rsynced to
|
||||||
|
the VPS and served alongside [docs](docs.md).
|
||||||
|
|
||||||
|
Two source roots, two output conventions:
|
||||||
|
|
||||||
|
| Source | Published at |
|
||||||
|
| --- | --- |
|
||||||
|
| `docs/presentations/<name>.md` | `https://slides.makerfloss.eu/<name>.html` |
|
||||||
|
| `slides/<path>/<name>.md` | `https://slides.makerfloss.eu/<path>/<name>.html` |
|
||||||
|
|
||||||
|
Use `slides/` when a deck belongs to a named series and the URL should read like a path — for
|
||||||
|
example `slides/tappaas/how-to/new-module/index.md` is served at
|
||||||
|
`https://slides.makerfloss.eu/tappaas/how-to/new-module/`. Only the markdown is committed; the
|
||||||
|
generated HTML under `slides/` is git-ignored.
|
||||||
|
|
|
||||||
232
slides/tappaas/how-to/new-module/index.md
Normal file
232
slides/tappaas/how-to/new-module/index.md
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
---
|
||||||
|
marp: true
|
||||||
|
theme: gaia
|
||||||
|
class: invert
|
||||||
|
paginate: true
|
||||||
|
title: How to implement a TAPPaaS module
|
||||||
|
description: Walk-through of adding a new module to the TAPPaaS repository
|
||||||
|
---
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section { font-size: 26px; }
|
||||||
|
h1 { font-size: 1.5em; }
|
||||||
|
h2 { font-size: 1.15em; }
|
||||||
|
pre { font-size: 0.72em; line-height: 1.35; }
|
||||||
|
table { font-size: 0.8em; }
|
||||||
|
th, td { padding: 0.25em 0.6em; }
|
||||||
|
/* Unfinished content, loud on purpose: an unfinished deck should never be
|
||||||
|
presented by accident. Written in the markdown as `TODO: ...`. */
|
||||||
|
code.todo { color: #ff8a80; font-weight: 600; }
|
||||||
|
div.mermaid { display: flex; justify-content: center; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- _paginate: false -->
|
||||||
|
|
||||||
|
# How to implement a TAPPaaS module
|
||||||
|
|
||||||
|
From empty directory to merged pull request
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Speaker notes go in HTML comments; press `p` in the browser for presenter view.
|
||||||
|
TODO: 30-second framing — who is in the room, what they walk out able to do.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Who this is for
|
||||||
|
|
||||||
|
- You want to add an **app** or a **foundation** capability to TAPPaaS
|
||||||
|
- You can read shell, JSON, and a little Nix
|
||||||
|
- You have a TAPPaaS test environment you are allowed to break
|
||||||
|
|
||||||
|
**Prerequisites**
|
||||||
|
|
||||||
|
- A clone of `TAPPaaS/TAPPaaS`, branch `TODO: ADR007 or stable`
|
||||||
|
- SSH access to a test site
|
||||||
|
- `TODO: tooling list`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vocabulary in 60 seconds
|
||||||
|
|
||||||
|
| Term | Means |
|
||||||
|
| --- | --- |
|
||||||
|
| **Module** | One installable unit under `src/apps/` or `src/foundation/` |
|
||||||
|
| **Service** | A part of a module with its own lifecycle scripts |
|
||||||
|
| **Manager** | Owns desired state for a domain |
|
||||||
|
| **Controller** | Applies desired state to one concrete technology |
|
||||||
|
|
||||||
|
`TODO: tighten against ADR-007 taxonomy; drop the terms that do not matter here`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anatomy of a module
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/apps/<module>/
|
||||||
|
├── README.md # what it is, why it exists
|
||||||
|
├── DESIGN.md # how it is put together
|
||||||
|
├── INSTALL.md # operator-facing install notes
|
||||||
|
├── UPGRADE.md # operator-facing upgrade notes
|
||||||
|
├── AUTHORS.md # who to ask
|
||||||
|
├── LICENSE
|
||||||
|
├── <module>.json # declaration: the module's contract
|
||||||
|
├── <module>.nix # runtime definition
|
||||||
|
├── install.sh # create it
|
||||||
|
├── update.sh # move it forward
|
||||||
|
├── test.sh # prove it works
|
||||||
|
└── services/<svc>/ # per-service install/update/test/delete
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The path from nothing to merged
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Scaffold from<br/>00-Template] --> B[Declare<br/>module.json]
|
||||||
|
B --> C[Define runtime<br/>module.nix]
|
||||||
|
C --> D[install.sh]
|
||||||
|
D --> E[services/]
|
||||||
|
E --> F[update.sh]
|
||||||
|
F --> G[test.sh]
|
||||||
|
G --> H[Docs]
|
||||||
|
H --> I[PR + CI]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1 — Scaffold
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp -r src/apps/00-Template src/apps/<module>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Rename `template.json` → `<module>.json`, `template.nix` → `<module>.nix`
|
||||||
|
- Read `README-template.md`, then delete it
|
||||||
|
- `TODO: is there a scaffold script? if not, should there be?`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2 — Declare the module
|
||||||
|
|
||||||
|
`<module>.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"TODO": "smallest working declaration"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- What the module *is*, not how it is built
|
||||||
|
- `TODO: required keys, optional keys, validation`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3 — Define the runtime
|
||||||
|
|
||||||
|
`<module>.nix`
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# TODO: minimal example
|
||||||
|
```
|
||||||
|
|
||||||
|
- `TODO: what belongs in nix vs. what belongs in install.sh`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4 — `install.sh`
|
||||||
|
|
||||||
|
- Idempotent: safe to run twice
|
||||||
|
- Fails loudly, never half-way
|
||||||
|
- `pre-install.sh` when something must exist first
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# TODO: skeleton with the house conventions
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5 — Services
|
||||||
|
|
||||||
|
```text
|
||||||
|
services/<svc>/install-service.sh
|
||||||
|
services/<svc>/update-service.sh
|
||||||
|
services/<svc>/test-service.sh
|
||||||
|
services/<svc>/delete-service.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
- One service = one thing with its own lifecycle
|
||||||
|
- `TODO: when to split into services vs. keep it in the module`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6 — `update.sh`
|
||||||
|
|
||||||
|
- Upgrade in place, no data loss
|
||||||
|
- Version pinning: `TODO`
|
||||||
|
- Rollback story: `TODO`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 7 — `test.sh`
|
||||||
|
|
||||||
|
- Runs against a real environment, not a mock
|
||||||
|
- Exit non-zero on failure, quiet on success
|
||||||
|
- `TODO: what CI runs, and what only a human can check`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 8 — Documentation
|
||||||
|
|
||||||
|
| File | Answers |
|
||||||
|
| --- | --- |
|
||||||
|
| `README.md` | What is this and why would I install it? |
|
||||||
|
| `DESIGN.md` | How is it built, what were the trade-offs? |
|
||||||
|
| `INSTALL.md` | How do I install it, step by step? |
|
||||||
|
| `UPGRADE.md` | What breaks between versions? |
|
||||||
|
|
||||||
|
Docs are synced to the docs site — a missing README is a missing page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Try it on a test site
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# TODO: the real loop
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Deploy to the test environment
|
||||||
|
2. Run `test.sh`
|
||||||
|
3. Break it on purpose, run it again
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Submit
|
||||||
|
|
||||||
|
- Branch: `TODO: naming convention`
|
||||||
|
- One module per pull request
|
||||||
|
- CI must be green
|
||||||
|
- Review checklist:
|
||||||
|
- [ ] Idempotent install
|
||||||
|
- [ ] `test.sh` passes on a clean site
|
||||||
|
- [ ] Docs complete
|
||||||
|
- [ ] `TODO`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pitfalls
|
||||||
|
|
||||||
|
- `TODO: the three mistakes reviewers actually see`
|
||||||
|
- `TODO`
|
||||||
|
- `TODO`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Where to go next
|
||||||
|
|
||||||
|
- `src/apps/00-Template/` — the canonical starting point
|
||||||
|
- `docs/ADR/ADR-007 - TAPPaaS Taxonomy.md`
|
||||||
|
- `TODO: contribution guide link`
|
||||||
|
|
||||||
|
**Questions?**
|
||||||
Loading…
Add table
Reference in a new issue