diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index b404bd2..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Deploy to GitHub Pages - -on: - push: - branches: [main] - workflow_dispatch: - -# Least privilege needed to publish to Pages. -permissions: - contents: read - pages: write - id-token: write - -# One deploy at a time; let a newer push cancel an in-flight run. -concurrency: - group: pages - cancel-in-progress: true - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: npm - - run: npm ci - - run: npm run build # tsc -b && vite build -> dist/ (base '/brushlog/') - - uses: actions/configure-pages@v6 - - uses: actions/upload-pages-artifact@v5 - with: - path: dist - - deploy: - needs: build - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - id: deployment - uses: actions/deploy-pages@v5 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml deleted file mode 100644 index 035e978..0000000 --- a/.github/workflows/docker.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Container image - -# Publishes ghcr.io/libreble/ (linux/amd64 + linux/arm64) with the built-in -# GITHUB_TOKEN — no registry account or secrets. Pull requests build and smoke-test only. -on: - push: - branches: [main] - tags: ['v*'] - pull_request: - workflow_dispatch: - -permissions: - contents: read - packages: write - -concurrency: - group: docker-${{ github.ref }} - cancel-in-progress: true - -jobs: - image: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - uses: docker/setup-qemu-action@v3 - - uses: docker/setup-buildx-action@v3 - - - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=raw,value=latest,enable={{is_default_branch}} - type=sha - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - # Build for the runner, run it, and check the page, assets, SW, manifest and SPA fallback. - - uses: docker/build-push-action@v6 - with: - context: . - load: true - tags: smoke:test - cache-from: type=gha - cache-to: type=gha,mode=max - - run: docker run -d --name smoke -p 8080:8080 smoke:test - - run: ./docker/smoke.sh http://localhost:8080/ - - if: failure() - run: docker logs smoke - - - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - if: github.event_name != 'pull_request' - uses: docker/build-push-action@v6 - with: - context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - annotations: ${{ steps.meta.outputs.annotations }} - cache-from: type=gha diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..01979aa --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,95 @@ +name: Release + +# Releases only: push a v* tag. Merging to main publishes nothing. +# +# One job installs once and builds on the runner — twice, because Vite bakes the base path in: +# default base (/brushlog/) → GitHub Pages BASE_PATH=/ → the container image +# The image copies that prebuilt dist/ into nginx (Dockerfile target `prebuilt`), so the +# arm64 variant needs no emulated Node build. Pull requests run the same build and +# smoke-test the image without publishing; workflow_dispatch on main redeploys Pages. +on: + push: + tags: ['v*'] + pull_request: + workflow_dispatch: + +permissions: + contents: read + packages: write + pages: write + id-token: write + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 + with: + node-version: 26 + cache: npm + - run: npm ci + - name: Build for GitHub Pages + run: npm run build && mv dist pages-dist + - name: Build for the container image + run: BASE_PATH=/ npm run build + + - if: github.event_name != 'pull_request' + uses: actions/configure-pages@v6 + - if: github.event_name != 'pull_request' + uses: actions/upload-pages-artifact@v5 + with: + path: pages-dist + + # ghcr.io/libreble/brushlog (linux/amd64 + linux/arm64) via the built-in GITHUB_TOKEN. + - uses: docker/setup-buildx-action@v3 + - id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + - uses: docker/build-push-action@v6 + with: + context: . + target: prebuilt + build-contexts: dist=dist + load: true + tags: smoke:test + - run: docker run -d --name smoke -p 8080:8080 smoke:test + - run: ./docker/smoke.sh http://localhost:8080/ + - if: failure() + run: docker logs smoke + - if: startsWith(github.ref, 'refs/tags/v') + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - if: startsWith(github.ref, 'refs/tags/v') + uses: docker/build-push-action@v6 + with: + context: . + target: prebuilt + build-contexts: dist=dist + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + annotations: ${{ steps.meta.outputs.annotations }} + + deploy: + if: github.event_name != 'pull_request' + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v5 diff --git a/Dockerfile b/Dockerfile index dfb8cca..c281c56 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # docker build -t brushlog . # served at / # docker build --build-arg BASE_PATH=/brushlog/ -t brushlog . # served at /brushlog/ -FROM node:22-alpine AS build +FROM node:26-alpine AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci @@ -11,10 +11,18 @@ COPY . . ARG BASE_PATH=/ RUN BASE_PATH="$BASE_PATH" npm run build -FROM nginxinc/nginx-unprivileged:1.29-alpine +FROM nginxinc/nginx-unprivileged:1.29-alpine AS serve ARG BASE_PATH=/ ENV BASE_PATH=$BASE_PATH COPY docker/nginx.conf.template /etc/nginx/templates/default.conf.template -COPY --from=build /app/dist /usr/share/nginx/html${BASE_PATH} EXPOSE 8080 HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO /dev/null http://127.0.0.1:8080/healthz || exit 1 + +# Release CI: serve a dist/ already built on the runner (.github/workflows/release.yml): +# docker buildx build --target prebuilt --build-context dist=dist . +FROM serve AS prebuilt +COPY --from=dist . /usr/share/nginx/html${BASE_PATH} + +# Default: build from source. +FROM serve +COPY --from=build /app/dist /usr/share/nginx/html${BASE_PATH} diff --git a/package-lock.json b/package-lock.json index 481aa98..08f3ba7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@tailwindcss/vite": "^4.0.0", - "@types/node": "^24.13.6", + "@types/node": "^26.6.2", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@types/web-bluetooth": "^0.0.21", @@ -2947,13 +2947,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.13.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.6.tgz", - "integrity": "sha512-SGrw/h3KPFshy3OE6ZL53LMBG5vGQQ8/gIpiqz/kRZhPJ7HgwCEs8LBuNtWLa8dvGZVpSF7+Bf+c11HUrCb/yg==", + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz", + "integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~8.9.0" } }, "node_modules/@types/react": { @@ -6234,9 +6234,9 @@ } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 52fc49d..8c971fc 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ }, "devDependencies": { "@tailwindcss/vite": "^4.0.0", - "@types/node": "^24.13.6", + "@types/node": "^26.6.2", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@types/web-bluetooth": "^0.0.21",