Product repository template

Layout, proplan.toml, README/CHANGELOG, placeholder product image, .gitignore, KiBot/release/docgen workflows and TEMPLATE.md. Source of truth: contrib/product-template in B4L/proplan (cargo test --test product_template checks it as Gitea 1.27 creates a repository from it).
This commit is contained in:
grabowski committed 2026-10-13 16:32:25 +07:00
commit 421b4202cc
24 files changed
+573

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
# Files Gitea fills in when a repository is created from this template: the
# new repository's name and description go into their text and their path
# (the board folder under hardware/ is renamed to the repository's name).
# Gitea 1.27: plain globs only. Everything else (workflows, TEMPLATE.md) is
# copied as it is.
proplan.toml
README.md
CHANGELOG.md
hardware/*/README.md
+54
View File
@@ -0,0 +1,54 @@
name: Fabrication document
# With a docs/input.md (typecad-docgen front matter, as in PowerModule):
# builds the fabrication document of the first board and pushes it to the
# docu branch, so main stays free of generated files. Adapted from
# LVSolarBuck and PowerModule.
on:
push:
branches: [main]
paths:
- 'docs/input.md'
- 'hardware/**'
- '.gitea/workflows/docgen.yml'
jobs:
docgen:
runs-on: kicad10_auto
steps:
- uses: actions/checkout@v4
- name: Inputs
id: inputs
run: |
pcb=$(find hardware -mindepth 2 -maxdepth 2 -name '*.kicad_pcb' | sort | head -1)
if [ -f docs/input.md ] && [ -n "$pcb" ]; then
echo "pcb=$pcb" >> "$GITHUB_OUTPUT"
else
echo "No docs/input.md or no board yet: nothing to build."
fi
- name: Build
if: steps.inputs.outputs.pcb != ''
run: |
mkdir -p output-docs
typecad-docgen docs/input.md "${{ steps.inputs.outputs.pcb }}" -o output-docs/index.html
- name: Push to the docu branch
if: steps.inputs.outputs.pcb != ''
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
git config --global user.email "${{ gitea.actor }}@noreply.localhost"
git config --global user.name "${{ gitea.actor }}"
REPO_URL=$(git config --get remote.origin.url)
AUTHED_URL=${REPO_URL/https:\/\//https:\/\/${GITEA_TOKEN}@}
git clone --depth 1 --branch docu "$AUTHED_URL" docu || git clone --depth 1 "$AUTHED_URL" docu
cd docu
git checkout -B docu
git rm -rq . || true
cp -r ../output-docs/* .
git add -A
if git diff --cached --quiet; then echo "No changes"; exit 0; fi
git commit -m "chore: fabrication document"
git push --force "$AUTHED_URL" docu
+48
View File
@@ -0,0 +1,48 @@
name: KiBot
# Checks every board (hardware/*/*.kicad_pro): ERC, DRC, schematic PDF,
# gerbers, drill, pick and place, BOM; the outputs are kept as an artifact.
# Adapted from LVSolarBuck (runner label kicad10_auto). Without a board yet
# the job ends right away.
on:
push:
branches: [main]
paths:
- 'hardware/**'
- 'kibot.yaml'
- '.gitea/workflows/kibot.yml'
pull_request:
paths:
- 'hardware/**'
- 'kibot.yaml'
workflow_dispatch:
jobs:
boards:
runs-on: kicad10_auto
steps:
- uses: actions/checkout@v4
- name: Find boards
id: boards
run: |
found=$(find hardware -mindepth 2 -maxdepth 2 -name '*.kicad_pro' | sort | tr '\n' ' ')
echo "projects=$found" >> "$GITHUB_OUTPUT"
if [ -z "$found" ]; then echo "No KiCad project under hardware/ yet."; fi
- name: KiBot
if: steps.boards.outputs.projects != ''
run: |
for pro in ${{ steps.boards.outputs.projects }}; do
dir=$(dirname "$pro"); board=$(basename "$dir")
echo "== $board"
(cd "$dir" && kibot -c "$GITHUB_WORKSPACE/kibot.yaml" -d "$GITHUB_WORKSPACE/Generated/$board" -v) \
|| echo "::warning::KiBot reported problems for $board"
done
- name: Keep the outputs
if: steps.boards.outputs.projects != ''
uses: https://github.com/christopherHX/gitea-upload-artifact@v4
with:
name: kibot-outputs
path: Generated
+57
View File
@@ -0,0 +1,57 @@
name: Release outputs
# On a tag v* (a prototype stage): the KiBot outputs of every board, zipped per
# board, attached to the tag's release (created when missing). The job token
# must be allowed to write releases. Adapted from LVSolarBuck; runs for the
# first time at the first tag.
on:
push:
tags: ['v*']
jobs:
release:
runs-on: kicad10_auto
steps:
- uses: actions/checkout@v4
- name: KiBot outputs per board
run: |
mkdir -p dist
for pro in $(find hardware -mindepth 2 -maxdepth 2 -name '*.kicad_pro' | sort); do
dir=$(dirname "$pro"); board=$(basename "$dir")
(cd "$dir" && kibot -c "$GITHUB_WORKSPACE/kibot.yaml" -d "$GITHUB_WORKSPACE/out/$board") \
|| echo "::warning::KiBot reported problems for $board"
python3 -c 'import shutil, sys; shutil.make_archive(sys.argv[1], "zip", sys.argv[2])' \
"dist/$board-${GITHUB_REF_NAME}" "out/$board"
done
ls -la dist
- name: Attach to the release
env:
TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
python3 - <<'EOF'
import json, os, pathlib, urllib.error, urllib.request, uuid
api = f"{os.environ['GITHUB_SERVER_URL']}/api/v1/repos/{os.environ['GITHUB_REPOSITORY']}"
tag = os.environ["GITHUB_REF_NAME"]
auth = {"Authorization": f"token {os.environ['TOKEN']}"}
def call(method, url, body=None, ctype="application/json"):
req = urllib.request.Request(url, data=body, method=method, headers={**auth, "Content-Type": ctype})
with urllib.request.urlopen(req) as r:
return json.load(r)
try:
rel = call("GET", f"{api}/releases/tags/{tag}")
except urllib.error.HTTPError as e:
if e.code != 404:
raise
rel = call("POST", f"{api}/releases", json.dumps({"tag_name": tag, "name": tag}).encode())
for f in sorted(pathlib.Path("dist").glob("*.zip")):
b = uuid.uuid4().hex
head = (f"--{b}\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"{f.name}\"\r\n"
"Content-Type: application/zip\r\n\r\n").encode()
body = head + f.read_bytes() + f"\r\n--{b}--\r\n".encode()
call("POST", f"{api}/releases/{rel['id']}/assets?name={f.name}", body, f"multipart/form-data; boundary={b}")
print("attached", f.name)
EOF
+37
View File
@@ -0,0 +1,37 @@
# KiCad: backups, locks, local settings, caches
*-backups/
*.kicad_prl
*.lck
*.bak
*.bck
*.kicad_pcb-bak
*.kicad_sch-bak
_autosave-*
fp-info-cache
\#auto_saved_files\#
# FreeCAD backups
*.FCBak
*.FCStd1
# IDE build output and tool caches
Debug/
Release/
build/
__pycache__/
*.pyc
.venv/
node_modules/
.history/
.claude/
.vscode/
.idea/
# Generated output: CI builds it and attaches it to the release
Generated/
Fabrication/
kibot_output_*/
# OS
.DS_Store
Thumbs.db
+7
View File
@@ -0,0 +1,7 @@
# Changelog: $REPO_NAME
One section per prototype stage, newest first; the heading is the tag.
## v0.1 (planned)
- First boards.
+25
View File
@@ -0,0 +1,25 @@
# $REPO_NAME
$REPO_DESCRIPTION
Created from [B4L/product-template](https://git.b4l.co.th/B4L/product-template).
TEMPLATE.md explains the layout, the rules and `proplan.toml`.
| Folder | What goes there |
|---|---|
| `assets/` | the product image (`assets/product.png`, shown by proplan), renders |
| `hardware/$REPO_NAME/` | the KiCad project of the board |
| `hardware/lib/` | symbols, footprints and 3D models shared by the boards |
| `firmware/` | MCU code, one folder per target; `firmware/lib/` for shared drivers |
| `external/` | third-party code as git submodules |
| `software/` | host tools and apps |
| `mech/` | FreeCAD sources and STEP exports |
| `sim/` | LTspice/ngspice models, calculation scripts and notebooks |
| `test/` | bench results per prototype stage (`test/v0.1/`) |
| `docs/` | documentation; `docs/datasheets/` for vendor PDFs, `docs/tex/` for LaTeX |
## Prototype stages
Each prototype is a git tag (`v0.1`, `v0.2`, ...). CI builds gerbers, BOM and
schematic PDFs for the tag and attaches them to its release; CHANGELOG.md says
what each stage changes. In proplan the prototype stage links the tag.
+168
View File
@@ -0,0 +1,168 @@
# B4L product repository template
Every B4L product repository has the same layout, so people and proplan find
the product image, the boards, the firmware and the libraries in the same
place. This file explains the layout, the rules and `proplan.toml`. It is
copied into every repository created from the template; keep it as the
reference.
## Create a product repository
In Gitea: **New Repository**, choose the template **B4L/product-template**,
tick **Git Content**, give the repository the product's name and a
description. Gitea puts the name into `README.md`, `CHANGELOG.md`,
`proplan.toml` and the board folder under `hardware/` (the files listed in
`.gitea/template`). Then:
1. Replace `assets/product.png` with a photo or render of the product (PNG,
JPEG or WebP, at most 5 MB).
2. Create the KiCad project in the board folder (KiCad: File, New Project; the
project file is `hardware/<name>/<name>.kicad_pro`).
3. Commit and push. The KiBot workflow checks the board on every push.
4. Tag the first prototype `v0.1` when its boards are ordered, and link the
tag to the stage on the project's Prototypes tab in proplan.
## Layout
| Path | What goes there |
|---|---|
| `proplan.toml` | the manifest proplan reads (below) |
| `README.md`, `CHANGELOG.md` | what the product is; one changelog section per stage (`## v0.2`) |
| `assets/product.png` | the product image |
| `hardware/<board>/` | one KiCad project per board |
| `hardware/lib/` | symbols, footprints and 3D models shared by the boards |
| `firmware/<target>/` | MCU code (STM32CubeIDE or CMake), `firmware/lib/` for shared drivers |
| `external/` | third-party code as git submodules (e.g. ST's USB-PD middleware) |
| `software/<tool>/` | host tools and apps |
| `mech/` | FreeCAD sources and STEP exports |
| `sim/` | LTspice/ngspice, calculation scripts and notebooks |
| `test/<stage>/` | bench results per stage (`test/v0.1/`) |
| `docs/`, `docs/datasheets/`, `docs/tex/` | documentation, vendor PDFs, LaTeX sources |
| `.gitea/workflows/` | KiBot, release outputs, fabrication document |
## Rules
- A board revision is a tag (`v0.1`, `v0.2`), never a copied folder
(`kicad_v1_5/` next to `kicad_v1_6/`). A real variant built side by side
(48 V and 24 V, 2-board and 3-board) is its own folder under `hardware/` and
its own `[[boards]]` entry.
- Generated output (gerbers, BOMs, production zips, PDFs, plots) is built by CI
and attached to the release; it is not committed.
- Vendor datasheets go to `docs/datasheets/`, not the repository root.
- Third-party code is a git submodule in `external/` and is not edited here.
- IDE build output and tool caches (`Debug/`, `__pycache__/`, `.claude/`,
KiCad backups) stay out; `.gitignore` covers them.
## proplan.toml
proplan reads this file and the image it names, nothing else of the
repository's content: at the default branch for the Code tab, and at a stage's
tagged commit for the stage's card (so the card shows the product as it was at
that tag). Every key is optional.
```toml
schema = 1 # 1; a newer schema is refused
[product]
name = "papir" # default: the repository's name
image = "assets/product.png" # PNG, JPEG or WebP, at most 5 MB; none when left out
[[boards]] # one per KiCad project, as many as needed
name = "papir" # default: the folder's last part
path = "hardware/papir" # required
[paths] # each default: the folder of the same name
firmware = "firmware"
software = "software"
mech = "mech"
sim = "sim"
test = "test"
docs = "docs"
external = "external"
```
Paths are relative to the repository root (no leading `/`, no `..`). A board,
a path written under `[paths]` or the image that does not exist is reported on
the Code tab ("proplan.toml: missing ..."); a path
left out that does not exist is simply not shown. Unknown keys are ignored.
## Adopting the layout in an existing repository
An existing repository does not have to move anything: a `proplan.toml` that
names the current folders is enough, and history and open work stay as they
are. Moving folders to the template's names can follow later.
papir (KiCad project in `papir/`, firmware in `code/`):
```toml
[[boards]]
name = "papir"
path = "papir"
[paths]
firmware = "code"
software = "pirnet"
mech = "mech"
docs = "docs"
```
LVSolarBuck (the current board in `kicad48V-R/LVSolarBuck`, firmware in `code64/`):
```toml
[product]
image = "housing.png"
[[boards]]
name = "LVSolarBuck"
path = "kicad48V-R/LVSolarBuck"
[paths]
firmware = "code64"
sim = "ltspice"
test = "testing"
docs = "docs"
```
PowerModule (three board variants):
```toml
[product]
image = "images/render-display.png"
[[boards]]
name = "PowerModule"
path = "kicad/PowerModule"
[[boards]]
name = "PowerModule48"
path = "kicad/PowerModule48"
[[boards]]
name = "PowerModule48PD"
path = "kicad/PowerModule48PD"
[paths]
firmware = "code"
software = "tools"
sim = "ltspice"
test = "testing"
docs = "tex"
external = "external"
```
## CI
- `.gitea/workflows/kibot.yml`: on a push that touches `hardware/`, KiBot runs
ERC and DRC and builds schematic PDF, gerbers, drill files, pick and place
and BOM for every board (`hardware/*/*.kicad_pro`); the outputs are kept as a
workflow artifact.
- `.gitea/workflows/release.yml`: on a tag `v*`, the same outputs are zipped
per board and attached to the tag's release (created when missing). The
Actions token must be allowed to write releases.
- `.gitea/workflows/docgen.yml`: with a `docs/input.md` (typecad-docgen front
matter, as in PowerModule), builds the fabrication document of the first
board and pushes it to the `docu` branch.
All three use the runner label `kicad10_auto` (as LVSolarBuck). They were
adapted from LVSolarBuck and PowerModule and run for the first time when the
first board is committed and tagged; check that first run.
+4
View File
@@ -0,0 +1,4 @@
# Assets
`product.png` is the product image proplan shows (PNG, JPEG or WebP, at most
5 MB). Renders and photos for documents go here too.
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

+5
View File
@@ -0,0 +1,5 @@
# Documentation
Datasheets of the parts in `docs/datasheets/`, LaTeX sources in `docs/tex/`.
A `docs/input.md` (typecad-docgen front matter) turns on the fabrication
document workflow.
+4
View File
@@ -0,0 +1,4 @@
# Datasheets
Vendor datasheets and application notes (PDF), named after the part, for
example `stm32c542-datasheet.pdf`.
+3
View File
@@ -0,0 +1,3 @@
# LaTeX sources
Sources of the developer documentation.
+5
View File
@@ -0,0 +1,5 @@
# External code
Third-party code as git submodules, never edited here, for example:
git submodule add https://github.com/STMicroelectronics/stm32-mw-usbpd-core external/stm32-mw-usbpd-core
+7
View File
@@ -0,0 +1,7 @@
# Firmware
MCU code, one folder per target (an STM32CubeIDE or CMake project each), for
example `firmware/main-mcu/`. Drivers shared by the targets go to
`firmware/lib/`; third-party stacks (HAL, USB-PD middleware) are submodules in
`external/`. IDE build folders (`Debug/`, `Release/`, `build/`) are not
committed.
+3
View File
@@ -0,0 +1,3 @@
# Shared firmware code
Drivers and modules used by more than one firmware target.
+11
View File
@@ -0,0 +1,11 @@
# $REPO_NAME board
The KiCad project of $REPO_NAME lives here: create it with KiCad (File, New
Project) as `hardware/$REPO_NAME/$REPO_NAME.kicad_pro`.
- A revision of this board is a git tag (`v0.1`), not a copy of this folder.
- A real variant built next to it (48 V, 2-board) gets its own folder under
`hardware/` and its own `[[boards]]` entry in `proplan.toml`.
- Shared symbols, footprints and 3D models go to `hardware/lib/`.
- Gerbers, BOMs and PDFs are built by CI (`.gitea/workflows/kibot.yml`); do not
commit them.
+6
View File
@@ -0,0 +1,6 @@
# Shared KiCad libraries
Symbols (`*.kicad_sym`), footprints (`*.pretty/`) and 3D models (`3dmodels/`)
used by more than one board. Add them to the board projects as project
libraries with a path relative to the project (KiCad's KIPRJMOD variable), so
the repository works on every computer.
+78
View File
@@ -0,0 +1,78 @@
# KiBot outputs for every board; the workflows run KiBot inside each board
# folder (hardware/<board>/). Adapted from LVSolarBuck's kibot.yaml.
kibot:
version: 1
preflight:
erc:
warnings_as_errors: false
dont_stop: true
drc:
warnings_as_errors: false
dont_stop: true
check_zone_fills: true
outputs:
- name: schematic_pdf
comment: Schematic PDF
type: pdf_sch_print
dir: Schematic
- name: gerbers
comment: Gerber files
type: gerber
dir: Gerbers
options:
exclude_edge_layer: true
exclude_pads_from_silkscreen: true
plot_footprint_refs: true
plot_footprint_values: false
create_gerber_job_file: true
subtract_mask_from_silk: true
layers:
- copper
- F.Paste
- B.Paste
- F.Silkscreen
- B.Silkscreen
- F.Mask
- B.Mask
- Edge.Cuts
- name: drill
comment: Drill files
type: excellon
dir: Gerbers
options:
metric_units: true
pth_and_npth_single_file: false
- name: position
comment: Pick and place
type: position
dir: Assembly
options:
format: CSV
units: millimeters
separate_files_for_front_and_back: true
only_smd: false
- name: bom
comment: Bill of materials
type: bom
dir: BoM
options:
format: CSV
- name: fabrication
comment: Fabrication package
type: compress
dir: .
options:
output: '%f-fabrication.%x'
format: ZIP
files:
- from_output: gerbers
dest: /
- from_output: drill
dest: /
+4
View File
@@ -0,0 +1,4 @@
# Mechanics
FreeCAD sources (`*.FCStd`) and STEP exports of enclosures, holders and
heatsinks. FreeCAD backups (`*.FCBak`, `*.FCStd1`) are not committed.
+24
View File
@@ -0,0 +1,24 @@
# proplan.toml: what proplan reads from this repository. The Code tab shows
# whether the folders named here exist, the product image appears on the
# project and on each prototype stage. Every key is optional; an omitted path
# means the folder of the same name. TEMPLATE.md has the full reference.
schema = 1
[product]
name = "$REPO_NAME"
image = "assets/product.png"
# One entry per KiCad project. A real variant (48 V, 2-board) is another
# entry with its own folder; a revision is a git tag, never a folder copy.
[[boards]]
name = "$REPO_NAME"
path = "hardware/$REPO_NAME"
[paths]
firmware = "firmware"
software = "software"
mech = "mech"
sim = "sim"
test = "test"
docs = "docs"
external = "external"
+4
View File
@@ -0,0 +1,4 @@
# Simulation and calculations
Circuit simulations (LTspice, ngspice) and calculation scripts or notebooks
(compensator, heatsink, inductor). Large generated results are not committed.
+5
View File
@@ -0,0 +1,5 @@
# Software
Host tools and apps (diagnostics, configuration, test rigs), one folder per
tool, for example `software/diag/` with its own README and pyproject.toml or
Cargo.toml.
+5
View File
@@ -0,0 +1,5 @@
# Bench results
One folder per prototype stage: `test/v0.1/`, `test/v0.2/`, with photos,
scope captures, measurements and short notes. Attach the key results to the
stage in proplan too.