From 19a8f18ec6d2e0a6a84f9f2538b9fdb0abc06d55 Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Fri, 21 Aug 2026 07:00:41 +0500 Subject: [PATCH 1/6] fix: use caret constraint for ensemble_test_runner's ensemble dependency melos version bumps hosted dependency constraints by regex-replacing only the leading token of the existing constraint string. That breaks on the compound ">=X <2.0.0" form written here, leaving a mangled, unparseable constraint (e.g. "^1.2.50-beta.13 <2.0.0") whenever a beta release runs. "^1.2.50" is semver-equivalent to ">=1.2.50 <2.0.0" and survives the rewrite intact. --- tools/ensemble_test_runner/example/pubspec.yaml | 2 +- tools/ensemble_test_runner/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/ensemble_test_runner/example/pubspec.yaml b/tools/ensemble_test_runner/example/pubspec.yaml index badf270e9..3677a4294 100644 --- a/tools/ensemble_test_runner/example/pubspec.yaml +++ b/tools/ensemble_test_runner/example/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: ensemble: hosted: https://pub.dev - version: ">=1.2.50 <2.0.0" + version: "^1.2.50" ensemble_test_runner: path: .. flutter: diff --git a/tools/ensemble_test_runner/pubspec.yaml b/tools/ensemble_test_runner/pubspec.yaml index 259494536..420958761 100644 --- a/tools/ensemble_test_runner/pubspec.yaml +++ b/tools/ensemble_test_runner/pubspec.yaml @@ -19,7 +19,7 @@ environment: flutter: ">=3.24.0" dependencies: - ensemble: ">=1.2.50 <2.0.0" + ensemble: "^1.2.50" device_frame: ^1.3.0 ensemble_device_preview: ^1.1.3 flutter: From 836f9e46ebe722613e7ad453b7b57058289b01bf Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Sun, 23 Aug 2026 15:24:01 +0500 Subject: [PATCH 2/6] fix: repair melos's dependency-constraint rewrite bug in release pipelines --- .../scripts/fix_melos_version_rewrite.dart | 120 ++++++++++++++++++ .github/scripts/repair_and_retag.sh | 22 ++++ .github/workflows/publish-ensemble-pubdev.yml | 38 +++++- .github/workflows/release-beta-version.yml | 26 +++- .github/workflows/release-melos-version.yml | 7 +- pubspec.yaml | 1 + .../ensemble_test_runner/example/pubspec.yaml | 2 +- tools/ensemble_test_runner/pubspec.yaml | 2 +- 8 files changed, 204 insertions(+), 14 deletions(-) create mode 100644 .github/scripts/fix_melos_version_rewrite.dart create mode 100755 .github/scripts/repair_and_retag.sh diff --git a/.github/scripts/fix_melos_version_rewrite.dart b/.github/scripts/fix_melos_version_rewrite.dart new file mode 100644 index 000000000..581fa0b3b --- /dev/null +++ b/.github/scripts/fix_melos_version_rewrite.dart @@ -0,0 +1,120 @@ +// melos's `version` command can leave a hosted dependency constraint +// mangled, e.g. `^1.2.50-beta.13 <2.0.0"`, when the original was a compound +// range (">=1.2.50 <2.0.0") rather than a bare caret -- it only replaces the +// leading token of the old text. This repairs that shape across the +// workspace. Anything starting with `^` that doesn't fit it exactly fails +// loudly rather than being guessed at. + +import 'dart:io'; + +import 'package:pub_semver/pub_semver.dart'; + +// Deliberately not restricted to the known corruption's exact tail shape, +// so a variant of it can't slip past silently -- see the fail-loudly branch. +final _caretLine = RegExp( + r'^(?[ \t]*[\w.-]+[ \t]*:[ \t]*)' + r'''(?["']?\^[^\r\n]*)$''', + multiLine: true, +); + +// What melos's rewrite always emits: a caret immediately followed by a +// valid version (pub_semver versions always start with a digit). +final _leadingCaretToken = RegExp(r'^\^[0-9][\w.\-+]*'); + +void main(List args) { + final root = Directory(args.isNotEmpty ? args[0] : '.'); + if (!root.existsSync()) { + stderr.writeln('::error::Root directory not found: ${root.path}'); + exitCode = 1; + return; + } + + final pubspecs = root + .listSync(recursive: true, followLinks: false) + .whereType() + .where((f) => f.path.endsWith('pubspec.yaml')) + .where((f) => !f.path.contains('${Platform.pathSeparator}build${Platform.pathSeparator}')) + .toList() + ..sort((a, b) => a.path.compareTo(b.path)); + + var filesFixed = 0; + var constraintsFixed = 0; + var hadUnrepairable = false; + + for (final file in pubspecs) { + final original = file.readAsStringSync(); + final matches = _caretLine.allMatches(original).toList(); + if (matches.isEmpty) continue; + + var updated = original; + var fileChanged = false; + + for (final match in matches.reversed) { + final rawValue = match.namedGroup('value')!; + final logicalValue = _stripMatchedQuotes(rawValue); + + if (_tryParse(logicalValue) != null) continue; // already valid + + final tokenMatch = _leadingCaretToken.firstMatch(logicalValue); + if (tokenMatch == null || _tryParse(tokenMatch[0]!) == null) { + stderr.writeln( + '::error::${file.path}: found unparseable constraint ' + '"$rawValue" that does not match the known melos corruption ' + 'pattern (valid "^" prefix). Needs manual review.', + ); + hadUnrepairable = true; + continue; + } + + final caretToken = tokenMatch[0]!; + final replacement = '${match.namedGroup('prefix')}$caretToken'; + updated = updated.replaceRange(match.start, match.end, replacement); + fileChanged = true; + constraintsFixed++; + stdout.writeln( + '${file.path}: repaired "$rawValue" -> "$caretToken"', + ); + } + + if (fileChanged) { + file.writeAsStringSync(updated); + filesFixed++; + } + } + + if (hadUnrepairable) { + stderr.writeln( + '::error::One or more constraints looked corrupted but could not be ' + 'safely auto-repaired. Aborting so a human can look.', + ); + exitCode = 1; + return; + } + + if (constraintsFixed == 0) { + stdout.writeln('No corrupted dependency constraints found.'); + } else { + stdout.writeln( + 'Repaired $constraintsFixed constraint(s) across $filesFixed file(s).', + ); + } +} + +String _stripMatchedQuotes(String value) { + if (value.length >= 2) { + final first = value[0]; + final last = value[value.length - 1]; + if ((first == '"' || first == "'") && last == first) { + return value.substring(1, value.length - 1); + } + } + return value; +} + +VersionConstraint? _tryParse(String value) { + try { + return VersionConstraint.parse(value); + } on FormatException { + return null; + } +} diff --git a/.github/scripts/repair_and_retag.sh b/.github/scripts/repair_and_retag.sh new file mode 100755 index 000000000..aff320bd9 --- /dev/null +++ b/.github/scripts/repair_and_retag.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +# melos already committed and tagged the versioned/dependent packages. +# Repair its dependency-constraint rewrite (fix_melos_version_rewrite.dart), +# fold it into that same commit, and re-point whatever tags melos created -- +# rather than hardcode which packages get tagged. Shared by both release +# workflows so they can't drift apart. + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +tags="$(git tag --points-at HEAD)" + +dart pub get +dart run "$script_dir/fix_melos_version_rewrite.dart" + +git add -u +git commit --amend --no-edit + +while IFS= read -r tag; do + [ -n "$tag" ] && git tag -f "$tag" HEAD +done <<< "$tags" diff --git a/.github/workflows/publish-ensemble-pubdev.yml b/.github/workflows/publish-ensemble-pubdev.yml index 21a8373b2..d36bfaeef 100644 --- a/.github/workflows/publish-ensemble-pubdev.yml +++ b/.github/workflows/publish-ensemble-pubdev.yml @@ -4,9 +4,20 @@ on: push: tags: - "ensemble-v*" + workflow_call: + inputs: + ref: + description: "Git tag to publish, e.g. ensemble-v1.2.47 or ensemble-v1.2.47-beta.1" + required: true + type: string + allow_prerelease: + description: "Allow publishing a -beta.N tag as a pub.dev prerelease" + required: false + type: boolean + default: false concurrency: - group: pubdev-ensemble-${{ github.ref_name }} + group: pubdev-ensemble-${{ inputs.ref || github.ref_name }} cancel-in-progress: false permissions: @@ -17,27 +28,38 @@ jobs: publish: name: Publish ensemble runtime runs-on: ubuntu-latest - if: "!contains(github.ref_name, '-beta')" + # The tag-push trigger only ever fires for stable tags (the workflow_call + # path is how a beta prerelease gets opted in, from release-beta-version.yml). + if: "github.event_name == 'workflow_call' || !contains(github.ref_name, '-beta')" outputs: version: ${{ steps.validate-release-tag.outputs.version }} steps: - name: Validate release tag id: validate-release-tag + env: + TAG: ${{ inputs.ref || github.ref_name }} + ALLOW_PRERELEASE: ${{ inputs.allow_prerelease }} run: | set -euo pipefail - if [[ ! "${{ github.ref_name }}" =~ ^ensemble-v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "::error::Expected a stable ensemble tag like ensemble-v1.2.47." + if [[ "$ALLOW_PRERELEASE" == "true" ]]; then + pattern='^ensemble-v[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?$' + else + pattern='^ensemble-v[0-9]+\.[0-9]+\.[0-9]+$' + fi + + if [[ ! "$TAG" =~ $pattern ]]; then + echo "::error::Tag '$TAG' doesn't match the expected format." exit 1 fi - echo "version=${GITHUB_REF_NAME#ensemble-v}" >> "$GITHUB_OUTPUT" + echo "version=${TAG#ensemble-v}" >> "$GITHUB_OUTPUT" - name: Checkout uses: actions/checkout@v4 with: - ref: ${{ github.ref_name }} + ref: ${{ inputs.ref || github.ref_name }} - name: Setup Flutter SDK uses: subosito/flutter-action@v2 @@ -201,7 +223,9 @@ jobs: name: Publish ${{ matrix.package.name }} runs-on: ubuntu-latest needs: publish - if: "!contains(github.ref_name, '-beta')" + # Modules are never versioned/published as part of a beta ensemble + # release, so this only ever runs on the native stable tag-push trigger. + if: "github.event_name != 'workflow_call' && !contains(github.ref_name, '-beta')" strategy: fail-fast: false matrix: diff --git a/.github/workflows/release-beta-version.yml b/.github/workflows/release-beta-version.yml index 57862682a..c44525344 100644 --- a/.github/workflows/release-beta-version.yml +++ b/.github/workflows/release-beta-version.yml @@ -5,8 +5,9 @@ # # Flow: # 1. Checkout branch, detach HEAD -# 2. melos version creates commit + tag on detached HEAD -# 3. Push only the tag (branch unchanged) +# 2. melos version creates commit + tags on detached HEAD +# 3. Repair and re-tag (.github/scripts/repair_and_retag.sh) +# 4. Push only the ensemble-v tag (branch unchanged) name: Release Beta Version @@ -23,6 +24,11 @@ on: required: true type: string default: "1.2.47-beta.1" + publish_to_pubdev: + description: "Also publish this beta to pub.dev as a prerelease" + required: false + type: boolean + default: false concurrency: group: melos-beta-release-${{ github.repository }}-${{ inputs.branch }} @@ -100,11 +106,25 @@ jobs: # Not strictly necessary since we only push the tag, but keeps intent clear git checkout --detach HEAD - # Melos creates version commit + tag on detached HEAD melos version ensemble "${{ inputs.version }}" --yes + - name: Repair melos dependency-constraint rewrite + run: .github/scripts/repair_and_retag.sh + - name: Push tag only run: | # Push only the tag - branch remains unchanged # 'tag' keyword ensures we push a tag, not a branch git push origin tag ensemble-v${{ inputs.version }} + + publish-to-pubdev: + name: Publish beta to pub.dev + needs: version-and-push + if: inputs.publish_to_pubdev + permissions: + contents: read + id-token: write + uses: ./.github/workflows/publish-ensemble-pubdev.yml + with: + ref: ensemble-v${{ inputs.version }} + allow_prerelease: true diff --git a/.github/workflows/release-melos-version.yml b/.github/workflows/release-melos-version.yml index e2849e3a3..33ffcb691 100644 --- a/.github/workflows/release-melos-version.yml +++ b/.github/workflows/release-melos-version.yml @@ -1,6 +1,6 @@ # Automates Releasing a New Version -# melos version ensemble [version] -# git push --follow-tags origin main +# melos version -> repair and re-tag (.github/scripts/repair_and_retag.sh) +# -> push main + tags name: Release Ensemble Version @@ -128,6 +128,9 @@ jobs: --manual-version ensemble_bracket:${{ steps.resolve-version.outputs.version }} \ --yes + - name: Repair melos dependency-constraint rewrite + run: .github/scripts/repair_and_retag.sh + - name: Push branch and tags run: | git push origin main diff --git a/pubspec.yaml b/pubspec.yaml index 810fdbd6e..6531d3b3a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,3 +5,4 @@ environment: dev_dependencies: melos: ^3.4.0 + pub_semver: ^2.1.4 diff --git a/tools/ensemble_test_runner/example/pubspec.yaml b/tools/ensemble_test_runner/example/pubspec.yaml index 3677a4294..badf270e9 100644 --- a/tools/ensemble_test_runner/example/pubspec.yaml +++ b/tools/ensemble_test_runner/example/pubspec.yaml @@ -9,7 +9,7 @@ environment: dependencies: ensemble: hosted: https://pub.dev - version: "^1.2.50" + version: ">=1.2.50 <2.0.0" ensemble_test_runner: path: .. flutter: diff --git a/tools/ensemble_test_runner/pubspec.yaml b/tools/ensemble_test_runner/pubspec.yaml index 420958761..259494536 100644 --- a/tools/ensemble_test_runner/pubspec.yaml +++ b/tools/ensemble_test_runner/pubspec.yaml @@ -19,7 +19,7 @@ environment: flutter: ">=3.24.0" dependencies: - ensemble: "^1.2.50" + ensemble: ">=1.2.50 <2.0.0" device_frame: ^1.3.0 ensemble_device_preview: ^1.1.3 flutter: From 5d47bd7f6138566f15901acea2a098a8fb5c02a4 Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Sun, 23 Aug 2026 15:44:01 +0500 Subject: [PATCH 3/6] fix: fetch release scripts from workflow's own ref, not the branch being released --- .github/workflows/release-beta-version.yml | 8 ++++++++ .github/workflows/release-melos-version.yml | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/.github/workflows/release-beta-version.yml b/.github/workflows/release-beta-version.yml index c44525344..08012724b 100644 --- a/.github/workflows/release-beta-version.yml +++ b/.github/workflows/release-beta-version.yml @@ -83,6 +83,14 @@ jobs: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN }} + # inputs.branch can be any feature branch, often without this workflow's + # own release scripts. Pull them from wherever this workflow file + # itself came from (github.sha), not from the branch being released. + - name: Use release scripts from this workflow's own ref + run: | + git fetch origin ${{ github.sha }} --depth=1 + git checkout ${{ github.sha }} -- .github/scripts + - name: Configure Git run: | git config user.name "github-actions[bot]" diff --git a/.github/workflows/release-melos-version.yml b/.github/workflows/release-melos-version.yml index 33ffcb691..9b2ef0b8c 100644 --- a/.github/workflows/release-melos-version.yml +++ b/.github/workflows/release-melos-version.yml @@ -30,6 +30,14 @@ jobs: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN }} + # If this workflow is ever dispatched against a non-default ref while + # main lags behind, pull the release scripts from wherever this + # workflow file itself came from, not from main. + - name: Use release scripts from this workflow's own ref + run: | + git fetch origin ${{ github.sha }} --depth=1 + git checkout ${{ github.sha }} -- .github/scripts + - name: Configure Git run: | git config user.name "github-actions[bot]" From 552b441c7a74d508f186ddf1e416cd9ab9905230 Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Sun, 23 Aug 2026 16:49:24 +0500 Subject: [PATCH 4/6] fix: preserve original range style and upper bound when repairing --- .../scripts/fix_melos_version_rewrite.dart | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/.github/scripts/fix_melos_version_rewrite.dart b/.github/scripts/fix_melos_version_rewrite.dart index 581fa0b3b..0f5b68c2d 100644 --- a/.github/scripts/fix_melos_version_rewrite.dart +++ b/.github/scripts/fix_melos_version_rewrite.dart @@ -1,9 +1,8 @@ // melos's `version` command can leave a hosted dependency constraint // mangled, e.g. `^1.2.50-beta.13 <2.0.0"`, when the original was a compound -// range (">=1.2.50 <2.0.0") rather than a bare caret -- it only replaces the -// leading token of the old text. This repairs that shape across the -// workspace. Anything starting with `^` that doesn't fit it exactly fails -// loudly rather than being guessed at. +// range rather than a bare caret -- it only replaces the leading token. +// Repairs the syntax without touching the surviving upper bound; anything +// starting with `^` that doesn't fit fails loudly rather than being guessed at. import 'dart:io'; @@ -55,24 +54,41 @@ void main(List args) { if (_tryParse(logicalValue) != null) continue; // already valid - final tokenMatch = _leadingCaretToken.firstMatch(logicalValue); - if (tokenMatch == null || _tryParse(tokenMatch[0]!) == null) { + final tokenMatch = _leadingCaretToken.firstMatch(rawValue); + final tail = tokenMatch == null ? '' : rawValue.substring(tokenMatch.end); + + // A non-empty tail without a real operator (e.g. just a stray quote) + // isn't evidence of a surviving range -- don't invent an unbounded one. + String? fixedValue; + if (tokenMatch != null && + tail.isNotEmpty && + _tryParse(tokenMatch[0]!) != null && + RegExp(r'[<>=]').hasMatch(tail)) { + final quote = tail[tail.length - 1]; + if (quote == '"' || quote == "'") { + final candidate = '$quote>=${tokenMatch[0]!.substring(1)}$tail'; + if (_tryParse(_stripMatchedQuotes(candidate)) != null) { + fixedValue = candidate; + } + } + } + + if (fixedValue == null) { stderr.writeln( '::error::${file.path}: found unparseable constraint ' '"$rawValue" that does not match the known melos corruption ' - 'pattern (valid "^" prefix). Needs manual review.', + 'pattern. Needs manual review.', ); hadUnrepairable = true; continue; } - final caretToken = tokenMatch[0]!; - final replacement = '${match.namedGroup('prefix')}$caretToken'; + final replacement = '${match.namedGroup('prefix')}$fixedValue'; updated = updated.replaceRange(match.start, match.end, replacement); fileChanged = true; constraintsFixed++; stdout.writeln( - '${file.path}: repaired "$rawValue" -> "$caretToken"', + '${file.path}: repaired "$rawValue" -> "$fixedValue"', ); } From 180a2363b16f213e6588becc8ec38c56961495d4 Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Sun, 23 Aug 2026 17:17:10 +0500 Subject: [PATCH 5/6] fix: streamline pub.dev publishing process for beta tags --- .github/workflows/publish-ensemble-pubdev.yml | 63 +++++++++---------- .github/workflows/release-beta-version.yml | 24 ++++--- 2 files changed, 41 insertions(+), 46 deletions(-) diff --git a/.github/workflows/publish-ensemble-pubdev.yml b/.github/workflows/publish-ensemble-pubdev.yml index d36bfaeef..3101a66c8 100644 --- a/.github/workflows/publish-ensemble-pubdev.yml +++ b/.github/workflows/publish-ensemble-pubdev.yml @@ -4,20 +4,9 @@ on: push: tags: - "ensemble-v*" - workflow_call: - inputs: - ref: - description: "Git tag to publish, e.g. ensemble-v1.2.47 or ensemble-v1.2.47-beta.1" - required: true - type: string - allow_prerelease: - description: "Allow publishing a -beta.N tag as a pub.dev prerelease" - required: false - type: boolean - default: false concurrency: - group: pubdev-ensemble-${{ inputs.ref || github.ref_name }} + group: pubdev-ensemble-${{ github.ref_name }} cancel-in-progress: false permissions: @@ -28,49 +17,58 @@ jobs: publish: name: Publish ensemble runtime runs-on: ubuntu-latest - # The tag-push trigger only ever fires for stable tags (the workflow_call - # path is how a beta prerelease gets opted in, from release-beta-version.yml). - if: "github.event_name == 'workflow_call' || !contains(github.ref_name, '-beta')" outputs: version: ${{ steps.validate-release-tag.outputs.version }} steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + fetch-depth: 0 + + # pub.dev only trusts publishes from a workflow run that a genuine git + # tag push triggered, so a beta's opt-in can't be passed through as a + # workflow input -- it has to travel on the tag itself. A beta tag + # only proceeds if release-beta-version.yml annotated it with the + # [publish-to-pubdev] marker; stable tags always proceed. - name: Validate release tag id: validate-release-tag - env: - TAG: ${{ inputs.ref || github.ref_name }} - ALLOW_PRERELEASE: ${{ inputs.allow_prerelease }} run: | set -euo pipefail - if [[ "$ALLOW_PRERELEASE" == "true" ]]; then - pattern='^ensemble-v[0-9]+\.[0-9]+\.[0-9]+(-beta\.[0-9]+)?$' + TAG="${GITHUB_REF_NAME}" + + if [[ "$TAG" =~ ^ensemble-v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "should_publish=true" >> "$GITHUB_OUTPUT" + elif [[ "$TAG" =~ ^ensemble-v[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+$ ]]; then + message="$(git tag -l -n1 --format='%(contents)' "$TAG")" + if [[ "$message" == *"[publish-to-pubdev]"* ]]; then + echo "should_publish=true" >> "$GITHUB_OUTPUT" + else + echo "Beta tag not opted in to pub.dev publishing; skipping." + echo "should_publish=false" >> "$GITHUB_OUTPUT" + fi else - pattern='^ensemble-v[0-9]+\.[0-9]+\.[0-9]+$' - fi - - if [[ ! "$TAG" =~ $pattern ]]; then - echo "::error::Tag '$TAG' doesn't match the expected format." + echo "::error::Unexpected tag '$TAG'." exit 1 fi echo "version=${TAG#ensemble-v}" >> "$GITHUB_OUTPUT" - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ inputs.ref || github.ref_name }} - - name: Setup Flutter SDK + if: steps.validate-release-tag.outputs.should_publish == 'true' uses: subosito/flutter-action@v2 with: flutter-version: "3.32.5" cache: true - name: Setup pub.dev credentials + if: steps.validate-release-tag.outputs.should_publish == 'true' uses: dart-lang/setup-dart@v1 - name: Build pub.dev example from starter + if: steps.validate-release-tag.outputs.should_publish == 'true' run: | set -euo pipefail @@ -216,6 +214,7 @@ jobs: PY - name: Publish to pub.dev + if: steps.validate-release-tag.outputs.should_publish == 'true' working-directory: modules/ensemble run: flutter pub publish --force @@ -223,9 +222,7 @@ jobs: name: Publish ${{ matrix.package.name }} runs-on: ubuntu-latest needs: publish - # Modules are never versioned/published as part of a beta ensemble - # release, so this only ever runs on the native stable tag-push trigger. - if: "github.event_name != 'workflow_call' && !contains(github.ref_name, '-beta')" + if: "!contains(github.ref_name, '-beta')" strategy: fail-fast: false matrix: diff --git a/.github/workflows/release-beta-version.yml b/.github/workflows/release-beta-version.yml index 08012724b..f31f7f72a 100644 --- a/.github/workflows/release-beta-version.yml +++ b/.github/workflows/release-beta-version.yml @@ -7,7 +7,9 @@ # 1. Checkout branch, detach HEAD # 2. melos version creates commit + tags on detached HEAD # 3. Repair and re-tag (.github/scripts/repair_and_retag.sh) -# 4. Push only the ensemble-v tag (branch unchanged) +# 4. If publish_to_pubdev, mark the tag with [publish-to-pubdev] so +# publish-ensemble-pubdev.yml's native tag-push trigger knows to publish +# 5. Push only the ensemble-v tag (branch unchanged) name: Release Beta Version @@ -119,20 +121,16 @@ jobs: - name: Repair melos dependency-constraint rewrite run: .github/scripts/repair_and_retag.sh + # pub.dev only trusts a publish triggered by a genuine tag push, so the + # opt-in can't be passed as a workflow input to that workflow -- it has + # to travel on the tag itself. publish-ensemble-pubdev.yml checks for + # this marker before publishing a beta. + - name: Mark tag for pub.dev publishing + if: inputs.publish_to_pubdev + run: git tag -f -a "ensemble-v${{ inputs.version }}" -m "[publish-to-pubdev]" HEAD + - name: Push tag only run: | # Push only the tag - branch remains unchanged # 'tag' keyword ensures we push a tag, not a branch git push origin tag ensemble-v${{ inputs.version }} - - publish-to-pubdev: - name: Publish beta to pub.dev - needs: version-and-push - if: inputs.publish_to_pubdev - permissions: - contents: read - id-token: write - uses: ./.github/workflows/publish-ensemble-pubdev.yml - with: - ref: ensemble-v${{ inputs.version }} - allow_prerelease: true From 3d72fbc31f466098a897587158e3a1035d642507 Mon Sep 17 00:00:00 2001 From: TheNoumanDev Date: Sun, 23 Aug 2026 17:42:39 +0500 Subject: [PATCH 6/6] fix: include publish-ensemble-pubdev.yml in release-scripts checkout, guard stale-ref overlay --- .github/workflows/release-beta-version.yml | 7 +++---- .github/workflows/release-melos-version.yml | 16 +++++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release-beta-version.yml b/.github/workflows/release-beta-version.yml index f31f7f72a..17431630e 100644 --- a/.github/workflows/release-beta-version.yml +++ b/.github/workflows/release-beta-version.yml @@ -85,13 +85,12 @@ jobs: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN }} - # inputs.branch can be any feature branch, often without this workflow's - # own release scripts. Pull them from wherever this workflow file - # itself came from (github.sha), not from the branch being released. + # inputs.branch may lack these files or have a stale copy; pull both + # from wherever this workflow file itself came from (github.sha). - name: Use release scripts from this workflow's own ref run: | git fetch origin ${{ github.sha }} --depth=1 - git checkout ${{ github.sha }} -- .github/scripts + git checkout ${{ github.sha }} -- .github/scripts .github/workflows/publish-ensemble-pubdev.yml - name: Configure Git run: | diff --git a/.github/workflows/release-melos-version.yml b/.github/workflows/release-melos-version.yml index 9b2ef0b8c..a8888252c 100644 --- a/.github/workflows/release-melos-version.yml +++ b/.github/workflows/release-melos-version.yml @@ -30,13 +30,19 @@ jobs: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN }} - # If this workflow is ever dispatched against a non-default ref while - # main lags behind, pull the release scripts from wherever this - # workflow file itself came from, not from main. + # Only overlay files from github.sha if it's ahead of main -- otherwise + # a stale dispatch ref would silently revert main's own release files. - name: Use release scripts from this workflow's own ref run: | - git fetch origin ${{ github.sha }} --depth=1 - git checkout ${{ github.sha }} -- .github/scripts + set -euo pipefail + # Full history, not --depth=1: merge-base --is-ancestor can't + # correctly resolve ancestry across a shallow fetch boundary. + git fetch origin ${{ github.sha }} + if git merge-base --is-ancestor HEAD ${{ github.sha }}; then + git checkout ${{ github.sha }} -- .github/scripts .github/workflows/publish-ensemble-pubdev.yml + else + echo "::warning::Dispatched ref is not ahead of main; keeping main's own release files." + fi - name: Configure Git run: |