Skip to content

About

Joomla Module packager for releasing modules

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Latest commit

 

History

197 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

GitHub release (latest by date) License: GPL v3

Joomla Extension Packager

A reusable GitHub composite action for packaging and releasing Joomla extensions. This action automates the entire process of versioning, packaging, and releasing Joomla modules, plugins, and components.
there is a workflows version documented here: Packager Documentation

🚀 Features

  • Automatic Versioning: Date-based versioning (YYYY.MM.DD format) with support for multiple releases per day
  • Manual Version Override: Specify custom versions (e.g., 1.0.0, 2.0.0-beta) for semantic versioning
  • File Updates: Automatically updates version and copyright information across all files (can be disabled)
  • Changelog Generation: Creates changelogs from commit messages following Keep a Changelog format
  • Package Creation: Builds properly structured ZIP files for Joomla installation
  • GitHub Releases: Creates releases with artifacts and release notes
  • Joomla Updates: Updates the Joomla update server XML
  • Multi-Extension Support: Works with modules, plugins, and components
  • Extensible: Easy to extend and customize for specific needs

📋 Quick Start

  1. Reference the action in your workflow using the uses: field, pointing to the public repository and release/tag (replace N6REJ/joomla-packager@v1 with the correct owner/repo and version/tag):
name: Package Extension
on:
  pull_request:
    types: [closed]
    branches: [main]
  workflow_dispatch:

jobs:
  package:
    if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged == true)
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.GH_PAT }}
      
      - uses: N6REJ/joomla-packager@v2025.6.24
        with:
          extension-name: 'mod_example'
          extension-xml: 'mod_example.xml'
          extension-type: 'module'
          author: 'Your Name'
          copyright-holder: 'Your Company'
          copyright-start-year: '2024'
          github-token: ${{ secrets.GH_PAT }}
  1. Set up your GitHub PAT in repository secrets as GH_PAT or whatever you use for github-token:

🔧 Configuration

Required Inputs

Input Description
extension-name Extension folder and file prefix (e.g., mod_example)
extension-xml Main XML manifest file (e.g., mod_example.xml)
extension-type Type: module, plugin, or component
author Your name or handle
copyright-holder Copyright holder name
copyright-start-year Year copyright started
github-token GitHub PAT with repo permissions

Optional Inputs

Input Default Description
manual-version '' Explicit version, overrides every scheme
version-scheme date date → 2025.10.02.3, or semver → bumps the patch of the manifest <version> when that version was already released
timezone America/Chicago IANA zone used for the date-based version, the manifest <creationDate> and the changelog date. GitHub runners are UTC, which stamps an evening US build with tomorrow's date
create-release true Create the GitHub release and upload the package
update-joomla-server true Publish updates.xml after the release asset is verified (requires create-release)
commit-changes false Commit and push the manifest, updates.xml and changelog back to the branch
updates-xml-file updates.xml Update feed file name
extension-client '' site or administrator for the feed entry. Empty detects it from the manifest client attribute, then falls back to administrator for components and site for everything else
targetplatform-name joomla name of the <targetplatform> element written into the feed
targetplatform-version 6.* version regex of the <targetplatform> element. Applied only when the feed does not already declare one, so a feed pinned to another Joomla major is left alone
changelog-file CHANGELOG.md Generated changelog file name
license-file License.txt License file name
file-updates true Rewrite version/date/copyright in manifest, PHP, INI and CSS files
generate-changelog true Generate a changelog from the commits since the previous tag
upload-artifact true Also upload the unpacked package as a workflow artifact
readme false Include README.md in the package ZIP
php-version 8.1 PHP version used for the packaging steps
dir-tree-file directory-structure.txt Directory listing attached to the release, empty to skip. Not included in the installable package

Note: helper-file, favicon-file, package-dir, css-dir, js-dir, tmpl-dir and language-dir are accepted for backwards compatibility but are not currently used by the action.

Releases and the update feed

