Document all CI/CD pipeline fixes: - Gitea API for releases (not GitHub action) - Git submodule with relative URL - Composer path repository - gettext installation - SIGPIPE fix Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
23 KiB
Wordpress Plugin to stream music over Activity Pub
Author: Marco Graetsch Author URL: https://src.bundespruefstelle.ch/magdev Author Email: magdev3.0@gmail.com Repository URL: https://src.bundespruefstelle.ch/magdev/wp-fedistream Issues URL: https://src.bundespruefstelle.ch/magdev/wp-fedistream/issues
Project Overview
This plugin provides a way for Musicians or Music-Labels to cut the ties with Spotify, Youtube Music et al and build their own Streaming-Platform (optionally including selling their work using WooCommerce) base on the ActivityPub protocol. This plugin implements the management of Musicians (Single or Bands), Albums (or Releases), Tracks and Playlists.
The plugin utilizes the ActivityPub protocol to publish Releases or Tracks, share Playlists and let users create Playlist from different Wordpress instances, which use this plugin. It also employs Fediverse reactions to build comprehensive profile for Musicians and Bands (or the Label at all), including classicla Blogposts from Musicians. The Plugin should serve as a full Fediverse profile for Musicians. Everyone in the Fediverse is able to subscribe to the Label's or Musician's account.
If WooCommerce is installed, the Musician (or the Label) is allowed to sell the Music either as Album or single track utilizing special WooCommerce product types.
The goal is to create an alternative to the big tech streaming plaforms and give the musicians their freedom back and concentrate to make music. The first goal is to publish, the second goal is to monetize the platform without ripping of the users or musicians.
Key Fact: 100% AI-Generated
This project is proudly "vibe-coded" using Claude.AI - the entire codebase was created through AI assistance.
Temporary Roadmap
Note for AI Assistants: Clean this section after the specific features are done or new releases are made. Effective changes are tracked in CHANGELOG.md. Do not add completed versions here - document them in the Session History section at the end of this file.
(No pending features - all roadmap items completed)
Technical Stack
- Language: PHP 8.3.x
- Framework: Latest WordPress Plugin API
- E-commerce (optional): WooCommerce 10.0+
- Template Engine: Twig 3.0 (via Composer)
- Communication Protocol: ActivityPub
- Wordpress Base Theme twentytwentyfive
- Frontend: Vanilla JavaScript
- Styling: Custom CSS
- Dependency Management: Composer
- Internationalization: WordPress i18n (.pot/.po/.mo files)
- Canonical Plugin Name:
wp-fedistream
Security Best Practices
- All user inputs are sanitized (integers for quantities/prices)
- Nonce verification on form submissions
- Output escaping in templates (
esc_attr,esc_html,esc_js) - Direct file access prevention via
ABSPATHcheck - XSS-safe DOM construction in JavaScript (no
innerHTMLwith user data) - SQL injection prevention using
$wpdb->prepare()throughout
Translation Ready
All user-facing strings use:
__('Text to translate', 'wp-fedistream')
_e('Text to translate', 'wp-fedistream')
Text domain: wp-fedistream
Translation Template
- Base
.potfile created:languages/wp-fedistream.pot - Ready for translation to any locale
- All translatable strings properly marked with text domain
Available Translations
en_US- English (United States) [base language - .pot template]de_CH- German (Switzerland, formal)
To compile translations to .mo files for production:
for po in languages/*.po; do msgfmt -o "${po%.po}.mo" "$po"; done
Create releases
- The
vendor/directory MUST be included in releases (Dependencies required for runtime) - Don't create any release files until version 0.1.x and up!
- CRITICAL: Build
vendor/for the MINIMUM supported PHP version, not the development version- Use
composer config platform.php 8.3.0before building release packages - Run
composer update --no-dev --optimize-autoloaderto rebuild dependencies
- Use
- CRITICAL: WordPress requires plugins in a subdirectory structure
- Run zip from the
plugins/parent directory, NOT from within the plugin directory - Package must extract to
wp-fedistream/subdirectory with main file atwp-fedistream/wp-fedistream.php - Correct command:
cd /wp-content/plugins/ && zip -r wp-fedistream/releases/wp-fedistream-x.x.x.zip wp-fedistream ... - Wrong: Running zip from inside the plugin directory creates files at root level
- Run zip from the
- CRITICAL: Exclude symlinks explicitly - zip follows symlinks by default
- Always use
-x "wp-fedistream/wp-core" -x "wp-fedistream/wp-core/*" -x "wp-fedistream/wp-plugins" -x "wp-fedistream/wp-plugins/*"to exclude development symlinks - Otherwise the entire linked directory contents will be included in the package
- Always use
- Exclusion patterns must match the relative path structure used in zip command
- Always verify the package structure with
unzip -lbefore distribution- Check all files are prefixed with
wp-fedistream/ - Verify main file is at
wp-fedistream/wp-fedistream.php - Check for duplicate entries (indicates multiple builds in same archive)
- Check all files are prefixed with
- Test installation on the minimum supported PHP version before final deployment
- Releases are stored in
releases/including checksums - Track release changes in a single
CHANGELOG.mdfile - Bump the version number to either bugfix release versions or on new features minor release versions
- CRITICAL: WordPress reads version from TWO places - BOTH must be updated:
- Plugin header comment
Version: x.x.x(line ~6 in wc-licensed-product.php) - WordPress uses THIS for admin display - PHP constant
WP_FEDISTREAM_VERSION(line ~28) - Used internally by the plugin
- If only the constant is updated, WordPress will show the old version in Plugins list
- Plugin header comment
Important Git Notes:
- Default branch while development is
dev - Create releases from branch
mainafter merging branchdev - Tags should use format
vX.X.X(e.g.,v1.1.22), start with v0.1.0 - Use annotated tags (
-a) not lightweight tags - Commit messages should follow the established format with Claude Code attribution
.claude/settings.local.jsonchanges are typically local-only (stash before rebasing)
What Gets Released
- All plugin source files
- Compiled vendor dependencies
- Translation files (.mo compiled from .po)
- Assets (CSS, JS)
- Documentation (README, CHANGELOG, etc.)
What's Excluded
- Git metadata (
.git/) - Development files (
.vscode/,.claude/,.gitea/,CLAUDE.md,wp-core,wp-plugins) - Logs and cache files
- Previous releases
composer.lock(butvendor/is included)
CI/CD Pipeline (Gitea Actions)
Automated release packages are created via Gitea Actions when a tag matching v* is pushed:
Workflow: .gitea/workflows/release.yml
Trigger: Push tag vX.X.X to repository
Steps:
- Checkout code
- Setup PHP 8.3 with required extensions
- Install production Composer dependencies
- Compile translations (.po to .mo)
- Verify plugin version matches tag version
- Build release zip with proper WordPress structure
- Generate SHA256 checksums
- Verify package structure
- Extract changelog for release notes
- Create Gitea release with attachments
Required Secret: GITEA_TOKEN - Personal access token with release permissions
Pre-release Detection: Tags containing - (e.g., v1.0.0-beta) are marked as pre-release
To create a release:
# Ensure version is updated in wp-fedistream.php (both header and constant)
git checkout main
git merge dev
git tag -a v0.4.0 -m "Release v0.4.0"
git push origin main --tags
The pipeline will automatically build and publish the release package.
For AI Assistants:
When starting a new session on this project:
- Read this CLAUDE.md file first
- Semantic versioning follows the
MAJOR.MINOR.BUGFIXpattern - Check git log for recent changes
- Verify you're on the
devbranch before making changes - Run
composer installif vendor/ is missing - Test changes before committing
- Follow commit message format with Claude Code attribution
- Update this session history section with learnings
- Never commit backup files (
*.po~,*.bak, etc.) - checkgit statusbefore committing - Follow markdown linting rules (see below)
Always refer to this document when starting work on this project.
Markdown Linting Rules
When editing CLAUDE.md or other markdown files, follow these rules to avoid linting errors:
-
MD031 - Blank lines around fenced code blocks: Always add a blank line before and after fenced code blocks, even when they follow list items. Example of correct format:
-
Item label:
(blank line here) ```php code example ``` (blank line here)
-
-
MD056 - Table column count: Table separators must have matching column counts and proper spacing. Use consistent dash lengths that match column header widths.
-
MD009 - No trailing spaces: Remove trailing whitespace from lines
-
MD012 - No multiple consecutive blank lines: Use only single blank lines between sections
-
MD040 - Fenced code blocks should have a language specified: Always add a language identifier to code blocks (e.g.,
txt,bash,php). For shortcode examples, usetxt. -
MD032 - Lists should be surrounded by blank lines: Add a blank line before AND after list blocks, including after bold labels like
**Attributes:**. -
MD034 - Bare URLs: Wrap URLs in angle brackets (e.g.,
<https://example.com>) or use markdown link syntax[text](url). -
Author section formatting: Use a heading (
### Name) instead of bold (**Name**) for the author name to maintain consistent document structure.
Project Architecture
Directory Structure
wp-fedistream/
├── .gitea/
│ └── workflows/
│ └── release.yml # CI/CD release pipeline
├── assets/
│ ├── css/
│ │ ├── admin.css # Admin interface styles
│ │ └── frontend.css # Frontend styles
│ ├── js/
│ │ ├── admin.js # Admin interface scripts
│ │ ├── frontend.js # Frontend scripts
│ │ ├── player.js # Audio player
│ │ ├── library.js # User library page
│ │ └── notifications.js # Notification system
│ └── images/
├── includes/
│ ├── ActivityPub/
│ │ ├── Integration.php # ActivityPub plugin integration
│ │ ├── ArtistActor.php # Artist as ActivityPub actor
│ │ ├── Inbox.php # Process incoming activities
│ │ ├── Outbox.php # Publish outgoing activities
│ │ ├── RestApi.php # REST endpoints for ActivityPub
│ │ └── Transformers/ # Object transformers
│ │ ├── TrackTransformer.php
│ │ ├── AlbumTransformer.php
│ │ └── PlaylistTransformer.php
│ ├── Admin/
│ │ └── ListColumns.php # Custom list table columns
│ ├── Frontend/
│ │ ├── Ajax.php # AJAX handlers
│ │ ├── Shortcodes.php # All shortcodes
│ │ ├── TemplateLoader.php # Template loading
│ │ └── Widgets.php # Widget registration
│ ├── PostTypes/
│ │ ├── Artist.php # fedistream_artist
│ │ ├── Album.php # fedistream_album
│ │ ├── Track.php # fedistream_track
│ │ └── Playlist.php # fedistream_playlist
│ ├── Roles/
│ │ └── Capabilities.php # User roles and caps
│ ├── Taxonomies/
│ │ ├── Genre.php # fedistream_genre
│ │ ├── Mood.php # fedistream_mood
│ │ └── License.php # fedistream_license
│ ├── User/
│ │ ├── Library.php # Favorites, follows, history
│ │ ├── LibraryPage.php # Library shortcode
│ │ └── Notifications.php # Notification system
│ ├── WooCommerce/
│ │ ├── Integration.php # WooCommerce setup
│ │ ├── AlbumProduct.php # Album product type
│ │ ├── TrackProduct.php # Track product type
│ │ ├── DigitalDelivery.php # Download handling
│ │ └── StreamingAccess.php # Access control
│ ├── Installer.php # Database setup, activation
│ └── Plugin.php # Main singleton class
├── languages/
│ ├── wp-fedistream.pot # Translation template
│ └── wp-fedistream-de_CH.po # German (Switzerland)
├── templates/ # Twig templates
│ ├── admin/
│ ├── archive/
│ ├── single/
│ └── player/
├── vendor/ # Composer dependencies
├── composer.json
├── uninstall.php
└── wp-fedistream.php # Plugin entry point
Database Tables
| Table | Purpose |
|---|---|
{prefix}_fedistream_plays |
Track play statistics |
{prefix}_fedistream_playlist_tracks |
Playlist-track relationships |
{prefix}_fedistream_followers |
ActivityPub followers |
{prefix}_fedistream_purchases |
WooCommerce purchase tracking |
{prefix}_fedistream_favorites |
User favorites |
{prefix}_fedistream_user_follows |
Local artist follows |
{prefix}_fedistream_listening_history |
Track play history |
{prefix}_fedistream_notifications |
User notifications |
{prefix}_fedistream_reactions |
Fediverse reactions |
Custom Post Types
| Post Type | Slug | Description |
|---|---|---|
fedistream_artist |
/artists/ |
Musicians, bands, collectives |
fedistream_album |
/albums/ |
Albums, EPs, singles, compilations |
fedistream_track |
/tracks/ |
Individual audio tracks |
fedistream_playlist |
/playlists/ |
Curated track collections |
Custom Taxonomies
| Taxonomy | Type | Applied To |
|---|---|---|
fedistream_genre |
Hierarchical | Artists, Albums, Tracks |
fedistream_mood |
Non-hierarchical | Tracks, Playlists |
fedistream_license |
Hierarchical | Albums, Tracks |
User Roles
| Role | Slug | Capabilities |
|---|---|---|
| Artist | fedistream_artist |
Manage own content, upload files |
| Label | fedistream_label |
Manage all content, taxonomies, stats |
Shortcodes
| Shortcode | Description |
|---|---|
[fedistream_player track_id="123"] |
Single track player |
[fedistream_playlist id="456"] |
Playlist display |
[fedistream_album id="789"] |
Album display |
[fedistream_artist id="101"] |
Artist profile |
[fedistream_recent_releases count="5"] |
Recent releases |
[fedistream_popular_tracks count="10"] |
Popular tracks |
[fedistream_library] |
User library page |
Key Classes
Plugin(singleton) - Main controller, initializes all componentsInstaller- Database setup, activation/deactivation hooksArtist,Album,Track,Playlist- Post type registration and meta boxesGenre,Mood,License- Taxonomy registration with defaultsCapabilities- User role and capability managementActivityPubIntegration- Integration with WordPress ActivityPub pluginArtistActor- Artist profiles as ActivityPub Person/Group actorsInbox- Process Follow, Like, Announce, Create activitiesOutbox- Publish Create, Update activities to followersWooCommerceIntegration- Custom product types for albums/tracksDigitalDelivery- Secure download handling with ZIP supportStreamingAccess- Purchase-based streaming controlLibrary- User favorites, follows, listening historyNotifications- In-app and email notification system
REST API Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/wp-json/fedistream/v1/artists/{id}/actor |
GET | ActivityPub actor profile |
/wp-json/fedistream/v1/artists/{id}/inbox |
POST | ActivityPub inbox |
/wp-json/fedistream/v1/artists/{id}/outbox |
GET | ActivityPub outbox |
/wp-json/fedistream/v1/artists/{id}/followers |
GET | Followers collection |
/wp-json/fedistream/v1/artists/{id}/following |
GET | Following collection |
AJAX Actions
| Action | Purpose |
|---|---|
fedistream_record_play |
Record track play |
fedistream_toggle_favorite |
Add/remove favorite |
fedistream_toggle_follow |
Follow/unfollow artist |
fedistream_get_library |
Get user library |
fedistream_get_followed_artists |
Get followed artists |
fedistream_get_history |
Get listening history |
fedistream_clear_history |
Clear listening history |
fedistream_get_notifications |
Get user notifications |
fedistream_mark_notification_read |
Mark notification read |
fedistream_mark_all_notifications_read |
Mark all read |
fedistream_delete_notification |
Delete notification |
Session History
2026-01-28 - Initial Release v0.1.0
Summary: Consolidated all development phases (0.0.1 through 0.7.0) into initial release v0.1.0.
Completed:
- Implemented Phase 6 (WooCommerce Integration):
- Custom product types for albums and tracks
- Pricing models (Fixed, PWYW, NYP)
- Digital delivery with secure downloads
- Streaming access control based on purchases
- Implemented Phase 7 (User Interactions):
- User library with favorites, follows, history
- Notification system (in-app and email)
- Library shortcode and frontend page
- Consolidated documentation:
- Moved implementation details from PLAN.md to CLAUDE.md
- Deleted PLAN.md (no longer needed)
- Merged all changelog entries into single v0.1.0 release
- Updated README.md with current features
- Git operations:
- Created initial commit on dev branch
- Merged to main branch
- Tagged as v0.1.0
- Push pending (requires credentials)
Files Created:
includes/WooCommerce/Integration.phpincludes/WooCommerce/AlbumProduct.phpincludes/WooCommerce/TrackProduct.phpincludes/WooCommerce/DigitalDelivery.phpincludes/WooCommerce/StreamingAccess.phpincludes/User/Library.phpincludes/User/LibraryPage.phpincludes/User/Notifications.phpassets/js/library.jsassets/js/notifications.js
Files Deleted:
PLAN.md
Notes:
- Successfully pushed dev, main branches and v0.1.0 tag to origin
- Remote URL updated from HTTPS to SSH for authentication
- First release is now live at the repository
2026-01-28 - Bugfix v0.1.1 and Feature v0.2.0
Summary: Fixed WooCommerce integration timing bug, added plugin action links and user guide.
v0.1.1 - Bugfix:
- Fixed WooCommerce product types not appearing in product selector
- Root cause:
Integrationconstructor hookedcheck_woocommercetoplugins_loadedpriority 5, but class was instantiated at priority 10 (too late) - Solution: Call
check_woocommerce()directly in constructor
v0.2.0 - Features:
- Added Dashboard and Settings links to WordPress Plugins page
- Created comprehensive
USERGUIDE.mdcovering all features
Files Modified:
includes/WooCommerce/Integration.php- Fixed hook timingincludes/Plugin.php- Addedadd_plugin_action_links()method
Files Created:
USERGUIDE.md- Comprehensive user documentation
Notes:
- All releases pushed to origin (v0.1.1 and v0.2.0 tags)
- Markdown linting fixes applied to USERGUIDE.md
2026-01-29 - License Management v0.3.0
Summary: Implemented license management integration and reorganized settings page into tabs.
Features:
- License management using
magdev/wc-licensed-product-clientpackage - Tabbed settings page: License, Default Settings, Integrations
- License validation and activation via AJAX
- License status banner with expiration display
- Frontend license checks (unlicensed sites show message instead of content)
- Admin/backend works regardless of license status
License Behavior:
- Backend (admin): Full access always
- Frontend (player, shortcodes, ActivityPub): Requires valid license
Files Created:
includes/License/Manager.php- License management wrapper class
Files Modified:
composer.json- Added VCS repository andmagdev/wc-licensed-product-clientdependencyincludes/Plugin.php- Tabbed settings page, license manager initialization, conditional frontend loadingincludes/Installer.php- Added default license optionsincludes/Frontend/Shortcodes.php- Added unlicensed mode supportincludes/Frontend/Ajax.php- Added license checks to public AJAX endpointsassets/js/admin.js- License validation AJAX handlersassets/css/admin.css- Tab and license status stylingwp-fedistream.php- Version bump to 0.3.0CHANGELOG.md- Added v0.3.0 entry
Notes:
- Package name is
magdev/wc-licensed-product-client(notwc-license-product-client) - Uses Symfony HTTP Client via the license client package
- License validation cached for 24 hours using WordPress transients
2026-01-29 - CI/CD Pipeline v0.4.0
Summary: Added Gitea Actions workflow for automated release package creation with multiple iterations to fix CI issues.
Features:
- Automated release builds triggered by
v*tags - PHP 8.3 environment with required extensions
- Production Composer dependency installation
- Automatic translation compilation (.po to .mo)
- Version verification (plugin version must match tag)
- Proper WordPress plugin zip structure
- SHA256 checksum generation
- Package structure verification
- Changelog extraction for release notes
- Automatic Gitea release creation via API
- Pre-release detection for tags containing
-
Files Created:
.gitea/workflows/release.yml- CI/CD release pipeline.gitmodules- Git submodule configurationlib/wc-licensed-product-client/- Submodule for private dependency
Files Modified:
CLAUDE.md- Added CI/CD documentation and updated directory structureCHANGELOG.md- Added v0.4.0 entrywp-fedistream.php- Version bump to 0.4.0composer.json- Changed to path repository for submoduleREADME.md- Updated for v0.4.0, added release/installation docs
CI/CD Fixes Applied:
actions/gitea-release-action@v1doesn't exist - use Gitea API directly with curl- Private repo network issue - use git submodule with relative URL (
../wc-licensed-product-client.git) - Composer path repository for submodule dependency
msgfmtnot found - install gettext package- SIGPIPE error (exit 141) - use
set +o pipefailand|| true
Notes:
- Requires
SRC_GITEA_TOKENsecret configured in repository settings - Uses
shivammathur/setup-php@v2for PHP setup - Uses Gitea API directly for release creation (not GitHub Actions)
- Submodule uses relative URL for CI compatibility
- Composer symlinks from
lib/wc-licensed-product-clientto vendor