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
49 changes: 49 additions & 0 deletions .github/workflows/publish_android_maven_central.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Publish Capacitor Plugin to Maven Central

on:
workflow_call:
secrets:
ANDROID_CENTRAL_USERNAME:
required: true
ANDROID_CENTRAL_PASSWORD:
required: true
ANDROID_SIGNING_KEY_ID:
required: true
ANDROID_SIGNING_PASSWORD:
required: true
ANDROID_SIGNING_KEY:
required: true
ANDROID_SONATYPE_STAGING_PROFILE_ID:
required: true
CAP_GH_RELEASE_TOKEN:
required: true
workflow_dispatch:

jobs:
publish-android:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
token: ${{ secrets.CAP_GH_RELEASE_TOKEN }}
- name: set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'zulu'
- name: Grant execute permission for publishing script
run: chmod +x ./scripts/publish-android.sh
- name: Run publish script
env:
ANDROID_CENTRAL_USERNAME: ${{ secrets.ANDROID_CENTRAL_USERNAME }}
ANDROID_CENTRAL_PASSWORD: ${{ secrets.ANDROID_CENTRAL_PASSWORD }}
ANDROID_SIGNING_KEY_ID: ${{ secrets.ANDROID_SIGNING_KEY_ID }}
ANDROID_SIGNING_PASSWORD: ${{ secrets.ANDROID_SIGNING_PASSWORD }}
ANDROID_SIGNING_KEY: ${{ secrets.ANDROID_SIGNING_KEY }}
ANDROID_SONATYPE_STAGING_PROFILE_ID: ${{ secrets.ANDROID_SONATYPE_STAGING_PROFILE_ID }}
run: ./scripts/publish-android.sh
38 changes: 38 additions & 0 deletions .github/workflows/publish_ios_cocoapods_trunk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Publish Capacitor Plugin to CocoaPods Trunk

on:
workflow_call:
secrets:
COCOAPODS_TRUNK_TOKEN:
required: true
CAP_GH_RELEASE_TOKEN:
required: true
workflow_dispatch:

