Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: PR title

# release-please builds the CHANGELOG from the commits that land on main. With
# squash merges the PR title becomes that commit message, so it has to follow
# Conventional Commits.
on:
# `synchronize` is here so the check re-reports on every push, which matters
# if this is made a required check.
pull_request:
types: [opened, reopened, edited, synchronize]

permissions:
pull-requests: read

jobs:
lint:
name: Validate the PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# Mirrors the types listed in release-please-config.json.
types: |
feat
fix
perf
refactor
docs
build
ci
chore
style
test
revert
55 changes: 0 additions & 55 deletions .github/workflows/publish.yml

This file was deleted.

89 changes: 89 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Release

on:
push:
branches: [main]
workflow_dispatch:
inputs:
tag:
description: "Existing release tag to (re)publish, e.g. v0.1.0"
required: true
type: string

permissions:
contents: write
pull-requests: write

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
release-please:
name: Maintain the release PR
# Manual runs skip straight to publishing an already-tagged version.
if: github.event_name == 'push'
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps:
- uses: googleapis/release-please-action@v5
id: release

publish:
name: Publish to GitHub Packages and Modrinth
needs: [release-please]
# `!cancelled()` keeps this job reachable when release-please is skipped
# (workflow_dispatch) instead of inheriting the skip.
if: >-
!cancelled()
&& (needs.release-please.outputs.release_created == 'true'
|| github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
permissions:
# `write` is needed to attach the JAR to the GitHub release.
contents: write
packages: write
env:
TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || needs.release-please.outputs.tag_name }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || needs.release-please.outputs.tag_name }}

- name: Set up JDK 21
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "21"

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6

# SQLite-only here; the MySQL matrix already ran on the release PR.
- name: Build
run: ./gradlew build --no-daemon --stacktrace

- name: Extract the release notes for Modrinth
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p build
gh release view "$TAG" --json body -q .body > build/modrinth-changelog.md

- name: Publish to GitHub Packages
env:
GITHUB_ACTOR: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew publish --no-daemon --stacktrace

- name: Publish to Modrinth
env:
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
run: ./gradlew modrinth modrinthSyncBody --no-daemon --stacktrace

- name: Attach the shaded JAR to the GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload "$TAG" build/libs/ModernVillagerShop-*.jar --clobber
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.0.0"
}
21 changes: 19 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,32 @@

## Commit & Pull Request Guidelines

