Skip to content

Commit aec4f73

Browse files
authored
Merge pull request #3 from LibreCodeCoop/feat/nextcloud-release-plan
feat: add reusable Nextcloud release plan
2 parents a4f1176 + 4b0c528 commit aec4f73

6 files changed

Lines changed: 597 additions & 0 deletions

File tree

‎.github/workflows/actionlint.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,8 @@ jobs:
2828
with:
2929
version: 1.7.12
3030
shellcheck: true
31+
# actionlint does not support GitHub's $/ self-repository syntax yet.
32+
# Remove this ignore when https://github.com/rhysd/actionlint/issues/711 is fixed.
33+
flags: >-
34+
-ignore
35+
specifying.action.*\$/.*invalid.format.because.ref.is.missing

‎.github/workflows/release-plan.yml‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
2+
# SPDX-License-Identifier: AGPL-3.0-or-later
3+
4+
name: Nextcloud release plan
5+
6+
on:
7+
workflow_call:
8+
inputs:
9+
version:
10+
description: Release version in MAJOR.MINOR.PATCH form
11+
required: true
12+
type: string
13+
stable_branch:
14+
description: Stable branch that is allowed to release
15+
required: true
16+
type: string
17+
milestone:
18+
description: Milestone title that must be closed with no open issues
19+
required: false
20+
type: string
21+
default: ''
22+
blocker_queries:
23+
description: JSON array of GitHub issue search fragments that must return zero open items
24+
required: false
25+
type: string
26+
default: '[]'
27+
appinfo_path:
28+
description: Path to the Nextcloud app info.xml
29+
required: false
30+
type: string
31+
default: appinfo/info.xml
32+
changelog_path:
33+
description: Path to the changelog
34+
required: false
35+
type: string
36+
default: CHANGELOG.md
37+
38+
permissions:
39+
contents: read
40+
issues: read
41+
pull-requests: read
42+
43+
jobs:
44+
plan:
45+
name: Release plan
46+
runs-on: ubuntu-latest
47+
timeout-minutes: 10
48+
steps:
49+
- name: Checkout caller
50+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
51+
with:
52+
persist-credentials: false
53+
54+
- name: Build release plan
55+
uses: $/actions/release-plan
56+
with:
57+
version: ${{ inputs.version }}
58+
stable-branch: ${{ inputs.stable_branch }}
59+
milestone: ${{ inputs.milestone }}
60+
blocker-queries: ${{ inputs.blocker_queries }}
61+
appinfo-path: ${{ inputs.appinfo_path }}
62+
changelog-path: ${{ inputs.changelog_path }}
63+
github-token: ${{ github.token }}

‎actions/release-plan/action.yml‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
2+
# SPDX-License-Identifier: AGPL-3.0-or-later
3+
4+
name: Nextcloud release plan
5+
description: Validate whether a Nextcloud app release is ready
6+
7+
inputs:
8+
version:
9+
description: Release version in MAJOR.MINOR.PATCH form
10+
required: true
11+
stable-branch:
12+
description: Stable branch that is allowed to release
13+
required: true
14+
milestone:
15+
description: Milestone title that must be closed with no open issues
16+
required: false
17+
default: ''
18+
blocker-queries:
19+
description: JSON array of GitHub issue search fragments that must return zero open items
20+
required: false
21+
default: '[]'
22+
appinfo-path:
23+
description: Path to the Nextcloud app info.xml
24+
required: false
25+
default: appinfo/info.xml
26+
changelog-path:
27+
description: Path to the changelog
28+
required: false
29+
default: CHANGELOG.md
30+
github-token:
31+
description: GitHub token used for milestone and blocker checks
32+
required: true
33+
34+
runs:
35+
using: composite
36+
steps:
37+
- name: Build release plan
38+
shell: bash
39+
env:
40+
GITHUB_TOKEN: ${{ inputs.github-token }}
41+
RELEASE_PLAN_VERSION: ${{ inputs.version }}
42+
RELEASE_PLAN_STABLE_BRANCH: ${{ inputs.stable-branch }}
43+
RELEASE_PLAN_MILESTONE: ${{ inputs.milestone }}
44+
RELEASE_PLAN_BLOCKER_QUERIES: ${{ inputs.blocker-queries }}
45+
RELEASE_PLAN_APPINFO_PATH: ${{ inputs.appinfo-path }}
46+
RELEASE_PLAN_CHANGELOG_PATH: ${{ inputs.changelog-path }}
47+
run: python3 "$GITHUB_ACTION_PATH/../../scripts/release_plan.py"

‎docs/nextcloud-release.md‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
3+
SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
# Nextcloud release planning
7+
8+
The reusable release-plan workflow is intentionally non-mutating. It validates
9+
release prerequisites before any tag, GitHub Release, signing or App Store
10+
publication occurs.
11+
12+
## Architecture
13+
14+
The reusable workflow owns orchestration concerns: permissions, runner selection
15+
and checking out the caller plus the workflow tooling repository.
16+
17+
The release-plan operation itself is exposed as the local composite action
18+
`actions/release-plan`. The action maps its declared inputs to a small,
19+
namespaced environment contract and invokes `scripts/release_plan.py`.
20+
21+
Business rules, input parsing, GitHub API checks, exit status and step-summary
22+
rendering live in the Python script and are covered by unit tests. The workflow
23+
does not contain release decision logic.
24+
25+
This follows GitHub's distinction between reusable workflows, which reuse whole
26+
workflow/job structures, and composite actions, which encapsulate a reusable
27+
sequence of steps within a job.
28+
29+
## Checks
30+
31+
The first implementation validates:
32+
33+
- semantic release version in `MAJOR.MINOR.PATCH` form;
34+
- execution from the declared stable branch;
35+
- `appinfo/info.xml` version matches the requested release;
36+
- changelog contains a level-2 section for the requested version;
37+
- optional milestone exists, is closed and has zero open issues;
38+
- optional GitHub blocker queries return zero open issues or pull requests.
39+
40+
Blocker queries are caller-owned. This keeps project conventions out of the
41+
shared workflow. A caller can model pending backports with a label query without
42+
making that label part of the reusable workflow contract.
43+
44+
## Example caller
45+
46+
```yaml
47+
jobs:
48+
release-plan:
49+
uses: LibreCodeCoop/github-workflows/.github/workflows/release-plan.yml@<full-release-sha> # v0.1.0
50+
with:
51+
version: 16.0.0
52+
stable_branch: stable36
53+
milestone: 16.0.0
54+
blocker_queries: '["label:\"backport pending\""]'
55+
```
56+
57+
The workflow only needs read permissions. Signing keys and App Store tokens are
58+
deliberately not accepted by the planning stage.
59+
60+
Publication will be implemented as a separate privileged workflow after the
61+
planning contract is proven with LibreSign and at least one additional app.

0 commit comments

Comments
 (0)