The action is careful about when a version becomes public:

  • The feed is published last. The copy of updates.xml that gets committed is only rewritten after the release has been created, and only once the release asset has been confirmed to exist. A failed release therefore never leaves installed sites being offered a download that 404s.
  • The packaged feed matches the manifest. An extension ships its own copy of updates.xml, but the package is assembled before the release exists, so that copy would otherwise still advertise the previous version and the previous download URL. It is synced to the build version up front, so the two never disagree. This does not weaken the guarantee above: installed sites read the feed from update_server in the manifest rather than from the bundled file, and the post-release pass finds nothing left to change.
  • Feed entries are matchable. Joomla matches an update against an installed extension by element, type, client and target platform, and silently ignores any entry that is missing <targetplatform>, carries an empty <type />, or names the wrong <client>. The action fills those in — detecting the type and client from the manifest, and defaulting the platform to targetplatform-name / targetplatform-version — but never overwrites a value the extension declared for itself, so a feed deliberately pinned to another Joomla major is left alone.
  • Re-runs do not mint empty versions. Before choosing a version, the action fingerprints the files that actually ship and compares that against the most recent release. If nothing has changed, that version is reused and the release steps are skipped — a re-run, a manual dispatch or a retried job cannot push an identical package onto every installed site as an "update available". The fingerprint mirrors the packaging step's exclusions, so it covers exactly what a user installs: it ignores what the action itself regenerates (CHANGELOG.md, updates.xml), the manifest's own <version>, <creationDate> and <copyright>, @version/@copyright headers in PHP, CSS and language files, and everything the package excludes such as .github/, .gitignore, build/ and README.md. Editing a CI workflow or re-pinning this action therefore does not bump your users' version, while a real source change — including a new field in the manifest — still does.
  • Nothing is left behind. The action updates files in the workspace only. Set commit-changes: 'true' to have it commit and push the version bump, the feed and the changelog back to your branch — otherwise your workflow must do that itself, and a diff check that ignores untracked files will silently drop the feed update. When a version is reused, the changelog is not regenerated and nothing is committed, so the branch stays untouched.
  • Only release metadata is generated. dir-tree-file is written next to the build directory and attached to the release, not inside the installable package, so it never ends up on the end user's server.
  • Dates follow your day, not the runner's. The version, the manifest <creationDate> and the changelog date are all computed in timezone (default America/Chicago). Set it to UTC or your own zone as needed. An unrecognised zone fails the run rather than silently falling back to UTC.

🔑 Token Permissions

The github-token input (or the default GITHUB_TOKEN) is used for creating GitHub Releases, uploading artifacts, updating files, and optionally interacting with pull requests, issues, and GitHub Packages. For the action to perform all its features, the token must have the following permissions:

Permission Why Needed
contents: write Create releases, upload release assets, update files in the repository
pull-requests: write Update or comment on pull requests (e.g., for changelog or status)
actions: write Trigger or manage other workflows, upload artifacts
packages: write Publish to GitHub Packages (optional)
issues: write Create or comment on issues (optional, e.g., for release notes)
  • Minimum required: contents: write (for releases, assets, and file updates)
  • Recommended for full functionality: Add pull-requests: write, actions: write, packages: write, and issues: write as needed for your workflow.
  • The default GITHUB_TOKEN provided by GitHub Actions usually has contents: write and pull-requests: write by default, but you may need to explicitly set these in your workflow’s permissions block for full access.
  • If using a Personal Access Token (PAT), it must have the repo scope for private repositories (includes all the above), or at least public_repo for public repositories. Add workflow and write:packages if you need to trigger workflows or publish packages.

Example permissions block for your workflow:

permissions:
  contents: write
  pull-requests: write
  actions: write
  packages: write
  issues: write

If you encounter permission errors, check your workflow's permissions block and your token's scopes.

📝 Commit Message Format

The action categorizes commits based on their prefix:

  • Added: Add, Create, Implement, Feature
  • Changed: Update, Improve, Enhance, Refactor, Change
  • Fixed: Fix, Bug, Correct, Resolve
  • Removed: Remove, Delete, Deprecate
  • Security: Security

🎯 Use Cases

Basic Module Packaging

- uses: N6REJ/joomla-packager@v1
  with:
    extension-name: 'mod_hello_world'
    extension-xml: 'mod_hello_world.xml'
    extension-type: 'module'
    author: 'John Doe'
    copyright-holder: 'Acme Corp'
    copyright-start-year: '2024'
    github-token: ${{ secrets.GH_PAT }}

Plugin with Custom Directories

- uses: N6REJ/joomla-packager@v1
  with:
    extension-name: 'plg_system_cache'
    extension-xml: 'plg_system_cache.xml'
    extension-type: 'plugin'
    author: 'Jane Smith'
    copyright-holder: 'Tech Solutions'
    copyright-start-year: '2023'
    github-token: ${{ secrets.GH_PAT }}
    css-dir: 'assets/css'
    js-dir: 'assets/js'
    package-dir: 'dist'

Component with All Features

