diff --git a/.github/workflows/javadoc.yaml b/.github/workflows/javadoc.yaml
index 0b00df5..2090191 100644
--- a/.github/workflows/javadoc.yaml
+++ b/.github/workflows/javadoc.yaml
@@ -11,16 +11,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: actions/setup-java@v3.11.0
with:
- java-version: '8'
+ fetch-depth: 0 # Required for git-version plugin
+ - uses: actions/setup-java@v4
+ with:
+ java-version: '11'
distribution: 'corretto'
- name: Validate Gradle wrapper
- uses: gradle/wrapper-validation-action@v1
+ uses: gradle/wrapper-validation-action@v2
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v3
- name: Generate Javadoc
- uses: gradle/gradle-build-action@v2
- with:
- arguments: javadoc
+ run: ./gradlew javadoc
- name: Check Javadoc generation
run: |
if [ -d "build/docs/javadoc" ]; then
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index 7d1c73d..853f1bd 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -1,4 +1,4 @@
-name: Publish package to GitHub Packages
+name: Publish package to GitHub Packages and Maven Central
on:
release:
types: [ created ]
@@ -11,17 +11,19 @@ jobs:
contents: read
packages: write
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-java@v3.11.0
+ - uses: actions/checkout@v4
with:
- java-version: '8'
+ fetch-depth: 0 # Required for git-version plugin
+ - uses: actions/setup-java@v4
+ with:
+ java-version: '11'
distribution: 'corretto'
- name: Validate Gradle wrapper
- uses: gradle/wrapper-validation-action@v1
+ uses: gradle/wrapper-validation-action@v2
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v3
- name: Verify Javadoc generation
- uses: gradle/gradle-build-action@v2
- with:
- arguments: javadoc
+ run: ./gradlew javadoc
- name: Check Javadoc generation
run: |
if [ -d "build/docs/javadoc" ]; then
@@ -30,15 +32,17 @@ jobs:
echo "ERROR: Javadoc directory not found"
exit 1
fi
- - name: Publish package
- uses: gradle/gradle-build-action@v2
- with:
- arguments: publish publishToSonatype closeAndReleaseSonatypeStagingRepository
+ - name: Publish to GitHub Packages
+ run: ./gradlew publish
env:
+ GITHUB_ACTOR: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
- OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
- GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
- GPG_SIGNING_PASSPHRASE: ${{ secrets.GPG_SIGNING_PASSPHRASE }}
- NEXUS_TOKEN_USERNAME: ${{ secrets.NEXUS_TOKEN_USERNAME }}
- NEXUS_TOKEN_PASSWORD: ${{ secrets.NEXUS_TOKEN_PASSWORD }}
+ - name: Publish to Maven Central
+ run: ./gradlew publishAndReleaseToMavenCentral
+ env:
+ # Nexus tokens were generated before we migrated to the new Maven Central,
+ # it's backward compatible.
+ ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.NEXUS_TOKEN_USERNAME }}
+ ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.NEXUS_TOKEN_PASSWORD }}
+ ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SIGNING_KEY }}
+ ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_SIGNING_PASSPHRASE }}
diff --git a/.gitignore b/.gitignore
index 9b21127..f9b596c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ build
# Ignore stg schemas
stg-schemas/
bin
+/key.asc
diff --git a/PUBLISHING.md b/PUBLISHING.md
new file mode 100644
index 0000000..882e1ca
--- /dev/null
+++ b/PUBLISHING.md
@@ -0,0 +1,183 @@
+# Publishing Guide
+
+This document describes how to publish the Permit.io Java SDK to Maven Central and GitHub Packages.
+
+## Overview
+
+The SDK is published to two repositories:
+
+- **Maven Central** - Primary distribution for public consumption
+- **GitHub Packages** - Secondary distribution for GitHub-based workflows
+
+## Prerequisites
+
+### Maven Central Portal Account
+
+1. Create an account at [central.sonatype.com](https://central.sonatype.com)
+2. Verify ownership of the `io.permit` namespace
+3. Generate a User Token: Account → Generate User Token
+
+### GPG Signing Key
+
+Maven Central requires all artifacts to be signed with GPG.
+[For more info see here.](https://central.sonatype.org/publish/requirements/gpg/)
+
+#### Generate a new key (if you don't have one)
+
+```bash
+gpg --full-generate-key
+```
+
+When prompted:
+
+1. **Key type**: Select `1` (RSA and RSA)
+2. **Key size**: Enter `4096`
+3. **Expiration**: Enter `0` (doesn't expire) or set a reasonable expiration
+4. **Name and email**: Use the same email as your Maven Central account
+5. **Passphrase**: Set a strong passphrase (this is your `signingInMemoryKeyPassword`)
+
+#### List your keys
+
+```bash
+gpg --list-secret-keys --keyid-format LONG
+```
+
+#### Export the private key
+
+For local use:
+
+```bash
+gpg --armor --export-secret-keys YOUR_KEY_ID > key.asc
+```
+
+For CI/CD (base64 encoded):
+
+```bash
+gpg --armor --export-secret-keys YOUR_KEY_ID | base64
+```
+
+#### Publish your public key (required for Maven Central verification)
+
+```bash
+gpg --keyserver keyserver.ubuntu.com --send-keys YOUR_KEY_ID
+# Note: if you get a "no route to host" error, try an alternative keyserver such as keys.openpgp.org or pgp.mit.edu.
+```
+
+## GitHub Secrets
+
+Configure these secrets in your GitHub repository:
+
+| Secret | Description |
+|--------------------------|--------------------------------------------------------|
+| `MAVEN_CENTRAL_USERNAME` | Username from Central Portal TOKEN (not the user) |
+| `MAVEN_CENTRAL_PASSWORD` | Password from Central Portal TOKEN (not user password) |
+| `GPG_SIGNING_KEY` | Base64-encoded GPG private key |
+| `GPG_SIGNING_PASSPHRASE` | Passphrase for the GPG key |
+
+## Publishing Methods
+
+### Automatic (CI/CD)
+
+Publishing is triggered automatically when:
+
+- A GitHub Release is created
+- The workflow is manually dispatched
+
+The workflow (`.github/workflows/publish.yaml`) handles:
+
+1. Javadoc verification
+2. Publishing to GitHub Packages
+3. Publishing to Maven Central
+
+### Manual (Local)
+
+#### Publish to Local Maven Repository
+
+Test artifact generation without uploading:
+
+```bash
+./gradlew publishToMavenLocal -PskipSigning
+```
+
+Artifacts are published to `~/.m2/repository/io/permit/permit-sdk-java/`
+
+Note: Use `-PskipSigning` for local testing without GPG keys. This flag is not available for Maven Central publishing (signing is required).
+
+#### Publish to Maven Central (Staging Only)
+
+Upload to Central Portal without releasing:
+
+```bash
+./gradlew publishToMavenCentral \
+ -PmavenCentralUsername=TOKEN_USERNAME \
+ -PmavenCentralPassword=TOKEN_PASSWORD \
+ -PsigningInMemoryKey="$(cat key.asc)" \
+ -PsigningInMemoryKeyPassword=KEY_PASSPHRASE
+```
+
+Review at [Central Portal Deployments](https://central.sonatype.com/publishing/deployments)
+
+#### Publish and Release to Maven Central
+
+Full publish with automatic release:
+
+```bash
+./gradlew publishAndReleaseToMavenCentral \
+ -PmavenCentralUsername=TOKEN_USERNAME \
+ -PmavenCentralPassword=TOKEN_PASSWORD \
+ -PsigningInMemoryKey="$(cat key.asc)" \
+ -PsigningInMemoryKeyPassword=KEY_PASSPHRASE
+```
+
+#### Publish to GitHub Packages
+
+```bash
+GITHUB_ACTOR=username GITHUB_TOKEN=token ./gradlew publish
+```
+
+## Gradle Tasks
+
+| Task | Description |
+|-----------------------------------|----------------------------------------------------------|
+| `publishToMavenLocal` | Publish to local Maven cache (~/.m2) |
+| `publishToMavenCentral` | Upload to Central Portal (staging) |
+| `publishAndReleaseToMavenCentral` | Upload and release to Maven Central |
+| `publish` | Publish to all configured repositories (GitHub Packages) |
+
+## Versioning
+
+Version is automatically determined by the `com.palantir.git-version` plugin based on git tags:
+
+- Tagged commit: `2.2.0`
+- Commits after tag: `2.2.0-1-gabcdef`
+- Dirty working directory: `2.2.0-1-gabcdef.dirty`
+
+To release a new version:
+
+```bash
+git tag 2.3.0
+git push origin 2.3.0
+```
+
+## Troubleshooting
+
+### 403 Forbidden
+
+- Credentials may be invalid or expired
+- Regenerate token at Central Portal
+
+### Signature Verification Failed
+
+- GPG key may be malformed
+- Ensure key is base64 encoded without line breaks
+
+### Version Already Exists
+
+- Maven Central doesn't allow overwriting versions
+- Bump the version and try again
+
+## References
+
+- [Maven Central Portal](https://central.sonatype.com)
+- [vanniktech/gradle-maven-publish-plugin](https://vanniktech.github.io/gradle-maven-publish-plugin/central/)
+- [Sonatype Publishing Guide](https://central.sonatype.org/publish/publish-portal-gradle/)
diff --git a/README.md b/README.md
index 8547414..aa3039e 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ For [Maven](https://maven.apache.org/) projects, use:
io.permit
permit-sdk-java
- 2.0.0
+ 2.2.2
```
@@ -25,7 +25,7 @@ For [Gradle](https://gradle.org/) projects, configure `permit-sdk-java` as a dep
dependencies {
// ...
- implementation 'io.permit:permit-sdk-java:2.0.0'
+ implementation 'io.permit:permit-sdk-java:2.2.2'
}
```
@@ -149,6 +149,6 @@ CreateOrUpdateResult result = permit.api.users.sync(new UserCreate("[U
## Javadoc reference
-To view the javadoc reference, [click here](https://javadoc.io/doc/io.permit/permit-sdk-java/2.0.0/index.html).
+To view the javadoc reference, [click here](https://javadoc.io/doc/io.permit/permit-sdk-java/latest/index.html).
-It's easiest to start with the root [Permit](https://javadoc.io/static/io.permit/permit-sdk-java/2.0.0/io/permit/sdk/Permit.html) class.
+It's easiest to start with the root [Permit](https://javadoc.io/doc/io.permit/permit-sdk-java/latest/io/permit/sdk/Permit.html) class.
diff --git a/build.gradle b/build.gradle
index 8d15bdb..917ee97 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,20 +9,19 @@
plugins {
// Apply the java-library plugin for API and implementation separation.
id 'java-library'
- // Maven publish plugins helps us to publish our library to maven repos
- id 'maven-publish'
// the signing plugin helps us to crypto-sign on our package (PGP key)
id 'signing'
// the git-version plugin helps us to publish an auto version (taken from git tags)
id 'com.palantir.git-version' version '0.13.0'
- // auto release to maven central (skip sonatype manual nexus release process)
- id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
+ // Maven Central Portal publishing (community plugin, recommended by Sonatype)
+ // See: https://vanniktech.github.io/gradle-maven-publish-plugin/central/
+ id 'com.vanniktech.maven.publish' version '0.28.0'
// translate json schemas to java classes
// id "org.jsonschema2pojo" version "1.1.3"
}
// It is important to set the group and the version to the root project
-// so the nexus-publish plugin can detect if it is a snapshot version
+// so the maven-publish plugin can detect if it is a snapshot version
// or not in order to select the correct repository where artifacts will
// be published
group = 'io.permit'
@@ -36,12 +35,9 @@ repositories {
java {
toolchain {
- languageVersion = JavaLanguageVersion.of(8)
+ languageVersion = JavaLanguageVersion.of(8)
}
- // sources are required by maven central in order to accept the package
- withSourcesJar()
- // javadoc jar is required by maven central in order to accept the package
- withJavadocJar()
+ // Note: sources and javadoc JARs are created by the vanniktech maven-publish plugin
}
// package dependencies
@@ -69,8 +65,6 @@ dependencies {
implementation 'ch.qos.logback:logback-core:1.4.14'
implementation 'org.slf4j:slf4j-api:1.7.33'
-
-
// Use JUnit Jupiter for testing.
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'
@@ -81,145 +75,66 @@ dependencies {
}
-//jsonSchema2Pojo {
-// // Location of the JSON Schema file(s). This may refer to a single file or a directory of files.
-// source = files("schemas/")
-//
-// // Target directory for generated Java source files. The plugin will add this directory to the
-// // java source set so the compiler will find and compile the newly generated source files.
-// targetDirectory = file("src/main/java")
-//
-// // Package name used for generated Java classes (for types where a fully qualified name has not
-// // been supplied in the schema using the 'javaType' property).
-// targetPackage = 'io.permit.sdk.openapi.models'
-//
-// // Whether to allow 'additional' properties to be supported in classes by adding a map to
-// // hold these. This is true by default, meaning that the schema rule 'additionalProperties'
-// // controls whether the map is added. Set this to false to globally disable additional properties.
-// includeAdditionalProperties = false
-//
-// // Whether to include a javax.annotation.Generated (Java 8 and lower) or
-// // javax.annotation.processing.Generated (Java 9+) in on generated types (default true).
-// // See also: targetVersion.
-// includeGeneratedAnnotation = true
-//
-// // Whether to use the 'title' property of the schema to decide the class name (if not
-// // set to true, the filename and property names are used).
-// useTitleAsClassname = true
-//
-// // Whether to empty the target directory before generation occurs, to clear out all source files
-// // that have been generated previously. Be warned, when activated this option
-// // will cause jsonschema2pojo to indiscriminately delete the entire contents of the target
-// // directory (all files and folders) before it begins generating sources.
-// removeOldOutput = false
-//
-// // Whether to generate builder-style methods of the form withXxx(value) (that return this),
-// // alongside the standard, void-return setters.
-// generateBuilders = true
-//
-// // If set to true, then the gang of four builder pattern will be used to generate builders on
-// // generated classes. Note: This property works in collaboration with generateBuilders.
-// // If generateBuilders is false then this property will not do anything.
-// useInnerClassBuilders = false
-//
-// // Whether to include hashCode and equals methods in generated Java types.
-// includeHashcodeAndEquals = false
-//
-// // Whether to include a toString method in generated Java types.
-// includeToString = false
-//
-// // Whether to include getters or to omit these accessor methods and create public fields instead.
-// includeGetters = false
-//
-// // Whether to include setters or to omit these accessor methods and create public fields instead.
-// includeSetters = false
-//
-// // Whether to use java.util.Optional for getters on properties that are not required
-// useOptionalForGetters = true
-//
-// // Whether to generate constructors or not.
-// includeConstructors = true
-//
-// // Whether to include only 'required' fields in generated constructors
-// constructorsRequiredPropertiesOnly = true
-//
-// annotationStyle = 'gson'
-//
-// // Whether to initialize Set and List fields as empty collections, or leave them as null.
-// initializeCollections = false
-//}
+// Maven Central Portal publishing configuration
+// See: https://vanniktech.github.io/gradle-maven-publish-plugin/central/
+mavenPublishing {
+ // Publish to Maven Central Portal
+ publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.CENTRAL_PORTAL)
-publishing {
- repositories {
- maven {
- name = "OSSRH"
- url = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
- credentials {
- username = System.getenv("OSSRH_USERNAME")
- password = System.getenv("OSSRH_PASSWORD")
+ // Sign all publications with GPG (required for Maven Central)
+ // For local testing without GPG, use: ./gradlew publishToMavenLocal -PskipSigning
+ if (!project.hasProperty('skipSigning')) {
+ signAllPublications()
+ }
+
+ // Artifact coordinates
+ coordinates("io.permit", "permit-sdk-java", version.toString())
+
+ // POM configuration required by Maven Central
+ pom {
+ name = "Permit.io Java SDK"
+ description = "Java SDK for Permit.io: fullstack permissions for cloud native applications"
+ url = "https://permit.io"
+ inceptionYear = "2021"
+
+ licenses {
+ license {
+ name = "The Apache License, Version 2.0"
+ url = "http://www.apache.org/licenses/LICENSE-2.0.txt"
+ distribution = "repo"
}
}
- maven {
- name = "GitHubPackages"
- url = "https://maven.pkg.github.com/permitio/permit-java"
- credentials {
- username = System.getenv("GITHUB_ACTOR")
- password = System.getenv("GITHUB_TOKEN")
+
+ developers {
+ developer {
+ id = "permit-io"
+ name = "Permit Team"
+ email = "support@permit.io"
}
}
- }
-
- publications {
- maven(MavenPublication) {
- groupId = 'io.permit'
- artifactId = 'permit-sdk-java'
-
- from components.java
-
- pom {
- name = "Permit.io Java SDK"
- description = 'Java SDK for Permit.io: fullstack permissions for cloud native applications'
- url = 'https://permit.io'
- licenses {
- license {
- name = 'The Apache License, Version 2.0'
- url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- }
- }
- developers {
- developer {
- id = 'asafc'
- name = 'Asaf Cohen'
- email = 'asaf@permit.io'
- }
- }
- scm {
- url = 'https://github.com/permitio/permit-java'
- }
- }
+ scm {
+ connection = "scm:git:git://github.com/permitio/permit-java.git"
+ developerConnection = "scm:git:ssh://github.com/permitio/permit-java.git"
+ url = "https://github.com/permitio/permit-java"
}
}
}
-nexusPublishing {
+// GitHub Packages publishing (separate from Maven Central)
+publishing {
repositories {
- sonatype {
- nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
- snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
- username = System.getenv("NEXUS_TOKEN_USERNAME")
- password = System.getenv("NEXUS_TOKEN_PASSWORD")
+ maven {
+ name = "GitHubPackages"
+ url = "https://maven.pkg.github.com/permitio/permit-java"
+ credentials {
+ username = System.getenv("GITHUB_ACTOR")
+ password = System.getenv("GITHUB_TOKEN")
+ }
}
}
}
-signing {
- def GPG_SIGNING_KEY = findProperty("signingKey") ?: System.getenv("GPG_SIGNING_KEY")
- def GPG_SIGNING_PASSPHRASE = findProperty("signingPassword") ?: System.getenv("GPG_SIGNING_PASSPHRASE")
- useInMemoryPgpKeys(GPG_SIGNING_KEY, GPG_SIGNING_PASSPHRASE)
- sign publishing.publications.maven
-}
-
tasks.named('test') {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
@@ -232,7 +147,12 @@ tasks.named('javadoc') {
tasks.named('jar') {
manifest {
- attributes('Implementation-Title': project.name,
- 'Implementation-Version': project.version)
+ attributes('Implementation-Title': project.name, 'Implementation-Version': project.version)
}
-}
\ No newline at end of file
+}
+
+// Fix Gradle 8.x task dependency validation issue with vanniktech plugin
+// The plainJavadocJar task must run before generateMetadataFileForMavenPublication
+tasks.withType(GenerateModuleMetadata).configureEach {
+ dependsOn tasks.matching { it.name == 'plainJavadocJar' }
+}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2e6e589..a595206 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists