From ef81a4156022f0d170104a1bc64c76c4909d1eb3 Mon Sep 17 00:00:00 2001 From: THEROER Date: Sat, 4 Jul 2026 13:35:10 +0300 Subject: [PATCH 1/3] fix(build-logic): send publish credentials when publishing the plugins The build-logic publish repository still used the old unauthenticated 'ghPages' block, so publishing the plugins to Reposilite failed (no credentials). Mirror the library's credential handling here: name the repo 'magicutilsPublish' and apply PUBLISH_USER/PUBLISH_TOKEN (or publish_user/publish_password) when set. --- build-logic/build.gradle.kts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index b4c6869b..69601af8 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -19,13 +19,26 @@ repositories { } publishing { - // Publish to the same gh-pages Maven repo as the library when -Ppublish_repo + // Publish to the same Reposilite Maven repo as the library when -Ppublish_repo // is set (CI); always available via mavenLocal for dogfooding/consumers. + // This build script can't use the magicUtilsPublishRepository helper (that's + // part of the plugins it builds), so it mirrors the same credential handling: + // user/password from PUBLISH_USER/PUBLISH_TOKEN, applied only when supplied. if (project.hasProperty("publish_repo")) { + val publishUser = (findProperty("publish_user") as? String)?.takeIf { it.isNotBlank() } + ?: System.getenv("PUBLISH_USER")?.takeIf { it.isNotBlank() } + val publishPassword = (findProperty("publish_password") as? String)?.takeIf { it.isNotBlank() } + ?: System.getenv("PUBLISH_TOKEN")?.takeIf { it.isNotBlank() } repositories { maven { - name = "ghPages" + name = "magicutilsPublish" url = uri(project.property("publish_repo") as String) + if (publishUser != null && publishPassword != null) { + credentials { + username = publishUser + password = publishPassword + } + } } } } From 50d17e3b1ef50e08ad76f3eb8b036932766a7912 Mon Sep 17 00:00:00 2001 From: THEROER Date: Sat, 4 Jul 2026 13:46:43 +0300 Subject: [PATCH 2/3] build: retire mkdocs/gh-pages docs; docs move to MagicUtilsWebsite Documentation now lives in the separate MagicUtilsWebsite (Nuxt) project, so drop the mkdocs sources (docs/, mkdocs.yml), the docs/javadoc gh-pages workflows, and the docs lint check. Rewire publishing: publish-maven.yml is dispatched directly by release.yml after tagging (it no longer chains from the docs workflow_run), and release.yml's dispatch-downstream now triggers only the Maven publish. --- .github/workflows/docs-check.yml | 35 --- .github/workflows/docs.yml | 77 ----- .github/workflows/javadoc.yml | 89 ------ .github/workflows/publish-maven.yml | 13 +- .github/workflows/release.yml | 18 +- docs/api.md | 8 - docs/contributing.md | 38 --- docs/faq.md | 86 ------ docs/getting-started/installation.md | 216 ------------- docs/getting-started/migration.md | 206 ------------- docs/getting-started/quickstart.md | 182 ----------- docs/getting-started/runtime.md | 178 ----------- docs/getting-started/versioning.md | 48 --- docs/index.md | 125 -------- docs/modules/commands-cheatsheet.md | 122 -------- docs/modules/commands.md | 424 -------------------------- docs/modules/config-advanced.md | 157 ---------- docs/modules/config.md | 240 --------------- docs/modules/diagnostics.md | 97 ------ docs/modules/http-client.md | 187 ------------ docs/modules/lang.md | 93 ------ docs/modules/logger-config.md | 97 ------ docs/modules/logger.md | 164 ---------- docs/modules/overview.md | 53 ---- docs/modules/permissions.md | 158 ---------- docs/modules/placeholders-advanced.md | 120 -------- docs/modules/placeholders.md | 104 ------- docs/platforms/bukkit.md | 60 ---- docs/platforms/core.md | 215 ------------- docs/platforms/fabric.md | 78 ----- docs/platforms/neoforge.md | 40 --- docs/platforms/velocity.md | 101 ------ docs/recipes/index.md | 50 --- mkdocs.yml | 88 ------ requirements-docs.txt | 3 - 35 files changed, 13 insertions(+), 3957 deletions(-) delete mode 100644 .github/workflows/docs-check.yml delete mode 100644 .github/workflows/docs.yml delete mode 100644 .github/workflows/javadoc.yml delete mode 100644 docs/api.md delete mode 100644 docs/contributing.md delete mode 100644 docs/faq.md delete mode 100644 docs/getting-started/installation.md delete mode 100644 docs/getting-started/migration.md delete mode 100644 docs/getting-started/quickstart.md delete mode 100644 docs/getting-started/runtime.md delete mode 100644 docs/getting-started/versioning.md delete mode 100644 docs/index.md delete mode 100644 docs/modules/commands-cheatsheet.md delete mode 100644 docs/modules/commands.md delete mode 100644 docs/modules/config-advanced.md delete mode 100644 docs/modules/config.md delete mode 100644 docs/modules/diagnostics.md delete mode 100644 docs/modules/http-client.md delete mode 100644 docs/modules/lang.md delete mode 100644 docs/modules/logger-config.md delete mode 100644 docs/modules/logger.md delete mode 100644 docs/modules/overview.md delete mode 100644 docs/modules/permissions.md delete mode 100644 docs/modules/placeholders-advanced.md delete mode 100644 docs/modules/placeholders.md delete mode 100644 docs/platforms/bukkit.md delete mode 100644 docs/platforms/core.md delete mode 100644 docs/platforms/fabric.md delete mode 100644 docs/platforms/neoforge.md delete mode 100644 docs/platforms/velocity.md delete mode 100644 docs/recipes/index.md delete mode 100644 mkdocs.yml delete mode 100644 requirements-docs.txt diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml deleted file mode 100644 index 55b11947..00000000 --- a/.github/workflows/docs-check.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: docs-check - -on: - pull_request: - branches: ["main"] - paths: - - "docs/**" - - "mkdocs.yml" - - "requirements-docs.txt" - - "README.md" - - ".github/workflows/docs*.yml" - push: - branches: ["dev", "main"] - paths: - - "docs/**" - - "mkdocs.yml" - - "requirements-docs.txt" - - "README.md" - - ".github/workflows/docs*.yml" - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - - name: Install docs dependencies - run: python -m pip install -r requirements-docs.txt - - - name: Build docs - run: python -m mkdocs build --strict diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 87d09327..00000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: docs - -on: - push: - branches: ["main"] - workflow_dispatch: - inputs: - version: - description: "Version to deploy (e.g. 1.10.0 or dev)" - required: true - default: "dev" - alias: - description: "Alias to update (optional)" - required: false - set_default: - description: "Set version as default (true/false)" - required: false - default: "false" - -permissions: - contents: write - -concurrency: - group: gh-pages - cancel-in-progress: true - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - - name: Install docs dependencies - run: python -m pip install -r requirements-docs.txt - - - name: Validate docs build - run: python -m mkdocs build --strict - - - name: Configure git - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git - - - name: Deploy docs (tag) - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - run: | - VERSION="${GITHUB_REF_NAME#v}" - MIKE_VERSION="$VERSION" mike deploy --push --update-aliases "$VERSION" stable - mike set-default --push stable - - - name: Deploy docs (main) - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - run: | - MIKE_VERSION="dev" mike deploy --push --update-aliases dev latest - mike set-default --push latest - - - name: Deploy docs (manual) - if: github.event_name == 'workflow_dispatch' - run: | - VERSION="${{ inputs.version }}" - if [ -n "${{ inputs.alias }}" ]; then - MIKE_VERSION="$VERSION" mike deploy --push --update-aliases "$VERSION" "${{ inputs.alias }}" - else - MIKE_VERSION="$VERSION" mike deploy --push "$VERSION" - fi - if [ "${{ inputs.set_default }}" = "true" ]; then - mike set-default --push "$VERSION" - fi diff --git a/.github/workflows/javadoc.yml b/.github/workflows/javadoc.yml deleted file mode 100644 index 9a5f2ba3..00000000 --- a/.github/workflows/javadoc.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: javadoc - -on: - workflow_dispatch: - inputs: - version: - description: "Version to deploy (e.g. 1.10.0 or dev)" - required: true - default: "dev" - -permissions: - contents: write - -concurrency: - group: gh-pages - cancel-in-progress: true - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-java@v5 - with: - distribution: "temurin" - java-version: "25" - cache: "gradle" - - - uses: gradle/actions/setup-gradle@v3 - - - name: Resolve Javadoc version - run: | - if [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then - VERSION="dev" - elif [ "${{ github.event_name }}" = "push" ] && [[ "${{ github.ref }}" == refs/tags/v* ]]; then - VERSION="${GITHUB_REF_NAME#v}" - elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - VERSION="${{ inputs.version }}" - else - VERSION="dev" - fi - echo "JAVADOC_VERSION=$VERSION" >> "$GITHUB_ENV" - - - name: Build Javadoc - run: | - bash ./gradlew javadoc - OUT="$RUNNER_TEMP/javadoc" - rm -rf "$OUT" - mkdir -p "$OUT" - for dir in */build/docs/javadoc; do - [ -d "$dir" ] || continue - module="$(echo "$dir" | cut -d/ -f1)" - cp -R "$dir" "$OUT/$module" - done - { - echo '' - echo 'MagicUtils Javadoc' - echo "

MagicUtils Javadoc ${JAVADOC_VERSION}

" - echo '' - } > "$OUT/index.html" - - - name: Publish Javadoc - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git - git fetch origin gh-pages - git checkout gh-pages - DEST="javadoc/${JAVADOC_VERSION}" - rm -rf "$DEST" - mkdir -p "$DEST" - cp -R "$RUNNER_TEMP/javadoc/"* "$DEST/" - git add "$DEST" - if git diff --cached --quiet; then - echo "No Javadoc changes to publish." - exit 0 - fi - git commit -m "docs: publish javadoc ${JAVADOC_VERSION}" - git push origin gh-pages diff --git a/.github/workflows/publish-maven.yml b/.github/workflows/publish-maven.yml index 38a5676d..4ff31b49 100644 --- a/.github/workflows/publish-maven.yml +++ b/.github/workflows/publish-maven.yml @@ -1,9 +1,9 @@ name: publish-maven on: - workflow_run: - workflows: ["docs"] - types: [completed] + # Dispatched by release.yml after the tag is created (documentation moved to + # the separate MagicUtilsWebsite project, so there is no docs workflow to chain + # from anymore). Also runnable manually. workflow_dispatch: # Publishing targets the self-hosted Reposilite (maven.theroer.dev) over HTTPS, @@ -21,14 +21,13 @@ jobs: # one-line edit to targets.properties — this workflow never lists targets. resolve-matrix: runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} outputs: units: ${{ steps.matrix.outputs.units }} repo_url: ${{ steps.coords.outputs.repo_url }} steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_sha || github.sha }} + ref: ${{ github.sha }} - uses: actions/setup-java@v5 with: distribution: 'temurin' @@ -61,7 +60,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_sha || github.sha }} + ref: ${{ github.sha }} - uses: actions/setup-java@v5 with: distribution: 'temurin' @@ -88,7 +87,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.workflow_run.head_sha || github.sha }} + ref: ${{ github.sha }} - uses: actions/setup-java@v5 with: distribution: 'temurin' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index efad4be8..45c5723a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,20 +88,16 @@ jobs: runs-on: ubuntu-latest needs: [resolve, tag] steps: - - name: Dispatch docs and javadoc workflows + - name: Dispatch Maven publish env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | VERSION="${{ needs.resolve.outputs.version }}" TAG="${{ needs.resolve.outputs.tag }}" - # Use workflow_dispatch (not the tag push) to launch docs.yml. - # This avoids GitHub's GITHUB_TOKEN-pushed-tag rule that suppresses - # downstream workflow_run events — publish-maven.yml will then see - # docs as a workflow_dispatch source and trigger correctly. - gh workflow run docs.yml --repo "$GITHUB_REPOSITORY" --ref "$TAG" \ - -f version="$VERSION" \ - -f alias=stable \ - -f set_default=true - gh workflow run javadoc.yml --repo "$GITHUB_REPOSITORY" --ref "$TAG" \ + # Documentation lives in the separate MagicUtilsWebsite (Nuxt) project, + # so the release only dispatches the Maven publish. Use workflow_dispatch + # (not the tag push) to avoid GitHub's GITHUB_TOKEN-pushed-tag rule that + # suppresses downstream workflow_run events. + gh workflow run publish-maven.yml --repo "$GITHUB_REPOSITORY" --ref "$TAG" \ -f version="$VERSION" - echo "Dispatched docs.yml and javadoc.yml for $TAG" + echo "Dispatched publish-maven.yml for $TAG" diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index d2deaca1..00000000 --- a/docs/api.md +++ /dev/null @@ -1,8 +0,0 @@ -# API reference - -The API reference is provided as Javadoc. - -- Local build: `./gradlew javadoc` -- Hosted docs (releases): `https://magicutils.theroer.dev/javadoc/{{ magicutils_version }}/` - -If you are on a dev build, the hosted Javadoc may not exist yet. diff --git a/docs/contributing.md b/docs/contributing.md deleted file mode 100644 index 44b5bb23..00000000 --- a/docs/contributing.md +++ /dev/null @@ -1,38 +0,0 @@ -# Contributing - -## Build - -```bash -./gradlew build -``` - -## Build the docs locally - -```bash -python -m pip install -r requirements-docs.txt -mkdocs build --strict -mkdocs serve -``` - -Preferred release/tag flow: - -```bash -python3 scripts/publish_release.py 1.10.0 --dry-run -python3 scripts/publish_release.py 1.10.0 -``` - -## Deploy versioned docs (maintainers) - -Manual docs deployment: - -```bash -mike deploy --update-aliases 1.10.0 stable -mike set-default stable -``` - -For dev docs: - -```bash -mike deploy --update-aliases dev latest -mike set-default latest -``` diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index 846e5f52..00000000 --- a/docs/faq.md +++ /dev/null @@ -1,86 +0,0 @@ -# FAQ / Troubleshooting - -Common issues and fixes when integrating MagicUtils. - -## YAML/TOML config not available - -If you see `YAMLFactory`/`TOMLFactory` missing, add the format helpers: - -- `magicutils-config-yaml` -- `magicutils-config-toml` - -If the config format is not required, switch to JSON/JSONC instead. - -## Jackson `NoSuchMethodError` - -Errors like `YAMLParser.createChildObjectContext(...)` or -`BufferRecycler.releaseToPool()` indicate mixed Jackson versions. Make sure -your project uses the same Jackson version as MagicUtils (currently `2.17.1`) -and exclude older transitive Jackson artifacts. - -## `NoClassDefFoundError: dev.ua.theroer.magicutils.platform.Platform` - -The platform API is missing from the runtime. Add `magicutils-api` (or use a -platform bundle such as `magicutils-bukkit` / `magicutils-fabric-bundle`). - -## `NoClassDefFoundError` for Adventure classes - -Missing classes like `net.kyori.ansi.StyleOps` or -`net.kyori.adventure.text.serializer.json.JSONComponentSerializer` mean the -Adventure serializer dependencies were not included. Use the platform bundle -or add the required Adventure modules explicitly. - -## Placeholders do not resolve - -MagicUtils bridges to external placeholder mods/plugins only when they are -installed: - -- Bukkit: [PlaceholderAPI](https://modrinth.com/plugin/placeholderapi) -- Fabric: [PB4 Placeholder API](https://placeholders.pb4.eu/) -- Fabric: [Text Placeholder API](https://modrinth.com/mod/placeholder-api) or - [MiniPlaceholders](https://modrinth.com/mod/miniplaceholders) - -## Which artifact should I use? - -For most users: - -- Bukkit/Paper plugin: `magicutils-bukkit` -- Fabric mod: `magicutils-fabric-bundle` -- Velocity plugin: `magicutils-velocity` -- NeoForge mod: `magicutils-neoforge` plus `magicutils-commands-neoforge` - -Use the modular artifacts only when you intentionally want custom wiring. - -## Permissions on Fabric - -Without `fabric-permissions-api`, MagicUtils falls back to op-level checks. -Install the permissions API (and a permissions plugin like LuckPerms) to get -full permission support. - -## Do I need to call `magic.runtime().close()`? - -Yes, if you built your services through `buildRuntime()` and your platform has a -clear shutdown phase. Closing the runtime unregisters hooks, closes managed -resources, and shuts down the config manager when it owns it. - -## Why do Fabric commands still register in `CommandRegistrationCallback`? - -Because Brigadier registration still belongs to Fabric's command callback. -`FabricBootstrap` prepares the shared services and command registry, but the -dispatcher itself only exists inside the event callback. - -## `Logger` or `LoggerCore`? - -Use: - -- `Logger` on Bukkit and Fabric -- `LoggerCore` on Velocity, NeoForge, and custom platforms - -Bootstrap helpers return the correct logger type for each platform. - -## Config watcher warnings - -If you see `Failed to register config watcher` / `inotify` warnings, your OS -file watch limit is too low. MagicUtils disables realtime reload in this -case. Increase the limit or call `ConfigManager.shutdown()` on plugin disable -to free watchers. diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md deleted file mode 100644 index 28a877ab..00000000 --- a/docs/getting-started/installation.md +++ /dev/null @@ -1,216 +0,0 @@ -# Installation - -MagicUtils is split into modules. For most projects you only need one platform -entry point (`magicutils-bukkit`, `magicutils-fabric-bundle`, -`magicutils-velocity`, or `magicutils-neoforge`) plus optional format helpers. - -Use the modular artifacts only when you need a custom wiring path. - -## Choose A Version - -This documentation is versioned. The examples below use -`{{ magicutils_version }}` which matches the current docs version. - -If you want to hardcode it in your build, replace it with the exact number -(for example `1.10.0`). - -## Repositories - -Add the GitHub Pages Maven repository. - -```kotlin -repositories { - maven("https://maven.theroer.dev/releases/") -} -``` - -## Which Artifact Do I Need? - -| Scenario | Recommended artifacts | Notes | -| --- | --- | --- | -| Bukkit/Paper plugin | `magicutils-bukkit` | Default choice for most plugins. | -| Shared Bukkit server install | `magicutils-bukkit-bundle` + plugin `compileOnly` dependency | Use when multiple plugins should share one install. | -| Fabric mod | `magicutils-fabric-bundle` | Default choice for most mods. | -| Shared Fabric server install | `magicutils-fabric-bundle:dev` without `include(...)` | Install the bundle mod on the server. | -| Modular Fabric setup | `magicutils-fabric` + Fabric integration modules | Use only when you want custom wiring. | -| Velocity plugin | `magicutils-velocity` | Includes config/logger/lang/commands support. | -| NeoForge mod | `magicutils-neoforge` + `magicutils-commands-neoforge` | Placeholder bridge is not available yet. | -| Extra config formats | `magicutils-config-yaml`, `magicutils-config-toml` | Optional helpers for YAML and TOML. | -| HTTP client | `magicutils-http-client` | Optional runtime-aware HTTP/WebSocket clients. | - -## Bukkit/Paper - -The Bukkit/Paper adapter bundles the core modules (config, logger, commands, -lang, placeholders). Add optional format helpers only when you need them. - -You can use it in two ways: - -1. Embed MagicUtils into your plugin. -2. Use a shared MagicUtils plugin (`magicutils-bukkit-bundle`) so multiple - plugins reuse the same runtime. - -Kotlin DSL: - -```kotlin -dependencies { - implementation("dev.ua.theroer:magicutils-bukkit:{{ magicutils_version }}") - implementation("dev.ua.theroer:magicutils-config-yaml:{{ magicutils_version }}") - implementation("dev.ua.theroer:magicutils-config-toml:{{ magicutils_version }}") -} -``` - -Groovy DSL: - -```groovy -dependencies { - implementation 'dev.ua.theroer:magicutils-bukkit:{{ magicutils_version }}' - implementation 'dev.ua.theroer:magicutils-config-yaml:{{ magicutils_version }}' - implementation 'dev.ua.theroer:magicutils-config-toml:{{ magicutils_version }}' -} -``` - -### Shared Bukkit Bundle - -If you want a single shared MagicUtils install for multiple plugins, use the -bundle plugin and do not embed MagicUtils inside your plugins. - -Dependencies (compile-only): - -```kotlin -dependencies { - compileOnly("dev.ua.theroer:magicutils-bukkit-bundle:{{ magicutils_version }}") -} -``` - -Install the bundle on the server: - -- Drop `magicutils-bukkit-bundle-{{ magicutils_version }}.jar` into `plugins/`. -- Add `depend: [MagicUtils]` (or `softdepend`) to your `plugin.yml`. - -## Fabric - -You have two options: - -### Embed The Bundle Inside Your Mod - -This is the recommended approach for single-mod setups and avoids requiring -server owners to install MagicUtils separately. - -Kotlin DSL: - -```kotlin -dependencies { - modImplementation(include("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}")) - modCompileOnly("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev") - modRuntimeOnly("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev") -} -``` - -Groovy DSL: - -```groovy -dependencies { - modImplementation(include('dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}')) - modCompileOnly 'dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev' - modRuntimeOnly 'dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev' -} -``` - -### Depend On A Shared Bundle Mod - -If you want one shared MagicUtils install for multiple mods, use a standard -dependency and install the bundle mod on the server. - -```kotlin -dependencies { - modImplementation("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev") -} -``` - -If you pick the shared bundle, add the `magicutils-fabric-bundle` mod to the -server `mods/` folder and do not embed it inside other mods. - -You can download the bundle from the Maven repository: -[`magicutils-fabric-bundle-{{ magicutils_version }}.jar`](https://maven.theroer.dev/releases/dev/ua/theroer/magicutils-fabric-bundle/{{ magicutils_version }}/magicutils-fabric-bundle-{{ magicutils_version }}.jar) - -You can also add a dependency in your `fabric.mod.json`: - -```json -{ - "depends": { - "magicutils-fabric-bundle": ">={{ magicutils_version }}" - } -} -``` - -### Modular Fabric Dependencies - -Use this only when you want to wire specific modules yourself instead of using -the bundle. `FabricBootstrap` lives in the command integration layer, so a -bootstrap-first modular setup usually pulls both the platform adapter and the -Fabric integration modules. - -```kotlin -dependencies { - modImplementation("dev.ua.theroer:magicutils-fabric:{{ magicutils_version }}:dev") - modImplementation("dev.ua.theroer:magicutils-logger-fabric:{{ magicutils_version }}:dev") - modImplementation("dev.ua.theroer:magicutils-commands-fabric:{{ magicutils_version }}:dev") - modImplementation("dev.ua.theroer:magicutils-placeholders-fabric:{{ magicutils_version }}:dev") - modImplementation("dev.ua.theroer:magicutils-config:{{ magicutils_version }}") - modImplementation("dev.ua.theroer:magicutils-lang:{{ magicutils_version }}") -} -``` - -## NeoForge - -NeoForge exposes platform, config, logger, and Brigadier command integrations. -There is no NeoForge placeholder bridge yet. - -Kotlin DSL: - -```kotlin -dependencies { - implementation("dev.ua.theroer:magicutils-neoforge:{{ magicutils_version }}") - implementation("dev.ua.theroer:magicutils-commands-neoforge:{{ magicutils_version }}") -} -``` - -Groovy DSL: - -```groovy -dependencies { - implementation 'dev.ua.theroer:magicutils-neoforge:{{ magicutils_version }}' - implementation 'dev.ua.theroer:magicutils-commands-neoforge:{{ magicutils_version }}' -} -``` - -## Velocity - -The Velocity adapter exposes platform, config, logger, lang, and command -integration in a single artifact. - -```kotlin -dependencies { - implementation("dev.ua.theroer:magicutils-velocity:{{ magicutils_version }}") -} -``` - -## Optional Format Helpers - -- `magicutils-config-yaml` enables YAML support. -- `magicutils-config-toml` enables TOML support. - -Without them, MagicUtils uses JSON or JSONC (Fabric default). - -## Optional HTTP Client - -```kotlin -dependencies { - implementation("dev.ua.theroer:magicutils-http-client:{{ magicutils_version }}") -} -``` - -## Notes On Shaded Artifacts - -Local builds produce `*-all` artifacts (shaded). The GitHub Pages repository -does not include these files due to the 100 MB limit, so do not depend on them. diff --git a/docs/getting-started/migration.md b/docs/getting-started/migration.md deleted file mode 100644 index 1915707c..00000000 --- a/docs/getting-started/migration.md +++ /dev/null @@ -1,206 +0,0 @@ -# Migration Guide - -This page shows how to move from the older manual wiring style to the current -bootstrap-first setup. - -## 1. Manual Bootstrap -> `buildRuntime()` - -### Bukkit/Paper - -Before: - -```java -Platform platform = new BukkitPlatformProvider(this); -ConfigManager configManager = new ConfigManager(platform); -Logger logger = new Logger(platform, this, configManager); -LanguageManager languageManager = new LanguageManager(this, configManager); -languageManager.init("en"); -languageManager.addMagicUtilsMessages(); -logger.setLanguageManager(languageManager); -Messages.register(getName(), languageManager); -``` - -After: - -```java -BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(this) - .buildRuntime(); -``` - -What you gain: - -- one managed shutdown handle -- consistent logger/lang/messages wiring -- optional command registry integration -- easy access through `MagicRuntime` - -### Fabric - -Before: - -```java -Platform platform = new FabricPlatformProvider(server); -ConfigManager configManager = new ConfigManager(platform); -Logger logger = new Logger(platform, configManager, "MyMod"); -``` - -After: - -```java -FabricBootstrap.RuntimeResult magic = FabricBootstrap.forMod("mymod", () -> server) - .buildRuntime(); -``` - -Important: Fabric command registration still happens inside -`CommandRegistrationCallback.EVENT`. - -### Velocity - -Before: - -```java -Platform platform = new VelocityPlatformProvider(proxy, slf4j, dataDirectory, this); -ConfigManager configManager = new ConfigManager(platform); -LoggerCore logger = new LoggerCore(platform, configManager, this, "MyPlugin"); -``` - -After: - -```java -VelocityBootstrap.RuntimeResult magic = VelocityBootstrap.forPlugin(proxy, this, "MyPlugin", dataDirectory) - .slf4j(slf4j) - .buildRuntime(); -``` - -## 2. `initialize(...)` -> Explicit Registry Or Bootstrap - -Older code often used the static default registry: - -```java -CommandRegistry.initialize(plugin, "myplugin", logger); -CommandRegistry.register(plugin, new DonateCommand()); -``` - -Preferred now: - -```java -CommandRegistry registry = CommandRegistry.create(plugin, "myplugin", logger); -registry.registerCommand(new DonateCommand()); -``` - -Or through bootstrap: - -```java -BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(plugin) - .permissionPrefix("myplugin") - .enableCommands() - .configureCommands(registry -> registry.registerCommand(new DonateCommand())) - .buildRuntime(); -``` - -Why prefer this: - -- easier testing -- fewer hidden globals -- clearer ownership in multi-plugin or multi-mod setups - -## 3. `CommandSpec.builder(...)` -> `MagicCommand.builder(...)` - -Older builder-based command code often produced a detached spec: - -```java -CommandSpec spec = CommandSpec.builder("donate") - .execute(ctx -> CommandResult.success("ok")) - .build(); - -registry.registerSpec(spec); -``` - -Preferred now: - -```java -MagicCommand command = MagicCommand.builder("donate") - .execute(ctx -> CommandResult.success("ok")) - .build(); - -registry.registerCommand(command); -``` - -This matters because builder-authored commands are now real `MagicCommand` -instances. They can use the same runtime adaptation API as annotation-based -commands: - -- `withName(...)` -- `addAlias(...)` -- `addSubCommand(...)` -- `setExecute(...)` -- `mount(existingCommand)` - -`registerSpec(...)` still works for migration, but it is now the compatibility -path rather than the primary one. - -## 4. Ad-Hoc Reload Logic -> `MagicRuntime` Bindings - -Older reload code often looked like this: - -```java -if (client != null) { - client.close(); -} -client = MagicHttpClient.builder(platform, configManager) - .baseUrl(config.monitoring.baseUrl) - .build(); -``` - -Preferred now: - -```java -MagicRuntimeConfigBinding binding = runtime.bindConfig( - "http.monitoring", - ServiceConfig.class, - config -> MagicHttpClient.builder(runtime.platform(), runtime.configManager()) - .baseUrl(config.monitoring.baseUrl) - .build(), - "monitoring" -); -``` - -Or use the higher-level profile wrapper: - -```java -MagicHttpClientProfile monitoring = MagicHttpClientProfile - .builder(runtime, "http.monitoring", ServiceConfig.class) - .sections("monitoring") - .baseUrl(config -> config.monitoring.baseUrl) - .build(); -``` - -## 4. Manual Shutdown -> One Runtime Close - -Before: - -```java -configManager.shutdown(); -CommandRegistry.shutdown(plugin); -Messages.unregister(getName()); -``` - -After: - -```java -magic.runtime().close(); -``` - -That only works when the services were created through bootstrap or registered -inside the runtime. - -## 5. When Not To Migrate - -Keep the manual style when: - -- you are integrating MagicUtils into an unusual platform -- you need partial module wiring without the bootstrap defaults -- you intentionally manage service lifecycles yourself - -Even in those cases, `MagicRuntime` is still useful as a local lifecycle -container. diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md deleted file mode 100644 index 4f577b0c..00000000 --- a/docs/getting-started/quickstart.md +++ /dev/null @@ -1,182 +0,0 @@ -# Quickstart - -The recommended path is bootstrap-first: use the platform bootstrap helper when -it exists, keep the returned runtime handle, and close it when the platform -shuts down. - -If most of your logic lives directly in `magicutils-core`, keep the platform -entrypoint thin and move the shared services behind `MagicRuntime`. - -That does not mean `common` should call `forPlugin(...)` or `forMod(...)`. -Those bootstrap helpers stay in the platform module, which then hands the -runtime into shared code. - -## Bukkit/Paper - -```java -public final class MyPlugin extends JavaPlugin { - private BukkitBootstrap.RuntimeResult magic; - - @Override - public void onEnable() { - magic = BukkitBootstrap.forPlugin(this) - .enableCommands() - .configureCommands(registry -> registry.registerCommand(new ExampleCommand())) - .buildRuntime(); - - magic.logger().info("Ready."); - } - - @Override - public void onDisable() { - if (magic != null) { - magic.runtime().close(); - magic = null; - } - } -} -``` - -`BukkitBootstrap` wires `Platform`, `ConfigManager`, `Logger`, -`LanguageManager`, `Messages`, and an optional `CommandRegistry`. - -## Fabric - -```java -public final class MyMod implements ModInitializer { - private static final String MOD_ID = "mymod"; - - private MinecraftServer server; - private FabricBootstrap.RuntimeResult magic; - - @Override - public void onInitialize() { - magic = FabricBootstrap.forMod(MOD_ID, () -> server) - .enableCommands() - .buildRuntime(); - - CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { - if (magic.commandRegistry() != null) { - magic.commandRegistry().registerCommand(dispatcher, new ExampleCommand()); - } - }); - - ServerLifecycleEvents.SERVER_STARTED.register(server -> this.server = server); - ServerLifecycleEvents.SERVER_STOPPING.register(server -> { - this.server = null; - if (magic != null) { - magic.runtime().close(); - magic = null; - } - }); - } -} -``` - -`FabricBootstrap` sets up the shared services early, while actual command -registration still happens inside Fabric's Brigadier callback. - -## Velocity - -```java -@Plugin(id = "myplugin", name = "MyPlugin", version = "1.0.0") -public final class MyPlugin { - private final ProxyServer proxy; - private final org.slf4j.Logger slf4j; - private final Path dataDirectory; - private VelocityBootstrap.RuntimeResult magic; - - @Inject - public MyPlugin(ProxyServer proxy, - org.slf4j.Logger slf4j, - @DataDirectory Path dataDirectory) { - this.proxy = proxy; - this.slf4j = slf4j; - this.dataDirectory = dataDirectory; - } - - @Subscribe - public void onProxyInitialize(ProxyInitializeEvent event) { - magic = VelocityBootstrap.forPlugin(proxy, this, "MyPlugin", dataDirectory) - .slf4j(slf4j) - .enableCommands() - .configureCommands(registry -> registry.registerCommand(new ExampleCommand())) - .buildRuntime(); - - magic.logger().info("Ready."); - } - - @Subscribe - public void onProxyShutdown(ProxyShutdownEvent event) { - if (magic != null) { - magic.runtime().close(); - magic = null; - } - } -} -``` - -`VelocityBootstrap` creates a managed `LoggerCore`, registers shutdown cleanup, -and can wire the Velocity command registry for you. - -## NeoForge - -NeoForge currently uses the manual wiring path. - -```java -public final class MyMod { - private static final String MOD_ID = "mymod"; - - private final Platform platform; - private final ConfigManager configManager; - private final LoggerCore logger; - private final CommandRegistry commands; - - public MyMod() { - platform = new NeoForgePlatformProvider(); - configManager = new ConfigManager(platform); - logger = new LoggerCore(platform, configManager, this, "MyMod"); - commands = CommandRegistry.create(MOD_ID, MOD_ID, logger); - - logger.info("Ready."); - } - - @SubscribeEvent - public void onRegisterCommands(RegisterCommandsEvent event) { - commands.registerCommand(event.getDispatcher(), new ExampleCommand()); - } -} -``` - -## Using The Runtime Handle - -Every `buildRuntime()` call returns `MagicRuntime`, which exposes the shared -services as typed components: - -```java -MagicRuntime runtime = magic.runtime(); -ConfigManager configManager = runtime.requireComponent(ConfigManager.class); -LoggerCore logger = runtime.requireComponent(LoggerCore.class); -LanguageManager languages = runtime.findComponent(LanguageManager.class).orElse(null); -``` - -Use named resources and config bindings when you want runtime-managed clients or -other reloadable services. - -## Core / Common Logic - -If your plugin or mod is mostly shared logic plus a thin platform bootstrap, -keep the common layer built around `MagicRuntime` and platform-agnostic -services. - -See [Core / Common Logic](../platforms/core.md) for the recommended split -between platform glue and shared code. - -## Next Steps - -- Pick the modules you need from the Modules section. -- Use the platform pages for more detailed bootstrap notes. -- Use the Core / Common Logic page for shared/common module structure. -- Read the Runtime guide for `MagicRuntime`, managed resources, and config bindings. -- Use the Migration guide if you are moving from older manual wiring examples. -- See the HTTP client page for `MagicRuntime`-bound profiles. diff --git a/docs/getting-started/runtime.md b/docs/getting-started/runtime.md deleted file mode 100644 index 439ca782..00000000 --- a/docs/getting-started/runtime.md +++ /dev/null @@ -1,178 +0,0 @@ -# Runtime - -`MagicRuntime` is the managed service container behind the bootstrap-first -setup. It gives you one place to access core services, register extra -components, manage closeable resources, and rebuild config-backed clients on -reload. - -## Getting A Runtime - -The recommended path is `buildRuntime()`: - -```java -BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(this) - .enableCommands() - .buildRuntime(); - -MagicRuntime runtime = magic.runtime(); -``` - -The same pattern exists for: - -- `BukkitBootstrap` -- `FabricBootstrap` -- `VelocityBootstrap` - -NeoForge and custom platforms can build `MagicRuntime` manually. - -## Core Components - -Every runtime starts with typed components for the core services: - -- `Platform` -- `ConfigManager` -- `LoggerCore` -- `LanguageManager` when configured - -Access them via the typed component registry: - -```java -Platform platform = runtime.requireComponent(Platform.class); -ConfigManager configManager = runtime.requireComponent(ConfigManager.class); -LoggerCore logger = runtime.requireComponent(LoggerCore.class); -CommandRegistry commands = runtime.findComponent(CommandRegistry.class).orElse(null); -``` - -Use `findComponent(...)` when the component is optional and -`requireComponent(...)` when its absence is a bug. - -## Typed Components - -Register and replace typed components at runtime: - -```java -runtime.putComponent(MyService.class, new MyService()); - -MyService service = runtime.requireComponent(MyService.class); -Optional opt = runtime.findComponent(MyService.class); -``` - -`findComponent(...)` also matches assignable types, so requesting an interface -will find a registered implementation. - -## Named Components - -`MagicRuntime` also exposes a named registry for dynamic resources: - -```java -runtime.putNamedComponent("service.cache", cacheClient); - -CacheClient cache = runtime.requireNamedComponent("service.cache", CacheClient.class); -Optional opt = runtime.findNamedComponent("service.cache", CacheClient.class); -``` - -Remove a named component when it is no longer needed: - -```java -runtime.removeNamedComponent("service.cache"); -``` - -Named components are especially useful for: - -- reloadable clients -- plugin-owned service singletons -- resources keyed by logical role (`http.monitoring`, `ws.gateway`) - -## Managed Resources - -Use `resource(...)` when you want a stable named slot that closes replaced -resources automatically: - -```java -MagicRuntimeResource monitoring = runtime.resource( - "http.monitoring", - MagicHttpClient.builder(runtime.platform(), runtime.configManager()) - .baseUrl("https://api.example.com/") - .build() -); - -MagicHttpClient client = monitoring.require(); -monitoring.set(MagicHttpClient.builder(runtime.platform(), runtime.configManager()) - .baseUrl("https://api-two.example.com/") - .build()); -``` - -The resource is also exposed through the named component registry under the same -name. - -## Config Bindings - -Use `bindConfig(...)` when a closeable resource should rebuild automatically on -matching config reloads: - -```java -MagicRuntimeConfigBinding binding = runtime.bindConfig( - "http.monitoring", - ServiceConfig.class, - config -> MagicHttpClient.builder(runtime.platform(), runtime.configManager()) - .baseUrl(config.monitoring.baseUrl) - .build(), - "monitoring" -); - -MagicHttpClient client = binding.require(); -``` - -This pattern works well for: - -- HTTP clients -- WebSocket clients -- database pools -- SDK clients - -The HTTP client module also provides higher-level wrappers: - -- `MagicHttpClientProfile` -- `MagicWebSocketClientProfile` - -Use those when the resource being managed is specifically an HTTP or WebSocket -client. - -## Lifecycle - -`MagicRuntime.close()`: - -- unregisters its platform shutdown hook when one was installed -- closes managed resources in reverse registration order -- closes runtime resources and config bindings -- can shut down `ConfigManager` automatically - -Bootstrap helpers already configure the runtime so that `magic.runtime().close()` -is the one shutdown call you usually need in your plugin or mod. - -## Building A Runtime Manually - -For NeoForge or custom platforms, build it directly: - -```java -MagicRuntime runtime = MagicRuntime.builder(platform, configManager, logger) - .languageManager(languageManager) - .component(MyPlugin.class, this) - .manage("database", databaseClient) - .onClose("metrics", metrics::flush) - .manageConfigManager(true) - .autoRegisterShutdown(true) - .build(); -``` - -Builder controls: - -- `languageManager(...)` -- `component(...)` -- `manage(...)` -- `onClose(...)` -- `manageConfigManager(...)` -- `autoRegisterShutdown(...)` - -Disable `autoRegisterShutdown(...)` when the platform already has an explicit -shutdown phase you want to own manually. diff --git a/docs/getting-started/versioning.md b/docs/getting-started/versioning.md deleted file mode 100644 index 08226f4e..00000000 --- a/docs/getting-started/versioning.md +++ /dev/null @@ -1,48 +0,0 @@ -# Versioning - -This site is versioned with `mike` and published to GitHub Pages. Each -documentation version matches a MagicUtils release tag. - -## Documentation versions - -The version selector in the header switches between: - -- `stable`: the latest tagged release. -- `dev`: the current development branch. - -Examples in the docs use `{{ magicutils_version }}` which is automatically -set to the active docs version. - -## Tag releases (for maintainers) - -Use the release helper to sync `gradle.properties`, push the release branch, -and dispatch the tag workflow safely: - -```bash -python3 scripts/publish_release.py 1.10.0 --dry-run -python3 scripts/publish_release.py 1.10.0 -``` - -The helper waits until `origin/` resolves to the pushed local HEAD before -dispatching `release.yml`, which avoids tagging an older commit when GitHub has -not caught up with the branch update yet. - -When a release tag is created (for example `v1.10.0`), the workflow deploys a -new docs version and updates the `stable` alias. - -``` -# Local -mike deploy --update-aliases 1.10.0 stable -mike set-default stable -``` - -## Development docs - -For development builds, the workflow deploys `dev` and sets `latest` as the -default alias. - -``` -# Local -mike deploy --update-aliases dev latest -mike set-default latest -``` diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index a97e582d..00000000 --- a/docs/index.md +++ /dev/null @@ -1,125 +0,0 @@ -# MagicUtils - -MagicUtils is a modular toolkit for Bukkit/Paper, Fabric, Velocity, and -NeoForge. It provides shared building blocks for configuration, localisation, -commands, logging, placeholders, HTTP clients, and platform adapters. - -The same stack also works well in multi-module projects where platform adapters -only bootstrap `MagicRuntime` and the real feature logic lives in shared -`common` modules. - -## Highlights - -- Bootstrap-first setup for Bukkit, Fabric, and Velocity via - `BukkitBootstrap`, `FabricBootstrap`, and `VelocityBootstrap`. -- `MagicRuntime` container for managed shutdown hooks, typed components, and - named runtime resources. -- Config manager with JSON/JSONC, YAML, and TOML support plus migrations. -- Annotation-first command framework with type parsers, options, and Brigadier - support where the platform supports it. -- Adventure-based logger with rich formatting, sub-loggers, and help styling. -- Language manager with MiniMessage, bundled messages, and per-player - overrides. -- HTTP client wrapper with JSON mapping, retries, multipart uploads, and - runtime-bound profiles. -- WebSocket client with the same builder pattern, config integration, and - runtime profiles. -- Platform-agnostic player lifecycle and message events. -- Config validation annotations (`@MinValue`, `@MaxValue`, `@ConfigSerializable`). -- Placeholder registry with Bukkit PlaceholderAPI and Fabric placeholder - bridges. - -## Modules At A Glance - -| Layer | Artifacts | Notes | -| --- | --- | --- | -| Platform API | `magicutils-api` | `Platform`, `Audience`, `TaskScheduler`, and shared interfaces. | -| Core stack | `magicutils-core` | Shared runtime container plus core config/lang/logger/placeholder wiring. | -| Feature modules | `magicutils-logger`, `magicutils-commands`, `magicutils-config`, `magicutils-lang`, `magicutils-placeholders`, `magicutils-http-client` | Mix and match for manual setups. | -| Format helpers | `magicutils-config-yaml`, `magicutils-config-toml` | Enable extra config formats. | -| Platform adapters | `magicutils-bukkit`, `magicutils-fabric`, `magicutils-velocity`, `magicutils-neoforge` | Wire MagicUtils to each runtime. | -| Platform bundles | `magicutils-bukkit-bundle`, `magicutils-fabric-bundle` | Shared server-side installs for Bukkit/Paper and Fabric. | -| Fabric integrations | `magicutils-commands-fabric`, `magicutils-logger-fabric`, `magicutils-placeholders-fabric` | Fabric-specific command, logger, and placeholder layers. | -| Brigadier integrations | `magicutils-commands-brigadier`, `magicutils-commands-neoforge` | Shared Brigadier base and NeoForge command wiring. | - -## Quick Start - -1. Add the GitHub Pages Maven repository. -2. Add one platform entry point. -3. Wire the runtime through the recommended bootstrap helper. - -```kotlin -repositories { - maven("https://maven.theroer.dev/releases/") -} -``` - -=== "Bukkit/Paper" - ```kotlin - dependencies { - implementation("dev.ua.theroer:magicutils-bukkit:{{ magicutils_version }}") - } - ``` - - ```java - BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(this) - .enableCommands() - .buildRuntime(); - ``` - -=== "Fabric" - ```kotlin - dependencies { - modImplementation(include("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}")) - modCompileOnly("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev") - modRuntimeOnly("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}:dev") - } - ``` - - ```java - FabricBootstrap.RuntimeResult magic = FabricBootstrap.forMod("mymod", () -> server) - .enableCommands() - .buildRuntime(); - ``` - -=== "Velocity" - ```kotlin - dependencies { - implementation("dev.ua.theroer:magicutils-velocity:{{ magicutils_version }}") - } - ``` - - ```java - VelocityBootstrap.RuntimeResult magic = VelocityBootstrap.forPlugin(proxy, this, "MyPlugin", dataDirectory) - .enableCommands() - .buildRuntime(); - ``` - -=== "NeoForge" - ```kotlin - dependencies { - implementation("dev.ua.theroer:magicutils-neoforge:{{ magicutils_version }}") - implementation("dev.ua.theroer:magicutils-commands-neoforge:{{ magicutils_version }}") - } - ``` - - ```java - Platform platform = new NeoForgePlatformProvider(); - ConfigManager configManager = new ConfigManager(platform); - LoggerCore logger = new LoggerCore(platform, configManager, this, "MyMod"); - CommandRegistry commands = CommandRegistry.create("mymod", "mymod", logger); - ``` - -`buildRuntime()` returns a managed `MagicRuntime` wrapper, so you can keep one -runtime handle and close it cleanly on shutdown. - -## Where To Go Next - -- Read the Core / Common Logic page when your code lives mostly in - `magicutils-core`. -- Read the installation guide for bundle options and modular setups. -- Jump to Quickstart for end-to-end bootstrap examples per platform. -- Read the Runtime guide for `MagicRuntime` patterns and lifecycle management. -- Use the Migration guide when updating older code samples or plugins. -- Use the module pages for deeper API examples and config details. -- Use the version selector in the header to switch between releases. diff --git a/docs/modules/commands-cheatsheet.md b/docs/modules/commands-cheatsheet.md deleted file mode 100644 index 60d45567..00000000 --- a/docs/modules/commands-cheatsheet.md +++ /dev/null @@ -1,122 +0,0 @@ -# Commands Cheat Sheet - -Quick reference for the MagicUtils command system. - -## Minimal Registry Setup - -```java -CommandRegistry registry = CommandRegistry.create(plugin, "myplugin", logger); -registry.registerCommand(new DonateCommand()); -``` - -## Minimal Annotated Command - -```java -@CommandInfo(name = "donate", description = "Main command") -public final class DonateCommand extends MagicCommand { - public CommandResult execute(@Sender MagicSender sender) { - return CommandResult.success("ok"); - } -} -``` - -## Threading (Async) - -```java -@CommandInfo(name = "donate", threading = CommandThreading.ASYNC) -public final class DonateCommand extends MagicCommand { - @SubCommand(name = "give", threading = CommandThreading.ASYNC) - public CommandResult give(@Sender MagicSender sender, Player target) { ... } -} -``` - -## Subcommands And Nested Paths - -```java -@SubCommand(name = "give") -public CommandResult give(@Sender MagicSender sender, Player target) { ... } - -@SubCommand(path = {"npc", "commands"}, name = "add") -public CommandResult addNpcCommand(...) { ... } -``` - -## Options / Flags - -```java -public CommandResult give( - @Option(shortNames = {"a"}, longNames = {"amount"}) int amount, - @Option(shortNames = {"s"}, longNames = {"silent"}, flag = true) boolean silent -) { ... } -``` - -Accepted forms: - -- `--amount 5` -- `-a 5` -- `--silent` / `-s` - -## Optional + Default Values - -```java -public CommandResult set( - @DefaultValue("en") String lang, - @OptionalArgument Player target -) { ... } -``` - -## Greedy Text - -```java -public CommandResult say(@Greedy String message) { ... } -``` - -## Suggestions - -```java -@Suggest("@players") Player target -@Suggest("{easy,hard}") String mode -@Suggest("getItems") String item -``` - -Suggestion methods can be: - -- `List getItems()` -- `List getItems(Player player)` -- `List getItems(ServerCommandSource sender)` -- `List getItems(CommandSource sender)` - -## Sender Injection - -```java -public CommandResult execute(@Sender MagicSender sender) { ... } -``` - -Allowed senders: - -`ANY`, `PLAYER`, `CONSOLE`, `BLOCK`, `MINECART`, `PROXIED`, `REMOTE`. - -## Builder API Snippet - -```java -MagicCommand command = MagicCommand.builder("donate") - .description("Main command") - .aliases("d") - .threading(CommandThreading.ASYNC) - .execute(ctx -> CommandResult.success("ok")) - .subCommand(SubCommandSpec.builder("give") - .argument(CommandArgument.builder("player", Player.class).build()) - .threading(CommandThreading.ASYNC) - .execute(ctx -> CommandResult.success("ok")) - .build()) - .build(); - -registry.registerCommand(command); -``` - -## Mount Existing Command Tree - -```java -MagicCommand admin = MagicCommand.builder("admin") - .mount("punish", new BanCommand()) - .build(); -``` diff --git a/docs/modules/commands.md b/docs/modules/commands.md deleted file mode 100644 index 38cb365e..00000000 --- a/docs/modules/commands.md +++ /dev/null @@ -1,424 +0,0 @@ -# Commands - -MagicUtils commands are annotation-first, but the runtime model is registry -based. Each platform exposes a `CommandRegistry` that owns parsers, -permissions, and command registration for that plugin or mod. - -See [Commands Cheat Sheet](commands-cheatsheet.md) for a quick reference and -[Permissions](permissions.md) for node generation details. - -## Registration Models - -There are three common ways to obtain a registry: - -1. Bootstrap helper creates it for you. -2. `CommandRegistry.create(...)` returns an instance you keep explicitly. -3. Legacy `CommandRegistry.initialize(...)` / `createDefault(...)` creates the - default registry for the current platform. - -For multi-plugin or multi-mod setups, prefer an explicit registry instance or -the scoped static overloads. The no-arg `register(...)` methods operate on the -default registry. - -## Platform Registration - -### Bukkit/Paper - -Bootstrap-first: - -```java -BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(plugin) - .permissionPrefix("myplugin") - .enableCommands() - .configureCommands(registry -> registry.registerCommand(new DonateCommand())) - .buildRuntime(); -``` - -Manual registry: - -```java -CommandRegistry registry = CommandRegistry.create(plugin, "myplugin", logger); -registry.registerCommand(new DonateCommand()); -registry.registerCommand(new AdminCommand()); -``` - -### Fabric - -Bootstrap-first: - -```java -FabricBootstrap.RuntimeResult magic = FabricBootstrap.forMod("mymod", () -> server) - .permissionPrefix("mymod") - .enableCommands() - .buildRuntime(); - -CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { - if (magic.commandRegistry() != null) { - magic.commandRegistry().registerCommand(dispatcher, new DonateCommand()); - } -}); -``` - -Manual registry: - -```java -CommandRegistry registry = CommandRegistry.create("mymod", "mymod", logger, 2); - -CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { - registry.registerCommand(dispatcher, new DonateCommand()); -}); -``` - -### Velocity - -Bootstrap-first: - -```java -VelocityBootstrap.RuntimeResult magic = VelocityBootstrap.forPlugin(proxy, plugin, "MyPlugin", dataDirectory) - .permissionPrefix("myplugin") - .enableCommands() - .configureCommands(registry -> registry.registerCommand(new DonateCommand())) - .buildRuntime(); -``` - -Manual registry: - -```java -CommandRegistry registry = CommandRegistry.create(proxy, plugin, "myplugin", loggerCore); -registry.registerCommand(new DonateCommand()); -``` - -### NeoForge - -NeoForge currently uses the manual path: - -```java -CommandRegistry registry = CommandRegistry.create("mymod", "mymod", loggerCore, 2); - -@SubscribeEvent -public void onRegisterCommands(RegisterCommandsEvent event) { - registry.registerCommand(event.getDispatcher(), new DonateCommand()); -} -``` - -The second argument is always the permission prefix used when generating nodes. - -## Annotation-Based Commands - -Use `@CommandInfo` on the class and `@SubCommand` on methods. A method named -`execute` without `@SubCommand` is treated as the root handler. - -```java -@CommandInfo( - name = "donate", - description = "DonateMenu main command", - aliases = {"d"}, - permission = "donate.use" -) -public final class DonateCommand extends MagicCommand { - - public CommandResult execute(@Sender MagicSender sender) { - return CommandResult.success("Opened menu"); - } - - @SubCommand(name = "give", description = "Give currency to a player") - public CommandResult give( - @Sender MagicSender sender, - @ParamName("player") Player target, - @Option(shortNames = {"a"}, longNames = {"amount"}) int amount, - @Option(shortNames = {"s"}, longNames = {"silent"}, flag = true) boolean silent - ) { - return CommandResult.success(silent ? "" : "Done"); - } -} -``` - -Nested subcommands are supported via `path`: - -```java -@SubCommand(path = {"npc", "commands"}, name = "add") -public CommandResult addNpcCommand(...) { ... } -``` - -### Common Annotations - -- `@ParamName` overrides argument names for help output. -- `@OptionalArgument` or `@DefaultValue("...")` marks a parameter optional. -- `@Greedy` captures the rest of the input. -- `@Suggest("source")` adds completion hints. -- `@Sender` injects the sender and hides it from help output. -- `@Option(shortNames = {"a"}, longNames = {"amount"})` enables `-a 5` and - `--amount 5`. Set `flag = true` for toggles such as `-s` / `--silent`. - -`@Sender` supports sender filtering via `AllowedSender`: - -- `ANY`, `PLAYER`, `CONSOLE` -- `BLOCK`, `MINECART`, `PROXIED`, `REMOTE` - -Platform-specific sender types can also be injected directly: - -- Bukkit: `CommandSender`, `Player` -- Fabric: `ServerCommandSource`, `ServerPlayerEntity` -- Velocity: `CommandSource`, `Player`, `ConsoleCommandSource` -- NeoForge: `CommandSourceStack`, `ServerPlayer` - -## Suggestions And Type Parsers - -Suggestions can come from: - -- Built-in type parsers (players, worlds, enums, booleans). -- Special sources such as `@players`, `@worlds`, `@commands`. -- Inline lists: `@Suggest("{on,off,reset}")`. -- Methods on the command class: `@Suggest("getItems")`. - -Suggestion methods can be: - -- `String[] getItems()` or `List getItems()` -- `getItems(Player player)` -- `getItems(ServerCommandSource sender)` -- `getItems(CommandSource sender)` - -Built-in sources: - -- `@players`, `@player`, `@allplayers` -- `@offlineplayers` (Bukkit only) -- `@worlds`, `@world` -- `@language_keys` (Bukkit only) -- `@commands` -- `{a,b,c}` inline list syntax - -Custom parsers are registered on the registry's parser registry: - -```java -registry.commandManager() - .getTypeParserRegistry() - .register(new MyTypeParser()); -``` - -## Permissions - -Permissions can be defined at three levels: - -- `@CommandInfo.permission` -- `@SubCommand.permission` -- `@Permission` on parameters - -Generated nodes use this shape when you do not provide explicit values: - -- Command: `commands.` -- Subcommand: `commands..subcommand.` -- Argument: `commands..subcommand..argument.` - -These nodes are prefixed by the registry permission prefix. See -[Permissions](permissions.md) for the platform-specific behaviour. - -`MagicPermissionDefault` controls the default access policy: - -- `TRUE` -- `OP` -- `NOT_OP` -- `FALSE` - -For manual checks outside annotation processing, use `MagicSender`: - -```java -MagicSender sender = MagicSender.wrap(rawSender); -if (MagicSender.hasPermission(rawSender, "myplugin.admin")) { - // adapter-default fallback -} -if (sender != null && sender.hasPermission("myplugin.admin", 4)) { - // explicit fallback override for this check -} -``` - -The overload with `fallbackOpLevel` is mainly useful on Fabric and NeoForge, -where adapters may fall back to command-source permission levels. - -## CommandResult - -`CommandResult.success()` and `CommandResult.failure()` control whether MagicUtils -sends feedback automatically. - -- `CommandResult.success()` means success with no reply text. -- `CommandResult.success("Done")` sends a success reply. -- `CommandResult.failure("No permission")` sends a failure reply. - -## Threading - -Commands run on the main thread by default. Use `CommandThreading.ASYNC` for -IO-heavy or CPU-heavy work: - -```java -@CommandInfo(name = "donate", threading = CommandThreading.ASYNC) -public final class DonateCommand extends MagicCommand { - public CommandResult execute(@Sender MagicSender sender) { - return CommandResult.success("done"); - } - - @SubCommand(name = "give", threading = CommandThreading.ASYNC) - public CommandResult give(@Sender MagicSender sender, Player target) { - return CommandResult.success("ok"); - } -} -``` - -Builder equivalents: - -```java -MagicCommand.builder("donate") - .threading(CommandThreading.ASYNC) - .execute(ctx -> CommandResult.success("done")) - .build(); - -SubCommandSpec.builder("give") - .threading(CommandThreading.ASYNC) - .execute(ctx -> CommandResult.success("ok")) - .build(); -``` - -Only mark commands as async when your code is thread-safe. When you need to -touch platform APIs again, switch back to the main thread via -`Platform.runOnMain(...)` or `Tasks.runOnMain(...)`. - -## Help Output - -The help renderer respects permissions and hides commands or arguments the -sender cannot access. It is styled through `logger.{ext}` under the `help` -section. - -### Standalone Help Command - -Bukkit and Fabric ship a ready-to-register `HelpCommand` wrapper: - -```java -registry.registerCommand(new HelpCommand(logger, registry)); -``` - -You can rename it at runtime: - -```java -registry.registerCommand(new HelpCommand(logger, registry) - .withName("donatehelp") - .addAlias("dhelp")); -``` - -### Help As A Subcommand - -Use `HelpCommandSupport` when you want help inside another command tree or on -platforms that do not ship a dedicated wrapper: - -```java -registry.registerCommand(new DonateCommand() - .addSubCommand(HelpCommandSupport.createHelpSubCommand( - "help", - loggerCore, - registry::commandManager - ))); -``` - -## MagicSender - -`MagicSender` is the platform-neutral sender wrapper used throughout the -command system: - -```java -MagicSender sender = MagicSender.wrap(rawSender); -if (MagicSender.hasPermission(rawSender, "my.permission")) { - // ... -} -``` - -Use it when you want shared command logic across Bukkit, Fabric, Velocity, and -NeoForge without branching on raw sender types. - -## Builder API - -Use the builder API when you need runtime composition but still want a real -`MagicCommand` instance: - -```java -MagicCommand donateCommand = MagicCommand.builder("donate") - .description("DonateMenu main command") - .aliases("d") - .execute(ctx -> CommandResult.success("Opened menu")) - .subCommand(SubCommandSpec.builder("give") - .description("Give currency") - .argument(CommandArgument.builder("player", Player.class).build()) - .argument(CommandArgument.builder("amount", Integer.class).build()) - .execute(ctx -> CommandResult.success("ok")) - .build()) - .build(); - -registry.registerCommand(donateCommand); -``` - -You can mix annotations with runtime overrides: - -- `withName(...)`, `addAlias(...)`, `removeAlias(...)` -- `addSubCommand(SubCommandSpec)` -- `setExecute(...)` -- `mount(MagicCommand)` / `mount("route", existingCommand)` - -Nested builder subcommands are supported as well: - -```java -SubCommandSpec npcAdd = SubCommandSpec.builder("add") - .path("npc", "commands") - .description("Add NPC command") - .execute(ctx -> CommandResult.success("ok")) - .build(); -``` - -## Composing Existing Commands - -Already-authored annotation commands can be mounted under another command tree -without rewriting them into `SubCommandSpec` form: - -```java -MagicCommand adminCommand = MagicCommand.builder("admin") - .mount("punish", new BanCommand()) - .build(); - -registry.registerCommand(adminCommand); -``` - -This is intentionally different from `withName("punish")`: - -- `withName(...)` mutates the authored command instance itself -- `mount("punish", command)` keeps the source command identity intact and only - changes the route segment inside the parent tree - -Mounting snapshots the child command structure at mount time. That means: - -- changing the child name or aliases later does not rewrite the already-mounted - parent tree -- the mounted command still executes against the original child instance -- when you override the mounted route, root aliases from the child are not - exposed automatically - -## Mutation Lifecycle - -`MagicCommand` is mutable only during definition and composition: - -```java -MagicCommand command = MagicCommand.builder("donate") - .execute(ctx -> CommandResult.success("ok")) - .build() - .addAlias("d"); -``` - -After `registry.registerCommand(...)` or `commandManager.register(...)`, the -command is frozen. Later calls to: - -- `withName(...)` -- `addAlias(...)` -- `removeAlias(...)` -- `addSubCommand(...)` -- `setExecute(...)` -- `mount(...)` - -throw `IllegalStateException`. If you still use `registerSpec(...)`, it remains -supported as a compatibility path and is internally converted into the same -`MagicCommand` runtime model. - -For runtime diagnostics mounted the same way, see [Diagnostics](diagnostics.md). diff --git a/docs/modules/config-advanced.md b/docs/modules/config-advanced.md deleted file mode 100644 index a3845915..00000000 --- a/docs/modules/config-advanced.md +++ /dev/null @@ -1,157 +0,0 @@ -# Config Advanced - -This page covers format selection, migrations, adapters, and runtime-aware -reload patterns. - -## Format Selection - -If your `@ConfigFile` uses `{ext}`, MagicUtils can switch formats using: - -- `.format` next to the config file -- `magicutils.format` in the config root directory -- `-Dmagicutils.config.format=...` -- `MAGICUTILS_CONFIG_FORMAT` - -Supported values include `json`, `jsonc`, `yml`, `yaml`, and `toml` depending -on the installed format helpers. - -If multiple candidate files exist, MagicUtils picks one and logs a warning. - -## Format Migration - -When the selected format changes and an older format file exists, MagicUtils -can migrate the data into the new target file. This is useful when moving from -JSONC to YAML or TOML without forcing users to recreate their configs. - -## Schema Migrations - -Register ordered `ConfigMigration` steps to evolve config schemas: - -```java -manager.registerMigrations(MyConfig.class, - new ConfigMigration() { - public String fromVersion() { return "0"; } - public String toVersion() { return "1"; } - public void migrate(Map root) { - root.put("enabled", true); - } - } -); -``` - -MagicUtils stores the current schema version in `config-version`. - -## Custom Adapters - -Use `ConfigAdapters.register(...)` for custom value types: - -```java -ConfigAdapters.register(Duration.class, new ConfigValueAdapter<>() { - public Duration deserialize(Object value) { ... } - public Object serialize(Duration value) { ... } -}); -``` - -Register adapters before the affected config class is first loaded. - -## Change Subscriptions - -Use `subscribeChanges(...)` when you want an unsubscribe handle: - -```java -ListenerSubscription subscription = manager.subscribeChanges(MyConfig.class, (cfg, sections) -> { - // apply live update -}); -``` - -Use `onChange(...)` when you only need fire-and-forget registration. - -## Runtime Resource Binding - -`MagicRuntime` can rebuild managed resources on matching config changes: - -```java -MagicRuntimeConfigBinding binding = runtime.bindConfig( - "service.client", - ServiceConfig.class, - config -> new ReloadableClient(config), // ReloadableClient implements AutoCloseable - "service" -); -``` - -Useful properties of this pattern: - -- the latest resource is accessible via `binding.require()` -- the same resource is exposed through - `runtime.requireNamedComponent("service.client", ReloadableClient.class)` -- replaced resources are closed automatically -- `binding.close()` removes the named runtime component and stops listening for - config changes - -## Validation And Constraints - -### `@MinValue` / `@MaxValue` - -Clamp numeric config values to a safe range on load: - -```java -@ConfigValue("interval_seconds") -@MinValue(5) -@MaxValue(3600) -private int intervalSeconds = 30; -``` - -Out-of-range values are silently clamped. Set `warn = false` to suppress the -log message. - -### `@SaveTo` - -Store a field in a separate file: - -```java -@ConfigValue("secrets") -@SaveTo("secrets.{ext}") -private Secrets secrets = new Secrets(); -``` - -### `@ConfigSerializable` - -Enable a class for use inside config lists or maps: - -```java -@ConfigSerializable -public class ServerEntry { - @ConfigValue("name") - private String name = ""; -} -``` - -### `@ListProcessor` - -Apply per-item validation when loading lists: - -```java -@ConfigValue("entries") -@ListProcessor(EntryProcessor.class) -private List entries = new ArrayList<>(); -``` - -The processor implements `ListItemProcessor` and returns `ProcessResult.ok()`, -`ProcessResult.modified(value)`, or `ProcessResult.replaceWithDefault()`. - -## Hot Reload - -Mark reloadable sections and listen for updates: - -```java -@ConfigReloadable(sections = {"messages"}) -public final class MyConfig { ... } -``` - -Use section-aware reloads to avoid rebuilding unrelated services: - -```java -manager.reload(MyConfig.class, "messages"); -manager.reloadAsync(MyConfig.class, "messages"); -manager.reloadSmart(MyConfig.class, "messages"); -``` diff --git a/docs/modules/config.md b/docs/modules/config.md deleted file mode 100644 index a6c531e7..00000000 --- a/docs/modules/config.md +++ /dev/null @@ -1,240 +0,0 @@ -# Config - -MagicUtils config maps files to POJOs using annotations while preserving user -comments and unknown keys when saving. - -## Define A Config - -```java -@ConfigFile("example.{ext}") -@Comment("Example configuration") -@ConfigReloadable(sections = {"messages"}) -public final class ExampleConfig { - @ConfigValue("enabled") - private boolean enabled = true; - - @ConfigSection("messages") - private Messages messages = new Messages(); - - public static final class Messages { - @ConfigValue("greeting") - @Comment("Greeting shown to players") - private String greeting = "Hello"; - } -} -``` - -```java -ConfigManager manager = new ConfigManager(platform); -ExampleConfig cfg = manager.register(ExampleConfig.class); -``` - -If your config path contains placeholders such as `{lang}` or `{service}`, pass -them during registration: - -```java -ExampleConfig cfg = manager.register(ExampleConfig.class, Map.of("service", "gateway")); -``` - -## Formats And Selection - -- JSON / JSONC are available out of the box. -- YAML requires `magicutils-config-yaml`. -- TOML requires `magicutils-config-toml`. - -If you use `{ext}` in `@ConfigFile`, MagicUtils chooses the extension from the -current config format rules: - -- `.format` next to the config -- `magicutils.format` in the root config directory -- `-Dmagicutils.config.format=jsonc` -- `MAGICUTILS_CONFIG_FORMAT` - -Fabric defaults to JSONC when no explicit format is selected. - -For advanced format selection and migrations, see -[Config Advanced](config-advanced.md). - -## Reloading And Change Listeners - -Register a listener for live updates: - -```java -ListenerSubscription subscription = manager.subscribeChanges(ExampleConfig.class, (config, sections) -> { - // apply live updates -}); -``` - -`onChange(...)` is available as a convenience alias when you do not need the -subscription handle. - -`@ConfigReloadable` restricts which sections can reload at runtime. - -## Threading Helpers - -Reloading touches disk. Use async or smart helpers on blocking-sensitive -threads: - -```java -manager.reloadAsync(cfg); -manager.reloadAsync(ExampleConfig.class, "messages"); -manager.reloadAllAsync(); - -manager.reloadSmart(cfg); -manager.reloadSmart(ExampleConfig.class, "messages"); -manager.reloadAllSmart(); -``` - -## Runtime-Managed Config Services - -When you already have `MagicRuntime`, you can bind a config-backed resource and -let MagicUtils rebuild it automatically on matching config reloads: - -```java -MagicRuntimeConfigBinding binding = runtime.bindConfig( - "service.example", - ExampleConfig.class, - config -> new ReloadableService(config), // ReloadableService implements AutoCloseable - "messages" -); - -ReloadableService service = binding.require(); -``` - -The bound service is also exposed as a named runtime component. - -## Migrations - -Config migrations are declared with `ConfigMigration` and tracked by the -`config-version` key inside the file: - -```java -manager.registerMigrations(ExampleConfig.class, - new ConfigMigration() { - public String fromVersion() { return "0"; } - public String toVersion() { return "1"; } - public void migrate(Map root) { - root.put("enabled", true); - } - } -); -``` - -## Validation Annotations - -### `@MinValue` / `@MaxValue` - -Clamp numeric fields to a safe range. Values outside the range are automatically -adjusted when the config is loaded. A warning is logged by default. - -```java -@ConfigValue("retry_interval") -@MinValue(5) -@Comment("Retry interval in seconds (minimum: 5)") -private int retryInterval = 10; - -@ConfigValue("max_players") -@MaxValue(100) -@Comment("Maximum players (maximum: 100)") -private int maxPlayers = 20; -``` - -Supported types: `byte`, `short`, `int`, `long`, `float`, `double` and their -wrapper types. - -Set `warn = false` to suppress the clamping log message: - -```java -@MinValue(value = 0, warn = false) -``` - -### `@DefaultValue` - -Provides a default string value for a config field when the key is missing from -the file: - -```java -@ConfigValue("channel") -@DefaultValue("stable") -private String channel; -``` - -For dynamic defaults, implement `DefaultValueProvider` and reference it: - -```java -@ConfigValue("name") -@DefaultValue(provider = MyDefaultProvider.class) -private String name; -``` - -## Serializable Types - -### `@ConfigSerializable` - -Marks a class so it can be used in config lists and maps: - -```java -@ConfigSerializable -public class ServerEntry { - @ConfigValue("name") - private String name = ""; - - @ConfigValue("port") - private int port = 25565; -} -``` - -Use `includeNulls = true` to serialize null fields explicitly. - -### `@SaveTo` - -Redirects a field to a different file: - -```java -@ConfigValue("secrets") -@SaveTo("secrets.{ext}") -private Secrets secrets = new Secrets(); -``` - -The path is relative to the plugin data folder. - -### `@ListProcessor` - -Applies per-item validation or transformation when loading list fields: - -```java -@ConfigValue("servers") -@ListProcessor(ServerListProcessor.class) -private List servers = new ArrayList<>(); -``` - -The processor implements `ListItemProcessor`: - -```java -public class ServerListProcessor implements ListItemProcessor { - @Override - public ProcessResult process(ServerEntry item, int index) { - if (item.name == null || item.name.isBlank()) { - return ProcessResult.replaceWithDefault(); - } - return ProcessResult.ok(item); - } -} -``` - -## Custom Value Adapters - -Register serializers via `ConfigAdapters.register(...)`: - -```java -ConfigAdapters.register(Duration.class, new ConfigValueAdapter<>() { - public Duration deserialize(Object value) { ... } - public Object serialize(Duration value) { ... } -}); -``` - -## Shutdown - -Bootstrap helpers and `MagicRuntime` can manage the config manager lifecycle -for you. In manual setups, call `ConfigManager.shutdown()` during plugin or mod -shutdown to stop file watchers cleanly. diff --git a/docs/modules/diagnostics.md b/docs/modules/diagnostics.md deleted file mode 100644 index 33759be3..00000000 --- a/docs/modules/diagnostics.md +++ /dev/null @@ -1,97 +0,0 @@ -# Diagnostics - -MagicUtils diagnostics provide runtime self-checks that can be executed inside a -live plugin or mod. The diagnostics service is built on top of `MagicRuntime` -and ships with safe infrastructure-focused checks for runtime wiring, -filesystem access, scheduler behavior, command registration exposure, and -placeholder registry access. - -## Bootstrap Wiring - -Enable diagnostics from the platform bootstrap builder: - -```java -BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(plugin) - .enableCommands() - .enableDiagnostics() - .configureDiagnostics(registry -> { - registry.register(new MyDatabaseCheck()); - }) - .buildRuntime(); - -DiagnosticsService diagnostics = magic.diagnosticsService(); -``` - -The service is also available from the runtime container: - -```java -DiagnosticsService diagnostics = - magic.runtime().requireComponent(DiagnosticsService.class); -``` - -## Running Checks - -Execute the full report or a single suite: - -```java -DiagnosticReport safeReport = diagnostics.runAll(DiagnosticRunRequest.safe()); -DiagnosticReport standardReport = diagnostics.runSuite( - "scheduler", - DiagnosticRunRequest.standard() -); -``` - -Built-in suites include: - -- `runtime` -- `filesystem` -- `config` -- `scheduler` -- `threading` -- `commands` -- `placeholders` - -`SAFE` mode avoids temp-file writes and reload probes. `STANDARD` mode enables -reversible probes such as temp-file writes and config reloadability checks. - -## Exporting Reports - -Reports can be rendered to text or exported as JSON: - -```java -List lines = DiagnosticReports.renderText(safeReport); -Path exported = diagnostics.exportJson(safeReport); -``` - -The default export path is: - -```text -/diagnostics/latest.json -``` - -## Command Helper - -Mount diagnostics into an existing command tree the same way you mount help: - -```java -MagicCommand admin = MagicCommand.builder("admin") - .subCommand(HelpCommandSupport.createHelpSubCommand( - logger.getCore(), - registry::commandManager - )) - .subCommand(DiagnosticsCommandSupport.createDiagnosticsSubCommand( - logger.getCore(), - magic::diagnosticsService - )) - .build(); -``` - -Supported command forms: - -- `/plugin diagnostics` -- `/plugin diagnostics safe` -- `/plugin diagnostics standard` -- `/plugin diagnostics export` -- `/plugin diagnostics export standard` -- `/plugin diagnostics suite magicutils.scheduler` -- `/plugin diagnostics suite magicutils.scheduler standard` diff --git a/docs/modules/http-client.md b/docs/modules/http-client.md deleted file mode 100644 index 5e74a142..00000000 --- a/docs/modules/http-client.md +++ /dev/null @@ -1,187 +0,0 @@ -# HTTP client - -MagicUtils HTTP client wraps `java.net.http.HttpClient` with config defaults, -JSON mapping, retries, and a small multipart helper. - -## Dependency - -```kotlin -dependencies { - implementation("dev.ua.theroer:magicutils-http-client:{{ magicutils_version }}") -} -``` - -## Configuration - -The module stores settings in `http-client.{ext}` (JSONC by default on Fabric). - -Key sections: - -- `timeouts`: connect + request timeouts -- `retry`: backoff settings and retry status codes -- `logging`: request/response logging toggles -- `defaults`: base URL, headers, HTTP version - -## Basic usage - -```java -Platform platform = new BukkitPlatformProvider(this); -ConfigManager configManager = new ConfigManager(platform); - -MagicHttpClient client = MagicHttpClient.builder(platform, configManager) - .baseUrl("https://api.example.com/") - .header("Authorization", "Bearer token") - .build(); - -HttpResponse response = client.get("status"); -``` - -## Runtime profiles - -When you already have a `MagicRuntime`, you can declare named HTTP/WebSocket -profiles that rebuild automatically on config reload: - -```java -MagicHttpClientProfile monitoring = MagicHttpClientProfile - .builder(runtime, "http.monitoring", ApiConfig.class) - .sections("monitoring") - .baseUrl(config -> config.monitoring.baseUrl) - .bearerAuth(config -> config.monitoring.token) - .build(); - -MagicHttpClient client = monitoring.require(); -MagicHttpClient sameClient = runtime.requireNamedComponent("http.monitoring", MagicHttpClient.class); -``` - -```java -MagicWebSocketClientProfile gateway = MagicWebSocketClientProfile - .builder(runtime, "ws.gateway", ApiConfig.class) - .sections("gateway") - .baseUrl(config -> config.gateway.baseUrl) - .bearerAuth(config -> config.gateway.token) - .subprotocols(config -> config.gateway.subprotocols) - .build(); -``` - -This removes the usual `close old client -> build new client -> swap references` -boilerplate from service reload paths. - -## Smart methods - -Smart methods switch to async automatically on blocking-sensitive threads and -return a `CompletableFuture`: - -```java -client.getSmart("status").thenAccept(response -> { - // ... -}); -``` - -## JSON to POJO - -```java -public record StatusResponse(String status) {} - -StatusResponse response = client.getJson("status", StatusResponse.class); -``` - -## JSON POST - -```java -public record CreateRequest(String name) {} - -client.postJson("items", new CreateRequest("Test")); -``` - -## Multipart upload - -```java -MultipartBody body = MultipartBody.builder() - .text("title", "Hello") - .file("file", Path.of("logo.png")) - .build(); - -client.postMultipart("upload", body); -``` - -## WebSocket client - -`MagicWebSocketClient` wraps `java.net.http.WebSocket` with the same builder -pattern and config integration as the HTTP client. - -### Basic usage - -```java -MagicWebSocketClient wsClient = MagicWebSocketClient.builder(platform, configManager) - .baseUrl("wss://api.example.com/ws") - .header("Authorization", "Bearer token") - .subprotocols(List.of("v1")) - .build(); - -CompletableFuture ws = wsClient.connectAsync("/events", myListener); -``` - -### Builder options - -The builder supports the same options as `MagicHttpClient.Builder`: - -- `baseUrl(...)` — base URL prepended to connect paths -- `header(...)` / `headers(...)` — default headers -- `userAgent(...)` — User-Agent header -- `connectTimeout(...)` — connection timeout -- `followRedirects(...)` — redirect policy -- `subprotocols(...)` — WebSocket subprotocol list -- `logger(...)` — platform logger for connection diagnostics -- `config(...)` / `logging(...)` — shared `HttpClientConfig` settings -- `mapper(...)` — custom Jackson `ObjectMapper` - -### Connect methods - -| Method | Description | -| --- | --- | -| `connect(path, listener)` | Synchronous connect (blocks). | -| `connectAsync(path, listener)` | Returns `CompletableFuture`. | -| `connectSmart(path, listener)` | Async on blocking-sensitive threads, sync otherwise. | - -### Runtime profiles - -Use `MagicWebSocketClientProfile` for config-aware WebSocket clients that -rebuild on config reload: - -```java -MagicWebSocketClientProfile gateway = MagicWebSocketClientProfile - .builder(runtime, "ws.gateway", ApiConfig.class) - .sections("gateway") - .baseUrl(config -> config.gateway.wsUrl) - .bearerAuth(config -> config.gateway.token) - .subprotocols(config -> config.gateway.subprotocols) - .build(); - -MagicWebSocketClient client = gateway.require(); -``` - -### Cleanup - -Call `wsClient.close()` to release resources. When using runtime profiles, the -profile handles cleanup automatically on config reload and runtime shutdown. - -## Async method variants - -Every convenience method on `MagicHttpClient` has three variants: - -| Suffix | Behaviour | -| --- | --- | -| _(none)_ | Synchronous. Throws on blocking-sensitive threads. | -| `...Async(...)` | Returns `CompletableFuture`. Always non-blocking. | -| `...Smart(...)` | Sync when safe, async when on a blocking-sensitive thread. | - -Available methods: `get`, `getJson`, `post`, `postJson`, `postMultipart`, -`send`. - -## Notes - -- Retries apply to the convenience methods (`get`, `post`, `postJson`, etc.). -- For raw `send(...)`, retries are not automatic. -- JSON mapping uses Jackson; invalid JSON throws `IllegalStateException`. -- Synchronous methods throw `IllegalStateException` on blocking-sensitive - threads. Use `...Async()` or `...Smart()` in handlers. diff --git a/docs/modules/lang.md b/docs/modules/lang.md deleted file mode 100644 index 36a5f961..00000000 --- a/docs/modules/lang.md +++ /dev/null @@ -1,93 +0,0 @@ -# Lang - -The lang module manages localisation files, custom messages, and per-player -language overrides. - -## Setup - -Manual setup: - -```java -LanguageManager languageManager = new LanguageManager(platform, configManager); -languageManager.init("en"); -languageManager.setFallbackLanguage("en"); -languageManager.addMagicUtilsMessages(); - -Messages.register("myplugin", languageManager); -logger.setLanguageManager(languageManager); -``` - -Bootstrap helpers perform the same wiring automatically when language support is -enabled. - -## File Layout And Formats - -Language files live under `lang/{lang}.{ext}` inside the platform config -directory. - -- YAML is supported when `magicutils-config-yaml` is installed. -- JSON / JSONC work out of the box. -- TOML works when `magicutils-config-toml` is installed. - -`Messages.register(scope, manager)` keeps each plugin or mod isolated. Use -`Messages.setLanguageManager(...)` only when you need the legacy global -fallback. - -## Resolving Messages - -```java -MessagesView messages = Messages.view("myplugin"); - -Component title = messages.get("myplugin.welcome"); -messages.send(playerAudience, "myplugin.goodbye"); - -String raw = messages.getRaw("myplugin.balance", "amount", "42"); -Component rich = messages.get("myplugin.balance", Map.of("amount", "42")); -``` - -`Messages` uses MiniMessage for rich output and supports `{placeholder}`-style -replacements. - -## Per-Player Languages - -```java -languageManager.setPlayerLanguage(playerUuid, "uk"); -String msg = languageManager.getMessageForAudience(audience, "myplugin.welcome"); -``` - -`setPlayerLanguage(...)` accepts UUIDs, `Audience`, or player objects that -expose `getUniqueId()`. - -## Custom Messages - -```java -languageManager.putCustomMessage("en", "myplugin.welcome", "Hello"); -``` - -Custom messages are persisted under the `messages` section of the active -language file. - -## Async And Smart Loading - -Language loading touches disk. Use the async or smart helpers on -blocking-sensitive threads: - -```java -languageManager.loadLanguageAsync("en"); -languageManager.setLanguageAsync("en"); -languageManager.reloadAsync(); - -languageManager.loadLanguageSmart("en"); -languageManager.setLanguageSmart("en"); -languageManager.reloadSmart(); -``` - -## Fallbacks And Missing Keys - -```java -languageManager.setFallbackLanguage("en"); -languageManager.setLogMissingMessages(true); -``` - -When the current language is missing a key, MagicUtils falls back to the -configured fallback language before logging a missing-message warning. diff --git a/docs/modules/logger-config.md b/docs/modules/logger-config.md deleted file mode 100644 index 2d19d2c0..00000000 --- a/docs/modules/logger-config.md +++ /dev/null @@ -1,97 +0,0 @@ -# Logger config reference - -`logger.{ext}` stores logger settings. On Bukkit it lives in the plugin config -folder. On Fabric it is namespaced under `config//` by default. - -Formats: - -- JSON/JSONC by default -- YAML/TOML when the format helpers are available - -## Minimal example - -```yaml -plugin-name: DonateMenu -short-name: DM -debug-placeholders: false - -prefix: - chat-mode: FULL - console-mode: SHORT - custom: "[DM]" - use-gradient-chat: true - use-gradient-console: false - -defaults: - target: BOTH - text-max-length: 262144 - placeholder-engine-order: [MINI_PLACEHOLDERS, PB4, PAPI] - miniplaceholders-mode: COMPONENT - pb4-mode: COMPONENT - -chat: - auto-generate-colors: true - gradient: ["#7c3aed", "#ec4899"] - colors: - error: ["#ff4444", "#cc0000"] - warn: ["#ffbb33", "#ff8800"] - debug: ["#33b5e5", "#0099cc"] - success: ["#00c851", "#007e33"] - -console: - auto-generate-colors: true - gradient: ["#ffcc00", "#ff6600"] - strip-formatting: false - colors: - error: ["#ff4444", "#cc0000"] - warn: ["#ffbb33", "#ff8800"] - debug: ["#33b5e5", "#0099cc"] - success: ["#00c851", "#007e33"] - -help: - use-logger-colors: true - primary-color: "#ff55ff" - muted-color: "gray" - text-color: "white" - line: "-----------------------------" - page-size: 7 - max-enum-values: 8 - -sub-loggers: - Commands: - enabled: true -``` - -## Key sections - -### plugin-name / short-name - -Auto-filled on first run and used in prefix rendering. `short-name` is used by -`PrefixMode.SHORT`. - -### prefix - -- `chat-mode` / `console-mode`: `NONE`, `SHORT`, `FULL`, `CUSTOM` -- `custom`: custom prefix string for `CUSTOM` -- `use-gradient-chat` / `use-gradient-console`: apply gradient to prefix - -### defaults - -- `target`: `CHAT`, `CONSOLE`, `BOTH` -- `text-max-length`: max JSON length (Fabric only) -- `placeholder-engine-order`: preferred external placeholder order -- `miniplaceholders-mode`: `COMPONENT` or `TAG` -- `pb4-mode`: `COMPONENT` or `RAW` - -### chat / console - -Color palettes and gradients. Set `auto-generate-colors` to `false` to force -custom palettes. - -### help - -Controls the built-in help renderer formatting and pagination. - -### sub-loggers - -Per-prefix enable flags for `logger.withPrefix(...)` instances. diff --git a/docs/modules/logger.md b/docs/modules/logger.md deleted file mode 100644 index ea7036f6..00000000 --- a/docs/modules/logger.md +++ /dev/null @@ -1,164 +0,0 @@ -# Logger - -MagicUtils logger builds on Adventure components and provides a consistent API -for console and chat output, with optional localisation, internal placeholders, -and external placeholder engines. - -## Setup - -The recommended path is to obtain the logger from a bootstrap result or -`MagicRuntime`. - -### Bukkit/Paper - -```java -BukkitBootstrap.RuntimeResult magic = BukkitBootstrap.forPlugin(plugin) - .buildRuntime(); - -Logger logger = magic.logger(); -``` - -### Fabric - -```java -FabricBootstrap.RuntimeResult magic = FabricBootstrap.forMod("mymod", () -> server) - .buildRuntime(); - -Logger logger = magic.logger(); -``` - -### Velocity - -Velocity bootstrap returns `LoggerCore` directly: - -```java -VelocityBootstrap.RuntimeResult magic = VelocityBootstrap.forPlugin(proxy, plugin, "MyPlugin", dataDirectory) - .buildRuntime(); - -LoggerCore logger = magic.logger(); -``` - -### NeoForge / Custom Platforms - -NeoForge and custom platforms typically wire `LoggerCore` manually: - -```java -Platform platform = new NeoForgePlatformProvider(); -ConfigManager configManager = new ConfigManager(platform); -LoggerCore logger = new LoggerCore(platform, configManager, this, "MyMod"); -``` - -## Basic Usage - -```java -logger.info("Ready."); -logger.warn("Slow query detected"); -logger.error("Database unavailable"); -``` - -The logger accepts MiniMessage markup and can target console, chat, or both. - -## Log Builder - -```java -logger.info() - .toConsole() - .noPrefix() - .send("Reloaded"); -``` - -Use the builder when you want fine-grained control: - -- `target(LogTarget.CHAT | CONSOLE | BOTH)` -- `to(audience)` -- `toConsole()` -- `noPrefix()` - -## Prefixed Loggers - -```java -PrefixedLogger db = logger.withPrefix("database", "[DB]"); -db.info("Connected"); -``` - -Each prefixed logger gets its own entry under `sub-loggers` in `logger.{ext}` -for enable or disable toggles. - -## Prefix Modes - -Prefix rendering is controlled by `PrefixMode`: - -- `FULL` -> full plugin/mod name -- `SHORT` -> short name from config -- `CUSTOM` -> `setCustomPrefix(...)` -- `NONE` -> no prefix - -`Logger` delegates these controls to the underlying `LoggerCore`. - -## Runtime Integration - -When you already have `MagicRuntime`, the logger is a shared typed component: - -```java -MagicRuntime runtime = magic.runtime(); -LoggerCore loggerCore = runtime.requireComponent(LoggerCore.class); -``` - -That makes it easy to pass the logger into reloadable services or register -named runtime resources that log through the same config and placeholder setup. - -## Logger Configuration - -`LoggerConfig` is stored as `logger.{ext}`. - -- Bukkit: plugin config directory -- Fabric: `config//` by default -- Velocity / custom platforms: resolved through the active `Platform` - -See [Logger Config](logger-config.md) for a full key reference. - -Key sections: - -- `prefix` -- `defaults` -- `chat` / `console` -- `help` -- `sub-loggers` - -## Localisation - -Attach a `LanguageManager` to enable `@key` lookups: - -```java -logger.setLanguageManager(languageManager); -logger.info("@myplugin.ready"); -``` - -The logger resolves the key through the attached `LanguageManager` and then -renders the result as MiniMessage. - -Bootstrap helpers bind the language manager automatically when language support -is enabled. - -## Internal And External Placeholders - -Logger messages pass through both: - -1. `MagicPlaceholders` for `{key}` and `{namespace:key}` tokens. -2. Platform-specific external placeholder engines. - -Examples: - -```java -logger.info("Balance: {economy:balance}"); -logger.info("Hello {player}"); -``` - -Platform integrations: - -- Bukkit logger installs the PlaceholderAPI bridge and a Bukkit external - placeholder engine. -- Fabric logger installs Text Placeholder API / MiniPlaceholders / PB4 support - when those mods are present. -- `LoggerCore` also supports a custom `ExternalPlaceholderEngine` for custom - platforms. diff --git a/docs/modules/overview.md b/docs/modules/overview.md deleted file mode 100644 index 96c9ad54..00000000 --- a/docs/modules/overview.md +++ /dev/null @@ -1,53 +0,0 @@ -# Module Overview - -MagicUtils is split into focused artifacts so you can depend on only what you -need. The platform adapters wire the shared modules to Bukkit/Paper, Fabric, -Velocity, or NeoForge. - -Bootstrap helpers are available on Bukkit, Fabric, and Velocity. NeoForge -currently uses manual wiring with the platform adapter plus command module. - -## Core Modules - -| Module | Artifact | Notes | -| --- | --- | --- | -| Platform API | `magicutils-api` | `Platform`, `Audience`, `TaskScheduler`, player lifecycle/message events, and shared interfaces. | -| Core | `magicutils-core` | Shared runtime container (`MagicRuntime`), reflective access helpers, plus core config/lang/logger/placeholder wiring. | -| HTTP client | `magicutils-http-client` | HTTP and WebSocket client wrappers with JSON, retries, multipart, and runtime profiles. | -| Config | `magicutils-config` | JSON/JSONC config engine, migrations, comments. | -| Config YAML | `magicutils-config-yaml` | Adds YAML support via Jackson. | -| Config TOML | `magicutils-config-toml` | Adds TOML support via Jackson. | -| Lang | `magicutils-lang` | Language manager and message helpers. | -| Commands | `magicutils-commands` | Annotation/builder command framework. | -| Diagnostics | `magicutils-diagnostics` | Runtime diagnostics service, built-in checks, JSON export, and command helper support. | -| Logger | `magicutils-logger` | Adventure-based logger core. | -| Placeholders | `magicutils-placeholders` | Placeholder registry. | -| Processor | `magicutils-processor` | Annotation processor used by internal tooling. | - -## Platform Adapters - -| Platform | Artifact | Notes | -| --- | --- | --- | -| Bukkit/Paper | `magicutils-bukkit` | Includes the shared stack and exposes `BukkitBootstrap`. | -| Bukkit bundle | `magicutils-bukkit-bundle` | Shared Bukkit/Paper plugin bundle for server installs. | -| Fabric | `magicutils-fabric` | Platform API adapter for Fabric modular setups. | -| Velocity | `magicutils-velocity` | Platform adapter plus config/logger/lang/commands for Velocity. | -| NeoForge | `magicutils-neoforge` | Platform API adapter for NeoForge manual setups. | - -## Brigadier Integrations - -| Module | Artifact | Notes | -| --- | --- | --- | -| Brigadier base | `magicutils-commands-brigadier` | Shared Brigadier command registry base. | -| Commands (NeoForge) | `magicutils-commands-neoforge` | Brigadier integration for NeoForge. | - -## Fabric Integrations - -| Module | Artifact | Notes | -| --- | --- | --- | -| Logger (Fabric) | `magicutils-logger-fabric` | Fabric logger adapter. | -| Commands (Fabric) | `magicutils-commands-fabric` | Brigadier integration plus `FabricBootstrap`. | -| Placeholders (Fabric) | `magicutils-placeholders-fabric` | Fabric placeholder bridge. | -| Fabric bundle | `magicutils-fabric-bundle` | Jar-in-jar distribution for shared server installs. | - -Use the per-module pages for API details, examples, and configuration options. diff --git a/docs/modules/permissions.md b/docs/modules/permissions.md deleted file mode 100644 index 3209e42c..00000000 --- a/docs/modules/permissions.md +++ /dev/null @@ -1,158 +0,0 @@ -# Permissions - -MagicUtils commands generate and evaluate permission nodes automatically. The -registry prefix controls namespacing, while annotations control explicit nodes, -defaults, and conditional argument checks. - -## Permission Prefix - -Every command registry has a permission prefix. You set it either directly on -the registry: - -```java -CommandRegistry registry = CommandRegistry.create(plugin, "donatemenu", logger); -``` - -or through the bootstrap helper: - -```java -BukkitBootstrap.forPlugin(plugin) - .permissionPrefix("donatemenu") - .enableCommands() - .buildRuntime(); -``` - -MagicUtils prepends that prefix to all generated nodes. - -## Generated Nodes - -When annotations omit explicit permission strings, MagicUtils builds defaults: - -- Command: `commands.` -- Subcommand: `commands..subcommand.` -- Argument: `commands..subcommand..argument.` - -With prefix `donatemenu`: - -- `donatemenu.commands.donate` -- `donatemenu.commands.donate.subcommand.give` -- `donatemenu.commands.donate.subcommand.give.argument.player` - -## Default Access - -`MagicPermissionDefault` controls what happens when the permission node is not -granted explicitly: - -- `TRUE` -> everyone -- `OP` -> operators or elevated senders -- `NOT_OP` -> non-operators / non-elevated senders -- `FALSE` -> nobody - -Platform behaviour differs slightly: - -- Bukkit registers permission nodes with Bukkit's permission manager and uses - Bukkit permission defaults. -- Fabric checks `fabric-permissions-api-v0` when available and falls back to - op-level checks. -- NeoForge falls back to command-source permission level checks. -- Velocity relies on the proxy's permission checks and uses the default policy - only when the node itself is absent. - -## Wildcards - -On Bukkit, MagicUtils also registers wildcard nodes: - -- `...commands..*` -- `...commands..subcommand.*` - -Velocity also honours prefix-style wildcard checks such as `prefix.*` and -`prefix.*` when the proxy reports them as granted. - -## Explicit Permission Annotations - -Use explicit nodes when you want stable names independent of the generated -shape: - -```java -@CommandInfo( - name = "donate", - permission = "donatemenu.open", - permissionDefault = MagicPermissionDefault.TRUE -) -public final class DonateCommand extends MagicCommand { -} -``` - -Subcommands support the same fields: - -```java -@SubCommand( - name = "reload", - permission = "donatemenu.admin.reload", - permissionDefault = MagicPermissionDefault.OP -) -public CommandResult reload(@Sender MagicSender sender) { - return CommandResult.success("Reloaded"); -} -``` - -## Argument Permissions - -Use `@Permission` on parameters to gate argument usage: - -```java -public CommandResult grant( - @Sender MagicSender sender, - @Permission(when = "other(player)") @ParamName("player") Player target -) { - return CommandResult.success("ok"); -} -``` - -You can override the generated node segment: - -```java -@Permission(node = "target", includeArgumentSegment = false) -``` - -## Conditional Permission Keywords - -- `self(arg)` / `other(arg)` / `anyother(arg)` -- `not_null(arg)` / `exists(arg)` -- `distinct(a,b)` / `all_distinct(a,b)` -- `equals(a,b)` / `not_equals(a,b)` - -Use `compare = CompareMode.UUID/NAME/EQUALS/AUTO` to control how values are -compared. - -## Manual Checks - -`MagicSender` exposes direct permission checks when you need custom logic -outside annotation processing: - -```java -MagicSender sender = MagicSender.wrap(rawSender); -if (MagicSender.hasPermission(rawSender, "donatemenu.commands.donate")) { - // ... -} -``` - -When the platform adapter uses op-level fallback semantics, the two-argument -form keeps the adapter default, while the three-argument form overrides it for -that one check: - -```java -if (MagicSender.hasPermission(rawSender, "leavepulse.whitelist.notify", 3)) { - // ... -} - -if (sender != null && sender.hasPermission("leavepulse.admin", 4)) { - // ... -} -``` - -This matters most on Fabric and NeoForge, where permission backends may fall -back to command-source op levels when a node is unknown or no backend responds. - -You can also use the registry prefix when building related manual nodes so the -manual and generated permissions stay in the same namespace. diff --git a/docs/modules/placeholders-advanced.md b/docs/modules/placeholders-advanced.md deleted file mode 100644 index d6c95375..00000000 --- a/docs/modules/placeholders-advanced.md +++ /dev/null @@ -1,120 +0,0 @@ -# Placeholders Advanced - -This page covers namespace metadata, listeners, local scopes, and platform -integration details. - -## Namespace Metadata - -Register namespace metadata to expose author and version information: - -```java -MagicPlaceholders.registerNamespace("donatemenu", "THEROER", "1.10.0"); -``` - -Metadata is consumed by PlaceholderAPI expansions and Fabric namespace -registration layers. - -## Key Normalization - -Namespaces and keys are normalized to lowercase. Use lowercase names to avoid -accidental duplicates. - -## Arguments - -Resolvers receive an optional `argument` string. - -PlaceholderAPI-style examples: - -- `%donatemenu_balance%` -- `%donatemenu_balance:bank%` - -Inside `MagicPlaceholders.render(...)`, the default argument separator is `|`: - -- `{balance|bank}` -- `{donatemenu:balance}` -- `{donatemenu:balance:bank}` - -Override the local separator per context when needed: - -```java -PlaceholderContext context = PlaceholderContext.builder() - .argumentSeparator("::") - .build(); -``` - -## Audience Handling - -`Audience` can be `null` for console or offline contexts. Use -`MagicPlaceholders.audienceFromUuid(UUID)` when you only have a player ID: - -```java -MagicPlaceholders.register("donatemenu", "balance", (audience, arg) -> { - if (audience == null) { - return "0"; - } - return "42"; -}); -``` - -## Local And Global Placeholders - -Global placeholders are namespace-free: - -```java -MagicPlaceholders.registerGlobal("server", (audience, arg) -> "Example"); -``` - -Local placeholders are tied to an owner key such as a plugin instance, a -logger, or another stable runtime object: - -```java -MagicPlaceholders.registerLocal(this, "balance", (audience, arg) -> "42"); -``` - -This is useful when you want one placeholder key to mean different things in -different runtime scopes. - -## Registry Listeners - -Listen to registry changes: - -```java -MagicPlaceholders.addListener(new MagicPlaceholders.PlaceholderListener() { - public void onPlaceholderRegistered(MagicPlaceholders.PlaceholderKey key) { - // refresh cache, update UI, etc. - } - - public void onPlaceholderUnregistered(MagicPlaceholders.PlaceholderKey key) { - } - - public void onNamespaceUpdated(String namespace) { - } -}); -``` - -## Debug Listeners - -Subscribe to resolution events: - -```java -MagicPlaceholders.addDebugListener((ownerKey, key, audience, arg, result) -> { - // inspect result.value() -}); -``` - -This is especially useful when debugging placeholder chains inside logger -output. - -## Safe Resolution - -`MagicPlaceholders.resolve(...)` returns an empty string when the placeholder is -missing or when a resolver throws. Keep resolvers fast and side-effect free. - -## Integration Summary - -- Bukkit: auto-bridges to [PlaceholderAPI](https://modrinth.com/plugin/placeholderapi) - when the Bukkit logger integration is active. -- Fabric: registers with [PB4 Placeholder API](https://placeholders.pb4.eu/) - when present. -- Fabric: registers with [Text Placeholder API](https://modrinth.com/mod/placeholder-api) - and [MiniPlaceholders](https://modrinth.com/mod/miniplaceholders) when present. diff --git a/docs/modules/placeholders.md b/docs/modules/placeholders.md deleted file mode 100644 index 1fd079aa..00000000 --- a/docs/modules/placeholders.md +++ /dev/null @@ -1,104 +0,0 @@ -# Placeholders - -The placeholder module offers a shared registry plus platform bridges for: - -- Bukkit PlaceholderAPI -- Fabric PB4 Placeholder API -- Fabric Text Placeholder API -- Fabric MiniPlaceholders - -## Register Placeholders - -```java -MagicPlaceholders.registerNamespace("myplugin", "MyPlugin", "1.0.0"); - -MagicPlaceholders.register("myplugin", "online", (audience, arg) -> "42"); -MagicPlaceholders.register("myplugin", "balance", (audience, arg) -> { - return arg != null ? arg : "0"; -}); -``` - -Global placeholders are available without a namespace: - -```java -MagicPlaceholders.registerGlobal("server", (audience, arg) -> "Example"); -``` - -Local placeholders are scoped to an owner key: - -```java -MagicPlaceholders.registerLocal(this, "balance", (audience, arg) -> "42"); -``` - -## Resolve And Render - -Resolve a single entry directly: - -```java -String value = MagicPlaceholders.resolve("myplugin", "online", audience, null); -``` - -Render tokens inside text: - -```java -PlaceholderContext context = PlaceholderContext.builder() - .audience(audience) - .defaultNamespace("myplugin") - .ownerKey(this) - .inline(Map.of("player", "Steve")) - .build(); - -String text = "Hello {player}! Balance: {balance|bank}"; -String rendered = MagicPlaceholders.render(context, text); -``` - -Convenience overloads: - -```java -String rendered = MagicPlaceholders.render(audience, "Hello {server}"); -``` - -## Resolution Order - -For `{key}` tokens: - -1. Inline values from the context -2. Local placeholders for the `ownerKey` -3. Default namespace (`defaultNamespace:key`) -4. Global placeholders - -Namespaced tokens use `{namespace:key}` or `{namespace:key:arg}`. For local -arguments in plain `{key}` form, use the argument separator (default `|`): - -```java -PlaceholderContext context = PlaceholderContext.builder() - .argumentSeparator("::") - .build(); -``` - -## Platform Bridges - -- Bukkit: the bridge is installed when the Bukkit `Logger` adapter is created - or when Bukkit bootstrap wiring creates that logger for you. -- Fabric: the bridge is installed by the Fabric logger integration and registers - available placeholders with PB4 Placeholder API, Text Placeholder API, and - MiniPlaceholders when those mods are present. - -Supported Fabric mods: - -- [PB4 Placeholder API](https://placeholders.pb4.eu/) -- [Text Placeholder API](https://modrinth.com/mod/placeholder-api) -- [MiniPlaceholders](https://modrinth.com/mod/miniplaceholders) - -## Registry Introspection - -Snapshots of the registry are available for tooling and debugging: - -```java -Set namespaces = MagicPlaceholders.namespaces(); -Map entries = - MagicPlaceholders.entries(); -``` - -See [Placeholders Advanced](placeholders-advanced.md) for metadata, listeners, -and debug hooks. diff --git a/docs/platforms/bukkit.md b/docs/platforms/bukkit.md deleted file mode 100644 index c736fced..00000000 --- a/docs/platforms/bukkit.md +++ /dev/null @@ -1,60 +0,0 @@ -# Bukkit/Paper - -Use `magicutils-bukkit` to wire MagicUtils to Bukkit/Paper. - -If you want a shared install for multiple plugins, use -`magicutils-bukkit-bundle` as a standalone plugin and add a dependency on it -in your `plugin.yml` (`depend: [MagicUtils]` or `softdepend`). - -## Recommended Bootstrap - -```java -public final class MyPlugin extends JavaPlugin { - private BukkitBootstrap.RuntimeResult magic; - - @Override - public void onEnable() { - magic = BukkitBootstrap.forPlugin(this) - .enableCommands() - .configureCommands(registry -> registry.registerCommand(new ExampleCommand())) - .buildRuntime(); - } - - @Override - public void onDisable() { - if (magic != null) { - magic.runtime().close(); - magic = null; - } - } -} -``` - -`build()` returns the legacy bootstrap view. `buildRuntime()` additionally gives -you a managed `MagicRuntime`. - -## Manual Wiring - -```java -Platform platform = new BukkitPlatformProvider(plugin); -ConfigManager configManager = new ConfigManager(platform); -Logger logger = new Logger(platform, plugin, configManager); -``` - -Use the manual path only when you need full control over how the services are -created. - -## Commands - -`BukkitBootstrap.enableCommands()` creates a `CommandRegistry` for the plugin. -The registry registers commands directly with the Bukkit `CommandMap`, so you do -not need to declare them in `plugin.yml`. - -Permission nodes are resolved using the prefix passed to -`permissionPrefix(...)` or, by default, the plugin name. - -## Placeholders - -When [PlaceholderAPI](https://modrinth.com/plugin/placeholderapi) is installed, -MagicUtils placeholders can be bridged into PlaceholderAPI through the Bukkit -integration layer. diff --git a/docs/platforms/core.md b/docs/platforms/core.md deleted file mode 100644 index fbc353bf..00000000 --- a/docs/platforms/core.md +++ /dev/null @@ -1,215 +0,0 @@ -# Core / Common Logic - -Use this page when your plugin or mod has a thin platform entrypoint and most of -the real logic lives in shared `common` or `core` code. - -That usually means: - -- Bukkit/Fabric/Velocity/NeoForge only bootstrap the runtime -- config, lang, logger, HTTP, placeholders, and business logic live in common -- platform modules stay as adapters instead of owning the feature logic - -## Typical Module Layout - -```text -my-plugin/ -|- common/ -|- bukkit/ -|- fabric/ -`- velocity/ -``` - -In this structure: - -- `bukkit/`, `fabric/`, and `velocity/` bootstrap MagicUtils for their runtime -- `common/` receives `MagicRuntime` and owns the actual feature logic -- platform modules do not reimplement the same services three times - -## The Recommended Split - -Keep these parts in the platform layer: - -- bootstrap helpers such as `BukkitBootstrap`, `FabricBootstrap`, - `VelocityBootstrap` -- platform event registration -- command registration against the platform dispatcher -- external placeholder bridge setup -- plugin or mod lifecycle entrypoints - -Keep these parts in `common` or other shared modules: - -- services built on `MagicRuntime` -- config models and reload logic -- logger and language driven messaging -- HTTP clients and runtime config bindings -- placeholder logic used by your own code -- business rules that should work across every platform - -## Common Code Should Depend On Shared Abstractions - -Inside common code, depend on `MagicRuntime` or the core services it exposes: - -- `Platform` -- `ConfigManager` -- `LoggerCore` -- `LanguageManager` -- named runtime resources and config bindings - -This keeps the shared layer free from Bukkit, Fabric, Velocity, or NeoForge -classes. - -## Wiring Shared Services From The Platform Layer - -The platform entrypoint should bootstrap MagicUtils and hand the runtime to your -common services: - -```java -public final class MyPlugin extends JavaPlugin { - private BukkitBootstrap.RuntimeResult magic; - private CommonBootstrap bootstrap; - - @Override - public void onEnable() { - magic = BukkitBootstrap.forPlugin(this) - .enableCommands() - .buildRuntime(); - - bootstrap = new CommonBootstrap(magic.runtime()); - bootstrap.start(); - } - - @Override - public void onDisable() { - if (magic != null) { - magic.runtime().close(); - magic = null; - } - } -} -``` - -The same structure works on Fabric, Velocity, and NeoForge: platform code -creates the runtime, shared code consumes it. - -`forPlugin(...)`, `forMod(...)`, and the other bootstrap helpers belong to the -platform module only. `common` should consume the resulting runtime, not create -it. - -## Example Shared Service - -```java -public final class CommonBootstrap { - private final MagicRuntime runtime; - private final ConfigManager configManager; - private final LoggerCore logger; - private final Optional languages; - - public CommonBootstrap(MagicRuntime runtime) { - this.runtime = runtime; - this.configManager = runtime.configManager(); - this.logger = runtime.logger(); - this.languages = runtime.findComponent(LanguageManager.class); - } - - public void start() { - logger.info("Starting shared services"); - - languages.ifPresent(manager -> logger.info("Language manager is available")); - - runtime.bindConfig( - "http.backend", - BackendConfig.class, - config -> MagicHttpClient.builder(runtime.platform(), configManager) - .baseUrl(config.baseUrl) - .build(), - "backend" - ); - } -} -``` - -This is the typical multi-platform pattern: the shared layer receives one -runtime from the adapter layer and builds everything else on top of it. - -## What Works Well In Common - -The `magicutils-core` path is especially good for: - -- `MagicRuntime` -- `ConfigManager` -- `LoggerCore` -- optional `LanguageManager` -- runtime-managed HTTP or WebSocket clients -- shared placeholder evaluation -- reloadable services built with `bindConfig(...)` - -If your modules already target multiple platforms, this is usually where the -majority of the code should live. - -## Player Events - -`Platform` provides normalized player lifecycle and message events that work -across all platforms without importing Bukkit, Fabric, or Velocity types. - -### Player Lifecycle - -Subscribe to join/leave events: - -```java -ListenerSubscription sub = platform.subscribePlayerLifecycle(event -> { - if (event.type() == PlayerLifecycleType.JOIN) { - logger.info(event.playerName() + " joined"); - } -}); -``` - -`PlayerLifecycle` contains: - -- `playerId()` — player UUID (when available) -- `playerName()` — display/login name -- `type()` — `JOIN` or `LEAVE` - -### Player Messages - -Subscribe to chat messages and commands: - -```java -ListenerSubscription sub = platform.subscribePlayerMessages(event -> { - if (event.type() == PlayerMessageType.CHAT) { - logger.info(event.playerName() + ": " + event.message()); - } -}); -``` - -`PlayerMessage` contains: - -- `playerId()` — player UUID (when available) -- `playerName()` — display/login name -- `message()` — raw chat content or command line -- `type()` — `CHAT` or `COMMAND` - -Both subscriptions return `ListenerSubscription` which can be closed to -unsubscribe. Both records expose `isValid()` for null-safety checks. - -## What Should Stay Out Of Common - -Try not to leak platform-specific APIs into the shared layer. - -Avoid putting these directly into `common`: - -- Bukkit `JavaPlugin`, Fabric callbacks, Velocity annotations, NeoForge events -- platform command dispatcher registration -- direct calls to platform plugin managers or server APIs -- external bridge setup that only exists on one runtime - -Keep those in the platform module and pass only the shared abstractions -downward. - -## If You Really Need A Custom Platform - -That is a separate case from normal `common` code. - -If you are actually building a new adapter around `Platform`, -`ShutdownHookRegistrar`, or your own bootstrap path, use the Runtime guide and -the platform API as the source of truth, but keep that adapter layer small and -let the feature logic remain in shared services. diff --git a/docs/platforms/fabric.md b/docs/platforms/fabric.md deleted file mode 100644 index afa9fa0a..00000000 --- a/docs/platforms/fabric.md +++ /dev/null @@ -1,78 +0,0 @@ -# Fabric - -For most mods, use `magicutils-fabric-bundle` and wire the runtime through -`FabricBootstrap`. - -## Recommended Bootstrap - -```java -public final class MyMod implements ModInitializer { - private static final String MOD_ID = "mymod"; - - private MinecraftServer server; - private FabricBootstrap.RuntimeResult magic; - - @Override - public void onInitialize() { - magic = FabricBootstrap.forMod(MOD_ID, () -> server) - .enableCommands() - .buildRuntime(); - - CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { - if (magic.commandRegistry() != null) { - magic.commandRegistry().registerCommand(dispatcher, new ExampleCommand()); - } - }); - - ServerLifecycleEvents.SERVER_STARTED.register(server -> this.server = server); - ServerLifecycleEvents.SERVER_STOPPING.register(server -> { - this.server = null; - if (magic != null) { - magic.runtime().close(); - magic = null; - } - }); - } -} -``` - -`FabricBootstrap` wires `Platform`, `ConfigManager`, `Logger`, -`LanguageManager`, `Messages`, and an optional Fabric command registry. Actual -command registration still happens in Fabric's Brigadier callback. - -## Modular Setup - -If you do not want the bundle, combine the platform adapter with the Fabric -integration modules you need: - -- `magicutils-fabric` -- `magicutils-commands-fabric` -- `magicutils-logger-fabric` -- `magicutils-placeholders-fabric` - -## Permissions - -Fabric permissions integrate with `fabric-permissions-api-v0` when installed. -If no permission provider exists, MagicUtils falls back to op-level checks. Use -`opLevel(...)` on `FabricBootstrap` or `CommandRegistry.create(...)` when you -need a different default operator level. - -## Placeholders - -Fabric placeholder support is optional and activates automatically when -placeholder mods are present: - -- [Text Placeholder API](https://modrinth.com/mod/placeholder-api) -- [MiniPlaceholders](https://modrinth.com/mod/miniplaceholders) - -## Config Format - -Default config format on Fabric is JSONC. You can override it via -`.format` or `magicutils.format` (see the Config module docs). - -## Bundle Strategy - -- Embed `magicutils-fabric-bundle` in your mod. -- Or ship one shared bundle mod on the server and depend on it. - -Do not use both approaches at the same time. diff --git a/docs/platforms/neoforge.md b/docs/platforms/neoforge.md deleted file mode 100644 index 8f003e55..00000000 --- a/docs/platforms/neoforge.md +++ /dev/null @@ -1,40 +0,0 @@ -# NeoForge - -NeoForge support currently uses the manual wiring path with -`magicutils-neoforge` and the optional `magicutils-commands-neoforge` module. - -## Platform Wiring - -```java -public final class MyMod { - private static final String MOD_ID = "mymod"; - - private final Platform platform; - private final ConfigManager configManager; - private final LoggerCore logger; - private final CommandRegistry commands; - - public MyMod() { - platform = new NeoForgePlatformProvider(); - configManager = new ConfigManager(platform); - logger = new LoggerCore(platform, configManager, this, "MyMod"); - commands = CommandRegistry.create(MOD_ID, MOD_ID, logger); - } - - @SubscribeEvent - public void onRegisterCommands(RegisterCommandsEvent event) { - commands.registerCommand(event.getDispatcher(), new ExampleCommand()); - } -} -``` - -If you need a different operator threshold, use -`CommandRegistry.create(..., opLevel)`. - -## Notes - -- NeoForge uses `LoggerCore` directly instead of the Bukkit/Fabric `Logger` - wrapper. -- There is no dedicated NeoForge bootstrap helper yet. -- There is no NeoForge placeholder bridge yet. Use MagicUtils core - placeholders directly where needed. diff --git a/docs/platforms/velocity.md b/docs/platforms/velocity.md deleted file mode 100644 index fe535ea8..00000000 --- a/docs/platforms/velocity.md +++ /dev/null @@ -1,101 +0,0 @@ -# Velocity - -`magicutils-velocity` exposes platform wiring, config, logger, lang, and -command integration in one artifact. - -## Dependency - -```kotlin -dependencies { - implementation("dev.ua.theroer:magicutils-velocity:{{ magicutils_version }}") -} -``` - -## Recommended Bootstrap - -```java -@Plugin(id = "myplugin", name = "MyPlugin", version = "1.0.0") -public final class MyPlugin { - private final ProxyServer proxy; - private final org.slf4j.Logger slf4j; - private final Path dataDirectory; - private VelocityBootstrap.RuntimeResult magic; - - @Inject - public MyPlugin(ProxyServer proxy, - org.slf4j.Logger slf4j, - @DataDirectory Path dataDirectory) { - this.proxy = proxy; - this.slf4j = slf4j; - this.dataDirectory = dataDirectory; - } - - @Subscribe - public void onProxyInitialize(ProxyInitializeEvent event) { - magic = VelocityBootstrap.forPlugin(proxy, this, "MyPlugin", dataDirectory) - .slf4j(slf4j) - .enableCommands() - .configureCommands(registry -> registry.registerCommand(new ExampleCommand())) - .buildRuntime(); - } - - @Subscribe - public void onProxyShutdown(ProxyShutdownEvent event) { - if (magic != null) { - magic.runtime().close(); - magic = null; - } - } -} -``` - -`VelocityBootstrap` creates the `Platform`, `ConfigManager`, `LoggerCore`, -`LanguageManager`, and optional `CommandRegistry` for the plugin. - -## Commands - -Velocity commands register directly with the proxy command manager. - -- Use `enableCommands()` to create the registry during bootstrap. -- Use `configureCommands(...)` to register commands right away. -- Use `asyncExecutor(...)` when you want to override the default async executor - used by the command layer. - -## Bootstrap Options - -`VelocityBootstrap.Builder` supports additional configuration beyond the basics: - -| Method | Description | -| --- | --- | -| `slf4j(logger)` | Bind SLF4J logger for console output. | -| `enableCommands()` | Create a `CommandRegistry` during bootstrap. | -| `permissionPrefix(prefix)` | Set the permission node prefix for commands. | -| `asyncExecutor(executor)` | Override the async executor used by the command layer. | -| `configureCommands(consumer)` | Register commands during bootstrap. | -| `initLanguage(boolean)` | Enable/disable language manager initialization. | -| `bindLoggerLanguage(boolean)` | Bind the language manager to the logger. | -| `setMessagesManager(boolean)` | Set the global `Messages` language manager. | -| `registerMessages(boolean)` | Register the plugin's messages scope. | -| `addMagicUtilsMessages(boolean)` | Register built-in MagicUtils messages. | -| `translations(consumer)` | Configure additional translations. | - -## Player Events - -Velocity supports the platform-agnostic player lifecycle and message events: - -```java -platform.subscribePlayerLifecycle(event -> { - if (event.type() == PlayerLifecycleType.JOIN) { - logger.info(event.playerName() + " connected"); - } -}); -``` - -See [Core / Common Logic](core.md#player-events) for the full event API. - -## Notes - -- `Platform.runOnMain(...)` executes immediately because Velocity has no main - game thread. -- `buildRuntime()` returns a managed `MagicRuntime` and wires shutdown cleanup - through the plugin instance you pass to the platform provider. diff --git a/docs/recipes/index.md b/docs/recipes/index.md deleted file mode 100644 index 7d207575..00000000 --- a/docs/recipes/index.md +++ /dev/null @@ -1,50 +0,0 @@ -# Recipes - -## Embed MagicUtils In A Fabric Mod - -Use `include("dev.ua.theroer:magicutils-fabric-bundle:{{ magicutils_version }}")` -and avoid installing the standalone bundle on the server. - -## Share A Single Bundle On A Server - -Install `magicutils-fabric-bundle` in `mods/` and use `modImplementation` -without `include(...)`. - -Download link: -[`magicutils-fabric-bundle-{{ magicutils_version }}.jar`](https://maven.theroer.dev/releases/dev/ua/theroer/magicutils-fabric-bundle/{{ magicutils_version }}/magicutils-fabric-bundle-{{ magicutils_version }}.jar) - -## Register The Built-In Help Command - -Bukkit or Fabric: - -```java -registry.registerCommand(new HelpCommand(logger, registry)); -``` - -## Add Help As A Subcommand - -Use this pattern when you want help attached to an existing command tree or on -platforms without a dedicated `HelpCommand` wrapper: - -```java -registry.registerCommand(new MyCommand() - .addSubCommand(HelpCommandSupport.createHelpSubCommand( - "help", - loggerCore, - registry::commandManager - ))); -``` - -## Force A Config Format - -Create `.format` next to the file or `magicutils.format` in the root -config directory with a single line: - -```text -jsonc -``` - -## Add Enum Suggestions - -Enum parameters are auto-suggested. You can additionally limit visible choices -via `@Suggest("{one,two}")` for a specific argument. diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index f0fd01a3..00000000 --- a/mkdocs.yml +++ /dev/null @@ -1,88 +0,0 @@ -site_name: MagicUtils -site_description: Modular toolkit for Bukkit/Paper, Fabric, Velocity, and NeoForge. -site_url: https://THEROER.github.io/MagicUtils/ -repo_name: THEROER/MagicUtils -repo_url: https://github.com/THEROER/MagicUtils -edit_uri: edit/main/docs/ - -theme: - name: material - language: en - features: - - navigation.tabs - - navigation.sections - - navigation.top - - content.code.copy - - content.tabs.link - - toc.follow - palette: - - scheme: slate - primary: deep purple - accent: purple - toggle: - icon: material/weather-sunny - name: Switch to light mode - - scheme: default - primary: deep purple - accent: purple - toggle: - icon: material/weather-night - name: Switch to dark mode - -plugins: - - search - - macros: - module_name: macros - -markdown_extensions: - - admonition - - attr_list - - md_in_html - - pymdownx.details - - pymdownx.superfences - - pymdownx.highlight: - anchor_linenums: true - - pymdownx.inlinehilite - - pymdownx.tabbed: - alternate_style: true - - pymdownx.snippets - - pymdownx.keys - - toc: - permalink: true - -extra: - version: - provider: mike - -nav: - - Home: index.md - - Getting Started: - - Installation: getting-started/installation.md - - Quickstart: getting-started/quickstart.md - - Runtime: getting-started/runtime.md - - Migration Guide: getting-started/migration.md - - Versioning: getting-started/versioning.md - - Modules: - - Overview: modules/overview.md - - Logger: modules/logger.md - - Logger Config: modules/logger-config.md - - Commands: modules/commands.md - - Permissions: modules/permissions.md - - Commands Cheat Sheet: modules/commands-cheatsheet.md - - Diagnostics: modules/diagnostics.md - - HTTP Client: modules/http-client.md - - Config: modules/config.md - - Config Advanced: modules/config-advanced.md - - Lang: modules/lang.md - - Placeholders: modules/placeholders.md - - Placeholders Advanced: modules/placeholders-advanced.md - - Platforms: - - Core / Common Logic: platforms/core.md - - Bukkit/Paper: platforms/bukkit.md - - Fabric: platforms/fabric.md - - Velocity: platforms/velocity.md - - NeoForge: platforms/neoforge.md - - Recipes: recipes/index.md - - API: api.md - - FAQ: faq.md - - Contributing: contributing.md diff --git a/requirements-docs.txt b/requirements-docs.txt deleted file mode 100644 index c5233937..00000000 --- a/requirements-docs.txt +++ /dev/null @@ -1,3 +0,0 @@ -mkdocs-material -mkdocs-macros-plugin -mike From 0f273dccf729f028d5667964666429365e6f7e6c Mon Sep 17 00:00:00 2001 From: THEROER Date: Sat, 4 Jul 2026 13:46:51 +0300 Subject: [PATCH 3/3] docs: point README/RELEASING at Reposilite and the Gradle release flow Update the release pipeline description and verification commands for the new publish-maven-from-release flow, replace the deprecated publish_release.py references with the Gradle release tasks, and fix stale gh-pages/GitHub Pages Maven mentions. --- README.md | 24 ++++++++++++++---------- RELEASING.md | 44 +++++++++++++++----------------------------- scripts/README.md | 4 ++-- 3 files changed, 31 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 07f09322..2dabd7c1 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,10 @@ dependencies { } ``` -Shared bundle install: +Standalone install: `magicutils-fabric-bundle` is also a runnable Fabric mod on +its own — drop the jar into `mods/` and it boots the shared MagicUtils runtime +and registers the `/magicutils` (alias `/mu`) command with diagnostics +sub-commands, no host mod required. Depend on it from your mod with: ```kotlin dependencies { @@ -124,8 +127,9 @@ https://magicutils.theroer.dev/getting-started/quickstart/ ## Notes -- GitHub Pages Maven does not host `*-all` shaded artifacts. Use the thin jars - from Maven or build shaded jars locally when you need them. +- Artifacts are published to the self-hosted Reposilite at + `https://maven.theroer.dev/releases`. Both the thin jars and the `*-all` + shaded jars (where produced) are available there. ## Reflection Automation @@ -142,14 +146,14 @@ new reflection usage automatically. ## Release Helper -Maintainers can prepare and dispatch a tagged release with: +Maintainers cut a tagged release with the Gradle release tasks (the former +`scripts/publish_release.py` is deprecated): ```bash -python3 scripts/publish_release.py 1.19.2 --dry-run -python3 scripts/publish_release.py 1.19.2 +./gradlew releasePreflight -Pversion=X.Y.Z # validate version + tags, no changes +./gradlew release -Pversion=X.Y.Z # preflight → bump → dispatch release.yml ``` -The helper mirrors the GitHub Actions flow: it can sync `gradle.properties`, -push the selected branch, wait until `origin/` resolves to the pushed -commit, and then dispatch `release.yml`. Optional flags are available for -manual `javadoc.yml` and `publish-maven.yml` dispatches when needed. +`release` validates the version, bumps `gradle.properties`, commits, and +dispatches `release.yml`. CI then tags `vX.Y.Z`, builds docs/javadoc, and +publishes the artifacts to Reposilite. See `RELEASING.md` for the full flow. diff --git a/RELEASING.md b/RELEASING.md index 8ca3c3f9..df4ba5c9 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -12,8 +12,8 @@ land on `dev`, then `dev` is fast-forwarded into `main` before a release. `release` chains `releasePreflight` → `bumpVersion` → `dispatchRelease`: it validates the version, bumps `gradle.properties` and commits, then -dispatches `release.yml`. The server-side chain (tag, docs/javadoc, -gh-pages publish) runs from CI. Verify afterwards with `./gradlew +dispatches `release.yml`. The server-side chain (tag, Reposilite Maven +publish) runs from CI. Verify afterwards with `./gradlew smokeTest -Pversion=1.21.4` (polls the published POM). ## Release tasks @@ -89,40 +89,29 @@ release.yml ├── resolve version + tag string outputs ├── tag git tag vX.Y.Z && git push origin vX.Y.Z └── dispatch-downstream - gh workflow run docs.yml --ref vX.Y.Z -f alias=stable -f set_default=true - gh workflow run javadoc.yml --ref vX.Y.Z + gh workflow run publish-maven.yml --ref vX.Y.Z -f version=X.Y.Z -docs.yml (from workflow_dispatch) - └── mkdocs build → mike deploy → push gh-pages - -publish-maven.yml (workflow_run after docs success) +publish-maven.yml (from workflow_dispatch) ├── resolve-matrix ./gradlew printPublishMatrix (targets from targets.properties) ├── publish (matrix) ./gradlew -Ptarget= → PUT into Reposilite releases └── publish-plugins ./gradlew -p build-logic publish → Reposilite releases -javadoc.yml (from workflow_dispatch) - └── javadoc per module → push gh-pages under javadoc/X.Y.Z/ - verify (manual) └── ./gradlew smokeTest -Pversion=X.Y.Z HEAD-polls https://maven.theroer.dev/releases/.../magicutils-core-X.Y.Z.pom ``` -The chain is fully automatic. If a previous release used the manual -`--dispatch-publish-maven` flag, you no longer need it — release.yml's -`dispatch-downstream` job replaces that fallback. +Documentation is not part of this pipeline — the docs site lives in the separate +MagicUtilsWebsite (Nuxt) project. Javadoc jars are published to Reposilite +alongside the artifacts. ## Why workflow_dispatch instead of tag-push triggers -GitHub suppresses downstream workflows whose source push was made with -the runner's `GITHUB_TOKEN`. The tag we push from `release.yml` is one -of those, so a `workflow_run` trigger that listens for the docs build -will not fire if docs were started by the tag push. - -`release.yml` instead calls `gh workflow run docs.yml` from -`dispatch-downstream`. That is a `workflow_dispatch` event, which has -no such restriction — `publish-maven.yml` then fires through -`workflow_run` as designed. +GitHub suppresses downstream workflows whose source push was made with the +runner's `GITHUB_TOKEN`. The tag pushed from `release.yml` is one of those, so a +`workflow_run` trigger would not fire. Instead `release.yml`'s +`dispatch-downstream` job calls `gh workflow run publish-maven.yml` directly — +a `workflow_dispatch` event, which has no such restriction. ## Branch model @@ -138,8 +127,8 @@ no such restriction — `publish-maven.yml` then fires through |-------|---------|--------| | `validate` | `./gradlew buildScenario` fails | Fix the failing tests on `main`, then re-run `./gradlew release` with the same version. | | `tag` | "Tag vX.Y.Z already exists" | Either bump to a new patch number or delete the stale tag remotely (`git push origin :refs/tags/vX.Y.Z`) and re-run. | -| `dispatch-downstream` | docs/javadoc not started | Re-run the failed job from the Actions UI; gh CLI: `gh workflow run docs.yml --ref vX.Y.Z -f version=X.Y.Z -f alias=stable -f set_default=true`. | -| `publish-maven` | Maven artifact missing | Inspect `gh run list -w publish-maven.yml`. The job uses `workflow_run` from docs success — if docs failed, fix and rerun docs first. Manual fallback: `gh workflow run publish-maven.yml`. | +| `dispatch-downstream` | publish-maven not started | Re-run the failed job from the Actions UI; gh CLI: `gh workflow run publish-maven.yml --ref vX.Y.Z -f version=X.Y.Z`. | +| `publish-maven` | Maven artifact missing | Inspect `gh run list -w publish-maven.yml`. Manual run: `gh workflow run publish-maven.yml -f version=X.Y.Z`. Check the `MAVEN_PUBLISH_USER`/`MAVEN_PUBLISH_TOKEN` secrets are set. | | smoke poll timeout | `404` persists | The publish job failed or its credentials were wrong. Check `gh run list -w publish-maven.yml` and that the `MAVEN_PUBLISH_USER`/`MAVEN_PUBLISH_TOKEN` secrets are set. Reposilite serves artifacts immediately (no CDN lag) — a lasting 404 means it wasn't uploaded. | To roll back a release that was tagged but not yet usable: @@ -157,9 +146,7 @@ usually isn't worth removing — bump the next version instead. ```bash # Was the chain successful? gh run list -R THEROER/MagicUtils -w release.yml --limit 1 -gh run list -R THEROER/MagicUtils -w docs.yml --limit 1 gh run list -R THEROER/MagicUtils -w publish-maven.yml --limit 1 -gh run list -R THEROER/MagicUtils -w javadoc.yml --limit 1 # Is the artifact visible? curl -fI https://maven.theroer.dev/releases/dev/ua/theroer/magicutils-lang/X.Y.Z/magicutils-lang-X.Y.Z.pom @@ -192,6 +179,5 @@ it for genuine emergencies. - `build-logic/src/main/kotlin/MagicUtilsReleaseTasks.kt` — the `release` group tasks (pure logic in `MagicUtilsReleaseModel.kt`). - `.github/workflows/release.yml` — orchestrator. -- `.github/workflows/docs.yml`, `publish-maven.yml`, `javadoc.yml` — the - three downstream workflows. +- `.github/workflows/publish-maven.yml` — the downstream Maven publish. - `scripts/README.md` — short pointer back here. diff --git a/scripts/README.md b/scripts/README.md index 71b1c10c..606c896a 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -12,8 +12,8 @@ group, implemented in `build-logic/src/main/kotlin/MagicUtilsReleaseTasks.kt` - `./gradlew smokeTest -Pversion=X.Y.Z` — poll the published POM. - `./gradlew release -Pversion=X.Y.Z` — preflight → bump → dispatch. -The server-side chain (tagging, docs/javadoc dispatch, gh-pages publish) still -lives in `.github/workflows/release.yml`. +The server-side chain (tagging, docs/javadoc dispatch, Reposilite Maven publish) +lives in `.github/workflows/release.yml` and `publish-maven.yml`. For the full release process — pipeline diagram, troubleshooting, and verification commands — see [`../RELEASING.md`](../RELEASING.md).