- Existing history is minimal (`initial commit`), so use clear imperative commits going forward.
- Suggested commit format: `type(scope): short summary` (example: `feat(ui): add listing pagination controls`).
- Commit format is [Conventional Commits](https://www.conventionalcommits.org/): `type(scope): short summary` (example: `feat(ui): add listing pagination controls`). This is not cosmetic — release-please derives the version bump and the CHANGELOG from it, so `feat` means a minor bump, `fix` a patch, and `!` or a `BREAKING CHANGE:` footer a major.
- Because merges are squashed, **the PR title becomes the commit message on `main`** and must follow the same convention. `.github/workflows/pr-title.yml` enforces it.
- Keep commits focused; avoid mixing refactors with feature work.
- PRs should include:
- What changed and why
- Related issue/ticket (if available)
- Validation steps (`./gradlew build`, local `runServer` checks)
- Screenshots/GIFs for UI or dialog flow changes

## Releasing

Releases are fully automated; nothing is bumped or tagged by hand.

1. Merge PRs into `main` as usual. `.github/workflows/release.yml` runs release-please, which opens (or updates) a **"chore: release x.y.z"** PR containing the CHANGELOG entries and the new version in `gradle.properties`.
2. Review that PR — the CHANGELOG is a normal file, so hand-edit it there if the generated wording needs help.
3. Merge it. release-please then creates the `vx.y.z` tag and the GitHub release, and the same workflow builds and publishes to GitHub Packages and to [Modrinth](https://modrinth.com/plugin/modernvillagershop), attaches the shaded JAR to the GitHub release, and syncs `README.md` to the Modrinth project description.

Notes:

- The version lives only in `gradle.properties`, inside the `x-release-please-start-version` block. `processResources` expands it into `paper-plugin.yml` and Minotaur uses it as the Modrinth version number.
- To force a specific version (e.g. going 0.x → 1.0.0), land an empty commit with a `Release-As: 1.0.0` footer: `git commit --allow-empty -m "chore: release 1.0.0" -m "Release-As: 1.0.0"`.
- The Minecraft versions advertised on Modrinth come from `modrinth.gameVersions` in `gradle.properties`. Keep them aligned with what BedrockDialog supports, since it is a hard dependency.
- Because `README.md` is pushed as the Modrinth description, every link in it must be an absolute URL.
- Re-publishing an existing tag (e.g. after a transient Modrinth failure): run the **Release** workflow manually with the tag name as input.
- Required repository setup: the `MODRINTH_TOKEN` secret, and *Settings → Actions → General → Allow GitHub Actions to create and approve pull requests*.

## Security & Configuration Tips

- Never commit real database credentials or server secrets.
Expand Down
10 changes: 10 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ CI runs both backends — see [.github/workflows/ci.yml](.github/workflows/ci.ym
- Tests using Bukkit types should extend or use `testsupport/BukkitTestSupport` to manage MockBukkit lifecycle.
- Test naming: `<ClassName>Test`; method names describe behavior (`createsShopWhenVillagerIsValid`).

## Release automation

Versioning, CHANGELOG and publishing are automated — never bump a version or write a CHANGELOG entry by hand.

- **Commits drive everything.** [release-please](https://github.com/googleapis/release-please) parses Conventional Commits on `main` and maintains a "chore: release x.y.z" PR that carries the CHANGELOG diff and the version bump. Merging that PR creates the tag, the GitHub release, and triggers publishing. Sections and hidden types are configured in [release-please-config.json](release-please-config.json); the last released version is in [.release-please-manifest.json](.release-please-manifest.json).
- **The version lives in [gradle.properties](gradle.properties)** inside the `x-release-please-start-version` / `x-release-please-end` block. Gradle reads it automatically (there is no `version =` in [build.gradle](build.gradle)), `processResources` expands it into `paper-plugin.yml`, and Minotaur reuses it as the Modrinth version number. The block-comment form matters: a trailing `# x-release-please-version` would be swallowed into the properties value.
- **[.github/workflows/release.yml](.github/workflows/release.yml)** holds both the release-please job and the publish job. They are one workflow on purpose — a release created with `GITHUB_TOKEN` does not fire `release: published`, so a separate publish workflow would never run without a PAT.
- **Modrinth** upload is the `modrinth` Gradle task ([com.modrinth.minotaur](https://github.com/modrinth/minotaur)); `modrinthSyncBody` pushes `README.md` as the project description, so links in it must stay absolute. Declared Minecraft versions come from `modrinth.gameVersions` in `gradle.properties` and track BedrockDialog's supported list. Modrinth dependencies are `bedrockdialog` (required) and `placeholderapi` (optional) — Vault has no Modrinth project and can only be mentioned in the body.
- **PR titles are linted** ([.github/workflows/pr-title.yml](.github/workflows/pr-title.yml)) because squash merges turn the title into the commit message release-please reads.

## Style

- Java 21, UTF-8, 4-space indent, no tabs.
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# ModernVillagerShop

[![Modrinth](https://img.shields.io/modrinth/dt/modernvillagershop?logo=modrinth&label=Modrinth&color=00AF5C)](https://modrinth.com/plugin/modernvillagershop)
[![GitHub release](https://img.shields.io/github/v/release/f0reachARR/ModernVillagerShop?logo=github&label=Release)](https://github.com/f0reachARR/ModernVillagerShop/releases/latest)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/f0reachARR/ModernVillagerShop/blob/main/LICENSE)

Turn Villagers into real shops — player-owned or admin-run — with selling, buying (order slots), co-ownership and revenue sharing, on Paper 1.21.8+.

ModernVillagerShop replaces the vanilla trade window with a **chest UI + Dialog** hybrid: a chest inventory to browse and manage listings, and native Paper Dialogs for every amount, price and confirmation prompt. Because dialogs are driven through [BedrockDialog](https://modrinth.com/plugin/bedrockdialog), Bedrock players (Geyser + Floodgate) get the same flows rendered as Bedrock forms.
Expand Down Expand Up @@ -31,7 +35,7 @@ ModernVillagerShop replaces the vanilla trade window with a **chest UI + Dialog*

## Installation

1. Drop `ModernVillagerShop-*.jar`, `Vault` and `BedrockDialog` into `plugins/`.
1. Download `ModernVillagerShop-*.jar` from [Modrinth](https://modrinth.com/plugin/modernvillagershop) (or the [GitHub releases](https://github.com/f0reachARR/ModernVillagerShop/releases/latest)) and drop it, together with `Vault` and `BedrockDialog`, into `plugins/`.
2. Make sure a Vault-compatible economy plugin is installed and running.
3. Start the server. `plugins/ModernVillagerShop/config.yml` and `lang/messages_{en,ja}.yml` are generated on first boot.
4. Set `locale` in `config.yml` (`ja_JP` by default, `en_US` also ships) and run `/vshop reload`.
Expand Down
34 changes: 33 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ plugins {
id 'maven-publish'
id("xyz.jpenilla.run-paper") version "2.3.1"
id("com.gradleup.shadow") version "9.0.0-beta13"
id("com.modrinth.minotaur") version "2.9.0"
}

group = 'me.f0reach.vshop'
version = '1.0.0'
// `version` comes from gradle.properties, which release-please keeps up to date.

repositories {
mavenCentral()
Expand Down Expand Up @@ -98,6 +99,37 @@ processResources {
}
}

// Written by the release workflow from the GitHub Release body. When it is
// absent (a hand-run publish), fall back to a link to the release page.
def modrinthChangelogFile = layout.buildDirectory.file("modrinth-changelog.md")

modrinth {
token = providers.environmentVariable("MODRINTH_TOKEN")
projectId = "modernvillagershop"
versionNumber = project.version.toString()
versionName = "ModernVillagerShop ${project.version}"
versionType = project.version.toString().contains("-") ? "beta" : "release"
uploadFile = tasks.shadowJar
gameVersions = providers.gradleProperty("modrinth.gameVersions")
.map { spec -> spec.split(",").collect { it.trim() }.findAll { !it.isEmpty() } }
loaders = ["paper"]
detectLoaders = false
changelog = providers.provider {
def file = modrinthChangelogFile.get().asFile
file.exists() && !file.text.trim().isEmpty()
? file.text
: "See https://github.com/f0reachARR/ModernVillagerShop/releases/tag/v${project.version}".toString()
}
// README.md doubles as the Modrinth project description; `modrinthSyncBody`
// pushes it. Every link in it must stay absolute for that reason.
syncBodyFrom = providers.fileContents(layout.projectDirectory.file("README.md")).asText
dependencies {
required.project "bedrockdialog"
optional.project "placeholderapi"
// Vault has no Modrinth project, so it can only be documented in the body.
}
}

publishing {
publications {
shadow(MavenPublication) {
Expand Down
13 changes: 13 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# The plugin version. This is the single source of truth: Gradle picks it up
# automatically, processResources expands it into paper-plugin.yml, and Minotaur
# uses it as the Modrinth version number.
#
# release-please rewrites the value inside the block below when a release PR is
# merged -- do not bump it by hand.
# x-release-please-start-version
version=0.0.0
# x-release-please-end

# Minecraft versions declared on the Modrinth release page. Kept in sync with
# BedrockDialog's supported versions, since it is a hard dependency.
modrinth.gameVersions=1.21.8,1.21.9,1.21.10,1.21.11,26.1,26.1.1,26.1.2,26.2
23 changes: 23 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"packages": {
".": {
"release-type": "simple",
"package-name": "ModernVillagerShop",
"include-component-in-tag": false,
"extra-files": ["gradle.properties"],
"changelog-sections": [
{ "type": "feat", "section": "Features" },
{ "type": "fix", "section": "Bug Fixes" },
{ "type": "perf", "section": "Performance" },
{ "type": "refactor", "section": "Refactoring" },
{ "type": "docs", "section": "Documentation" },
{ "type": "build", "section": "Build System", "hidden": true },
{ "type": "ci", "section": "CI", "hidden": true },
{ "type": "chore", "section": "Chores", "hidden": true },
{ "type": "style", "section": "Styling", "hidden": true },
{ "type": "test", "section": "Tests", "hidden": true }
]
}
}
}
Loading