Build and publish a module in CI
Run Maru builds in GitHub Actions, retain the output and publish a trusted release to your Maru profile.
Run the same Maru commands in GitHub Actions that you use locally. Start with a build you can download and inspect, then add publication when you create a stable GitHub release. Your module source stays in its repository; Maru serves the published version on your profile.
Prepare a working build environment
This example is for a trusted repository and a runner where your module
already builds. Installing the public maruaudio npm package alone does not
currently provision all module-build components. The private native toolkit
also requires approved access; its current macOS ARM64 compiler-tools alpha
does not establish complete native plug-in exports or Windows/Linux support.
Arrange the matching build components before enabling this workflow.
Use a disposable self-hosted runner with your verified tools installed and add
the custom label maru-build. Keep it restricted to trusted repositories.
The example uses GitHub.com Actions and a macOS ARM64 runner, Node 24, an npm
project with a committed package-lock.json, and an exact maruaudio dependency
version. Use your project's package manager and lockfile if they differ.
From that environment, confirm the installed version provides these commands:
npx --no-install maru --version npx --no-install maru doctor npx --no-install maru module pack --help npx --no-install maru publish --help
Resolve missing components before relying on CI. Do not download a different CLI automatically to work around a version mismatch. The project should build without a sibling checkout or dependencies that exist only on your laptop. See Check, build and export for what each command produces.
Build directly in GitHub Actions
Save this as .github/workflows/maru.yml. It runs on manual invocation and
stable GitHub releases. It does not publish to Maru yet.
name: Maru module
on:
workflow_dispatch:
release:
types: [published]
permissions:
contents: read
concurrency:
group: maru-module-${{ github.repository }}
cancel-in-progress: false
jobs:
build:
if: github.event_name != 'release' || !github.event.release.prerelease
runs-on: [self-hosted, macOS, ARM64, maru-build]
timeout-minutes: 30
defaults:
run:
shell: bash
steps:
- name: Check out the requested commit
uses: actions/checkout@v7
with:
ref: ${{ github.sha }}
persist-credentials: false
- name: Select Node
uses: actions/setup-node@v7
with:
node-version: '24'
package-manager-cache: false
- name: Install locked dependencies
run: npm ci
- name: Check CLI and command availability
run: |
npx --no-install maru --version
npx --no-install maru doctor
npx --no-install maru module pack --help
- name: Check the module and build a local Release
run: |
mkdir -p ci-reports
npx --no-install maru module check . --fast --json > ci-reports/check.json
npx --no-install maru module pack . --out ci-output --json > ci-reports/pack.json
- name: Archive the built files
run: tar -czf module-candidate.tar.gz -C ci-output .
- name: Retain the build
uses: actions/upload-artifact@v7
with:
name: module-${{ github.sha }}-${{ github.run_attempt }}
path: module-candidate.tar.gz
if-no-files-found: error
retention-days: 7
- name: Retain reports, including failed checks
if: always()
uses: actions/upload-artifact@v7
with:
name: reports-${{ github.sha }}-${{ github.run_attempt }}
path: ci-reports/*.json
if-no-files-found: warn
retention-days: 7
Replace the version tags on Actions with the full reviewed commit SHAs before enabling credentialed publication. The upstream checkout, setup-node and upload-artifact references explain their runner requirements and inputs.
The example assumes the module is at the repository root. For a monorepo,
set the run steps' working-directory to the module directory and adjust the
artifact paths, which are relative to the checkout root. Install dependencies
from the directory that owns the lockfile.
--fast is an early check, and the packed output is a build candidate. Run
your project's full checks before publication; passing this workflow does not
establish browser, native format, audio or DAW validation. Reports distinguish
checks that passed from those that were unavailable or skipped.
Open the workflow run's Artifacts section to download the candidate and reports. Extract and test those exact files. The archive preserves file modes; do not upload raw macOS plug-in bundles as loose files. Actions artifacts are temporary build downloads, rather than a permanent customer release.
Publish when a GitHub release is ready
First replace the module's provisional local. identity with your permanent
module ID. Prepare its listing and source license as described in
Publishing.
Create a GitHub environment named maru-publish, restrict its allowed release
refs, and add a secret named MARU_TOKEN containing a Maru token with
modules:publish. It authenticates to Maru; GitHub's own GITHUB_TOKEN cannot
replace it. Use an expiry and revoke the token when you stop using the workflow.
Add environment: maru-publish to the build job and add this step after your
full release checks, before report upload:
- name: Run the full release checks
if: github.event_name == 'release' && !github.event.release.prerelease
run: |
npx --no-install maru module check . --json > ci-reports/release-check.json
- name: Publish the trusted release to Maru
if: github.event_name == 'release' && !github.event.release.prerelease
env:
MARU_TOKEN: ${{ secrets.MARU_TOKEN }}
run: |
npx --no-install maru publish . --source --json > ci-reports/publish.json
maru publish builds and uploads the project, then returns the module page and
its @handle/slug@version reference. It currently builds again rather than
uploading ci-output directly. --source includes the project source, private
by default. Making source public is a separate choice; do not add
--source-visibility public unless you intend to share it and have set its license.
The publication step evaluates module code while authenticated. Use reviewed release source only. Keep this workflow off untrusted pull requests and do not attach publication or compiler credentials to their checks. Protecting the environment controls admission; it does not isolate the token from module code.
GitHub's published event includes prereleases, which is why the examples
filter them. Avoid also publishing on tag pushes: one GitHub release should have
one publication trigger. See the
release-event reference.
Maru assigns its publication version; a GitHub tag does not choose that number.
Check the existing release before retrying an interrupted upload on a fresh
runner. Local resume state is not automatically transferred between jobs, and
an upload that includes source should not be assumed to be an unchanged no-op.
Use --expected-version when you need to refuse replacing a newer publication.
Workflow concurrency reduces overlap, but does not order every queued GitHub
run or protect against a concurrent local or Studio publication.
Add native targets separately
On a runner with a verified native toolkit, SDK and validators for the target, use the same native build command as locally. For example, a VST3 build step is:
- name: Build VST3 on a qualified runner
run: |
npx --no-install maru plugin build src/index.ts --release --format vst3 --out build/vst3
npx --no-install maru module verify src/index.ts --build-root build
This step requires a working native export setup, beyond the compiler-tools
alpha. Expand to separate macOS, Windows and Linux jobs only as you can build
and test each target. Use strategy.fail-fast: false, enforce every required
target's result, and retain reports from failed jobs. AU uses a macOS runner.
Signing, notarization and installation checks remain separate steps.
Publish the web module once. Running maru publish in every native matrix row
does not attach those binaries to one website release. Website ingestion of
arbitrary CI-built native archives is not part of this example. Follow
Package and release audio plug-ins
for archives, installers and native download delivery.
Use GitHub-hosted runners or managed builds
The build commands are the same on a GitHub-hosted runner once its clean setup
can install the exact authorized components and pass the project's checks.
Changing runs-on alone does not supply the toolkit. Keep component provisioning
explicit and pinned before removing the preconfigured-runner requirement.
A workflow that compiles in your repository uses the repository owner's GitHub allowance and billing. Calling a hosted build service instead runs its compute in that service's account. See current limits and costs in GitHub Actions billing. Connecting a GitHub account alone does not enable automatic Maru publication; managed repository import and push-to-publish remain a separate integration.
For agent-assisted setup, read maru guide read build-preview-export and use
the installed CLI's --help before selecting build and publication options.