- uses: N6REJ/joomla-packager@v1
  with:
    extension-name: 'com_myapp'
    extension-xml: 'com_myapp.xml'
    extension-type: 'component'
    author: 'Dev Team'
    copyright-holder: 'My Company'
    copyright-start-year: '2022'
    github-token: ${{ secrets.GH_PAT }}
    php-version: '8.2'
    generate-changelog: 'true'
    create-release: 'true'
    update-joomla-server: 'true'

Package with Multiple Extensions

This example shows how to package a complete Joomla package containing a component, module, and multiple plugins (based on N6REJ/bears_aichatbot):

- uses: N6REJ/joomla-packager@v1
  with:
    extension-name: 'pkg_bears_aichatbot'
    extension-xml: 'pkg_bears_aichatbot.xml'
    extension-type: 'package'
    author: 'N6REJ'
    copyright-holder: 'N6REJ'
    copyright-start-year: '2024'
    github-token: ${{ secrets.GH_PAT }}
    file-updates: 'true'  # Updates version in all included extensions

When packaging multiple extensions, the action will:

  • Update the package XML manifest
  • Update all referenced component, module, and plugin XML files
  • Update version and copyright in all PHP, CSS, and language files
  • Maintain consistent versioning across all included extensions

Using Manual Version

- uses: N6REJ/joomla-packager@v1
  with:
    extension-name: 'mod_example'
    extension-xml: 'mod_example.xml'
    extension-type: 'module'
    author: 'Your Name'
    copyright-holder: 'Your Company'
    copyright-start-year: '2024'
    github-token: ${{ secrets.GH_PAT }}
    manual-version: '2.0.0'  # Specify your own version

Using Semantic Versioning

- uses: N6REJ/joomla-packager@main
  with:
    extension-name: 'mod_example'
    extension-xml: 'mod_example.xml'
    extension-type: 'module'
    author: 'Your Name'
    copyright-holder: 'Your Company'
    copyright-start-year: '2024'
    github-token: ${{ secrets.GH_PAT }}
    version-scheme: 'semver'   # uses the manifest <version>, bumping the patch if already released
    commit-changes: 'true'     # push the version bump and update feed back to main

🔄 Extending the Action

You can extend this action in several ways:

  1. Fork and Modify: Customize the action for your specific needs
  2. Wrapper Workflows: Add pre/post processing steps
  3. Use Outputs: Access version, package path, and release URL in subsequent steps
  4. Custom Deployment: Add deployment steps after packaging

Example with custom deployment:

- name: Package Extension
  id: package
  uses: N6REJ/joomla-packager@v1
  with:
    # ... your inputs ...

- name: Deploy to Production
  run: |
    echo "Deploying version ${{ steps.packager.outputs.version }}"
    # Your deployment script here

🤝 Contributing

Contributions are welcome! Feel free to:

  • Report bugs
  • Suggest new features
  • Submit pull requests
  • Share your use cases

Testing

Workflow What it covers
test-component.yml, test-package.yml, test-plugin.yml End-to-end packaging against a real release. Smoke tests: they prove the action completes, not that it produced correct output.
test-module.yml Packaging plus assertions that the manifest, the working-tree feed and the copy inside the package all agree. test-module is the only test extension that ships an updates.xml, so this is the only end-to-end feed coverage.
test-feed-logic.yml Unit tests for the two steps that maintain the feed.

The feed steps are gated on create-release: 'true', so the test-module workflow can never reach them: this repository is the packager, and the action packages the repository root, which would publish a copy of the whole packager as a release asset. test/test_feed_sync.py and test/test_feed_publish.py cover that logic instead, by extracting each step from action.yml and running it offline against temporary fixtures. They can be run directly:

python -m pip install pyyaml
python test/test_feed_sync.py
python test/test_feed_publish.py

Both extract their step from action.yml at run time rather than copying it, so they cannot drift from the logic they test, and both fail loudly if a step is renamed or gains an expression the harness does not model. The sync step makes no network calls; the publish step reaches for gh and sleep, which are replaced with bash function stubs so the release-asset guard and its retry loop can be driven deterministically. test_feed_sync.py additionally asserts that the two steps' sed expressions remain byte-identical, because they are separate copies of the same rewrite — a divergence there is what allowed a packaged feed to advertise the previous version.

📄 License

This project is open source and available under the GPL3+ License.

🙏 Credits

Based on the workflow from N6REJ/mod_bears_pricing_tables.

Example package implementation: N6REJ/bears_aichatbot - A complete Joomla package with component, module, and multiple plugins.


Made with ❤️ for the Joomla community

Additional Documentation

For more detailed documentation and usage examples, visit: https://www.hallhome.us/joomla-packager

About

Joomla Module packager for releasing modules

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages