Skip to content

Commit b044107

Browse files
ci(android,ios): Publish Plugin as native library (#3)
* chore(android): fix scripts path * ci(android): Publish to Maven Central * ci(ios): Publish to CocoaPods Trunk * ci: Call native publish after semantic release * chore: fix gitignore comment
1 parent 79b8629 commit b044107

File tree

9 files changed

+361
-3
lines changed

9 files changed

+361
-3
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Publish Capacitor Plugin to Maven Central
2+
3+
on:
4+
workflow_call:
5+
secrets:
6+
ANDROID_CENTRAL_USERNAME:
7+
required: true
8+
ANDROID_CENTRAL_PASSWORD:
9+
required: true
10+
ANDROID_SIGNING_KEY_ID:
11+
required: true
12+
ANDROID_SIGNING_PASSWORD:
13+
required: true
14+
ANDROID_SIGNING_KEY:
15+
required: true
16+
ANDROID_SONATYPE_STAGING_PROFILE_ID:
17+
required: true
18+
CAP_GH_RELEASE_TOKEN:
19+
required: true
20+
workflow_dispatch:
21+
22+
jobs:
23+
publish-android:
24+
if: github.ref == 'refs/heads/main'
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 60
27+
permissions:
28+
contents: read
29+
packages: write
30+
steps:
31+
- uses: actions/checkout@v3
32+
with:
33+
fetch-depth: 0
34+
token: ${{ secrets.CAP_GH_RELEASE_TOKEN }}
35+
- name: set up JDK 21
36+
uses: actions/setup-java@v4
37+
with:
38+
java-version: '21'
39+
distribution: 'zulu'
40+
- name: Grant execute permission for publishing script
41+
run: chmod +x ./scripts/publish-android.sh
42+
- name: Run publish script
43+
env:
44+
ANDROID_CENTRAL_USERNAME: ${{ secrets.ANDROID_CENTRAL_USERNAME }}
45+
ANDROID_CENTRAL_PASSWORD: ${{ secrets.ANDROID_CENTRAL_PASSWORD }}
46+
ANDROID_SIGNING_KEY_ID: ${{ secrets.ANDROID_SIGNING_KEY_ID }}
47+
ANDROID_SIGNING_PASSWORD: ${{ secrets.ANDROID_SIGNING_PASSWORD }}
48+
ANDROID_SIGNING_KEY: ${{ secrets.ANDROID_SIGNING_KEY }}
49+
ANDROID_SONATYPE_STAGING_PROFILE_ID: ${{ secrets.ANDROID_SONATYPE_STAGING_PROFILE_ID }}
50+
run: ./scripts/publish-android.sh
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Publish Capacitor Plugin to CocoaPods Trunk
2+
3+
on:
4+
workflow_call:
5+
secrets:
6+
COCOAPODS_TRUNK_TOKEN:
7+
required: true
8+
CAP_GH_RELEASE_TOKEN:
9+
required: true
10+
workflow_dispatch:
11+
12+
jobs:
13+
publish-ios:
14+
runs-on: macos-15
15+
if: github.ref == 'refs/heads/main'
16+
timeout-minutes: 30
17+
steps:
18+
- run: sudo xcode-select --switch /Applications/Xcode_16.app
19+
- run: xcrun simctl list > /dev/null
20+
- run: xcodebuild -downloadPlatform iOS
21+
- uses: actions/setup-node@v4
22+
with:
23+
node-version: 20
24+
- uses: actions/checkout@v3
25+
with:
26+
fetch-depth: 0
27+
token: ${{ secrets.CAP_GH_RELEASE_TOKEN }}
28+
- name: Install Cocoapods
29+
run: |
30+
gem install cocoapods
31+
- name: Grant execute permission for publishing script
32+
run: chmod +x ./scripts/publish-ios.sh
33+
- name: Deploy to Cocoapods
34+
env:
35+
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
36+
run: |
37+
set -eo pipefail
38+
./scripts/publish-ios.sh

.github/workflows/release_plugin.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,23 @@ jobs:
4747
GITHUB_TOKEN: ${{ secrets.CAP_GH_RELEASE_TOKEN }}
4848
GH_TOKEN: ${{ secrets.CAP_GH_RELEASE_TOKEN }}
4949
NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
50-
run: npx semantic-release
50+
run: npx semantic-release
51+
52+
publish-android:
53+
needs: ['release']
54+
uses: ./.github/workflows/publish_android_maven_central.yml
55+
secrets:
56+
ANDROID_CENTRAL_USERNAME: ${{ secrets.ANDROID_CENTRAL_USERNAME }}
57+
ANDROID_CENTRAL_PASSWORD: ${{ secrets.ANDROID_CENTRAL_PASSWORD }}
58+
ANDROID_SIGNING_KEY_ID: ${{ secrets.ANDROID_SIGNING_KEY_ID }}
59+
ANDROID_SIGNING_KEY: ${{ secrets.ANDROID_SIGNING_KEY }}
60+
ANDROID_SIGNING_PASSWORD: ${{ secrets.ANDROID_SIGNING_PASSWORD }}
61+
ANDROID_SONATYPE_STAGING_PROFILE_ID: ${{ secrets.ANDROID_SONATYPE_STAGING_PROFILE_ID }}
62+
CAP_GH_RELEASE_TOKEN: ${{ secrets.CAP_GH_RELEASE_TOKEN }}
63+
64+
publish-ios:
65+
needs: ['release']
66+
uses: ./.github/workflows/publish_ios_cocoapods_trunk.yml
67+
secrets:
68+
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
69+
CAP_GH_RELEASE_TOKEN: ${{ secrets.CAP_GH_RELEASE_TOKEN }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,6 @@ captures
6868

6969
# External native build folder generated in Android Studio 2.2 and later
7070
.externalNativeBuild
71+
72+
# Maven Central / CocoaPods Trunk release logs
73+
tmp/*.txt

android/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ buildscript {
2525
apply plugin: 'com.android.library'
2626
if (System.getenv("CAP_PLUGIN_PUBLISH") == "true") {
2727
apply plugin: 'io.github.gradle-nexus.publish-plugin'
28-
apply from: file('../../scripts/android/publish-root.gradle')
29-
apply from: file('../../scripts/android/publish-module.gradle')
28+
apply from: file('../scripts/android/publish-root.gradle')
29+
apply from: file('../scripts/android/publish-module.gradle')
3030
}
3131

3232
android {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
apply plugin: 'maven-publish'
2+
apply plugin: 'signing'
3+
4+
def LIB_VERSION = System.getenv('PLUGIN_VERSION')
5+
def PLUGIN_NAME = System.getenv('PLUGIN_NAME')
6+
def PLUGIN_REPO = System.getenv('PLUGIN_REPO')
7+
def PLUGIN_SCM = System.getenv('PLUGIN_SCM')
8+
9+
task androidSourcesJar(type: Jar) {
10+
archiveClassifier.set('sources')
11+
if (project.plugins.findPlugin("com.android.library")) {
12+
from android.sourceSets.main.java.srcDirs
13+
from android.sourceSets.main.kotlin.srcDirs
14+
} else {
15+
from sourceSets.main.java.srcDirs
16+
from sourceSets.main.kotlin.srcDirs
17+
}
18+
}
19+
20+
artifacts {
21+
archives androidSourcesJar
22+
}
23+
24+
group = 'com.capacitorjs'
25+
version = LIB_VERSION
26+
27+
afterEvaluate {
28+
publishing {
29+
publications {
30+
release(MavenPublication) {
31+
// Coordinates
32+
groupId 'com.capacitorjs'
33+
artifactId PLUGIN_NAME
34+
version LIB_VERSION
35+
36+
// Two artifacts, the `aar` (or `jar`) and the sources
37+
if (project.plugins.findPlugin("com.android.library")) {
38+
from components.release
39+
} else {
40+
artifact("$buildDir/libs/${project.getName()}-${version}.jar")
41+
}
42+
43+
artifact androidSourcesJar
44+
45+
// POM Data
46+
pom {
47+
name = PLUGIN_NAME
48+
description = 'Capacitor Android ' + PLUGIN_NAME + ' plugin native library'
49+
url = PLUGIN_REPO
50+
licenses {
51+
license {
52+
name = 'MIT'
53+
url = PLUGIN_REPO + '/blob/main' + '/LICENSE'
54+
}
55+
}
56+
developers {
57+
developer {
58+
name = 'Ionic'
59+
60+
}
61+
}
62+
63+
// Version Control Info
64+
scm {
65+
connection = 'scm:git:' + PLUGIN_SCM + '.git'
66+
developerConnection = 'scm:git:ssh://' + PLUGIN_SCM + '.git'
67+
url = PLUGIN_REPO + '/tree/main'
68+
}
69+
}
70+
}
71+
}
72+
}
73+
}
74+
75+
signing {
76+
useInMemoryPgpKeys(
77+
rootProject.ext["signing.keyId"],
78+
rootProject.ext["signing.key"],
79+
rootProject.ext["signing.password"],
80+
)
81+
sign publishing.publications
82+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Create variables with empty default values
2+
ext["signing.keyId"] = ''
3+
ext["signing.key"] = ''
4+
ext["signing.password"] = ''
5+
ext["centralTokenUsername"] = ''
6+
ext["centralTokenPassword"] = ''
7+
ext["sonatypeStagingProfileId"] = ''
8+
9+
File globalSecretPropsFile = file('../../scripts/android/local.properties')
10+
File secretPropsFile = project.rootProject.file('local.properties')
11+
if (globalSecretPropsFile.exists()) {
12+
// Read global local.properties file first if it exists (scripts/android/local.properties)
13+
Properties p = new Properties()
14+
new FileInputStream(globalSecretPropsFile).withCloseable { is -> p.load(is) }
15+
p.each { name, value -> ext[name] = value }
16+
} else if (secretPropsFile.exists()) {
17+
// Read plugin project specific local.properties file next if it exists
18+
Properties p = new Properties()
19+
new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) }
20+
p.each { name, value -> ext[name] = value }
21+
} else {
22+
// Use system environment variables
23+
ext["centralTokenUsername"] = System.getenv('ANDROID_CENTRAL_USERNAME')
24+
ext["centralTokenPassword"] = System.getenv('ANDROID_CENTRAL_PASSWORD')
25+
ext["sonatypeStagingProfileId"] = System.getenv('ANDROID_SONATYPE_STAGING_PROFILE_ID')
26+
ext["signing.keyId"] = System.getenv('ANDROID_SIGNING_KEY_ID')
27+
ext["signing.key"] = System.getenv('ANDROID_SIGNING_KEY')
28+
ext["signing.password"] = System.getenv('ANDROID_SIGNING_PASSWORD')
29+
}
30+
31+
// Set up Sonatype repository
32+
nexusPublishing {
33+
repositories {
34+
sonatype {
35+
stagingProfileId = sonatypeStagingProfileId
36+
username = centralTokenUsername
37+
password = centralTokenPassword
38+
nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
39+
snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
40+
}
41+
}
42+
repositoryDescription = 'Capacitor Android ' + System.getenv('PLUGIN_NAME') + ' plugin v' + System.getenv('PLUGIN_VERSION')
43+
}

scripts/publish-android.sh

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/usr/bin/env bash
2+
3+
publish_plugin_android () {
4+
PLUGIN_PATH=$1
5+
if [ -d "$PLUGIN_PATH" ]; then
6+
# Android dir path
7+
ANDROID_PATH=$PLUGIN_PATH/android
8+
GRADLE_FILE=$ANDROID_PATH/build.gradle
9+
10+
# Only try to publish if the directory contains a package.json and android package
11+
if test -f "$PLUGIN_PATH/package.json" && test -d "$ANDROID_PATH" && test -f "$GRADLE_FILE"; then
12+
PLUGIN_VERSION=$(grep '"version": ' "$PLUGIN_PATH"/package.json | awk '{print $2}' | tr -d '",')
13+
PLUGIN_NAME=$(grep '"name": ' "$PLUGIN_PATH"/package.json | awk '{print $2}' | tr -d '",')
14+
PLUGIN_NAME=${PLUGIN_NAME#@capacitor/}
15+
LOG_OUTPUT=./tmp/$PLUGIN_NAME.txt
16+
17+
# Get latest plugin info from MavenCentral
18+
PLUGIN_PUBLISHED_URL="https://repo1.maven.org/maven2/com/capacitorjs/$PLUGIN_NAME/maven-metadata.xml"
19+
PLUGIN_PUBLISHED_DATA=$(curl -s $PLUGIN_PUBLISHED_URL)
20+
PLUGIN_PUBLISHED_VERSION="$(perl -ne 'print and last if s/.*<latest>(.*)<\/latest>.*/\1/;' <<< $PLUGIN_PUBLISHED_DATA)"
21+
22+
if [[ $PLUGIN_VERSION == $PLUGIN_PUBLISHED_VERSION ]]; then
23+
printf %"s\n\n" "Duplicate: a published plugin $PLUGIN_NAME exists for version $PLUGIN_VERSION, skipping..."
24+
else
25+
# Make log dir if doesnt exist
26+
mkdir -p ./tmp
27+
28+
printf %"s\n" "Attempting to build and publish plugin $PLUGIN_NAME for version $PLUGIN_VERSION to production..."
29+
30+
# Export ENV variables used by Gradle for the plugin
31+
export PLUGIN_NAME
32+
export PLUGIN_VERSION
33+
export CAPACITOR_VERSION
34+
export CAP_PLUGIN_PUBLISH=true
35+
export PLUGIN_REPO="https://github.com/ionic-team/capacitor-haptics"
36+
export PLUGIN_SCM="github.com:ionic-team/capacitor-haptics"
37+
38+
# Build and publish
39+
"$ANDROID_PATH"/gradlew clean build publishReleasePublicationToSonatypeRepository closeAndReleaseSonatypeStagingRepository --no-daemon --max-workers 1 -b "$ANDROID_PATH"/build.gradle -Pandroid.useAndroidX=true > $LOG_OUTPUT 2>&1
40+
41+
if grep --quiet "BUILD SUCCESSFUL" $LOG_OUTPUT; then
42+
printf %"s\n\n" "Success: $PLUGIN_NAME published to MavenCentral."
43+
else
44+
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/"
45+
cat $LOG_OUTPUT
46+
exit 1
47+
fi
48+
fi
49+
else
50+
printf %"s\n\n" "$PLUGIN_PATH does not appear to be a plugin (has no package.json file or Android package), skipping..."
51+
fi
52+
fi
53+
}
54+
55+
# Get latest com.capacitorjs:core XML version info
56+
CAPACITOR_PUBLISHED_URL="https://repo1.maven.org/maven2/com/capacitorjs/core/maven-metadata.xml"
57+
CAPACITOR_PUBLISHED_DATA=$(curl -s $CAPACITOR_PUBLISHED_URL)
58+
CAPACITOR_PUBLISHED_VERSION="$(perl -ne 'print and last if s/.*<latest>(.*)<\/latest>.*/\1/;' <<< $CAPACITOR_PUBLISHED_DATA)"
59+
60+
printf %"s\n" "The latest published Android library version of Capacitor Core is $CAPACITOR_PUBLISHED_VERSION in MavenCentral."
61+
62+
# Determine Capacitor Version to use as gradle dependency.
63+
STABLE_PART=$(echo "$CAPACITOR_PUBLISHED_VERSION" | cut -d'-' -f1)
64+
IFS='.' read -r MAJOR MINOR PATCH <<< "$STABLE_PART"
65+
if [[ "$CAPACITOR_PUBLISHED_VERSION" == *"-"* ]]; then
66+
# prerelease - go one major lower (latest stable major), but also allow next upcoming major
67+
PREV_MAJOR=$((MAJOR - 1))
68+
NEXT_MAJOR=$((MAJOR + 1))
69+
CAPACITOR_VERSION="[$PREV_MAJOR.0,$NEXT_MAJOR.0)"
70+
else
71+
# stable - current major range
72+
NEXT_MAJOR=$((MAJOR + 1))
73+
CAPACITOR_VERSION="[$MAJOR.0,$NEXT_MAJOR.0)"
74+
fi
75+
printf %"s\n" "Publishing plugin with dependency on Capacitor version $CAPACITOR_VERSION"
76+
77+
publish_plugin_android '.'

scripts/publish-ios.sh

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env bash
2+
3+
publish_plugin_ios () {
4+
PLUGIN_PATH=$1
5+
# Only try to publish if the directory contains a package.json podspec file
6+
if ! test -f "$PLUGIN_PATH/package.json"; then
7+
printf %"s\n\n" "$PLUGIN_PATH does not appear to be a plugin (has no package.json), skipping..."
8+
return
9+
fi
10+
11+
PLUGIN_VERSION=$(grep '"version": ' "$PLUGIN_PATH"/package.json | awk '{print $2}' | tr -d '",')
12+
PLUGIN_NAME=$(grep '"name": ' "$PLUGIN_PATH"/package.json | awk '{print $2}' | tr -d '",')
13+
PLUGIN_NAME=${PLUGIN_NAME#@capacitor/}
14+
# capitalize the name, because .podspec file name is capitalized
15+
first_char=$(printf '%s' "$PLUGIN_NAME" | cut -c1 | tr '[:lower:]' '[:upper:]')
16+
rest=$(printf '%s' "$PLUGIN_NAME" | cut -c2-)
17+
PLUGIN_NAME="${first_char}${rest}"
18+
POD_NAME="Capacitor$PLUGIN_NAME"
19+
PODSPEC_FILE_PATH="$PLUGIN_PATH/$POD_NAME.podspec"
20+
if ! test -f $PODSPEC_FILE_PATH; then
21+
printf %"s\n\n" "Was looking for podspec file $PODSPEC_FILE_PATH, but does not seem to exist, skipping..."
22+
return
23+
fi
24+
25+
# check if version already exists in Trunk
26+
if pod trunk info "$POD_NAME" 2>/dev/null | grep -q " - $PLUGIN_VERSION"; then
27+
printf %"s\n\n" "Duplicate: a published plugin $PLUGIN_NAME exists for version $PLUGIN_VERSION, skipping..."
28+
return
29+
fi
30+
31+
LOG_OUTPUT=./tmp/$PLUGIN_NAME.txt
32+
# Make log dir if doesnt exist
33+
mkdir -p ./tmp
34+
# publish to Trunk
35+
printf %"s\n" "Attempting to build and publish plugin $PLUGIN_NAME for version $PLUGIN_VERSION to production..."
36+
pod trunk push $PODSPEC_FILE_PATH --allow-warnings > $LOG_OUTPUT 2>&1
37+
if grep -q "passed" $LOG_OUTPUT; then
38+
printf %"s\n\n" "Success: $PLUGIN_NAME published to CocoaPods Trunk."
39+
else
40+
printf %"s\n\n" "Error publishing $PLUGIN_NAME, check $LOG_OUTPUT for more info!"
41+
cat $LOG_OUTPUT
42+
exit 1
43+
fi
44+
}
45+
46+
publish_plugin_ios '.'

0 commit comments

Comments
 (0)