Merge pull request #418 from cbcoutinho/feature/appstore-deployment

feat: add App Store deployment and commitizen monorepo support
This commit is contained in:
Chris Coutinho
2025-12-19 20:33:40 +01:00
committed by GitHub
15 changed files with 662 additions and 8 deletions
@@ -0,0 +1,87 @@
name: Build and Publish Astrolabe App Release
on:
push:
tags:
- 'astrolabe-v*'
env:
APP_NAME: astrolabe
APP_DIR: third_party/astrolabe
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get version from tag
id: tag
run: |
echo "TAG=${GITHUB_REF#refs/tags/astrolabe-v}" >> $GITHUB_OUTPUT
- name: Validate version in info.xml matches tag
working-directory: ${{ env.APP_DIR }}
run: |
INFO_VERSION=$(sed -n 's/.*<version>\(.*\)<\/version>.*/\1/p' appinfo/info.xml | tr -d '\t')
if [ "$INFO_VERSION" != "${{ steps.tag.outputs.TAG }}" ]; then
echo "Version mismatch: info.xml has $INFO_VERSION but tag is ${{ steps.tag.outputs.TAG }}"
exit 1
fi
echo "Version validated: $INFO_VERSION"
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
coverage: none
- name: Checkout Nextcloud server (for signing)
uses: actions/checkout@v4
with:
repository: nextcloud/server
ref: stable30
path: server
- name: Install dependencies and build
working-directory: ${{ env.APP_DIR }}
run: |
composer install --no-dev --optimize-autoloader
npm ci
npm run build
- name: Setup signing certificate
run: |
mkdir -p $HOME/.nextcloud/certificates
echo "${{ secrets.APP_PRIVATE_KEY }}" > $HOME/.nextcloud/certificates/${{ env.APP_NAME }}.key
echo "${{ secrets.APP_PUBLIC_CRT }}" > $HOME/.nextcloud/certificates/${{ env.APP_NAME }}.crt
- name: Build app store package
working-directory: ${{ env.APP_DIR }}
run: make appstore server_dir=${{ github.workspace }}/server
- name: Create GitHub release and attach tarball
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ env.APP_DIR }}/build/artifacts/${{ env.APP_NAME }}.tar.gz
asset_name: ${{ env.APP_NAME }}-${{ steps.tag.outputs.TAG }}.tar.gz
tag: ${{ github.ref }}
release_name: Astrolabe ${{ steps.tag.outputs.TAG }}
prerelease: ${{ contains(steps.tag.outputs.TAG, '-alpha') || contains(steps.tag.outputs.TAG, '-beta') || contains(steps.tag.outputs.TAG, '-rc') }}
- name: Upload to Nextcloud App Store
uses: R0Wi/nextcloud-appstore-push-action@v1.0.6
with:
app_name: ${{ env.APP_NAME }}
appstore_token: ${{ secrets.APPSTORE_TOKEN }}
download_url: ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ github.ref_name }}/${{ env.APP_NAME }}-${{ steps.tag.outputs.TAG }}.tar.gz
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}
nightly: ${{ contains(steps.tag.outputs.TAG, '-alpha') || contains(steps.tag.outputs.TAG, '-beta') || contains(steps.tag.outputs.TAG, '-rc') }}
+7
View File
@@ -1,3 +1,10 @@
# Changelog - MCP Server
All notable changes to the Nextcloud MCP Server will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [PEP 440](https://peps.python.org/pep-0440/).
## v0.53.0 (2025-12-19)
### Feat
+116
View File
@@ -0,0 +1,116 @@
# Contributing to Nextcloud MCP Server
## Version Management
This monorepo uses commitizen for version management with **independent versioning** for three components:
### Components
| Component | Scope | Bump Command | Tag Example |
|-----------|-------|--------------|-------------|
| MCP Server | `mcp` or none | `./scripts/bump-mcp.sh` | `v0.54.0` |
| Helm Chart | `helm` | `./scripts/bump-helm.sh` | `nextcloud-mcp-server-0.54.0` |
| Astrolabe App | `astrolabe` | `./scripts/bump-astrolabe.sh` | `astrolabe-v0.2.0` |
### Commit Message Format
Use conventional commits with **scopes** to target specific components:
```bash
# MCP server changes
feat(mcp): add calendar sync API
fix(mcp): resolve authentication bug
# Helm chart changes
feat(helm): add resource limits
docs(helm): update values documentation
# Astrolabe app changes
feat(astrolabe): add dark mode toggle
fix(astrolabe): resolve search UI bug
```
**Unscoped commits** default to the MCP server:
```bash
feat: add new feature # → MCP server (v0.54.0)
```
### Release Workflow
#### 1. Make Changes with Scoped Commits
```bash
git commit -m "feat(astrolabe): add dark mode toggle"
git commit -m "feat(helm): add ingress annotations"
git commit -m "feat(mcp): add calendar sync"
```
#### 2. Bump Component Versions
```bash
# Bump MCP server (reads commits with scope=mcp or unscoped)
./scripts/bump-mcp.sh
# → Creates tag: v0.54.0
# → Updates: pyproject.toml, Chart.yaml:appVersion
# Bump Helm chart (reads commits with scope=helm)
./scripts/bump-helm.sh
# → Creates tag: nextcloud-mcp-server-0.54.0
# → Updates: Chart.yaml:version
# Bump Astrolabe (reads commits with scope=astrolabe)
./scripts/bump-astrolabe.sh
# → Creates tag: astrolabe-v0.2.0
# → Updates: info.xml, package.json
```
#### 3. Push Tags
```bash
git push --follow-tags
```
### Changelog Filtering
Each component maintains its own `CHANGELOG.md`:
- **MCP Server**: `CHANGELOG.md` (root) - includes `feat(mcp):` and unscoped commits
- **Helm Chart**: `charts/nextcloud-mcp-server/CHANGELOG.md` - includes `feat(helm):` only
- **Astrolabe**: `third_party/astrolabe/CHANGELOG.md` - includes `feat(astrolabe):` only
### Manual Version Bumps
For specific increments:
```bash
# Patch bump (0.53.0 → 0.53.1)
uv run cz bump --increment PATCH
# Minor bump (0.53.0 → 0.54.0)
uv run cz bump --increment MINOR
# Major bump (0.53.0 → 1.0.0)
uv run cz bump --increment MAJOR
# For non-MCP components, use --config
cd charts/nextcloud-mcp-server
uv run cz --config .cz.toml bump --increment MINOR
```
### Versioning Philosophy
- **MCP Server**: Follows PEP 440, `major_version_zero = true` (0.x.x for pre-1.0)
- **Helm Chart**: Follows PEP 440, starts at 0.53.0 (continues from current)
- **Astrolabe**: Follows PEP 440, `major_version_zero = true` (0.x.x for alpha/beta)
### Chart.yaml Version vs appVersion
The Helm chart has TWO version fields:
- **`version`**: Chart packaging version (bumped by `feat(helm):`)
- Example: `0.53.0``0.54.0` when adding resource limits
- **`appVersion`**: MCP server version being deployed (bumped by `feat(mcp):`)
- Example: `"0.53.0"``"0.54.0"` when MCP server releases
This allows the chart to evolve independently from the application.
+24
View File
@@ -0,0 +1,24 @@
[tool.commitizen]
name = "cz_conventional_commits"
version = "0.53.0"
tag_format = "nextcloud-mcp-server-$version"
version_scheme = "semver"
update_changelog_on_bump = true
major_version_zero = true
# Update chart version only (NOT appVersion)
version_files = [
"Chart.yaml:^version:"
]
# Ignore tags from other components
ignored_tag_formats = [
"v*", # MCP server tags
"astrolabe-v*", # Astrolabe tags
]
# Filter commits by scope
[tool.commitizen.customize]
changelog_pattern = "^(feat|fix|docs|refactor|perf|test|build|ci|chore)\\(helm\\)(!)?:"
schema_pattern = "^(feat|fix|docs|refactor|perf|test|build|ci|chore)\\(helm\\)(!)?:\\s.+"
message_template = "{{change_type}}(helm): {{message}}"
+18
View File
@@ -0,0 +1,18 @@
# Changelog - Helm Chart
All notable changes to the Helm chart will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.53.0] - 2024-12-19
### Added
- Initial independent versioning release
- Support for Nextcloud MCP server deployment
- Qdrant subchart integration
- Ollama subchart integration
- Configurable resource limits
- Grafana dashboard annotations
+12 -3
View File
@@ -89,14 +89,23 @@ version_scheme = "pep440"
version_provider = "uv"
update_changelog_on_bump = true
major_version_zero = true
# MCP server version files + Helm appVersion
version_files = [
"charts/nextcloud-mcp-server/Chart.yaml:appVersion",
"charts/nextcloud-mcp-server/Chart.yaml:version"
"charts/nextcloud-mcp-server/Chart.yaml:^appVersion:",
]
# Ignore tags from other components
ignored_tag_formats = [
"nextcloud-mcp-server-*"
"nextcloud-mcp-server-*", # Helm chart tags
"astrolabe-v*", # Astrolabe tags
]
# Filter commits by scope (mcp or unscoped)
[tool.commitizen.customize]
changelog_pattern = "^(feat|fix|docs|refactor|perf|test|build|ci|chore)(\\(mcp\\))?(!)?:"
schema_pattern = "^(feat|fix|docs|refactor|perf|test|build|ci|chore)(\\(mcp\\))?(!)?:\\s.+"
[tool.ruff.lint]
extend-select = ["I"]
+56
View File
@@ -0,0 +1,56 @@
#!/bin/bash
# Bump Astrolabe app version
set -euo pipefail
# Validate dependencies
command -v uv >/dev/null 2>&1 || {
echo "❌ Error: uv not found" >&2
echo " Install from https://docs.astral.sh/uv/" >&2
exit 1
}
# Validate Astrolabe directory exists
if [ ! -d "third_party/astrolabe" ]; then
echo "❌ Error: Must run from repository root (third_party/astrolabe not found)" >&2
exit 1
fi
cd third_party/astrolabe
# Validate required files exist
if [ ! -f "appinfo/info.xml" ]; then
echo "❌ Error: appinfo/info.xml not found" >&2
exit 1
fi
if [ ! -f "package.json" ]; then
echo "❌ Error: package.json not found" >&2
exit 1
fi
echo "Bumping Astrolabe version..."
# Run commitizen bump and capture output
if ! output=$(uv run cz --config .cz.toml bump --yes 2>&1); then
cd ../..
echo "❌ Error: Version bump failed" >&2
echo "$output" >&2
echo "" >&2
echo "Common causes:" >&2
echo " - No commits with scope 'astrolabe' since last version" >&2
echo " - No conventional commits found (use feat(astrolabe):, fix(astrolabe):, etc.)" >&2
echo " - Git working directory not clean" >&2
exit 1
fi
echo "$output"
echo ""
echo "✓ Astrolabe version bumped successfully"
echo " Updated: appinfo/info.xml, package.json"
echo " Tag format: astrolabe-v\${version}"
echo ""
echo "Next steps:"
echo " cd ../.."
echo " git push --follow-tags"
cd ../..
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
# Bump Helm chart version
set -euo pipefail
# Validate dependencies
command -v uv >/dev/null 2>&1 || {
echo "❌ Error: uv not found" >&2
echo " Install from https://docs.astral.sh/uv/" >&2
exit 1
}
# Validate Helm chart directory exists
if [ ! -d "charts/nextcloud-mcp-server" ]; then
echo "❌ Error: Must run from repository root (charts/ not found)" >&2
exit 1
fi
cd charts/nextcloud-mcp-server
# Validate Chart.yaml exists
if [ ! -f "Chart.yaml" ]; then
echo "❌ Error: Chart.yaml not found" >&2
exit 1
fi
echo "Bumping Helm chart version..."
# Run commitizen bump and capture output
if ! output=$(uv run cz --config .cz.toml bump --yes 2>&1); then
cd ../..
echo "❌ Error: Version bump failed" >&2
echo "$output" >&2
echo "" >&2
echo "Common causes:" >&2
echo " - No commits with scope 'helm' since last version" >&2
echo " - No conventional commits found (use feat(helm):, fix(helm):, etc.)" >&2
echo " - Git working directory not clean" >&2
exit 1
fi
echo "$output"
echo ""
echo "✓ Helm chart version bumped successfully"
echo " Updated: Chart.yaml:version"
echo " Tag format: nextcloud-mcp-server-\${version}"
echo " Note: appVersion stays at MCP server version"
echo ""
echo "Next steps:"
echo " cd ../.."
echo " git push --follow-tags"
cd ../..
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# Bump MCP server version
set -euo pipefail
# Validate dependencies
command -v uv >/dev/null 2>&1 || {
echo "❌ Error: uv not found" >&2
echo " Install from https://docs.astral.sh/uv/" >&2
exit 1
}
# Validate we're in the repository root
if [ ! -f "pyproject.toml" ]; then
echo "❌ Error: Must run from repository root (pyproject.toml not found)" >&2
exit 1
fi
echo "Bumping MCP server version..."
# Run commitizen bump and capture output
if ! output=$(uv run cz bump --yes 2>&1); then
echo "❌ Error: Version bump failed" >&2
echo "$output" >&2
echo "" >&2
echo "Common causes:" >&2
echo " - No commits since last version" >&2
echo " - No conventional commits found (use feat:, fix:, etc.)" >&2
echo " - Git working directory not clean" >&2
exit 1
fi
echo "$output"
echo ""
echo "✓ MCP server version bumped successfully"
echo " Updated: pyproject.toml, Chart.yaml:appVersion"
echo " Tag format: v\${version}"
echo ""
echo "Next steps:"
echo " git push --follow-tags"
+102
View File
@@ -0,0 +1,102 @@
#!/bin/bash
# Test commitizen scope filtering patterns
set -uo pipefail
echo "Testing commitizen scope filtering patterns..."
echo
# Regex patterns from configs
MCP_PATTERN='^(feat|fix|docs|refactor|perf|test|build|ci|chore)(\(mcp\))?(!)?:'
HELM_PATTERN='^(feat|fix|docs|refactor|perf|test|build|ci|chore)\(helm\)(!)?:'
ASTROLABE_PATTERN='^(feat|fix|docs|refactor|perf|test|build|ci|chore)\(astrolabe\)(!)?:'
test_pattern() {
local message="$1"
local pattern="$2"
if echo "$message" | grep -qE "$pattern"; then
return 0
else
return 1
fi
}
run_test() {
local message="$1"
local expected="$2"
local matched_components=()
# Check which components match
if test_pattern "$message" "$MCP_PATTERN"; then
matched_components+=("mcp")
fi
if test_pattern "$message" "$HELM_PATTERN"; then
matched_components+=("helm")
fi
if test_pattern "$message" "$ASTROLABE_PATTERN"; then
matched_components+=("astrolabe")
fi
# Convert array to space-separated string, or "none" if empty
local matched
if [ ${#matched_components[@]} -eq 0 ]; then
matched="none"
else
matched="${matched_components[*]}"
fi
# Validate expectation
if [ "$matched" = "$expected" ]; then
echo "✓ PASS: '$message'"
echo " → Matched: $matched"
return 0
else
echo "✗ FAIL: '$message'"
echo " → Matched: $matched (expected: $expected)"
return 1
fi
}
# Run all test cases
failed=0
passed=0
# MCP server commits (scope=mcp or unscoped)
run_test "feat: add new feature" "mcp" && passed=$((passed+1)) || failed=$((failed+1))
run_test "feat(mcp): add API endpoint" "mcp" && passed=$((passed+1)) || failed=$((failed+1))
run_test "fix(mcp): resolve authentication bug" "mcp" && passed=$((passed+1)) || failed=$((failed+1))
run_test "docs: update README" "mcp" && passed=$((passed+1)) || failed=$((failed+1))
# Helm chart commits
run_test "feat(helm): add resource limits" "helm" && passed=$((passed+1)) || failed=$((failed+1))
run_test "fix(helm): correct values schema" "helm" && passed=$((passed+1)) || failed=$((failed+1))
run_test "docs(helm): update deployment guide" "helm" && passed=$((passed+1)) || failed=$((failed+1))
# Astrolabe commits
run_test "feat(astrolabe): add dark mode" "astrolabe" && passed=$((passed+1)) || failed=$((failed+1))
run_test "fix(astrolabe): resolve UI bug" "astrolabe" && passed=$((passed+1)) || failed=$((failed+1))
run_test "perf(astrolabe): optimize rendering" "astrolabe" && passed=$((passed+1)) || failed=$((failed+1))
# Breaking changes
run_test "feat(mcp)!: breaking API change" "mcp" && passed=$((passed+1)) || failed=$((failed+1))
run_test "feat(helm)!: rename values" "helm" && passed=$((passed+1)) || failed=$((failed+1))
run_test "feat(astrolabe)!: remove deprecated feature" "astrolabe" && passed=$((passed+1)) || failed=$((failed+1))
# Invalid commits (should not match any)
run_test "feat(invalid): test" "none" && passed=$((passed+1)) || failed=$((failed+1))
run_test "random commit message" "none" && passed=$((passed+1)) || failed=$((failed+1))
run_test "feat (mcp): space before scope" "none" && passed=$((passed+1)) || failed=$((failed+1))
# Summary
echo
echo "=========================================="
echo "Results: $passed passed, $failed failed"
echo "=========================================="
if [ $failed -gt 0 ]; then
echo "❌ Some tests failed - scope patterns may need adjustment"
exit 1
else
echo "✅ All tests passed - scope patterns working correctly"
exit 0
fi
+25
View File
@@ -0,0 +1,25 @@
[tool.commitizen]
name = "cz_conventional_commits"
version = "0.1.0"
tag_format = "astrolabe-v$version"
version_scheme = "semver"
update_changelog_on_bump = true
major_version_zero = true
# Update Astrolabe-specific files only
version_files = [
"appinfo/info.xml:version",
"package.json:version"
]
# Ignore tags from other components
ignored_tag_formats = [
"v*", # MCP server tags
"nextcloud-mcp-server-*", # Helm chart tags
]
# Filter commits by scope
[tool.commitizen.customize]
changelog_pattern = "^(feat|fix|docs|refactor|perf|test|build|ci|chore)\\(astrolabe\\)(!)?:"
schema_pattern = "^(feat|fix|docs|refactor|perf|test|build|ci|chore)\\(astrolabe\\)(!)?:\\s.+"
message_template = "{{change_type}}(astrolabe): {{message}}"
+1
View File
@@ -8,6 +8,7 @@
/tests/.phpunit.cache
dist/
build/
node_modules/
js/
css/
+21 -4
View File
@@ -1,12 +1,29 @@
# Changelog
# Changelog - Astrolabe
All notable changes to this project will be documented in this file.
All notable changes to the Astrolabe Nextcloud app will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.1.0] - 2024-12-19
### Added
- First release
- Initial alpha release
- Semantic search across Notes, Files, Calendar, Deck, and Contacts
- Integration with Nextcloud Unified Search
- Personal settings UI for MCP server configuration
- Admin settings for global MCP server URL
- OAuth PKCE authentication flow
- Vector visualization of semantic relationships
- Hybrid search combining semantic and keyword matching
- Background content indexing
- Support for Nextcloud 30-32
### Notes
- This is an alpha release intended for early adopters and testing
- Requires external MCP server deployment
- See documentation for setup: https://github.com/cbcoutinho/nextcloud-mcp-server
+101
View File
@@ -0,0 +1,101 @@
# Nextcloud App Store Release Makefile for Astrolabe
#
# Based on: https://nextcloudappstore.readthedocs.io/en/latest/developer.html
app_name=astrolabe
project_dir=$(CURDIR)
build_dir=$(project_dir)/build
appstore_dir=$(build_dir)/artifacts
package_name=$(appstore_dir)/$(app_name)
cert_dir=$(HOME)/.nextcloud/certificates
# Nextcloud server path (configurable via environment variable)
server_dir?=../../server
occ=$(server_dir)/occ
# Signing
private_key=$(cert_dir)/$(app_name).key
certificate=$(cert_dir)/$(app_name).crt
sign_cmd=php $(occ) integrity:sign-app --privateKey=$(private_key) --certificate=$(certificate)
# Clean build artifacts
.PHONY: clean
clean:
rm -rf $(build_dir)
# Validate required dependencies
.PHONY: validate-deps
validate-deps:
@command -v composer >/dev/null 2>&1 || { echo "Error: composer not found. Install from https://getcomposer.org/"; exit 1; }
@command -v npm >/dev/null 2>&1 || { echo "Error: npm not found. Install Node.js from https://nodejs.org/"; exit 1; }
@command -v php >/dev/null 2>&1 || { echo "Error: php not found. Install PHP 8.1 or higher."; exit 1; }
@echo "✓ All dependencies found"
# Install PHP and Node dependencies
.PHONY: install-deps
install-deps: validate-deps
composer install --no-dev --optimize-autoloader
npm ci
# Build production frontend assets
.PHONY: build-frontend
build-frontend:
npm run build
# Run all linters
.PHONY: lint
lint:
composer lint
composer cs:check
npm run lint
npm run stylelint
# Assemble app files into build directory (exclude dev files)
.PHONY: assemble
assemble: clean install-deps build-frontend
mkdir -p $(package_name)
# Copy app files
rsync -av \
--exclude='.git*' \
--exclude='build/' \
--exclude='tests/' \
--exclude='node_modules/' \
--exclude='*.log' \
--exclude='.github/' \
--exclude='composer.json' \
--exclude='composer.lock' \
--exclude='package.json' \
--exclude='package-lock.json' \
--exclude='vite.config.js' \
--exclude='.eslintrc.js' \
--exclude='.php-cs-fixer.*' \
--exclude='psalm.xml' \
--exclude='*.iml' \
--exclude='.idea' \
--exclude='src/' \
./ $(package_name)/
# Validate signing prerequisites
.PHONY: validate-signing
validate-signing:
@test -f $(occ) || { echo "Error: Nextcloud server not found at $(server_dir)"; echo "Set server_dir variable: make appstore server_dir=/path/to/server"; exit 1; }
@test -f $(private_key) || { echo "Error: Private key not found at $(private_key)"; exit 1; }
@test -f $(certificate) || { echo "Error: Certificate not found at $(certificate)"; exit 1; }
@echo "✓ Signing prerequisites validated"
# Create signed release tarball for App Store
.PHONY: appstore
appstore: assemble validate-signing
# Sign the app
$(sign_cmd) --path=$(package_name)
# Create tarball
cd $(appstore_dir) && \
tar -czf $(app_name).tar.gz $(app_name)
# Show package info
@echo "========================================="
@echo "App package created:"
@echo " $(appstore_dir)/$(app_name).tar.gz"
@echo ""
@echo "Signature:"
@cat $(package_name)/appinfo/signature.json | head -n 5
@echo "========================================="
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "astrolabe",
"version": "1.0.0",
"version": "0.1.0",
"license": "AGPL-3.0-or-later",
"engines": {
"node": "^22.0.0",