Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,29 @@ what is committed on `main`.
1. `scripts/check-versions.mjs` — aborts if any mirrored version source drifted.
2. `scripts/publish-npm.mjs` — publishes any npm package (`core`, `configs`,
`codemods`) whose committed version is not on the registry yet.
3. `scripts/release-tags.sh` — pushes the namespaced tag (`core-v*`,
`setup-v*`, `starter-v*`) for any mirrored package version that has no tag
yet and dispatches the matching split workflow:
- `core-v*` -> `split-webentor-core.yml` (also pings Packagist)
- `setup-v*` -> `split-webentor-setup.yml`
- `starter-v*` -> `split-webentor-starter.yml`
3. `scripts/release-tags.sh` — for every released package (`core`, `configs`,
`codemods`, `setup`, `starter`; the theme ships inside the starter and has no
tag of its own) ensures three things exist for the committed version:
- **the namespaced tag** (`core-v*`, `configs-v*`, `codemods-v*`, `setup-v*`,
`starter-v*`) if it does not exist yet;
- **a GitHub Release** on that tag, titled with the package's canonical
manifest name (`@webikon/webentor-core@0.15.7`,
`webikon/webentor-setup@1.1.0`) and carrying that version's CHANGELOG
section as its notes;
- **a split-workflow dispatch**, for mirrored packages only:
- `core-v*` -> `split-webentor-core.yml` (also pings Packagist)
- `setup-v*` -> `split-webentor-setup.yml`
- `starter-v*` -> `split-webentor-starter.yml`

The tag and Release checks are independent: a version whose tag exists but
whose Release is missing gets the Release backfilled on the next run. Split
dispatch only fires for a freshly created tag, so mirrors are never
re-pushed. Run with `DRY_RUN=true` to preview.

The Release title deliberately reproduces the `@webikon/webentor-*@*` scheme the
changesets action used before it was removed, so the Releases page reads
continuously across the migration even though the underlying tags use
`<ns>-v<version>`.

No manual tag pushes are needed for a normal release. To re-run a mirror split
manually, dispatch the split workflow with the tag as input, or push the tag
Expand Down
84 changes: 69 additions & 15 deletions scripts/release-tags.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail

# Pushes the namespaced release tag for every mirrored package whose
# committed version is not tagged yet, then dispatches the matching split
# workflow. The dispatch is required because tag pushes made with the
# workflow's GITHUB_TOKEN do not trigger `on: push: tags` workflows.
# For every released package, ensures three things exist for the committed
# version: the namespaced git tag, a GitHub Release carrying that version's
# CHANGELOG section, and (for mirrored packages) a split-workflow dispatch.
#
# The tag keeps the "<ns>-v<version>" form the split workflows key off, while
# the Release is *titled* with the package's canonical manifest name —
# "@webikon/webentor-core@0.15.7" — restoring the scheme the changesets action
# used before it was removed. Nothing else creates Releases, so without this
# the Releases page silently stops updating while tags keep advancing.
#
# The tag and Release checks are independent on purpose: a package whose tag
# already exists but whose Release is missing gets the Release backfilled on the
# next run. Split dispatch stays gated on a freshly created tag so mirrors are
# never re-pushed.
#
# Requires: git remote "origin", gh CLI authenticated (GH_TOKEN in CI).
# Set DRY_RUN=true to only report what would happen.
Expand All @@ -14,33 +24,77 @@ cd "$ROOT_DIR"

DRY_RUN="${DRY_RUN:-false}"

# namespace | version manifest | split workflow
# The theme is intentionally absent: it ships inside webentor-starter and has no
# independent distribution.
#
# namespace | manifest (name + version) | changelog | heading level | split workflow ("-" = not mirrored)
SPECS="
core|packages/webentor-core/package.json|split-webentor-core.yml
setup|packages/webentor-setup/composer.json|split-webentor-setup.yml
starter|packages/webentor-starter/composer.json|split-webentor-starter.yml
core|packages/webentor-core/package.json|packages/webentor-core/CHANGELOG.md|##|split-webentor-core.yml
configs|packages/webentor-configs/package.json|packages/webentor-configs/CHANGELOG.md|##|-
codemods|packages/webentor-codemods/package.json|packages/webentor-codemods/CHANGELOG.md|##|-
setup|packages/webentor-setup/composer.json|packages/webentor-setup/CHANGELOG.md|##|split-webentor-setup.yml
starter|packages/webentor-starter/composer.json|packages/webentor-starter/CHANGELOG.md|###|split-webentor-starter.yml
"

while IFS='|' read -r ns manifest workflow; do
# Prints the CHANGELOG body for version $3 in file $1, whose entries sit at
# heading level $2. Any following heading ends the section — fence tracking keeps
# a "# comment" inside a code block from truncating the notes.
changelog_notes() {
awk -v heading="$2 $3" '
/^```/ { fence = !fence }
$0 == heading { found = 1; next }
found && !fence && /^#/ { exit }
found { print }
' "$1"
}

while IFS='|' read -r ns manifest changelog heading workflow; do
[ -z "$ns" ] && continue

version="$(node -p "require('./${manifest}').version")"
name="$(node -p "require('./${manifest}').name")"
tag="${ns}-v${version}"
title="${name}@${version}"

# --- Tag, plus the split dispatch for mirrored packages ---------------------
if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
echo "${tag} already exists on origin, skipping."
echo "${tag} already exists on origin."
elif [ "$DRY_RUN" = "true" ]; then
if [ "$workflow" = "-" ]; then
echo "[dry-run] Would tag ${tag}."
else
echo "[dry-run] Would tag ${tag} and dispatch ${workflow}."
fi
else
echo "Tagging ${tag}..."
git tag "$tag" 2>/dev/null || echo "Tag ${tag} already exists locally."
git push origin "refs/tags/${tag}"

if [ "$workflow" != "-" ]; then
echo "Dispatching ${workflow}..."
gh workflow run "$workflow" -f tag="$tag"
fi
fi

# --- GitHub Release --------------------------------------------------------
if gh release view "$tag" >/dev/null 2>&1; then
echo "Release ${tag} already exists."
continue
fi

notes="$(changelog_notes "$changelog" "$heading" "$version")"
if [ -z "${notes//[[:space:]]/}" ]; then
echo "No ${heading} ${version} section in ${changelog}, falling back to a pointer."
notes="See [${changelog}](${changelog}) for details."
fi

if [ "$DRY_RUN" = "true" ]; then
echo "[dry-run] Would tag ${tag} and dispatch ${workflow}."
echo "[dry-run] Would create release ${tag} titled \"${title}\"."
continue
fi

echo "Tagging ${tag} and dispatching ${workflow}..."
git tag "$tag" 2>/dev/null || echo "Tag ${tag} already exists locally."
git push origin "refs/tags/${tag}"
gh workflow run "$workflow" -f tag="$tag"
echo "Creating release ${tag} (\"${title}\")..."
printf '%s\n' "$notes" | gh release create "$tag" --title "$title" --notes-file -
done <<EOF
$SPECS
EOF
Expand Down
Loading