diff --git a/.github/scripts/build-install-zip.sh b/.github/scripts/build-install-zip.sh new file mode 100755 index 0000000..6b327dd --- /dev/null +++ b/.github/scripts/build-install-zip.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# +# Builds the full, self-hostable install package: dist/linkforge-v.zip +# Files sit at the archive ROOT (app/, public/, vendor/, artisan, ...), so a buyer extracts +# straight into their web root. Run from the repo root AFTER: +# composer install --no-dev --optimize-autoloader +# npm ci && npm run build +# +set -euo pipefail + +VERSION="${1:?usage: build-install-zip.sh }" +ROOT="$(pwd)" +STAGE="$(mktemp -d)" + +# Export every TRACKED file. .env, node_modules, vendor, public/build and runtime cruft are +# gitignored and therefore excluded automatically. +git archive --format=tar HEAD | tar -x -C "$STAGE" + +# Add the production dependencies + compiled assets (both gitignored; built in CI). +cp -r vendor "$STAGE/vendor" +cp -r public/build "$STAGE/public/build" + +# Drop development-only files a production server never needs. +rm -rf \ + "$STAGE/.github" \ + "$STAGE/tests" \ + "$STAGE/phpunit.xml" \ + "$STAGE/pint.json" \ + "$STAGE/package.json" \ + "$STAGE/package-lock.json" \ + "$STAGE/vite.config.js" \ + "$STAGE/.editorconfig" \ + "$STAGE/.gitattributes" + +mkdir -p "$ROOT/dist" +( cd "$STAGE" && zip -rqX "$ROOT/dist/linkforge-v${VERSION}.zip" . ) +rm -rf "$STAGE" + +echo "Built dist/linkforge-v${VERSION}.zip ($(du -h "$ROOT/dist/linkforge-v${VERSION}.zip" | cut -f1))" diff --git a/.github/scripts/build-update-zip.sh b/.github/scripts/build-update-zip.sh new file mode 100755 index 0000000..0dce2c1 --- /dev/null +++ b/.github/scripts/build-update-zip.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# +# Builds the incremental in-app update package: dist/linkforge-update-v.zip +# Layout matches what App\Services\Update\Updater expects: +# update.json – { version, requires, name, notes } +# files/... – only the files changed since the previous tag, mirrored at app-root paths +# An operator uploads this in Admin -> Updates; the Updater backs up, overlays files/, then migrates. +# Run from the repo root AFTER `npm run build` (so freshly-compiled assets are available). +# +set -euo pipefail + +VERSION="${1:?usage: build-update-zip.sh }" +TAG="v${VERSION}" +ROOT="$(pwd)" +STAGE="$(mktemp -d)" +mkdir -p "$STAGE/files" + +# Only ship runtime paths an operator actually needs overlaid (never tests/, CI, or dev configs). +INCLUDE='^(app/|resources/|config/|routes/|database/|bootstrap/app\.php|bootstrap/providers\.php|public/docs/|public/(favicon\.(ico|png)|apple-touch-icon\.png|logo\.png)|composer\.(json|lock)|artisan)' + +PREV="$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || true)" +if [ -n "$PREV" ]; then + echo "Diffing $PREV..$TAG for changed files" + mapfile -t CHANGED < <(git diff --name-only "$PREV".."$TAG" | grep -E "$INCLUDE" || true) +else + echo "No previous tag — shipping the full runtime tree" + mapfile -t CHANGED < <(git ls-files | grep -E "$INCLUDE" || true) +fi + +for f in "${CHANGED[@]}"; do + if [ -n "$f" ] && [ -f "$f" ]; then + mkdir -p "$STAGE/files/$(dirname "$f")" + cp "$f" "$STAGE/files/$f" + fi +done + +# Bundle the freshly-built assets whenever front-end sources (or the manifest) changed. +if [ -z "$PREV" ] || git diff --name-only "$PREV".."$TAG" | grep -qE '^(resources/(css|js)/|vite\.config\.js|package(-lock)?\.json)'; then + mkdir -p "$STAGE/files/public" + cp -r public/build "$STAGE/files/public/build" +fi + +cat > "$STAGE/update.json" <> "$GITHUB_OUTPUT" + + - name: Install production dependencies + build assets + run: | + composer install --no-dev --optimize-autoloader --no-interaction --no-progress + npm ci + npm run build + + - name: Build install package (full) + run: bash .github/scripts/build-install-zip.sh "${{ steps.ver.outputs.version }}" + + - name: Build update package (incremental) + run: bash .github/scripts/build-update-zip.sh "${{ steps.ver.outputs.version }}" + + - name: Publish GitHub Release + uses: softprops/action-gh-release@v2 + with: + name: LinkForge v${{ steps.ver.outputs.version }} + generate_release_notes: true + files: | + dist/linkforge-v${{ steps.ver.outputs.version }}.zip + dist/linkforge-update-v${{ steps.ver.outputs.version }}.zip diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7713353..e77bf29 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,6 +50,22 @@ npm run build # assets must compile 4. CI (tests + lint + assets build) must pass, and a maintainer must approve before merge. `main` is protected — changes land only through reviewed PRs. +## Releasing (maintainers) + +Releases are fully automated by [`.github/workflows/release.yml`](.github/workflows/release.yml). +To cut a release: + +1. Bump the version in `config/linkforge.php` and merge it through a PR. +2. Tag the merge commit and push the tag: + ```bash + git tag v1.1.0 + git push origin v1.1.0 + ``` +3. The `Release` workflow builds two artifacts and publishes a GitHub Release with auto-generated notes: + - **`linkforge-v.zip`** — the full, self-hostable install package (extract into your web root). + - **`linkforge-update-v.zip`** — an incremental package (only files changed since the previous + tag) for the in-app updater under **Admin → Updates**. + ## Reporting bugs / requesting features Use the [issue templates](https://github.com/sanmaxdev/linkforge/issues/new/choose). For **security**