jobs:
publish-ios:
runs-on: macos-15
if: github.ref == 'refs/heads/main'
timeout-minutes: 30
steps:
- run: sudo xcode-select --switch /Applications/Xcode_16.app
- run: xcrun simctl list > /dev/null
- run: xcodebuild -downloadPlatform iOS
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: actions/checkout@v3
with:
fetch-depth: 0
token: ${{ secrets.CAP_GH_RELEASE_TOKEN }}
- name: Install Cocoapods
run: |
gem install cocoapods
- name: Grant execute permission for publishing script
run: chmod +x ./scripts/publish-ios.sh
- name: Deploy to Cocoapods
env:
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
run: |
set -eo pipefail
./scripts/publish-ios.sh
21 changes: 20 additions & 1 deletion .github/workflows/release_plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,23 @@ jobs:
GITHUB_TOKEN: ${{ secrets.CAP_GH_RELEASE_TOKEN }}
GH_TOKEN: ${{ secrets.CAP_GH_RELEASE_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
run: npx semantic-release
run: npx semantic-release

publish-android:
needs: ['release']
uses: ./.github/workflows/publish_android_maven_central.yml
secrets:
ANDROID_CENTRAL_USERNAME: ${{ secrets.ANDROID_CENTRAL_USERNAME }}
ANDROID_CENTRAL_PASSWORD: ${{ secrets.ANDROID_CENTRAL_PASSWORD }}
ANDROID_SIGNING_KEY_ID: ${{ secrets.ANDROID_SIGNING_KEY_ID }}
ANDROID_SIGNING_KEY: ${{ secrets.ANDROID_SIGNING_KEY }}
ANDROID_SIGNING_PASSWORD: ${{ secrets.ANDROID_SIGNING_PASSWORD }}
ANDROID_SONATYPE_STAGING_PROFILE_ID: ${{ secrets.ANDROID_SONATYPE_STAGING_PROFILE_ID }}
CAP_GH_RELEASE_TOKEN: ${{ secrets.CAP_GH_RELEASE_TOKEN }}

publish-ios:
needs: ['release']
uses: ./.github/workflows/publish_ios_cocoapods_trunk.yml
secrets:
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
CAP_GH_RELEASE_TOKEN: ${{ secrets.CAP_GH_RELEASE_TOKEN }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,6 @@ captures

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild

# Maven Central / CocoaPods Trunk release logs
tmp/*.txt
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ buildscript {
apply plugin: 'com.android.library'
if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") {
apply plugin: 'io.github.gradle-nexus.publish-plugin'
apply from: file('../../scripts/android/publish-root.gradle')
apply from: file('../../scripts/android/publish-module.gradle')
apply from: file('../scripts/android/publish-root.gradle')
apply from: file('../scripts/android/publish-module.gradle')
}

android {
Expand Down
82 changes: 82 additions & 0 deletions scripts/android/publish-module.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
apply plugin: 'maven-publish'
apply plugin: 'signing'

def LIB_VERSION = System.getenv('PLUGIN_VERSION')
def PLUGIN_NAME = System.getenv('PLUGIN_NAME')
def PLUGIN_REPO = System.getenv('PLUGIN_REPO')
def PLUGIN_SCM = System.getenv('PLUGIN_SCM')

task androidSourcesJar(type: Jar) {
archiveClassifier.set('sources')
if (project.plugins.findPlugin("com.android.library")) {
from android.sourceSets.main.java.srcDirs
from android.sourceSets.main.kotlin.srcDirs
} else {
from sourceSets.main.java.srcDirs
from sourceSets.main.kotlin.srcDirs
}
}

artifacts {
archives androidSourcesJar
}

group = 'com.capacitorjs'
version = LIB_VERSION

afterEvaluate {
publishing {
publications {
release(MavenPublication) {
// Coordinates
groupId 'com.capacitorjs'
artifactId PLUGIN_NAME
version LIB_VERSION

// Two artifacts, the `aar` (or `jar`) and the sources
if (project.plugins.findPlugin("com.android.library")) {
from components.release
} else {
artifact("$buildDir/libs/${project.getName()}-${version}.jar")
}

artifact androidSourcesJar

// POM Data
pom {
name = PLUGIN_NAME
description = 'Capacitor Android ' + PLUGIN_NAME + ' plugin native library'
url = PLUGIN_REPO
licenses {
license {
name = 'MIT'
url = PLUGIN_REPO + '/blob/main' + '/LICENSE'
}
}
developers {
developer {
name = 'Ionic'
email = '[email protected]'
}
}

// Version Control Info
scm {
connection = 'scm:git:' + PLUGIN_SCM + '.git'
developerConnection = 'scm:git:ssh://' + PLUGIN_SCM + '.git'
url = PLUGIN_REPO + '/tree/main'
}
}
}
}
}
}

signing {
useInMemoryPgpKeys(
rootProject.ext["signing.keyId"],
rootProject.ext["signing.key"],
rootProject.ext["signing.password"],
)
sign publishing.publications
}
43 changes: 43 additions & 0 deletions scripts/android/publish-root.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Create variables with empty default values
ext["signing.keyId"] = ''
ext["signing.key"] = ''
ext["signing.password"] = ''
ext["centralTokenUsername"] = ''
ext["centralTokenPassword"] = ''
ext["sonatypeStagingProfileId"] = ''

File globalSecretPropsFile = file('../../scripts/android/local.properties')
File secretPropsFile = project.rootProject.file('local.properties')
if (globalSecretPropsFile.exists()) {
// Read global local.properties file first if it exists (scripts/android/local.properties)
Properties p = new Properties()
new FileInputStream(globalSecretPropsFile).withCloseable { is -> p.load(is) }
p.each { name, value -> ext[name] = value }
} else if (secretPropsFile.exists()) {
// Read plugin project specific local.properties file next if it exists
Properties p = new Properties()
new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) }
p.each { name, value -> ext[name] = value }
} else {
// Use system environment variables
ext["centralTokenUsername"] = System.getenv('ANDROID_CENTRAL_USERNAME')
ext["centralTokenPassword"] = System.getenv('ANDROID_CENTRAL_PASSWORD')
ext["sonatypeStagingProfileId"] = System.getenv('ANDROID_SONATYPE_STAGING_PROFILE_ID')
ext["signing.keyId"] = System.getenv('ANDROID_SIGNING_KEY_ID')
ext["signing.key"] = System.getenv('ANDROID_SIGNING_KEY')
ext["signing.password"] = System.getenv('ANDROID_SIGNING_PASSWORD')
}

// Set up Sonatype repository
nexusPublishing {
repositories {
sonatype {
stagingProfileId = sonatypeStagingProfileId
username = centralTokenUsername
password = centralTokenPassword
nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
}
}
repositoryDescription = 'Capacitor Android ' + System.getenv('PLUGIN_NAME') + ' plugin v' + System.getenv('PLUGIN_VERSION')
}
77 changes: 77 additions & 0 deletions scripts/publish-android.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env bash

publish_plugin_android () {
PLUGIN_PATH=$1
if [ -d "$PLUGIN_PATH" ]; then
# Android dir path
ANDROID_PATH=$PLUGIN_PATH/android
GRADLE_FILE=$ANDROID_PATH/build.gradle

# Only try to publish if the directory contains a package.json and android package
if test -f "$PLUGIN_PATH/package.json" && test -d "$ANDROID_PATH" && test -f "$GRADLE_FILE"; then
PLUGIN_VERSION=$(grep '"version": ' "$PLUGIN_PATH"/package.json | awk '{print $2}' | tr -d '",')
PLUGIN_NAME=$(grep '"name": ' "$PLUGIN_PATH"/package.json | awk '{print $2}' | tr -d '",')
PLUGIN_NAME=${PLUGIN_NAME#@capacitor/}
LOG_OUTPUT=./tmp/$PLUGIN_NAME.txt

# Get latest plugin info from MavenCentral
PLUGIN_PUBLISHED_URL="https://repo1.maven.org/maven2/com/capacitorjs/$PLUGIN_NAME/maven-metadata.xml"
PLUGIN_PUBLISHED_DATA=$(curl -s $PLUGIN_PUBLISHED_URL)
PLUGIN_PUBLISHED_VERSION="$(perl -ne 'print and last if s/.*<latest>(.*)<\/latest>.*/\1/;' <<< $PLUGIN_PUBLISHED_DATA)"

if [[ $PLUGIN_VERSION == $PLUGIN_PUBLISHED_VERSION ]]; then
printf %"s\n\n" "Duplicate: a published plugin $PLUGIN_NAME exists for version $PLUGIN_VERSION, skipping..."
else
# Make log dir if doesnt exist
mkdir -p ./tmp

printf %"s\n" "Attempting to build and publish plugin $PLUGIN_NAME for version $PLUGIN_VERSION to production..."

# Export ENV variables used by Gradle for the plugin
export PLUGIN_NAME
export PLUGIN_VERSION
export CAPACITOR_VERSION
export CAP_PLUGIN_PUBLISH=true
export PLUGIN_REPO="https://github.com/ionic-team/capacitor-keyboard"
export PLUGIN_SCM="github.com:ionic-team/capacitor-keyboard"

# Build and publish
"$ANDROID_PATH"/gradlew clean build publishReleasePublicationToSonatypeRepository closeAndReleaseSonatypeStagingRepository --no-daemon --max-workers 1 -b "$ANDROID_PATH"/build.gradle -Pandroid.useAndroidX=true > $LOG_OUTPUT 2>&1

if grep --quiet "BUILD SUCCESSFUL" $LOG_OUTPUT; then
printf %"s\n\n" "Success: $PLUGIN_NAME published to MavenCentral."
else
printf %"s\n\n" "Error publishing $PLUGIN_NAME, check $LOG_OUTPUT for more info! Manually review and release from the Central Portal may be necessary https://central.sonatype.com/publishing/deployments/"
cat $LOG_OUTPUT
exit 1
fi
fi
else
printf %"s\n\n" "$PLUGIN_PATH does not appear to be a plugin (has no package.json file or Android package), skipping..."
fi
fi
}

# Get latest com.capacitorjs:core XML version info
CAPACITOR_PUBLISHED_URL="https://repo1.maven.org/maven2/com/capacitorjs/core/maven-metadata.xml"
CAPACITOR_PUBLISHED_DATA=$(curl -s $CAPACITOR_PUBLISHED_URL)
CAPACITOR_PUBLISHED_VERSION="$(perl -ne 'print and last if s/.*<latest>(.*)<\/latest>.*/\1/;' <<< $CAPACITOR_PUBLISHED_DATA)"

printf %"s\n" "The latest published Android library version of Capacitor Core is $CAPACITOR_PUBLISHED_VERSION in MavenCentral."

# Determine Capacitor Version to use as gradle dependency.
STABLE_PART=$(echo "$CAPACITOR_PUBLISHED_VERSION" | cut -d'-' -f1)
IFS='.' read -r MAJOR MINOR PATCH <<< "$STABLE_PART"
if [[ "$CAPACITOR_PUBLISHED_VERSION" == *"-"* ]]; then
# prerelease - go one major lower (latest stable major), but also allow next upcoming major
PREV_MAJOR=$((MAJOR - 1))
NEXT_MAJOR=$((MAJOR + 1))
CAPACITOR_VERSION="[$PREV_MAJOR.0,$NEXT_MAJOR.0)"
else
# stable - current major range
NEXT_MAJOR=$((MAJOR + 1))
CAPACITOR_VERSION="[$MAJOR.0,$NEXT_MAJOR.0)"
fi
printf %"s\n" "Publishing plugin with dependency on Capacitor version $CAPACITOR_VERSION"

publish_plugin_android '.'
46 changes: 46 additions & 0 deletions scripts/publish-ios.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash

publish_plugin_ios () {
PLUGIN_PATH=$1
# Only try to publish if the directory contains a package.json podspec file
if ! test -f "$PLUGIN_PATH/package.json"; then
printf %"s\n\n" "$PLUGIN_PATH does not appear to be a plugin (has no package.json), skipping..."
return
fi

PLUGIN_VERSION=$(grep '"version": ' "$PLUGIN_PATH"/package.json | awk '{print $2}' | tr -d '",')
PLUGIN_NAME=$(grep '"name": ' "$PLUGIN_PATH"/package.json | awk '{print $2}' | tr -d '",')
PLUGIN_NAME=${PLUGIN_NAME#@capacitor/}
# capitalize the name, because .podspec file name is capitalized
first_char=$(printf '%s' "$PLUGIN_NAME" | cut -c1 | tr '[:lower:]' '[:upper:]')
rest=$(printf '%s' "$PLUGIN_NAME" | cut -c2-)
PLUGIN_NAME="${first_char}${rest}"
POD_NAME="Capacitor$PLUGIN_NAME"
PODSPEC_FILE_PATH="$PLUGIN_PATH/$POD_NAME.podspec"
if ! test -f $PODSPEC_FILE_PATH; then
printf %"s\n\n" "Was looking for podspec file $PODSPEC_FILE_PATH, but does not seem to exist, skipping..."
return
fi

# check if version already exists in Trunk
if pod trunk info "$POD_NAME" 2>/dev/null | grep -q " - $PLUGIN_VERSION"; then
printf %"s\n\n" "Duplicate: a published plugin $PLUGIN_NAME exists for version $PLUGIN_VERSION, skipping..."
return
fi

LOG_OUTPUT=./tmp/$PLUGIN_NAME.txt
# Make log dir if doesnt exist
mkdir -p ./tmp
# publish to Trunk
printf %"s\n" "Attempting to build and publish plugin $PLUGIN_NAME for version $PLUGIN_VERSION to production..."
pod trunk push $PODSPEC_FILE_PATH --allow-warnings > $LOG_OUTPUT 2>&1
if grep -q "passed" $LOG_OUTPUT; then
printf %"s\n\n" "Success: $PLUGIN_NAME published to CocoaPods Trunk."
else
printf %"s\n\n" "Error publishing $PLUGIN_NAME, check $LOG_OUTPUT for more info!"
cat $LOG_OUTPUT
exit 1
fi
}

publish_plugin_ios '.'