0012-External CI/CD Distribution Plug-ins: Technical Design Document¶
TL;DR
Appcircle ships eight external CI/CD distribution plug-ins across four ecosystems: Fastlane, GitHub Actions, Jenkins, Azure DevOps. Each ecosystem has two product lines: Testing Distribution (TD) and Enterprise App Store (EAS). All of them do the same two jobs against the same public API. They exchange a Personal API Token (PAT) for a Bearer token, upload a build artifact, poll the task, and (for EAS) publish. They differ only in the host platform's packaging, manifest, and marketplace conventions. This document is the single reference for the full lifecycle: development, local testing, beta/pre-release, and production validation. It documents each plug-in with the same structure. The release mechanism differs per ecosystem and is not yet standardized. For the verified per-ecosystem reality and the target standard, see Plugin Release Policy.
A third product line, Publish (app store publishing), is being added across the same four ecosystems as separate plug-ins. Unlike TD/EAS, it can upload a binary and/or trigger the profile's publish flow; it is documented in its own Publish Module section below.
Status: π Draft β’ Linear: BE-8868 (analysis) β’ Team: Backend β’ Project: Marketplace Plugins
Introduction¶
Purpose: The development, local testing, beta/pre-release, and production validation of Appcircle's external CI/CD distribution plug-ins is currently scattered across idoc (releases/plugins) notes and the public marketplace docs, and is not described in sufficient detail anywhere. This TDD consolidates that knowledge into one authoritative reference for contributors and maintainers.
Scope:
- External CI/CD distribution plug-ins only: Fastlane, GitHub Actions, Jenkins, Azure DevOps. Each ships in two product lines, Testing Distribution (TD) and Enterprise App Store (EAS).
- Per plug-in: (1) development (repo structure, code layout, required files, conventions), (2) local testing, (3) beta/pre-release (release branch flow, versioning, tagging), (4) production validation (post-release verification).
- No new authorization scopes. Documents the existing Personal API Token auth model and the self-hosted endpoint configuration (authEndpoint / apiEndpoint).
- No UI surface, activity-log, webhook, or notification changes. This is a documentation deliverable.
System Overview¶
High-Level Description: Every plug-in implements the same two-phase contract against the Appcircle public API:
- Authenticate: exchange the user-supplied PAT for a short-lived Bearer access token at
POST {authEndpoint}/auth/v{1|2}/token(bodyapplication/x-www-form-urlencoded,pat=<PAT>). - Distribute: upload the artifact, poll
GET /task/v1/tasks/{taskId}until completion, and for EAS optionally publish.
The plug-ins are thin clients. All business logic lives server-side in Appcircle. The differences between the eight repos are limited to host-platform packaging (gem, Node action, Jenkins HPI, Azure DevOps VSIX), manifest format, and the target marketplace.
Inventory & current versions¶
| Ecosystem | Product | Repository | Default branch | Marketplace | Version |
|---|---|---|---|---|---|
| Fastlane | TD | appcircleio/fastlane_plugin_appcircle_testing_distribution |
main |
RubyGems | 0.2.2 |
| Fastlane | EAS | appcircleio/fastlane-plugin-appcircle_enterprise_app_store |
main |
RubyGems | 0.0.1 |
| GitHub Actions | TD | appcircleio/appcircle-testing-distribution-githubaction |
main |
GitHub Marketplace | 0.0.2 |
| GitHub Actions | EAS | appcircleio/appcircle-enterprise-app-store-githubaction |
main |
GitHub Marketplace | 0.0.2 |
| Jenkins | TD | jenkinsci/appcircle-testing-distribution-plugin |
master |
Jenkins Update Center | 2.0.0 (JEP-229 CD) |
| Jenkins | EAS | jenkinsci/appcircle-enterprise-app-store-plugin |
master |
Jenkins Update Center | 2.0.0 (JEP-229 CD) |
| Azure DevOps | TD | appcircleio/appcircle-azuredevops-testdist-task-extension |
main |
Visual Studio Marketplace | task 0.1.0 / pkg 0.0.70 |
| Azure DevOps | EAS | appcircleio/appcircle-azuredevops-entstore-task-extension |
main |
Visual Studio Marketplace | task 0.1.0 / pkg 0.0.70 |
Jenkins plug-ins live under the
jenkinsciorg (notappcircleio). This is a prerequisite for publishing to the Jenkins Update Center.
Architecture¶
System Architecture (data flow):
CI/CD step (Fastlane / GH Action / Jenkins / Azure DevOps task)
β PAT
βΌ
POST {authEndpoint}/auth/v{1|2}/token βββΊ auth.appcircle.io β access_token
β Authorization: Bearer <access_token>
βΌ
ββ Testing Distribution (signed-URL upload) βββββββββββββββββββββββββββ
GET/POST /distribution/v2/profiles (resolve / create profile)
GET /distribution/v1/profiles/{id}/app-versions?action=uploadInformation β { uploadUrl, configuration }
PUT/POST <signed uploadUrl> (octet-stream, or multipart with the file field last)
POST /distribution/v1/profiles/{id}/app-versions?action=commitFileUpload β taskId
ββ Enterprise App Store (signed-URL upload) βββββββββββββββββββββββββββ
GET /store/v1/profiles/app-versions?action=uploadInformation β { uploadUrl, configuration }
PUT/POST <signed uploadUrl>
POST /store/v1/profiles/app-versions?action=commitFileUpload&createNewProfile=true β taskId
PATCH /store/v2/profiles/{id}/app-versions/{vid}?action=publish (summary, releaseNotes, publishType)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
GET /task/v1/tasks/{taskId} βββΊ poll until completed / failed
Technology Stack:
| Ecosystem | Language / runtime | Packaging | Build / bundle |
|---|---|---|---|
| Fastlane | Ruby >= 2.6 |
RubyGem | bundler / rake; dep rest-client |
| GitHub Actions | TypeScript, Node 20 | Action (dist/index.js) |
@vercel/ncc; deps @actions/core, axios, form-data |
| Jenkins | Java, plugin-parent 4.85, jenkins.version 2.440.3 |
HPI | Maven; deps json-api, apache-httpcomponents-client-4-api |
| Azure DevOps | TypeScript, Node 16 handler | VSIX | tsc + tfx-cli; deps azure-pipelines-task-lib, axios |
External Dependencies:
- Appcircle Auth (
auth.appcircle.io/auth/vN/token), Distribution API (profiles on/distribution/v2, signed-URL upload/commit on/distribution/v1), Store API (upload/commit on/store/v1, publish on/store/v2), Task API (/task/v1). - Marketplaces: RubyGems, GitHub Marketplace, Jenkins Update Center, Visual Studio Marketplace.
- Build agents: macOS 14 (arm64), Ubuntu 22.04, RHEL 9 where applicable. Jenkins supports Linux (build matrix: Linux JDK 21 + Windows JDK 17).
Auth version drift: Fastlane and Jenkins call
/auth/v2/token; GitHub Actions and Azure DevOps call/auth/v1/token. Both work today; new development should standardize onv2.
Key Components¶
Authentication (Personal API Token): all plug-ins¶
- Purpose: Authenticate to Appcircle without storing long-lived credentials.
- Responsibilities: Take the user-supplied PAT, exchange it for a Bearer access token, attach
Authorization: Bearer <token>to all subsequent calls. - Interactions:
POST {authEndpoint}/auth/v{1|2}/token. - Security: No new scopes. Jenkins stores the PAT encrypted as
hudson.util.Secret; GitHub Actions / Azure DevOps rely on platform secret masking.
Self-hosted endpoint configuration: all plug-ins¶
- Purpose: Point the plug-in at a self-hosted Appcircle instance.
- Responsibilities: Optional
authEndpoint(defaulthttps://auth.appcircle.io) andapiEndpoint(defaulthttps://api.appcircle.io) inputs override the cloud hosts. Unset β cloud defaults, so existing cloud users are unaffected. - Status: Implemented across all four ecosystems (Fastlane, GitHub Actions, Jenkins, Azure DevOps). The plug-ins never disable TLS verification; for a self-signed / private-CA server, trust that CA on the machine or agent running the plug-in (Ruby
SSL_CERT_FILE, NodeNODE_EXTRA_CA_CERTS, JVM truststore).
Parameter matrix¶
Testing Distribution
| Parameter | Required | Note |
|---|---|---|
personalAPIToken |
β | PAT |
profileName |
β | distribution profile |
createProfileIfNotExists |
β | default false |
appPath |
β | artifact path (.ipa, .apk, .aab, .zip/.xcarchive) |
message |
varies | required in Fastlane/GH; optional in Azure DevOps |
authEndpoint / apiEndpoint |
β | self-hosted endpoints (all ecosystems; cloud defaults) |
Enterprise App Store
| Parameter | Required | Note |
|---|---|---|
personalAPIToken |
β | PAT |
appPath |
β | artifact path (.ipa, .apk; Azure DevOps EAS also .aab) |
summary |
β | store summary |
releaseNotes |
β | store release notes |
publishType |
varies | 0=None / 1=Beta / 2=Live; optional (default None) in GH |
authEndpoint / apiEndpoint |
β | self-hosted endpoints (all ecosystems; cloud defaults) |
Detailed Design: Per-Plug-in Lifecycle¶
Each plug-in is documented with the same four headings: Development, Local Testing, Beta / Pre-release, Production Validation.
Fastlane¶
Development
lib/fastlane/plugin/<plugin>/
version.rb # VERSION constant (single source of truth)
<plugin>.rb # module + all_classes loader
actions/<plugin>_action.rb # Fastlane::Actions::<Name>Action
helper/ # auth + upload services
<plugin>.gemspec # spec.version = ...::VERSION
Gemfile / Rakefile / .rubocop.yml
fastlane/Fastfile, fastlane/Pluginfile
Jenkinsfile / pipeline.sh / ci/release.rb / Dockerfile.ci # release automation (see Beta / Pre-release)
- Action class extends
Fastlane::Action; parameters declared asFastlaneCore::ConfigItemwithenv_namebindings (AC_PERSONAL_API_TOKEN,AC_PROFILE_NAME,AC_APP_PATH,AC_MESSAGE, β¦). - The only runtime dependency is
rest-client.rubygems_mfa_required = 'false'. TD platforms[:ios, :android]; EAS all. - Self-hosted ready: both gems accept optional
authEndpoint/apiEndpoint(envAC_AUTH_ENDPOINT/AC_API_ENDPOINT). When unset they default to the Appcircle cloud, so existing cloud users are unaffected. If the self-hosted server uses a self-signed or private-CA certificate, trust that CA on the machine running Fastlane (for example,SSL_CERT_FILEpointing at a PEM bundle). The gem does not disable TLS verification.
Local Testing
bundle install
bundle exec rake # spec + rubocop (default task)
bundle exec rake spec # rspec only
bundle exec rake rubocop # lint only
Wire the local checkout into a real Fastlane project to run end-to-end:
# test app fastlane/Pluginfile
gem 'fastlane-plugin-appcircle_testing_distribution', path: '/path/to/local/checkout'
# Fastfile
lane :smoke do
appcircle_testing_distribution(
personalAPIToken: ENV['AC_PERSONAL_API_TOKEN'], profileName: 'QA',
createProfileIfNotExists: true, appPath: 'build/app.ipa', message: 'local smoke test'
)
end
EAS has a SimpleCov-enabled placeholder
spec/; TD has no spec suite yet (gap).
Beta / Pre-release
Release runs on a Jenkins Multibranch Pipeline (Jenkinsfile β pipeline.sh β ci/release.rb, all executed inside the Dockerfile.ci Ruby image; RubyGems credential exposed as GEM_HOST_API_KEY via the Jenkins credential Appcircle-Fastlane-RubyGems-Cred). It is branch-gated, so a build never double-publishes:
- Version source of truth = the latest git tag (
vX.Y.Z). Tagging is manual: the developer pushesgit tag vX.Y.Z; the pipeline never creates tags.version.rbis rewritten by the release script at publish time. - Push to
release/x.y.zβpipeline.sh prereleaseβ publishes a RubyGems pre-releasex.y.z.beta.N.Nauto-increments from the betas already on RubyGems for that base version. - Merge to
mainβpipeline.sh productionβ publishes the productionx.y.z; idempotent (skips if that version is already on RubyGems).
RubyGems beta semantics: a pre-release version is dotted (
x.y.z.beta.N, not npm-style-beta.1) and is hidden fromgem install/fastlane add_pluginunless the user opts in. There is no dist-tag channel.
See Plugin Release Policy β Fastlane.
Production Validation
Test the published gem (not just local code), and verify the beta does not shadow production:
- Production:
gem install fastlane-plugin-appcircle_<...>/fastlane add_plugin appcircle_<...>must resolve the latest stable version. - Beta: only an explicit opt-in pulls it:
gem install <gem> --pre(or an exact--version x.y.z.beta.N). Confirm a normal install still resolves the prod version.
Then run a lane against a real Appcircle profile and confirm: upload + task completion, every previously-documented parameter still accepted (backward compatible), the new authEndpoint/apiEndpoint work against a self-hosted instance and default to cloud when unset, and the README documents the new behavior.
GitHub Actions¶
Development
action.yml # name, branding, inputs, runs.using: node20, main: dist/index.js
src/index.ts # entry β run()
src/main.ts # input parsing + orchestration
src/api/authApi.ts # PAT β token
src/api/uploadApi.ts # upload + poll (+ publish for EAS)
dist/index.js # ncc-bundled output, COMMITTED to the repo
package.json / tsconfig.json / script/release
- Runtime
node20; bundled with@vercel/ncc.action.ymlinputs mirror the parameter matrix; TD brandingblue, EASorange. - GitHub runs the committed
dist/. Regenerate and commit it wheneversrc/changes.
Local Testing
npm install
npm run package # ncc build src/index.ts -o dist (regenerate bundle)
npm test # jest (no test files yet: gap)
npm run lint
test.yml) that runs the action via uses: ./; EAS has no CI workflow (gap).
- For full local execution use act or a throwaway branch workflow.
Beta / Pre-release
β οΈ No release branches and no publish automation today. script/release only creates a vX.Y.Z tag and pushes it. There is no GitHub Release / Marketplace publish step, no moving major tag (v1), and no check-dist guard. dist/ is usually rebuilt on a feature branch, so the tagged commit's dist/ and package.json can drift. See Plugin Release Policy β GitHub Actions for the verified state and target standard.
Production Validation
Reference the action by published tag in a test workflow; confirm upload + task completion, old inputs still resolve, and new inputs appear in the Marketplace listing.
Jenkins¶
Development
pom.xml # artifactId, version ${changelist} (JEP-229 CD; changelist 999999-SNAPSHOT), parent 4.85, jenkins.version 2.440.3
src/main/java/io/jenkins/plugins/appcircle/.../<Name>Builder.java
src/main/java/.../AuthService.java, UploadService.java, Models/
src/main/resources/.../<Builder>/config.jelly # UI form (declarative `clazz="required"` validation)
Jenkinsfile # buildPlugin matrix (Linux + Windows, JDK 21)
.github/workflows/ # both TD and EAS: cd.yaml + jenkins-security-scan.yml
- Main class extends
Builder implements SimpleBuildStep;@Symbol("appcircleTestingDistribution" | "appcircleEnterpriseAppStore")enables pipeline DSL. - Parameters bound via
@DataBoundConstructor/@DataBoundSetter; PAT stored encrypted ashudson.util.Secretand the access token is threaded end-to-end asSecret. - Self-hosted supported via optional
authEndpoint/apiEndpoint(@DataBoundSetter, cloud defaults) β backward-compatible with existing job configs. - The artifact is read from the build workspace (
FilePath, agent-safe streaming), not from the controller filesystem. - artifactIds:
appcircle-testing-distribution,appcircle-enterprise-store.
Local Testing
mvn hpi:run # dev Jenkins with the plugin loaded
mvn test # JenkinsRule tests (only configRoundtrip stubs: gap)
mvn package # build target/<artifactId>.hpi
.hpi via Manage Jenkins β Plugins β Advanced β Deploy Plugin, then validate with a freestyle job and a pipeline step:
appcircleTestingDistribution(
personalAPIToken: env.AC_PAT, profileName: 'QA',
createProfileIfNotExists: true, appPath: 'app.apk', message: 'pipeline test'
)
Beta / Pre-release
Releases use the official Continuous Delivery of Plugins (JEP-229) on master (not maven-release-plugin, no release branches). The version is pom.xml's ${changelist} (kept at 999999-SNAPSHOT in the repo; .mvn/maven.config sets -Dchangelist.format=%d.v%s), and maven-cd derives the actual released version from the merged PR's labels β producing a clean incremental <n>.v<sha> (no -rc prefix, which previously confused users). The flow is label-driven: label a PR with a release-worthy label (e.g. enhancement, bug, developer), merge it, and a successful CI build (check_run) triggers cd.yaml, which delegates to the jenkins-infra reusable maven-cd workflow and deploys to repo.jenkins-ci.org; the Update Center picks it up within a few hours. workflow_dispatch (with a validate_only input) allows a manual run / dry run. Both TD and EAS ship cd.yaml (workflow_dispatch + check_run); distributionManagement is omitted because maven-cd provides it. See Plugin Release Policy β Jenkins.
Production Validation
Install from the Update Center on a clean Jenkins; run freestyle + pipeline jobs (including a Linux agent); confirm config-roundtrip, old job configs still load, and new features present.
Azure DevOps¶
Development
vss-extension.json # publisher Appcircle, version, files, categories
<taskfolder>/task.json # id (UUID), inputs, execution Node16 -> index.js
<taskfolder>/index.ts # entry point (TD: buildandreleasetask/, EAS: task/)
configs/dev.json # id ...-dev, public:false
configs/release.json # id ..., public:true
package.json # tsc build, tfx-cli; CI via pipeline.sh + Jenkinsfile
- TD task
AppcircleTestingDistribution(folderbuildandreleasetask/, targetes6); EAS taskAppcircleEnterpriseStore(foldertask/, targetes2022). - Inputs include the standard matrix plus
authEndpoint/apiEndpoint, which is the self-hosted hook. Compiled withtsc(no bundler).
Local Testing
yarn install
(cd <taskfolder> && yarn install)
yarn package # tsc ./<taskfolder>/index.ts -> index.js
tfx extension create --manifest-globs vss-extension.json --overrides-file configs/dev.json
.vsix into a test org and run it in a pipeline. No unit-test framework is wired up (gap), so validation is integration-based.
Beta / Pre-release
β
This is the one ecosystem that follows a release-branch β main flow end-to-end. pipeline.sh publishes the private -dev extension from release/* (beta) and the public extension from main. The version is bumped on the release branch (task.json + vss-extension.json), merged to main, tagged, then merged back to develop. β οΈ Branch naming is inconsistent (release/0.1.0 semver vs release/6031 numeric id). See Plugin Release Policy β Azure DevOps.
Production Validation
Add the task to a test pipeline; exercise both cloud and a self-hosted endpoint via authEndpoint/apiEndpoint; confirm upload + task completion + (EAS) publish, old inputs still resolve, and publishType options render.
Publish Module¶
A third product line, Publish (app store publishing), is being added across all four ecosystems as separate plug-ins. Unlike TD/EAS (which only upload/distribute), the Publish plug-in can upload a binary to a Publish profile and/or trigger the profile's publish flow (submitting an app version to the App Store / Google Play). Behavior is identical across the four ecosystems; only host-platform packaging differs (gem, Node action, Azure VSIX, Jenkins HPI).
Repositories & status¶
| Ecosystem | Repo | Status |
|---|---|---|
| Fastlane | fastlane-plugin-appcircle_publish (RubyGems) |
Beta 0.1.0.beta.1 published; dev-verified |
| GitHub Actions | appcircle-publish-githubaction |
dev-verified; beta cut |
| Azure DevOps | appcircle-azuredevops-publish-task-extension |
dev-verified (mock-run) |
| Jenkins | jenkinsci/appcircle-publish-plugin |
hosted in jenkinsci (hosting request completed); dev-verified |
Parameter matrix¶
| Parameter | Required | Note |
|---|---|---|
personalAPIToken |
β | PAT (Fastlane also accepts personalAccessKey) |
platform |
β | ios / android (Publish profiles are platform-scoped) |
publishProfile |
β | Publish profile name (unique per platform) |
upload |
β | default false; upload the binary as a new app version |
publish |
β | default false; trigger the profile's publish flow |
appPath |
β
if upload |
artifact path (.ipa, .apk, .aab) |
authEndpoint / apiEndpoint |
β | self-hosted endpoints (cloud defaults) |
At least one of upload / publish must be enabled.
Behavior modes¶
upload |
publish |
Behavior |
|---|---|---|
| β | β | Upload appPath as a new app version on the profile. |
| β | β | Trigger the publish flow for the profile's current release candidate. |
| β | β | Upload, mark the new version as release candidate, then publish it. |
| β | β | Error: nothing to do. |
Rules:
- In-progress guard: a new publish is never started if one is already running for the target profile.
- Release candidate: in the upload+publish mode the uploaded version is auto-marked as release candidate; publish-only publishes the profile's existing release candidate.
- Progress: the publish status is polled and each flow step is logged once with a human-readable status label + icon.
API contract (verified on dev)¶
| Purpose | Method | Path |
|---|---|---|
| Resolve profile | GET | /publish/v2/profiles/{platform} (match by name) |
| Upload information | GET | /publish/v1/profiles/{platform}/{id}/app-versions?action=uploadInformation&fileName=&fileSize= |
| Upload binary | PUT / POST | signed uploadUrl (PUT octet-stream, or POST multipart with file field last) |
| Commit upload | POST | /publish/v1/profiles/{platform}/{id}/app-versions?action=commitFileUpload β {taskId} |
| Upload task poll | GET | /task/v1/tasks/{taskId} (stateValue 3=Completed, 2=Canceled) |
| Mark release candidate | PATCH | /publish/v2/profiles/{platform}/{id}/app-versions/{vid}?action=releaseCandidate body {"ReleaseCandidate":true} |
| In-progress check | GET | /build/v1/queue/my-dashboard?page=1&size=1000 (filter publishId != null && profileId == target) |
| Publish steps β run id | GET | /publish/v2/profiles/{platform}/{id}/app-versions/{vid}/publish β steps[0].publishId |
| Start publish | POST | /publish/v2/profiles/{platform}/{id}/publish/{publishId}?action=restart body "{}" |
| Publish status poll | GET | /publish/v1/profiles/{platform}/{id}/app-versions/{vid}/publish β status (0=success, 1=failed, else running) |
v1 vs v2: the signed-URL upload/commit actions live on
publish/v1(thepublish/v2app-versions path ignoresaction=uploadInformationand returns the version list instead). Profile listing and the publish trigger are onpublish/v2. Auth is the same PAT model as TD/EAS (/auth/v1/tokenon GitHub/Jenkins/Azure,/auth/v2/tokenfor PAT on Fastlane).
FlowStepStatus (step-log labels): 0 Success, 1 Failed, 2 Cancelled, 3 Timeout, 90 Waiting, 91 Running, 92 Completing, 99 Unknown, 100 Skipped, 200 Not Started, 201 Stopped, 202 In Progress, 203 Awaiting Response.
CI note: if the profile's publish flow contains a manual-approval step (e.g. "Get Approval via Email"), the flow blocks on it and the plug-in keeps polling until it completes or the poll times out. Prefer flows without manual gates for CI.
Integration and Interfaces¶
External Interfaces:
| Purpose | Default host | Path family |
|---|---|---|
| Auth | https://auth.appcircle.io |
/auth/vN/token |
| API (TD) | https://api.appcircle.io |
profiles /distribution/v2/...; signed-URL upload/commit /distribution/v1/... |
| API (EAS) | https://api.appcircle.io |
signed-URL upload/commit /store/v1/...; publish /store/v2/... |
| Task polling | https://api.appcircle.io |
/task/v1/tasks/{taskId} |
- TD flow: resolve profile by
profileName(create ifcreateProfileIfNotExists); upload via the signed-URL contract (GET .../distribution/v1/profiles/{id}/app-versions?action=uploadInformationβ PUT/POST the signeduploadUrlβPOST ...?action=commitFileUploadβtaskId); poll task. - EAS flow: upload via the signed-URL contract on
/store/v1/profiles/app-versions(uploadInformationβ PUT/POST βcommitFileUpload&createNewProfile=trueβtaskId); poll task; and ifpublishType != Noneselect the most recent profile (bylastBinaryReceivedDate), thenPATCH /store/v2/.../app-versions/{vid}?action=publish.
Internal Interfaces: None added. The plug-ins are external API clients only.
Deployment and Infrastructure¶
Cloud Deployment¶
No Appcircle Helm/cluster changes. The plug-ins are published to external marketplaces (RubyGems, GitHub Marketplace, Jenkins Update Center, Visual Studio Marketplace). See Plugin Release Policy for the per-marketplace release flow.
Self-Hosted Deployment¶
No Docker Compose / install-script changes. Self-hosted customers set each plug-in's optional authEndpoint / apiEndpoint inputs to target their instance; this is supported across all four ecosystems (cloud defaults when unset).
Testing and Quality Assurance¶
Test Strategy:
| Integration | Local-test recipe |
|---|---|
| Fastlane | bundle exec rake; path:-link the gem into a test app's Pluginfile and run a lane. |
| GitHub Actions | npm run package; run via uses: ./ on a branch or with act. |
| Jenkins | mvn hpi:run, or mvn package + deploy .hpi; run freestyle + pipeline. |
| Azure DevOps | yarn package + tfx extension create --overrides-file configs/dev.json; install private .vsix and run in a pipeline. |
Release-flow verification: confirm the documented release produces the correct published version on each marketplace (RubyGems install, GH @vX.Y.Z + major tag, Jenkins Update Center pickup, Visual Studio Marketplace public extension validated first via the private -dev).
Post-release checklist (reproducible):
- No regression: a known-good build still uploads and the task completes.
- Backward-compatible params: every previously-documented input still accepted; Jenkins saved job configs still load.
- New features present: new inputs/behaviors appear and function.
- Smoke test: end-to-end against a real Appcircle profile (TD) / store profile + publish (EAS), including a self-hosted endpoint run for Azure DevOps.
Quality Assurance: RuboCop (Fastlane), ESLint/Prettier (GitHub Actions, Azure DevOps), Jenkins security scan + Release Drafter (Jenkins). Test coverage is a known gap across all repos.
Performance Considerations¶
The plug-ins are short-lived CI steps; throughput is not a concern. Two practical limits apply: artifact uploads can be large (up to ~2 GB) and use multipart streaming, and task polling caps at ~100 attempts. No caching or scaling strategy applies.
Maintenance and Support¶
- Monitoring: plug-in execution surfaces in the host CI logs; server-side behavior is observed in the Appcircle backend. No plug-in-side telemetry.
- Support and Maintenance: keep marketplace listings current, regenerate the GitHub Actions
dist/bundle on every source change, and keep Jenkins baseline (jenkins.version) and plugin-parent up to date.
Risks and Mitigation Strategies¶
| Risk | Impact | Mitigation |
|---|---|---|
Stale GitHub Actions dist/ bundle |
Published action runs old code | Enforce npm run package + commit on the tagged commit; add a check-dist CI guard that the bundle matches src/ |
| Thin test coverage (all repos) | Regressions ship undetected | Add real unit tests; rely on the reproducible post-release checklist meanwhile |
| Inconsistent release automation (Fastlane/GH manual & unautomated; Azure DevOps follows a release-branch flow) | Human error, orphaned release branches, tag/bundle drift | Converge toward the per-marketplace standard in the Plugin Release Policy; note release/x.y.z is not idiomatic for Jenkins (both Jenkins plug-ins now use JEP-229 label-driven CD, not release branches) |
Auth version drift (v1 vs v2) |
Divergent behavior, future breakage | Standardize on /auth/v2/token |
| ~~Self-hosted parity~~ (resolved) | β | authEndpoint/apiEndpoint are now implemented across all four ecosystems (Fastlane, GitHub Actions, Jenkins, Azure DevOps) |
Appendix¶
- Plugin Release Policy: the verified per-ecosystem release mechanism and the target standard.
- Marketplaces: RubyGems Testing Distribution, GitHub Marketplace, Jenkins Update Center, Visual Studio Marketplace (publisher
Appcircle).