Merge release workflow into the main build workflow
Build package / package (push) Successful in 58s
Lint info.xml / xml-lint (push) Successful in 13s
Lint PHP / php-lint (8.2) (push) Successful in 45s
Lint PHP / php-lint (8.3) (push) Successful in 41s
Lint PHP / php-lint (8.4) (push) Successful in 34s
PHPUnit / unit-tests (push) Successful in 42s
Build package / package (push) Successful in 58s
Lint info.xml / xml-lint (push) Successful in 13s
Lint PHP / php-lint (8.2) (push) Successful in 45s
Lint PHP / php-lint (8.3) (push) Successful in 41s
Lint PHP / php-lint (8.4) (push) Successful in 34s
PHPUnit / unit-tests (push) Successful in 42s
build-release.yml and build-main.yml ran identical steps (checkout, node, npm install, npm run build, make appstore) and differed only in where the package landed. Fold the v* tag trigger into build-main.yml and branch on gitea.ref in the publishing step: main keeps the rolling latest-main pre-release, tags get a normal release on the tag that was just pushed. Tag deletion is now guarded - only latest-main is recreated, since deleting a version tag would destroy the ref that triggered the run. A stale release is still removed in both cases so moved tags republish cleanly. Side effects: version builds are published as durable release assets with a stable URL instead of expiring workflow artifacts, which drops the last actions/upload-artifact usage and with it the @v3 pin forced by the GHES restriction; and the setup-php step goes away, since make appstore only shells out to mkdir/tar/rm.
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
name: Build main artifact
|
name: Build package
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
package:
|
package:
|
||||||
@@ -24,34 +26,55 @@ jobs:
|
|||||||
- name: Package appstore artifact
|
- name: Package appstore artifact
|
||||||
run: make appstore
|
run: make appstore
|
||||||
|
|
||||||
- name: Publish rolling "latest-main" release
|
- name: Publish release
|
||||||
env:
|
env:
|
||||||
GITEA_API: ${{ gitea.server_url }}/api/v1
|
GITEA_API: ${{ gitea.server_url }}/api/v1
|
||||||
REPO: ${{ gitea.repository }}
|
REPO: ${{ gitea.repository }}
|
||||||
TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
SHA: ${{ gitea.sha }}
|
SHA: ${{ gitea.sha }}
|
||||||
|
REF: ${{ gitea.ref }}
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
TAG="latest-main"
|
|
||||||
ASSET="build/artifacts/appstore/workflow_deck_automation.tar.gz"
|
ASSET="build/artifacts/appstore/workflow_deck_automation.tar.gz"
|
||||||
|
|
||||||
# Drop any previous release+tag so the download URL always serves the newest build
|
case "$REF" in
|
||||||
curl -s -H "Authorization: token $TOKEN" \
|
refs/tags/*)
|
||||||
"$GITEA_API/repos/$REPO/releases/tags/$TAG" -o /tmp/old_release.json
|
TAG="${REF#refs/tags/}"
|
||||||
|
RECREATE_TAG=false
|
||||||
|
PRERELEASE=false
|
||||||
|
TITLE="$TAG"
|
||||||
|
BODY="Release $TAG"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
TAG="latest-main"
|
||||||
|
RECREATE_TAG=true
|
||||||
|
PRERELEASE=true
|
||||||
|
TITLE="Latest main build"
|
||||||
|
BODY="Automatisch aus $SHA gebaut - nur zum schnellen Testen/Deployen, kein offizielles Release."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
api() { curl -sS -H "Authorization: token $TOKEN" "$@"; }
|
||||||
|
|
||||||
|
# Always drop an existing release for this tag first: the rolling tag is
|
||||||
|
# rebuilt on every push, and a moved version tag has to publish the new
|
||||||
|
# build instead of failing on a duplicate asset name.
|
||||||
|
api "$GITEA_API/repos/$REPO/releases/tags/$TAG" -o /tmp/old_release.json
|
||||||
OLD_ID=$(node -e "try{const j=require('/tmp/old_release.json');console.log(j.id||'')}catch(e){console.log('')}")
|
OLD_ID=$(node -e "try{const j=require('/tmp/old_release.json');console.log(j.id||'')}catch(e){console.log('')}")
|
||||||
if [ -n "$OLD_ID" ]; then
|
if [ -n "$OLD_ID" ]; then
|
||||||
curl -s -X DELETE -H "Authorization: token $TOKEN" \
|
api -X DELETE "$GITEA_API/repos/$REPO/releases/$OLD_ID"
|
||||||
"$GITEA_API/repos/$REPO/releases/$OLD_ID"
|
|
||||||
fi
|
fi
|
||||||
curl -s -X DELETE -H "Authorization: token $TOKEN" \
|
|
||||||
"$GITEA_API/repos/$REPO/tags/$TAG" > /dev/null || true
|
|
||||||
|
|
||||||
# Re-create the tag+release pointing at the commit that was just built
|
# Only latest-main gets its tag recreated - a version tag was just pushed
|
||||||
curl -s -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
# and must survive, otherwise this job would delete what triggered it.
|
||||||
-d "{\"tag_name\":\"$TAG\",\"target_commitish\":\"$SHA\",\"name\":\"Latest main build\",\"body\":\"Automatisch aus $SHA gebaut - nur zum schnellen Testen/Deployen, kein offizielles Release.\",\"prerelease\":true}" \
|
if [ "$RECREATE_TAG" = true ]; then
|
||||||
|
api -X DELETE "$GITEA_API/repos/$REPO/tags/$TAG" > /dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
api -X POST -H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\":\"$TAG\",\"target_commitish\":\"$SHA\",\"name\":\"$TITLE\",\"body\":\"$BODY\",\"prerelease\":$PRERELEASE}" \
|
||||||
"$GITEA_API/repos/$REPO/releases" -o /tmp/new_release.json
|
"$GITEA_API/repos/$REPO/releases" -o /tmp/new_release.json
|
||||||
NEW_ID=$(node -e "console.log(require('/tmp/new_release.json').id)")
|
NEW_ID=$(node -e "console.log(require('/tmp/new_release.json').id)")
|
||||||
|
|
||||||
curl -s -X POST -H "Authorization: token $TOKEN" \
|
api -X POST -F "attachment=@$ASSET" \
|
||||||
-F "attachment=@$ASSET" \
|
|
||||||
"$GITEA_API/repos/$REPO/releases/$NEW_ID/assets?name=workflow_deck_automation.tar.gz"
|
"$GITEA_API/repos/$REPO/releases/$NEW_ID/assets?name=workflow_deck_automation.tar.gz"
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
name: Build release artifact
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
package:
|
|
||||||
runs-on: gitea-runner-server03
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v7
|
|
||||||
with:
|
|
||||||
node-version: '24'
|
|
||||||
|
|
||||||
- uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: '8.3'
|
|
||||||
coverage: none
|
|
||||||
|
|
||||||
- name: Install JS dependencies
|
|
||||||
run: npm install
|
|
||||||
|
|
||||||
- name: Build frontend
|
|
||||||
run: npm run build
|
|
||||||
|
|
||||||
- name: Package appstore artifact
|
|
||||||
run: make appstore
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: workflow_deck_automation-${{ gitea.ref_name }}
|
|
||||||
path: build/artifacts/appstore/*.tar.gz
|
|
||||||
@@ -47,17 +47,24 @@ There is no meaningful way to test the Deck integration itself outside a real Ne
|
|||||||
|
|
||||||
1. Bump `<version>` in `appinfo/info.xml` and `version` in `package.json` (keep them in sync).
|
1. Bump `<version>` in `appinfo/info.xml` and `version` in `package.json` (keep them in sync).
|
||||||
2. Commit, push to `main`, confirm the lint/phpunit pipelines are green.
|
2. Commit, push to `main`, confirm the lint/phpunit pipelines are green.
|
||||||
3. `git tag vX.Y.Z && git push origin vX.Y.Z` — this triggers `.gitea/workflows/build-release.yml`, which builds the frontend and runs `make appstore`, producing `build/artifacts/appstore/workflow_deck_automation.tar.gz` as a **workflow artifact**, downloadable from the Gitea Actions run for that tag.
|
3. `git tag vX.Y.Z && git push origin vX.Y.Z` — this triggers the tag branch of `.gitea/workflows/build-main.yml`, which builds the frontend, runs `make appstore`, and attaches `workflow_deck_automation.tar.gz` as the asset on a normal (non-pre-) release for that tag.
|
||||||
|
|
||||||
**If the tagged build fails and no artifact was ever produced**, fix the issue on `main` and *move* the existing tag to the fixed commit instead of bumping to a new version number (`git tag -d vX.Y.Z && git tag -a vX.Y.Z -m "..." && git push origin :refs/tags/vX.Y.Z && git push origin vX.Y.Z`) — this is how `v0.1.0` was handled through several `build-release.yml` fixes before it first built successfully. Once a version has actually produced a published artifact, don't move its tag anymore; bump normally instead.
|
**If the tagged build fails and no artifact was ever produced**, fix the issue on `main` and *move* the existing tag to the fixed commit instead of bumping to a new version number (`git tag -d vX.Y.Z && git tag -a vX.Y.Z -m "..." && git push origin :refs/tags/vX.Y.Z && git push origin vX.Y.Z`) — this is how `v0.1.0` was handled through several build fixes before it first built successfully. The job deletes any pre-existing *release* for the tag before creating the new one, so a moved tag republishes cleanly instead of failing on a duplicate asset name. Once a version has actually produced a published artifact, don't move its tag anymore; bump normally instead.
|
||||||
|
|
||||||
**`actions/upload-artifact` must stay on `@v3`, not `@v4`+.** This self-hosted Gitea instance identifies as GHES to the official `actions/*` JS actions, which refuse to run `@actions/artifact` v2.0.0+ (used internally by `upload-artifact@v4` and later) against GHES: `GHESNotSupportedError`. `actions/checkout` and `actions/setup-node` don't have this problem and can be kept on their latest majors — only the artifact upload/download actions are affected, because they're the ones that talk to a version-specific backend API rather than just running local commands.
|
**No `actions/upload-artifact` anywhere — and don't reintroduce it.** This self-hosted Gitea instance identifies as GHES to the official `actions/*` JS actions, which refuse to run `@actions/artifact` v2.0.0+ (used internally by `upload-artifact@v4` and later) against GHES: `GHESNotSupportedError`, which pinned us to `@v3` for a while. Publishing through the Gitea release REST API with `curl` sidesteps that version-specific backend API entirely, so the build workflow uses that instead. `actions/checkout` and `actions/setup-node` were never affected — only the artifact upload/download actions are, because they're the ones talking to a versioned backend rather than just running local commands — and can stay on their latest majors.
|
||||||
|
|
||||||
### Rolling build for manual server deploys
|
### One workflow, two triggers
|
||||||
|
|
||||||
`.gitea/workflows/build-main.yml` builds the same appstore package on every push to `main` (no tag needed) and publishes it as the asset on a rolling pre-release tagged `latest-main` — the release+tag are deleted and recreated each run via the Gitea REST API (`curl`, not an `actions/*` artifact action, so the GHES artifact-API restriction above doesn't apply here), so the download URL for `workflow_deck_automation.tar.gz` on that release always serves the newest `main` build. This exists specifically so a build can be grabbed and copied onto the server (`nextcloud.gnilebein.de`) without cutting a version tag first — it produced `v0.1.0`-shaped raw-repo confusion once already when the app directory was populated by copying the git working tree instead of a built package; this gives a one-click alternative to that mistake.
|
`.gitea/workflows/build-main.yml` handles both cases; there is deliberately no separate release workflow, because the build steps were identical and only the publishing target differed. It runs on pushes to `main` **and** on `v*` tags, and the final step branches on `gitea.ref`:
|
||||||
|
|
||||||
This requires a **repo secret named `RELEASE_TOKEN`**, a Gitea Personal Access Token with repo write access (create it under the repo owner's Gitea user settings → Applications, then add it under the repo's Settings → Actions → Secrets). Without that secret, the `curl` calls to the Gitea release API in the job will fail with 401s — the workflow doesn't fall back to anything else.
|
- **`main`** → rolling pre-release tagged `latest-main`; both the release *and* the tag are deleted and recreated each run, so the download URL for `workflow_deck_automation.tar.gz` always serves the newest `main` build.
|
||||||
|
- **`v*`** → normal release on the tag that was just pushed. Only a stale release is deleted here — **never the tag**, since deleting it would destroy the ref that triggered the run. That's what the `RECREATE_TAG` flag guards.
|
||||||
|
|
||||||
|
The `latest-main` half exists so a build can be grabbed and copied onto the server (`nextcloud.gnilebein.de`) without cutting a version tag first — it produced `v0.1.0`-shaped raw-repo confusion once already when the app directory was populated by copying the git working tree instead of a built package; this gives a one-click alternative to that mistake.
|
||||||
|
|
||||||
|
Note that `make appstore` is pure `mkdir`/`tar`/`rm` — no `composer`, no `php`. The workflow therefore needs Node only; a `setup-php` step here would be dead weight (the deleted release workflow had one).
|
||||||
|
|
||||||
|
Both halves require a **repo secret named `RELEASE_TOKEN`**, a Gitea Personal Access Token with repo write access (create it under the repo owner's Gitea user settings → Applications, then add it under the repo's Settings → Actions → Secrets). Without that secret, the `curl` calls to the Gitea release API in the job will fail with 401s — the workflow doesn't fall back to anything else.
|
||||||
|
|
||||||
## Data flow / architecture map
|
## Data flow / architecture map
|
||||||
|
|
||||||
@@ -68,11 +75,11 @@ This requires a **repo secret named `RELEASE_TOKEN`**, a Gitea Personal Access T
|
|||||||
|
|
||||||
## Composer/npm lockfiles are intentionally not committed
|
## Composer/npm lockfiles are intentionally not committed
|
||||||
|
|
||||||
`.gitignore` excludes `composer.lock` and `package-lock.json`. This means CI must use `composer install`/`npm install`, not `composer install --no-dev` assumptions tied to a lock file or `npm ci` (which hard-requires a lock file and will fail otherwise — this has already broken `build-release.yml` once). If you add a lockfile-dependent step, either commit the lockfile deliberately or keep using the non-`ci`/non-locked install form.
|
`.gitignore` excludes `composer.lock` and `package-lock.json`. This means CI must use `composer install`/`npm install`, not `composer install --no-dev` assumptions tied to a lock file or `npm ci` (which hard-requires a lock file and will fail otherwise — this has already broken the build workflow once). If you add a lockfile-dependent step, either commit the lockfile deliberately or keep using the non-`ci`/non-locked install form.
|
||||||
|
|
||||||
## Frontend build gotchas already hit (don't reintroduce)
|
## Frontend build gotchas already hit (don't reintroduce)
|
||||||
|
|
||||||
None of these were discoverable locally — there's no npm here (see "Local-only notes" below), so all three were only caught by an actual Gitea Actions run against `build-release.yml`:
|
None of these were discoverable locally — there's no npm here (see "Local-only notes" below), so all three were only caught by an actual Gitea Actions run against the build workflow:
|
||||||
|
|
||||||
- **`vite` version must satisfy `@nextcloud/vite-config`'s peer requirement.** `package.json` pins `@nextcloud/vite-config` to `^2.2.0`, which currently resolves to `2.5.4` and peer-requires `vite@^7.3.6`. If you bump `@nextcloud/vite-config`, check its `peerDependencies.vite` and bump our `vite` devDependency to match, or `npm install` fails with `ERESOLVE`.
|
- **`vite` version must satisfy `@nextcloud/vite-config`'s peer requirement.** `package.json` pins `@nextcloud/vite-config` to `^2.2.0`, which currently resolves to `2.5.4` and peer-requires `vite@^7.3.6`. If you bump `@nextcloud/vite-config`, check its `peerDependencies.vite` and bump our `vite` devDependency to match, or `npm install` fails with `ERESOLVE`.
|
||||||
- **`package.json` needs `"type": "module"`.** `vite.config.js` uses `import`/`export` syntax and `@nextcloud/vite-config` is ESM-only; without `"type": "module"`, Node treats `.js` as CommonJS and `vite build` fails trying to `require()` an ESM-only package.
|
- **`package.json` needs `"type": "module"`.** `vite.config.js` uses `import`/`export` syntax and `@nextcloud/vite-config` is ESM-only; without `"type": "module"`, Node treats `.js` as CommonJS and `vite build` fails trying to `require()` an ESM-only package.
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ In [`.gitea/workflows/`](.gitea/workflows/):
|
|||||||
| `lint-php.yml` | `php -l` über eine PHP-8.2–8.4-Matrix |
|
| `lint-php.yml` | `php -l` über eine PHP-8.2–8.4-Matrix |
|
||||||
| `lint-info-xml.yml` | validiert `appinfo/info.xml` gegen das Appstore-XML-Schema |
|
| `lint-info-xml.yml` | validiert `appinfo/info.xml` gegen das Appstore-XML-Schema |
|
||||||
| `phpunit.yml` | führt die PHPUnit-Tests aus (SQLite/rein logisch, kein DB-Service nötig) |
|
| `phpunit.yml` | führt die PHPUnit-Tests aus (SQLite/rein logisch, kein DB-Service nötig) |
|
||||||
| `build-release.yml` | baut bei Push eines `v*`-Tags das Frontend und paketiert das Appstore-Archiv |
|
| `build-main.yml` | baut Frontend + Appstore-Archiv und veröffentlicht es als Release-Asset — bei Push auf `main` als rollendes Pre-Release `latest-main`, bei Push eines `v*`-Tags als reguläres Release |
|
||||||
|
|
||||||
## Datenmodell
|
## Datenmodell
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user