Enhance release process: require clean worktree for publishing, update tagging behavior, and improve UI for audio file uploads

This commit is contained in:
2026-09-28 08:14:27 +02:00
parent 0f7d5e950c
commit cb16c77bad
7 changed files with 18 additions and 11 deletions
+1 -1
View File
@@ -25,4 +25,4 @@ Faerro KB is a self-hosted voice-note inbox designed for an iPhone-installed web
## Releases
Git tags are the source of truth for image versions. The publish script requires the checked-out commit to have an exact `MAJOR.MINOR.PATCH` tag, optionally prefixed with `v`. Do not invent a version for a future release; use the version explicitly selected by the user or ask. Publishing pushes both the versioned image and `latest` to the configured private Gitea registry, so confirm registry authentication is available before treating a release as complete.
Git tags are the source of truth for image versions. The publish script requires a clean worktree, chooses the next version from existing `MAJOR.MINOR.PATCH` tags, and creates an annotated local tag on the checked-out commit. Do not invent a version for a future release; use the script's next-version behavior or the version explicitly selected by the user. The script does not push Git tags. Publishing pushes both the versioned image and `latest` to the configured private Gitea registry, so confirm registry authentication is available before treating a release as complete.
+2 -2
View File
@@ -9,9 +9,9 @@ Use this skill when asked to tag or publish a Faerro KB image. The release proce
## Procedure
1. Check the worktree and identify the exact commit being released. Do not tag unrelated or unreviewed changes.
1. Require a clean worktree and identify the exact committed `HEAD` being released. Staged, unstaged, or untracked changes must be committed or removed before running the script. Do not tag unrelated or unreviewed changes.
2. The script selects the next patch version by default. Use `--minor` or `--major` only when the user requests that increment; lower components are reset. Tags must be exact `MAJOR.MINOR.PATCH`.
3. The script creates an unused local tag on the checked-out commit; do not push Git tags to a remote unless requested.
3. The script creates an unused annotated local tag on the checked-out commit; do not push Git tags to a remote unless requested.
4. Confirm Docker is available. For non-interactive authentication, put `GITEA_USERNAME` and a package-write `GITEA_TOKEN` in the ignored `.env` file. The script parses only these keys and sends the token through Docker's `--password-stdin`; never put credentials in tracked files, command-line arguments, or chat. Without these settings it relies on Docker's existing login credentials.
5. Run `./scripts/publish-image.sh` from the repository. It builds, logs in when `.env` credentials are set, and pushes both the versioned tag and `latest`.
6. Report the tagged commit, image tags, and actual build/push result. A successful local build is not a successful release if either registry push fails.
+1 -1
View File
@@ -18,7 +18,7 @@ cp .env.example .env
./scripts/publish-image.sh
```
Pass `--minor` to increment the minor version and reset patch to zero, or `--major` to increment the major version and reset minor and patch to zero. The options cannot be combined. The script requires at least one existing `MAJOR.MINOR.PATCH` Git tag, creates the next local tag on the checked-out commit, and publishes both the versioned image and `latest`. `.env` is excluded from Git; the script reads the Gitea credentials from it and passes the token to Docker through standard input. If those credentials are unset, the script uses Docker's existing login credentials.
Pass `--minor` to increment the minor version and reset patch to zero, or `--major` to increment the major version and reset minor and patch to zero. The options cannot be combined. Run the script from a clean worktree: staged, unstaged, and untracked changes must be committed or removed first. It creates an annotated local Git tag on the checked-out commit, then publishes both the versioned image and `latest`. The commit does not need to be pushed before creating the local tag, but push the commit to the Git server first when it should be shared as part of the release; the script does not push Git tags. `.env` is excluded from Git; the script reads the Gitea credentials from it and passes the token to Docker through standard input. If those credentials are unset, the script uses Docker's existing login credentials.
Open `http://localhost` for a desktop smoke test. Set `HTTP_PORT` to publish the app on a different host port (for example, `HTTP_PORT=8000 docker compose up`). For iPhone use, serve the app over HTTPS through your VPN/reverse proxy. iOS only grants microphone access in a secure context. Keep the service reachable only through your VPN and firewall; this first version has no login layer.
+5 -4
View File
@@ -44,9 +44,11 @@ const getQueued = () => queueTransaction('readonly', (store) => store.getAll());
const putQueued = (recording) => queueTransaction('readwrite', (store) => store.put(recording));
const deleteQueued = (id) => queueTransaction('readwrite', (store) => store.delete(id));
function setMessage(message, isError = false) {
function setMessage(message, isError = false, isSuccess = false) {
elements['capture-message'].textContent = message;
elements['capture-message'].style.color = isError ? 'var(--coral)' : '';
elements['capture-message'].classList.toggle('upload-confirmed', isSuccess);
elements['capture-message'].setAttribute('aria-label', isSuccess ? 'Server confirmed the recording is stored.' : message);
}
function updateNetwork() {
@@ -229,7 +231,7 @@ async function syncQueue() {
const response = await fetch('/api/notes', { method: 'POST', body: form });
if (!response.ok) throw new Error(`Upload failed: ${response.status}`);
await deleteQueued(recording.id);
setMessage('Server confirmed the recording is stored.');
setMessage('✓', false, true);
} catch {
setMessage('Upload paused. Your recording is still saved on this device.');
break;
@@ -379,8 +381,7 @@ async function loadAwaitingNotes() {
elements['record-button'].addEventListener('click', () => mediaRecorder?.state === 'recording' ? stopRecording() : startRecording());
elements['audio-file-input'].addEventListener('change', async (event) => {
const [file] = event.target.files;
if (file) await queueAudioFile(file);
for (const file of event.target.files) await queueAudioFile(file);
event.target.value = '';
});
elements['sync-button'].addEventListener('click', syncQueue);
+2 -2
View File
@@ -34,7 +34,7 @@
</div>
<div class="audio-upload-row">
<label class="audio-upload-button" for="audio-file-input">↑ Upload audio</label>
<input id="audio-file-input" type="file" accept="audio/*,.mp3,.m4a,.aac,.ogg,.opus,.flac,.wav" />
<input id="audio-file-input" type="file" accept="audio/*,.mp3,.m4a,.aac,.ogg,.opus,.flac,.wav" multiple />
<span>Files stay on this device until the server confirms storage.</span>
</div>
<div class="record-meter"><span id="record-meter-fill"></span></div>
@@ -59,7 +59,7 @@
</section>
</main>
<footer><p>Faerro KB - Copyright Andrea Ferronato © 2026</p></footer>
<footer><p>Faerro KB © 2026</p></footer>
<script src="/app.js" defer></script>
</body>
</html>
+1
View File
@@ -44,6 +44,7 @@ h1 em { color: var(--moss); font-weight: 500; }
.record-meter { height: 2px; max-width: 590px; margin-inline: auto; background: #e4e3dc; }
.record-meter span { display: block; height: 100%; width: 0; background: var(--coral); transition: width .25s linear; }
.capture-message { height: 20px; max-width: 590px; margin-inline: auto; padding-top: 6px; color: var(--muted); font-size: 11px; text-align: center; }
.capture-message.upload-confirmed { color: forestgreen; font-size: 16px; }
.audio-upload-row { max-width: 590px; margin: 14px auto 0; display: flex; align-items: center; justify-content: center; gap: 12px; }
.audio-upload-button { display: inline-flex; min-height: 36px; align-items: center; gap: 7px; border: 1px solid var(--line); padding: 0 12px; color: var(--moss-dark); background: #fffefa; font-size: 12px; cursor: pointer; }
.audio-upload-button:hover { border-color: var(--moss); }
+6 -1
View File
@@ -32,6 +32,11 @@ if ! command -v git >/dev/null 2>&1; then
exit 1
fi
if [[ -n "$(git -C "$repo_root" status --porcelain)" ]]; then
echo "Commit or discard all staged, unstaged, and untracked changes before publishing." >&2
exit 1
fi
latest_version=""
while IFS= read -r candidate; do
if [[ "$candidate" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
@@ -67,7 +72,7 @@ if git -C "$repo_root" show-ref --verify --quiet "refs/tags/$version"; then
exit 1
fi
git -C "$repo_root" tag "$version"
git -C "$repo_root" tag --annotate "$version" --message "Release $version" HEAD
cd "$repo_root"
docker build --tag "$image:$version" --tag "$image:latest" .