Skip to content

Commit 566bd6a

Browse files
warengonzagaCopilotcodesmith-bot
authored
📦 new(monorepo): add planned package publishing (#41)
* 📦 new(monorepo): add planned package publishing Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * 🐛 fix(monorepo): detect duplicate planned paths after normalization Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com> * 🔧 update(monorepo): ignore inactive planned inputs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
1 parent 099a97e commit 566bd6a

5 files changed

Lines changed: 229 additions & 19 deletions

File tree

.github/workflows/ci.yml

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,124 @@ jobs:
244244
rm -rf "$fixture_dir"
245245
rm -f "$output_file"
246246
247+
planned-monorepo-publishing:
248+
name: Planned Monorepo Publishing
249+
runs-on: ubuntu-latest
250+
251+
steps:
252+
- name: Checkout code
253+
uses: actions/checkout@v6.0.3
254+
255+
- name: Assert planned package versions and aggregate publishing
256+
shell: bash
257+
run: |
258+
set -euo pipefail
259+
260+
run_case() {
261+
local npm_a_result="$1"
262+
local npm_b_result="$2"
263+
local github_a_result="$3"
264+
local github_b_result="$4"
265+
local expected_exit="$5"
266+
local expected_artifact="$6"
267+
local fixture_dir bin_dir output_file build_results actual_exit=0
268+
269+
fixture_dir=$(mktemp -d)
270+
bin_dir=$(mktemp -d)
271+
output_file=$(mktemp)
272+
mkdir -p "$fixture_dir/packages/pkg-a" "$fixture_dir/packages/pkg-b"
273+
printf '%s\n' '{"name":"planned-monorepo-a","version":"0.1.0"}' > "$fixture_dir/packages/pkg-a/package.json"
274+
printf '%s\n' '{"name":"planned-monorepo-b","version":"0.1.0"}' > "$fixture_dir/packages/pkg-b/package.json"
275+
printf '%s\n' '#!/bin/bash' \
276+
'if [ "$1" = "install" ]; then exit 0; fi' \
277+
'if [ "$1" = "publish" ]; then' \
278+
' case "$*" in' \
279+
' *registry.npmjs.org*) case "$PWD" in *pkg-a) exit "${NPM_A_RESULT}" ;; *) exit "${NPM_B_RESULT}" ;; esac ;;' \
280+
' *npm.pkg.github.com*) case "$PWD" in *pkg-a) exit "${GITHUB_A_RESULT}" ;; *) exit "${GITHUB_B_RESULT}" ;; esac ;;' \
281+
' esac' \
282+
'fi' \
283+
'exit 1' > "$bin_dir/npm"
284+
chmod +x "$bin_dir/npm"
285+
286+
PATH="$bin_dir:$PATH" \
287+
GITHUB_CONTEXT='{"event_name":"push","ref_name":"main","sha":"0123456789abcdef"}' \
288+
MAIN_BRANCH="main" \
289+
DEV_BRANCH="dev" \
290+
PACKAGE_PATHS="$fixture_dir/packages/pkg-a/package.json,$fixture_dir/packages/pkg-b/package.json" \
291+
WORKSPACE_DETECTION="false" \
292+
CHANGED_ONLY="false" \
293+
DEPENDENCY_ORDER="false" \
294+
BUILD_SCRIPT="" \
295+
PACKAGE_MANAGER="npm" \
296+
REGISTRY="both" \
297+
NPM_TOKEN="test" \
298+
NPM_REGISTRY_URL="https://registry.npmjs.org" \
299+
GITHUB_TOKEN="test" \
300+
GITHUB_REGISTRY_URL="https://npm.pkg.github.com" \
301+
GITHUB_REPOSITORY_OWNER="wgtechlabs" \
302+
PUBLISH_ENABLED="true" \
303+
DRY_RUN="false" \
304+
ACCESS="public" \
305+
AUDIT_ENABLED="false" \
306+
ACTION_PATH="$GITHUB_WORKSPACE" \
307+
PLANNED_NPM_TAG="rc" \
308+
PLANNED_PACKAGE_VERSIONS='[{"name":"planned-monorepo-a","path":"'"$fixture_dir"'/packages/pkg-a","oldVersion":"0.1.0","version":"1.2.3","bumpType":"minor","tag":"planned-monorepo-a@1.2.3"},{"name":"planned-monorepo-b","path":"'"$fixture_dir"'/packages/pkg-b","oldVersion":"0.1.0","version":"2.3.4","bumpType":"minor","tag":"planned-monorepo-b@2.3.4"}]' \
309+
NPM_A_RESULT="$npm_a_result" \
310+
NPM_B_RESULT="$npm_b_result" \
311+
GITHUB_A_RESULT="$github_a_result" \
312+
GITHUB_B_RESULT="$github_b_result" \
313+
GITHUB_OUTPUT="$output_file" \
314+
bash ./scripts/monorepo-orchestrator.sh || actual_exit=$?
315+
316+
test "$actual_exit" = "$expected_exit"
317+
build_results=$(grep '^build-results=' "$output_file" | cut -d= -f2-)
318+
test "$(grep '^artifact-published=' "$output_file" | tail -1 | cut -d= -f2)" = "$expected_artifact"
319+
MONOREPO_MODE="true" BUILD_RESULTS_JSON="$build_results" CHANGED_PACKAGES_JSON="[]" GITHUB_OUTPUT="$output_file" \
320+
bash ./scripts/generate-outputs.sh
321+
test "$(grep '^artifact-published=' "$output_file" | tail -1 | cut -d= -f2)" = "$expected_artifact"
322+
test "$(jq -r '.version' "$fixture_dir/packages/pkg-a/package.json")" = "1.2.3"
323+
test "$(jq -r '.version' "$fixture_dir/packages/pkg-b/package.json")" = "2.3.4"
324+
325+
rm -rf "$fixture_dir" "$bin_dir"
326+
rm -f "$output_file"
327+
}
328+
329+
run_case 0 1 1 1 0 true
330+
run_case 1 1 1 1 1 false
331+
332+
fixture_dir=$(mktemp -d)
333+
bin_dir=$(mktemp -d)
334+
output_file=$(mktemp)
335+
mkdir -p "$fixture_dir/packages/pkg"
336+
printf '%s\n' '{"name":"planned-monorepo-non-main","version":"0.1.0"}' > "$fixture_dir/packages/pkg/package.json"
337+
printf '%s\n' '#!/bin/bash' 'if [ "$1" = "install" ]; then exit 0; fi' 'exit 1' > "$bin_dir/npm"
338+
chmod +x "$bin_dir/npm"
339+
340+
PATH="$bin_dir:$PATH" \
341+
GITHUB_CONTEXT='{"event_name":"push","ref_name":"dev","sha":"0123456789abcdef"}' \
342+
MAIN_BRANCH="main" \
343+
DEV_BRANCH="dev" \
344+
PACKAGE_PATHS="$fixture_dir/packages/pkg/package.json" \
345+
WORKSPACE_DETECTION="false" \
346+
CHANGED_ONLY="false" \
347+
DEPENDENCY_ORDER="false" \
348+
BUILD_SCRIPT="" \
349+
PACKAGE_MANAGER="npm" \
350+
REGISTRY="npm" \
351+
PUBLISH_ENABLED="false" \
352+
DRY_RUN="false" \
353+
ACCESS="public" \
354+
AUDIT_ENABLED="false" \
355+
ACTION_PATH="$GITHUB_WORKSPACE" \
356+
PLANNED_NPM_TAG="rc" \
357+
PLANNED_PACKAGE_VERSIONS='[{"name":"planned-monorepo-non-main","path":"'"$fixture_dir"'/packages/pkg","version":"1.2.3"}]' \
358+
GITHUB_OUTPUT="$output_file" \
359+
bash ./scripts/monorepo-orchestrator.sh
360+
361+
test "$(jq -r '.version' "$fixture_dir/packages/pkg/package.json")" = "0.1.0-dev.0123456"
362+
rm -rf "$fixture_dir" "$bin_dir"
363+
rm -f "$output_file"
364+
247365
single-package-bun:
248366
name: Single Package Bun
249367
runs-on: ubuntu-latest

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ The action automatically detects the build flow based on GitHub context:
7171

7272
When `commit-convention-enabled: 'true'` blocks a build, the `build-flow-type` output is set to `skip` and `build-skipped` becomes `true`.
7373

74-
For a planned main-branch release candidate, set both `planned-version` and `planned-npm-tag`. The action publishes those values on the main push; a planned publish fails only when every selected registry fails.
74+
For a planned main-branch release candidate, set both `planned-version` and `planned-npm-tag`. The action publishes those values on the main push; a planned publish fails only when every selected registry fails. In monorepo mode, pass the release action's `packages-updated` output as `planned-package-versions` with `planned-npm-tag`; its `{path, version}` entries select and version the planned packages.
7575

7676
### Flow Examples
7777

@@ -182,6 +182,7 @@ Tag: patch
182182
| `version-prefix` | Accepted for backward compatibility but ignored at runtime because npm package versions must remain valid SemVer | - | No |
183183
| `planned-version` | SemVer version to publish for a push to `main`; must be paired with `planned-npm-tag` | - | No |
184184
| `planned-npm-tag` | npm dist-tag to use with `planned-version`; must be paired with `planned-version` | - | No |
185+
| `planned-package-versions` | `packages-updated` JSON from `release-build-flow-action`; in monorepo mode on a push to `main`, each unique `{path, version}` entry is published with `planned-npm-tag` (directory paths and `package.json` paths are accepted; extra release-plan fields are ignored) | - | No |
185186

186187
### Commit Convention Gate Configuration
187188

@@ -243,7 +244,7 @@ Tag: patch
243244
| `short-sha` | Short commit SHA (single-package mode) |
244245
| `npm-published` | Whether published to NPM (true/false) (single-package mode) |
245246
| `github-published` | Whether published to GitHub Packages (true/false) (single-package mode) |
246-
| `artifact-published` | Whether publishing succeeded in at least one registry (true/false) (single-package mode) |
247+
| `artifact-published` | Whether publishing succeeded in at least one configured registry (single package or any monorepo package) |
247248
| `audit-completed` | Whether security audit completed (single-package mode) |
248249
| `total-vulnerabilities` | Total vulnerabilities found (single-package mode) |
249250
| `critical-vulnerabilities` | Critical vulnerabilities count (single-package mode) |

action.yml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,12 @@ inputs:
7373
description: 'Optional npm dist-tag for planned-version; requires planned-version'
7474
required: false
7575
default: ''
76-
76+
77+
planned-package-versions:
78+
description: 'JSON packages-updated output from release-build-flow-action for a planned monorepo publish; requires planned-npm-tag'
79+
required: false
80+
default: ''
81+
7782
# Security Configuration
7883
audit-enabled:
7984
description: 'Enable package-manager-aware security scanning (npm audit or bun audit)'
@@ -197,8 +202,8 @@ outputs:
197202
value: ${{ steps.publish.outputs.github-published }}
198203

199204
artifact-published:
200-
description: 'Whether published to at least one registry (true/false)'
201-
value: ${{ steps.publish.outputs.artifact-published }}
205+
description: 'Whether publishing succeeded in at least one configured registry for the package or monorepo (true/false)'
206+
value: ${{ inputs.monorepo == 'true' && steps.monorepo-orchestrator.outputs.artifact-published || steps.publish.outputs.artifact-published }}
202207

203208
# Security Audit Outputs
204209
audit-completed:
@@ -361,6 +366,8 @@ runs:
361366
AUDIT_ENABLED: ${{ inputs.audit-enabled }}
362367
AUDIT_LEVEL: ${{ inputs.audit-level }}
363368
FAIL_ON_AUDIT: ${{ inputs.fail-on-audit }}
369+
PLANNED_NPM_TAG: ${{ inputs.planned-npm-tag }}
370+
PLANNED_PACKAGE_VERSIONS: ${{ inputs.planned-package-versions }}
364371
ACTION_PATH: ${{ github.action_path }}
365372
BOT_DRY_RUN: ${{ steps.bot-gate.outputs.is-bot }}
366373

scripts/generate-outputs.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ if [ "$MONOREPO_MODE" = "true" ]; then
2727
PACKAGES_PUBLISHED=$(echo "$BUILD_RESULTS_JSON" | jq -r '[.[] | select((.result == "success") and ((.["npm-published"] == "true") or (.["github-published"] == "true"))) | .name] | join(",")')
2828
PACKAGES_FAILED=$(echo "$BUILD_RESULTS_JSON" | jq -r '[.[] | select(.result == "failed") | .name] | join(",")')
2929
TOTAL_PACKAGES=$(echo "$BUILD_RESULTS_JSON" | jq '. | length')
30+
ARTIFACT_PUBLISHED=$(echo "$BUILD_RESULTS_JSON" | jq -r '[.[] | select(.["artifact-published"] == "true")] | any')
3031

3132
# Determine changed packages count:
3233
# - If CHANGED_COUNT is -1, upstream signaled "all packages changed"
@@ -51,6 +52,7 @@ if [ "$MONOREPO_MODE" = "true" ]; then
5152
echo "packages-failed=$PACKAGES_FAILED" >> "$GITHUB_OUTPUT"
5253
echo "total-packages=$TOTAL_PACKAGES" >> "$GITHUB_OUTPUT"
5354
echo "changed-packages-count=$CHANGED_PACKAGES_COUNT" >> "$GITHUB_OUTPUT"
55+
echo "artifact-published=$ARTIFACT_PUBLISHED" >> "$GITHUB_OUTPUT"
5456

5557
echo ""
5658
echo "✅ Monorepo outputs generated"

scripts/monorepo-orchestrator.sh

Lines changed: 96 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,69 @@ else
109109
exit 1
110110
fi
111111

112+
# A release plan names only packages that should be published and supplies
113+
# their immutable versions. The release action's extra fields are ignored.
114+
PLANNED_MONOREPO_PUBLISH=false
115+
if [ -n "${PLANNED_PACKAGE_VERSIONS:-}" ]; then
116+
if [ -z "${PLANNED_NPM_TAG:-}" ]; then
117+
echo "❌ Error: planned-package-versions requires planned-npm-tag"
118+
exit 1
119+
fi
120+
121+
if ! echo "$PLANNED_PACKAGE_VERSIONS" | jq -e 'type == "array" and length > 0 and all(.[]; type == "object" and (.path | type == "string" and length > 0) and (.version | type == "string" and length > 0)) and (map(.path) as $paths | ($paths | length) == ($paths | unique | length))' >/dev/null 2>&1; then
122+
echo "❌ Error: planned-package-versions must be a JSON array of unique {path, version} objects"
123+
exit 1
124+
fi
125+
126+
PLAN_EVENT=$(echo "$GITHUB_CONTEXT" | jq -r '.event_name')
127+
PLAN_REF=$(echo "$GITHUB_CONTEXT" | jq -r '(.ref_name // .ref // "") | sub("^refs/heads/"; "")')
128+
if [ "$PLAN_EVENT" = "push" ] && [ "$PLAN_REF" = "$MAIN_BRANCH" ]; then
129+
declare -A PLANNED_PATHS=()
130+
declare -A PLANNED_VERSIONS=()
131+
PLAN_COUNT=$(echo "$PLANNED_PACKAGE_VERSIONS" | jq 'length')
132+
while IFS=$'\t' read -r planned_path planned_version; do
133+
if [ "$(basename "$planned_path")" = "package.json" ]; then
134+
resolved_path=$(realpath -m "$planned_path")
135+
else
136+
resolved_path=$(realpath -m "$planned_path/package.json")
137+
fi
138+
PLANNED_PATHS["$resolved_path"]=1
139+
PLANNED_VERSIONS["$resolved_path"]="$planned_version"
140+
done < <(echo "$PLANNED_PACKAGE_VERSIONS" | jq -r '.[] | [.path, .version] | @tsv' | tr -d '\r')
141+
142+
# Distinct raw paths can normalize to the same package.json (for example
143+
# "packages/a" and "packages/a/package.json"), so re-check uniqueness here.
144+
if [ "${#PLANNED_PATHS[@]}" -ne "$PLAN_COUNT" ]; then
145+
echo "❌ Error: planned-package-versions contains duplicate package paths after normalization"
146+
exit 1
147+
fi
148+
149+
FILTERED_PACKAGES=()
150+
for package_path in "${PACKAGE_ARRAY[@]}"; do
151+
if [ -n "${PLANNED_PATHS[$(realpath -m "$package_path")]+_}" ]; then
152+
FILTERED_PACKAGES+=("$package_path")
153+
fi
154+
done
155+
if [ "${#FILTERED_PACKAGES[@]}" -ne "$PLAN_COUNT" ]; then
156+
echo "❌ Error: planned-package-versions contains a path that is not configured for this monorepo"
157+
exit 1
158+
fi
159+
160+
PACKAGE_ARRAY=("${FILTERED_PACKAGES[@]}")
161+
TOTAL_PACKAGES=${#PACKAGE_ARRAY[@]}
162+
CHANGED_ONLY="false"
163+
PLANNED_MONOREPO_PUBLISH=true
164+
echo "🎯 Publishing $TOTAL_PACKAGES package(s) from the immutable release plan"
165+
fi
166+
fi
167+
112168
# Initialize results array
113169
BUILD_RESULTS="[]"
114170

115171
# Track success/failure
116172
SUCCESSFUL_PACKAGES=0
117173
FAILED_PACKAGES=0
174+
PUBLISHED_ARTIFACTS=0
118175

119176
echo "📦 Found $TOTAL_PACKAGES package(s) to process"
120177
echo ""
@@ -384,6 +441,14 @@ for i in "${!PACKAGE_ARRAY[@]}"; do
384441

385442
# Set environment for this package
386443
export PACKAGE_PATH
444+
PLANNED_VERSION=""
445+
PLANNED_NPM_TAG_FOR_FLOW=""
446+
if [ "$PLANNED_MONOREPO_PUBLISH" = "true" ]; then
447+
PLANNED_VERSION="${PLANNED_VERSIONS[$(realpath -m "$PACKAGE_PATH")]}"
448+
PLANNED_NPM_TAG_FOR_FLOW="$PLANNED_NPM_TAG"
449+
fi
450+
export PLANNED_VERSION
451+
export PLANNED_NPM_TAG="$PLANNED_NPM_TAG_FOR_FLOW"
387452

388453
# Create a temporary output file for this package's steps
389454
TEMP_OUTPUT=$(mktemp)
@@ -404,6 +469,7 @@ for i in "${!PACKAGE_ARRAY[@]}"; do
404469
if [ -f "$GITHUB_OUTPUT" ]; then
405470
PACKAGE_VERSION=$(grep "^version=" "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2-)
406471
NPM_TAG=$(grep "^npm-tag=" "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2-)
472+
PLANNED_PUBLISH=$(grep "^planned-publish=" "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2-)
407473
fi
408474

409475
# Fallback: parse from temp output if not in GITHUB_OUTPUT
@@ -479,6 +545,7 @@ for i in "${!PACKAGE_ARRAY[@]}"; do
479545
export PACKAGE_VERSION
480546
export NPM_TAG
481547
export DISCOVERED_PACKAGES
548+
export PLANNED_PUBLISH
482549

483550
# Save original GITHUB_OUTPUT and use per-package temp file
484551
ORIGINAL_GITHUB_OUTPUT="$GITHUB_OUTPUT"
@@ -490,16 +557,6 @@ for i in "${!PACKAGE_ARRAY[@]}"; do
490557
RESULT="success"
491558
SUCCESSFUL_PACKAGES=$((SUCCESSFUL_PACKAGES + 1))
492559

493-
# Extract publish status from per-package output file
494-
NPM_PUBLISHED="false"
495-
GITHUB_PUBLISHED="false"
496-
if [ -f "$PACKAGE_OUTPUT" ]; then
497-
NPM_PUBLISHED=$(grep "^npm-published=" "$PACKAGE_OUTPUT" | tail -1 | cut -d= -f2-)
498-
GITHUB_PUBLISHED=$(grep "^github-published=" "$PACKAGE_OUTPUT" | tail -1 | cut -d= -f2-)
499-
# Default to false if grep found nothing
500-
[ -z "$NPM_PUBLISHED" ] && NPM_PUBLISHED="false"
501-
[ -z "$GITHUB_PUBLISHED" ] && GITHUB_PUBLISHED="false"
502-
fi
503560
else
504561
cat "$TEMP_OUTPUT"
505562
echo "❌ Build and publish failed (but continuing with remaining packages)"
@@ -509,6 +566,16 @@ for i in "${!PACKAGE_ARRAY[@]}"; do
509566
NPM_PUBLISHED="false"
510567
GITHUB_PUBLISHED="false"
511568
fi
569+
570+
NPM_PUBLISHED=$(grep "^npm-published=" "$PACKAGE_OUTPUT" | tail -1 | cut -d= -f2- || true)
571+
GITHUB_PUBLISHED=$(grep "^github-published=" "$PACKAGE_OUTPUT" | tail -1 | cut -d= -f2- || true)
572+
ARTIFACT_PUBLISHED=$(grep "^artifact-published=" "$PACKAGE_OUTPUT" | tail -1 | cut -d= -f2- || true)
573+
[ -z "$NPM_PUBLISHED" ] && NPM_PUBLISHED="false"
574+
[ -z "$GITHUB_PUBLISHED" ] && GITHUB_PUBLISHED="false"
575+
[ -z "$ARTIFACT_PUBLISHED" ] && ARTIFACT_PUBLISHED="false"
576+
if [ "$ARTIFACT_PUBLISHED" = "true" ]; then
577+
PUBLISHED_ARTIFACTS=$((PUBLISHED_ARTIFACTS + 1))
578+
fi
512579

513580
# Restore original GITHUB_OUTPUT
514581
export GITHUB_OUTPUT="$ORIGINAL_GITHUB_OUTPUT"
@@ -547,15 +614,17 @@ for i in "${!PACKAGE_ARRAY[@]}"; do
547614
--arg result "$RESULT" \
548615
--arg npm_published "$NPM_PUBLISHED" \
549616
--arg github_published "$GITHUB_PUBLISHED" \
550-
'. += [{"name": $name, "version": $version, "result": $result, "npm-published": $npm_published, "github-published": $github_published}]')
617+
--arg artifact_published "$ARTIFACT_PUBLISHED" \
618+
'. += [{"name": $name, "version": $version, "result": $result, "npm-published": $npm_published, "github-published": $github_published, "artifact-published": $artifact_published}]')
551619
else
552620
BUILD_RESULTS=$(echo "$BUILD_RESULTS" | jq --arg name "$PACKAGE_NAME" \
553621
--arg version "$PACKAGE_VERSION" \
554622
--arg result "$RESULT" \
555623
--arg error "${ERROR_MESSAGE:-Unknown error}" \
556624
--arg npm_published "$NPM_PUBLISHED" \
557625
--arg github_published "$GITHUB_PUBLISHED" \
558-
'. += [{"name": $name, "version": $version, "result": $result, "error": $error, "npm-published": $npm_published, "github-published": $github_published}]')
626+
--arg artifact_published "$ARTIFACT_PUBLISHED" \
627+
'. += [{"name": $name, "version": $version, "result": $result, "error": $error, "npm-published": $npm_published, "github-published": $github_published, "artifact-published": $artifact_published}]')
559628
fi
560629

561630
rm -f "$TEMP_OUTPUT" "$PACKAGE_OUTPUT"
@@ -576,9 +645,22 @@ echo ""
576645

577646
# Set outputs
578647
echo "build-results=$(echo "$BUILD_RESULTS" | jq -c '.')" >> "$GITHUB_OUTPUT"
648+
if [ "$PUBLISHED_ARTIFACTS" -gt 0 ]; then
649+
echo "artifact-published=true" >> "$GITHUB_OUTPUT"
650+
else
651+
echo "artifact-published=false" >> "$GITHUB_OUTPUT"
652+
fi
579653

580-
# Exit with error if any package failed
581-
if [ "$FAILED_PACKAGES" -gt 0 ]; then
654+
# A planned publish succeeds when any configured registry/package destination
655+
# accepts its artifact; every planned destination must fail to fail the run.
656+
if [ "$PLANNED_MONOREPO_PUBLISH" = "true" ] && [ "$PUBLISH_ENABLED" = "true" ] && [ "$DRY_RUN" != "true" ]; then
657+
if [ "$PUBLISHED_ARTIFACTS" -eq 0 ]; then
658+
echo "❌ Planned monorepo publish failed in every selected registry"
659+
exit 1
660+
fi
661+
echo "✅ Planned monorepo publish succeeded in $PUBLISHED_ARTIFACTS package(s)"
662+
exit 0
663+
elif [ "$FAILED_PACKAGES" -gt 0 ]; then
582664
echo "❌ Monorepo build completed with $FAILED_PACKAGES failure(s)"
583665
exit 1
584666
else

0 commit comments

Comments
 (0)