From 17d23cf62b4b3afb35339dff6d2d8f17dd66c609 Mon Sep 17 00:00:00 2001 From: Oleksii Date: Fri, 31 Jul 2026 00:23:13 -0300 Subject: [PATCH 1/5] fix: make mobile publishing pipelines idempotent --- .../android/orch8-mobile/build.gradle.kts | 7 +++- .../flutter/.github/workflows/publish.yml | 37 +++++++++++++++++++ .../.github/workflows/publish.yml | 35 +++++++++++++++++- scripts/test-review-fix-guards.sh | 11 ++++++ 4 files changed, 87 insertions(+), 3 deletions(-) diff --git a/packages/android/orch8-mobile/build.gradle.kts b/packages/android/orch8-mobile/build.gradle.kts index 7bcf7452..4345ffb5 100644 --- a/packages/android/orch8-mobile/build.gradle.kts +++ b/packages/android/orch8-mobile/build.gradle.kts @@ -5,8 +5,11 @@ plugins { } group = "io.orch8" -version = providers.gradleProperty("VERSION_NAME") - .orElse(providers.environmentVariable("ORCH8_MOBILE_VERSION")) +// A release tag is authoritative. Keep the checked-in VERSION_NAME as the +// local/CI default, but never let it override the version supplied by the +// tag-publishing workflow. +version = providers.environmentVariable("ORCH8_MOBILE_VERSION") + .orElse(providers.gradleProperty("VERSION_NAME")) .orElse("0.0.0-local") .get() .removePrefix("v") diff --git a/packages/flutter/.github/workflows/publish.yml b/packages/flutter/.github/workflows/publish.yml index bb4be9a3..2c343e2b 100644 --- a/packages/flutter/.github/workflows/publish.yml +++ b/packages/flutter/.github/workflows/publish.yml @@ -3,9 +3,46 @@ name: Publish to pub.dev on: push: tags: ["[0-9]+.[0-9]+.[0-9]+"] + workflow_dispatch: jobs: + check: + name: Check registry + runs-on: ubuntu-latest + outputs: + exists: ${{ steps.registry.outputs.exists }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Read and validate package version + id: package + shell: bash + run: | + package="$(sed -n 's/^name:[[:space:]]*//p' pubspec.yaml)" + version="$(sed -n 's/^version:[[:space:]]*\([^+[:space:]]*\).*/\1/p' pubspec.yaml)" + test -n "$package" && test -n "$version" + if [[ "$GITHUB_REF_TYPE" == "tag" && "$GITHUB_REF_NAME" != "$version" ]]; then + echo "::error::Tag $GITHUB_REF_NAME does not match pubspec version $version." + exit 1 + fi + echo "package=$package" >> "$GITHUB_OUTPUT" + echo "version=$version" >> "$GITHUB_OUTPUT" + - name: Check whether the version is already published + id: registry + env: + PACKAGE: ${{ steps.package.outputs.package }} + VERSION: ${{ steps.package.outputs.version }} + run: | + if curl -fsS "https://pub.dev/api/packages/${PACKAGE}/versions/${VERSION}" > /dev/null; then + echo "${PACKAGE} ${VERSION} is already on pub.dev; treating this run as verified." + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "${PACKAGE} ${VERSION} is not on pub.dev; publication is required." + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + publish: + needs: check + if: needs.check.outputs.exists != 'true' permissions: id-token: write uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 diff --git a/packages/react-native/.github/workflows/publish.yml b/packages/react-native/.github/workflows/publish.yml index b3aac45e..97f0d9b2 100644 --- a/packages/react-native/.github/workflows/publish.yml +++ b/packages/react-native/.github/workflows/publish.yml @@ -3,6 +3,7 @@ name: Publish npm package on: push: tags: ["[0-9]+.[0-9]+.[0-9]+"] + workflow_dispatch: permissions: contents: read @@ -21,4 +22,36 @@ jobs: cache: npm - run: npm ci - run: npm run typescript - - run: npm publish --provenance --access public + - name: Read and validate package version + id: package + shell: bash + run: | + package="$(node -p 'require("./package.json").name')" + version="$(node -p 'require("./package.json").version')" + if [[ "$GITHUB_REF_TYPE" == "tag" && "$GITHUB_REF_NAME" != "$version" ]]; then + echo "::error::Tag $GITHUB_REF_NAME does not match package version $version." + exit 1 + fi + echo "package=$package" >> "$GITHUB_OUTPUT" + echo "version=$version" >> "$GITHUB_OUTPUT" + - name: Check whether the version is already published + id: registry + env: + PACKAGE: ${{ steps.package.outputs.package }} + VERSION: ${{ steps.package.outputs.version }} + run: | + if npm view "${PACKAGE}@${VERSION}" version > /dev/null 2>&1; then + echo "${PACKAGE} ${VERSION} is already on npm; treating this run as verified." + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "${PACKAGE} ${VERSION} is not on npm; publication is required." + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + - name: Publish npm package + if: steps.registry.outputs.exists != 'true' + run: npm publish --provenance --access public + - name: Verify npm package + env: + PACKAGE: ${{ steps.package.outputs.package }} + VERSION: ${{ steps.package.outputs.version }} + run: test "$(npm view "${PACKAGE}@${VERSION}" version)" = "$VERSION" diff --git a/scripts/test-review-fix-guards.sh b/scripts/test-review-fix-guards.sh index 0dd66a17..23259dec 100755 --- a/scripts/test-review-fix-guards.sh +++ b/scripts/test-review-fix-guards.sh @@ -163,6 +163,17 @@ assert_contains .github/workflows/mobile.yml 'cp LICENSE build/Orch8Mobile.xcfra assert_contains .github/workflows/mobile.yml 'bash ../../scripts/embed-aar-license.sh' assert_not_contains .github/workflows/mobile.yml 'gh release create' assert_not_contains .github/workflows/mobile.yml 'gh release upload' +assert_contains packages/flutter/.github/workflows/publish.yml 'workflow_dispatch:' +assert_contains packages/flutter/.github/workflows/publish.yml 'https://pub.dev/api/packages/${PACKAGE}/versions/${VERSION}' +assert_contains packages/flutter/.github/workflows/publish.yml "if: needs.check.outputs.exists != 'true'" +assert_contains packages/react-native/.github/workflows/publish.yml 'workflow_dispatch:' +assert_contains packages/react-native/.github/workflows/publish.yml 'npm view "${PACKAGE}@${VERSION}" version' +assert_contains packages/react-native/.github/workflows/publish.yml "if: steps.registry.outputs.exists != 'true'" +android_version_file="$repo_root/packages/android/orch8-mobile/build.gradle.kts" +release_version_line="$(grep -nF 'providers.environmentVariable("ORCH8_MOBILE_VERSION")' "$android_version_file" | cut -d: -f1)" +fallback_version_line="$(grep -nF 'providers.gradleProperty("VERSION_NAME")' "$android_version_file" | cut -d: -f1)" +[[ -n "$release_version_line" && -n "$fallback_version_line" && "$release_version_line" -lt "$fallback_version_line" ]] \ + || fail "ORCH8_MOBILE_VERSION must take precedence over the checked-in VERSION_NAME" assert_contains packages/android/orch8-mobile/build.gradle.kts 'name.set("Business Source License 1.1")' assert_not_contains packages/android/orch8-mobile/build.gradle.kts 'Apache License 2.0' assert_contains .github/workflows/ci.yml 'Verify Cloud management surface' From e13235abbbd83b5ed3a0a85b967abb4ae36f8d0a Mon Sep 17 00:00:00 2001 From: Oleksii Date: Fri, 31 Jul 2026 00:27:34 -0300 Subject: [PATCH 2/5] fix: require tag pushes for new pub releases --- packages/flutter/.github/workflows/publish.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/flutter/.github/workflows/publish.yml b/packages/flutter/.github/workflows/publish.yml index 2c343e2b..2f7ab8cb 100644 --- a/packages/flutter/.github/workflows/publish.yml +++ b/packages/flutter/.github/workflows/publish.yml @@ -37,6 +37,10 @@ jobs: echo "exists=true" >> "$GITHUB_OUTPUT" else echo "${PACKAGE} ${VERSION} is not on pub.dev; publication is required." + if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then + echo "::error::pub.dev only authorizes new publications from version-tag pushes." + exit 1 + fi echo "exists=false" >> "$GITHUB_OUTPUT" fi From c41e53c77bda75dc5d61b640c77d529b3704f93b Mon Sep 17 00:00:00 2001 From: Oleksii Date: Fri, 31 Jul 2026 00:29:31 -0300 Subject: [PATCH 3/5] fix: add guarded Android package recovery --- .github/workflows/mobile.yml | 18 +++++++++++++++--- scripts/test-review-fix-guards.sh | 5 +++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mobile.yml b/.github/workflows/mobile.yml index 74672ee0..025699f7 100644 --- a/.github/workflows/mobile.yml +++ b/.github/workflows/mobile.yml @@ -23,6 +23,11 @@ on: - "scripts/check-sdk-versions.sh" - ".github/workflows/mobile.yml" workflow_dispatch: + inputs: + publish_version: + description: "Android package version to publish from main (for release recovery)" + required: false + type: string env: CARGO_TERM_COLOR: always @@ -256,13 +261,20 @@ jobs: name: orch8-mobile.aar path: packages/android/orch8-mobile/build/outputs/aar/orch8-mobile-release.aar - name: Publish Android package - if: startsWith(github.ref, 'refs/tags/v') + if: startsWith(github.ref, 'refs/tags/v') || inputs.publish_version != '' working-directory: packages/android env: GITHUB_ACTOR: ${{ github.actor }} GITHUB_TOKEN: ${{ github.token }} - ORCH8_MOBILE_VERSION: ${{ github.ref_name }} - run: gradle :orch8-mobile:publishReleasePublicationToGitHubPackagesRepository + ORCH8_MOBILE_VERSION: ${{ inputs.publish_version || github.ref_name }} + run: | + requested_version="${ORCH8_MOBILE_VERSION#v}" + checked_in_version="$(sed -n 's/^VERSION_NAME=//p' gradle.properties)" + if [[ "$requested_version" != "$checked_in_version" ]]; then + echo "::error::Requested Android package version $requested_version does not match VERSION_NAME=$checked_in_version." + exit 1 + fi + gradle :orch8-mobile:publishReleasePublicationToGitHubPackagesRepository # GitHub Release creation and native release assets are owned exclusively by # release.yml. Keeping a second publisher here creates a race and can produce diff --git a/scripts/test-review-fix-guards.sh b/scripts/test-review-fix-guards.sh index 23259dec..098665de 100755 --- a/scripts/test-review-fix-guards.sh +++ b/scripts/test-review-fix-guards.sh @@ -161,6 +161,11 @@ assert_contains .github/workflows/release.yml 'bash ../../scripts/embed-aar-lice assert_contains .github/workflows/release.yml 'cp LICENSE bindings/LICENSE' assert_contains .github/workflows/mobile.yml 'cp LICENSE build/Orch8Mobile.xcframework/LICENSE' assert_contains .github/workflows/mobile.yml 'bash ../../scripts/embed-aar-license.sh' +assert_contains .github/workflows/mobile.yml 'publish_version:' +assert_contains .github/workflows/mobile.yml "startsWith(github.ref, 'refs/tags/v') || inputs.publish_version != ''" +assert_contains .github/workflows/mobile.yml 'ORCH8_MOBILE_VERSION: ${{ inputs.publish_version || github.ref_name }}' +assert_contains .github/workflows/mobile.yml 'requested_version="${ORCH8_MOBILE_VERSION#v}"' +assert_contains .github/workflows/mobile.yml 'does not match VERSION_NAME=$checked_in_version' assert_not_contains .github/workflows/mobile.yml 'gh release create' assert_not_contains .github/workflows/mobile.yml 'gh release upload' assert_contains packages/flutter/.github/workflows/publish.yml 'workflow_dispatch:' From 73102318afed668636d90ca1ff303f34816ddaa9 Mon Sep 17 00:00:00 2001 From: Oleksii Date: Fri, 31 Jul 2026 00:30:48 -0300 Subject: [PATCH 4/5] fix: normalize npm repository metadata --- packages/react-native/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 4ee39dfe..dd7257fe 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -16,7 +16,10 @@ "typescript": "tsc --noEmit" }, "keywords": ["react-native", "orch8", "workflow", "orchestration"], - "repository": "https://github.com/orch8-io/react-native-orch8", + "repository": { + "type": "git", + "url": "git+https://github.com/orch8-io/react-native-orch8.git" + }, "license": "BUSL-1.1", "publishConfig": { "access": "public", From 6897acb6fa9ac282bde1d46173a0526f0c23e6a1 Mon Sep 17 00:00:00 2001 From: Oleksii Date: Fri, 31 Jul 2026 00:31:25 -0300 Subject: [PATCH 5/5] test: guard npm repository metadata --- scripts/test-review-fix-guards.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/test-review-fix-guards.sh b/scripts/test-review-fix-guards.sh index 098665de..68bb19d8 100755 --- a/scripts/test-review-fix-guards.sh +++ b/scripts/test-review-fix-guards.sh @@ -174,6 +174,7 @@ assert_contains packages/flutter/.github/workflows/publish.yml "if: needs.check. assert_contains packages/react-native/.github/workflows/publish.yml 'workflow_dispatch:' assert_contains packages/react-native/.github/workflows/publish.yml 'npm view "${PACKAGE}@${VERSION}" version' assert_contains packages/react-native/.github/workflows/publish.yml "if: steps.registry.outputs.exists != 'true'" +assert_contains packages/react-native/package.json '"url": "git+https://github.com/orch8-io/react-native-orch8.git"' android_version_file="$repo_root/packages/android/orch8-mobile/build.gradle.kts" release_version_line="$(grep -nF 'providers.environmentVariable("ORCH8_MOBILE_VERSION")' "$android_version_file" | cut -d: -f1)" fallback_version_line="$(grep -nF 'providers.gradleProperty("VERSION_NAME")' "$android_version_file" | cut -d: -f1)"