Merge feature/readme-overhaul-#47: README overhaul and MIT license (v… #9
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 |