Skip to content

Merge feature/readme-overhaul-#47: README overhaul and MIT license (v… #9

Merge feature/readme-overhaul-#47: README overhaul and MIT license (v…

Merge feature/readme-overhaul-#47: README overhaul and MIT license (v… #9

Workflow file for this run

# Builds and packages Tasklog for all platforms when a version tag is pushed.
#
# Triggers:
# - Automatic: push a tag matching v* (e.g. v2.7, v3.0.1)
# - Manual: "Run workflow" button in GitHub Actions tab (for testing)
#
# Produces 4 packages uploaded to the GitHub Release:
# - Tasklog-win-x64.zip
# - Tasklog-mac-arm64.tar.gz
# - Tasklog-mac-x64.tar.gz
# - Tasklog-linux-x64.tar.gz
name: Build Release Packages
on:
push:
tags:
- "v*"
workflow_dispatch:
# Minimum permissions - only contents:write is needed to upload release assets.
permissions:
contents: write
# Shared versions used across jobs.
env:
DOTNET_VERSION: "10.0.x"
NODE_VERSION: "22"
PORTABLE_NODE_VERSION: "22.16.0"
# Keep in sync with <TargetFramework> in the seed project inside the Seed step.
SEED_TFM: "net10.0"
jobs:
# ----------------------------------------------------------------
# Job 1: Build the Next.js frontend (platform-independent).
# The standalone output is pure JS - only needs to be built once.
# ----------------------------------------------------------------
build-frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
working-directory: frontend
run: npm ci
- name: Build standalone
working-directory: frontend
run: npm run build
- name: Upload standalone output
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: frontend-standalone
path: |
frontend/.next/standalone/
frontend/.next/static/
frontend/public/
include-hidden-files: true
retention-days: 1
# ----------------------------------------------------------------
# Job 2: Build platform-specific packages.
# Runs on 4 different OS runners in parallel.
# fail-fast is disabled so a single platform failure does not cancel
# the other runners mid-build and leave the release partially uploaded.
# ----------------------------------------------------------------
build-release:
needs: build-frontend
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
rid: win-x64
package-name: Tasklog-win-x64
archive-ext: zip
node-platform: win-x64
launcher-src: Tasklog.Launcher.exe
launcher-dest: Tasklog.exe
backend-bin: Tasklog.Api.exe
- os: macos-latest
rid: osx-arm64
package-name: Tasklog-mac-arm64
archive-ext: tar.gz
node-platform: darwin-arm64
launcher-src: Tasklog.Launcher
launcher-dest: Tasklog
backend-bin: Tasklog.Api
# Mac x64 (Intel) cross-compiled from Ubuntu - macos-13 runner is discontinued.
# dotnet publish supports cross-compilation for osx-x64 from any platform.
- os: ubuntu-latest
rid: osx-x64
package-name: Tasklog-mac-x64
archive-ext: tar.gz
node-platform: darwin-x64
launcher-src: Tasklog.Launcher
launcher-dest: Tasklog
backend-bin: Tasklog.Api
- os: ubuntu-latest
rid: linux-x64
package-name: Tasklog-linux-x64
archive-ext: tar.gz
node-platform: linux-x64
launcher-src: Tasklog.Launcher
launcher-dest: Tasklog
backend-bin: Tasklog.Api
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
# --- Publish .NET backend ---
- name: Publish backend
working-directory: backend/Tasklog.Api
run: dotnet publish -p:PublishProfile=${{ matrix.rid }}-distributable
# --- Publish launcher ---
- name: Publish launcher
working-directory: launcher/Tasklog.Launcher
run: dotnet publish -p:PublishProfile=${{ matrix.rid }}-distributable
# --- Download frontend artifact ---
- name: Download frontend artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: frontend-standalone
path: frontend-artifact
# --- Download portable Node.js ---
- name: Download portable Node.js
shell: bash
run: |
NODE_VER="${{ env.PORTABLE_NODE_VERSION }}"
PLATFORM="${{ matrix.node-platform }}"
if [[ "$PLATFORM" == "win-x64" ]]; then
ARCHIVE="node-v${NODE_VER}-win-x64.zip"
else
ARCHIVE="node-v${NODE_VER}-${PLATFORM}.tar.gz"
fi
URL="https://nodejs.org/dist/v${NODE_VER}/${ARCHIVE}"
SHASUMS_URL="https://nodejs.org/dist/v${NODE_VER}/SHASUMS256.txt"
echo "Downloading $URL"
curl -fSL -o "$ARCHIVE" "$URL"
# Verify the download against the official Node.js SHA-256 checksums.
echo "Verifying checksum..."
curl -fSL -o SHASUMS256.txt "$SHASUMS_URL"
grep " ${ARCHIVE}$" SHASUMS256.txt | sha256sum --check -
# Extract just the node binary.
mkdir -p node-extracted
if [[ "$ARCHIVE" == *.zip ]]; then
unzip -q "$ARCHIVE" -d node-extracted
else
tar xzf "$ARCHIVE" -C node-extracted
fi
# --- Assemble package directory ---
- name: Assemble package
shell: bash
run: |
PKG="${{ matrix.package-name }}"
RID="${{ matrix.rid }}"
NODE_VER="${{ env.PORTABLE_NODE_VERSION }}"
PLATFORM="${{ matrix.node-platform }}"
mkdir -p "$PKG/backend"
mkdir -p "$PKG/frontend/.next/static"
mkdir -p "$PKG/node"
# Copy backend.
cp -r "backend/Tasklog.Api/bin/publish/${RID}/." "$PKG/backend/"
# Copy launcher to package root.
LAUNCHER_PUBLISH="launcher/Tasklog.Launcher/bin/publish/${RID}"
cp "$LAUNCHER_PUBLISH/${{ matrix.launcher-src }}" "$PKG/${{ matrix.launcher-dest }}"
# Copy frontend standalone output.
# upload-artifact@v4 strips the common path prefix (frontend/), so the
# artifact is rooted at .next/ and public/ directly inside frontend-artifact/.
cp -r frontend-artifact/.next/standalone/. "$PKG/frontend/"
# Copy static assets (Next.js requires these alongside standalone server).
if [ -d "frontend-artifact/.next/static" ]; then
cp -r frontend-artifact/.next/static/. "$PKG/frontend/.next/static/"
fi
# Copy public assets if they exist.
if [ -d "frontend-artifact/public" ]; then
mkdir -p "$PKG/frontend/public"
cp -r frontend-artifact/public/. "$PKG/frontend/public/"
fi
# Copy Node.js portable binary.
if [[ "$PLATFORM" == "win-x64" ]]; then
NODE_DIR="node-extracted/node-v${NODE_VER}-win-x64"
cp "$NODE_DIR/node.exe" "$PKG/node/node.exe"
else
NODE_DIR="node-extracted/node-v${NODE_VER}-${PLATFORM}"
cp "$NODE_DIR/bin/node" "$PKG/node/node"
chmod +x "$PKG/node/node"
fi
# Set execute permissions on Mac/Linux binaries.
if [[ "$RID" != "win-x64" ]]; then
chmod +x "$PKG/${{ matrix.launcher-dest }}"
chmod +x "$PKG/backend/${{ matrix.backend-bin }}"
fi
# --- Seed sample database ---
- name: Seed sample database
shell: bash
run: |
PKG="${{ matrix.package-name }}"
# The .db file is gitignored (local data only). In CI we create it fresh
# from the EF Core migrations, then fill it with sample data.
# This never touches the local developer's database.
# Install dotnet-ef only if not already present.
export PATH="$PATH:$HOME/.dotnet/tools"
if ! dotnet tool list --global | grep -q dotnet-ef; then
dotnet tool install --global dotnet-ef --prerelease
fi
(cd backend/Tasklog.Api && dotnet ef database update)
# Copy the migrated (empty schema) database to the package.
cp "backend/Tasklog.Api/TasklogDatabase.db" "$PKG/backend/TasklogDatabase.db"
# Create a temporary .NET project to run the SQL seed script.
# Uses Microsoft.Data.Sqlite - same version as the backend (9.0.12).
# TargetFramework must match SEED_TFM env var and the installed SDK.
SEED_DIR=$(mktemp -d)
DB_PATH="$(pwd)/$PKG/backend/TasklogDatabase.db"
SQL_PATH="$(pwd)/dist/seed-sample-data.sql"
cat > "$SEED_DIR/SeedDb.csproj" << 'CSPROJ'
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.12" />
</ItemGroup>
</Project>
CSPROJ
cat > "$SEED_DIR/Program.cs" << 'PROGRAM'
using Microsoft.Data.Sqlite;
var dbPath = args[0];
var sqlPath = args[1];
var sql = File.ReadAllText(sqlPath);
using var connection = new SqliteConnection("Data Source=" + dbPath);
connection.Open();
using var command = connection.CreateCommand();
command.CommandText = sql;
command.ExecuteNonQuery();
string[] tables = ["Projects", "Tasks", "Labels", "LabelTaskModel"];
foreach (var table in tables)
{
using var countCmd = connection.CreateCommand();
countCmd.CommandText = "SELECT COUNT(*) FROM " + table;
var count = countCmd.ExecuteScalar();
Console.WriteLine(" " + table + ": " + count);
}
PROGRAM
dotnet run --project "$SEED_DIR" -- "$DB_PATH" "$SQL_PATH"
rm -rf "$SEED_DIR"
# --- Generate platform-specific README ---
- name: Generate README
shell: bash
run: |
PKG="${{ matrix.package-name }}"
RID="${{ matrix.rid }}"
if [[ "$RID" == "win-x64" ]]; then
cat > "$PKG/README.txt" << 'EOF'
========================================
Tasklog - Quick Start (Windows)
========================================
1. Double-click "Tasklog.exe" to start the application.
2. A console window will appear showing:
- The local URL (http://localhost:3000) for this computer
- A LAN URL (http://192.168.x.x:3000) for your phone
3. Open the URL in your browser to use Tasklog.
4. To use on your phone: connect to the same Wi-Fi network
and open the LAN URL shown in the console.
5. Press any key in the console window to stop Tasklog.
TROUBLESHOOTING
- If Windows Firewall asks for permission, click "Allow access"
so your phone can connect over the local network.
- If the app does not start, make sure you extracted the
full zip before running (do not run from inside the zip).
- Tasklog stores its data in backend/TasklogDatabase.db.
Your changes are saved automatically.
ABOUT
Tasklog is a self-hosted task management app.
Built with .NET, Next.js, and SQLite.
Source: https://github.com/hydraInsurgent/Tasklog
EOF
elif [[ "$RID" == osx-* ]]; then
cat > "$PKG/README.txt" << 'EOF'
========================================
Tasklog - Quick Start (Mac)
========================================
FIRST-TIME SETUP
Mac blocks unsigned apps by default (Gatekeeper).
Before running Tasklog for the first time, open Terminal
in this folder and run:
xattr -rd com.apple.quarantine .
Or: right-click "Tasklog" > Open > click "Open" in the dialog.
You only need to do this once.
RUNNING TASKLOG
1. Open Terminal in this folder and run:
./Tasklog
2. The console will show:
- The local URL (http://localhost:3000) for this computer
- A LAN URL (http://192.168.x.x:3000) for your phone
3. Open the URL in your browser to use Tasklog.
4. Press any key in the terminal to stop Tasklog.
TROUBLESHOOTING
- If you see "permission denied", run: chmod +x Tasklog
- Tasklog stores its data in backend/TasklogDatabase.db.
Your changes are saved automatically.
ABOUT
Tasklog is a self-hosted task management app.
Built with .NET, Next.js, and SQLite.
Source: https://github.com/hydraInsurgent/Tasklog
EOF
else
cat > "$PKG/README.txt" << 'EOF'
========================================
Tasklog - Quick Start (Linux)
========================================
1. Open a terminal in this folder and run:
./Tasklog
2. The console will show:
- The local URL (http://localhost:3000) for this computer
- A LAN URL (http://192.168.x.x:3000) for your phone
3. Open the URL in your browser to use Tasklog.
4. Press any key in the terminal to stop Tasklog.
TROUBLESHOOTING
- If you see "permission denied", run: chmod +x Tasklog
- If your firewall blocks LAN connections, allow port 3000:
sudo ufw allow 3000
- Tasklog stores its data in backend/TasklogDatabase.db.
Your changes are saved automatically.
ABOUT
Tasklog is a self-hosted task management app.
Built with .NET, Next.js, and SQLite.
Source: https://github.com/hydraInsurgent/Tasklog
EOF
fi
# --- Create archive ---
- name: Create archive
shell: bash
run: |
PKG="${{ matrix.package-name }}"
if [[ "${{ matrix.archive-ext }}" == "zip" ]]; then
# Windows: zip (run from inside the package dir so paths are relative).
cd "$PKG"
7z a -tzip "../${PKG}.zip" .
cd ..
else
# Mac/Linux: tar.gz preserves execute permissions.
tar czf "${PKG}.tar.gz" -C "$PKG" .
fi
# --- Upload to GitHub Release ---
# /ship creates the release first, then CI uploads packages to it.
# The retry loop handles the race window between the tag push and release creation.
- name: Upload to GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${GITHUB_REF#refs/tags/}"
PKG="${{ matrix.package-name }}"
EXT="${{ matrix.archive-ext }}"
ARCHIVE="${PKG}.${EXT}"
echo "Waiting for release $TAG to exist..."
# Retry up to 30 times (5 minutes) waiting for the release to be created.
for i in $(seq 1 30); do
if gh release view "$TAG" > /dev/null 2>&1; then
echo "Release $TAG found. Uploading $ARCHIVE..."
gh release upload "$TAG" "$ARCHIVE" --clobber
echo "Uploaded $ARCHIVE to release $TAG"
exit 0
fi
echo " Release not found yet, retrying in 10s... ($i/30)"
sleep 10
done
echo "ERROR: Release $TAG was not created within 5 minutes."
echo "Upload the archive manually: $ARCHIVE"
exit 1
# --- Upload as artifact (for workflow_dispatch test runs) ---
# Packages are available in the Actions tab under this run for 7 days.
- name: Upload build artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ matrix.package-name }}
path: |
${{ matrix.package-name }}.zip
${{ matrix.package-name }}.tar.gz
retention-days: 7