Template
ci: npm release archives from source + two zips (Refs #35) #38
@@ -3,8 +3,8 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# MCP-specific release pipeline that builds TypeScript, runs validation,
|
||||
# attaches the compiled dist/ as a release artifact, and creates a GitHub
|
||||
# Release with tool inventory in the release notes.
|
||||
# attaches a full-repository zip and a source-directory zip as release
|
||||
# artifacts, and creates a GitHub Release with tool inventory in the notes.
|
||||
#
|
||||
# This replaces the generic auto-release.yml for MCP server repos.
|
||||
|
||||
@@ -15,6 +15,9 @@ on:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
# Transitional: match both the new 'source/' layout and legacy 'src/'.
|
||||
# This static YAML filter cannot use the resolve-source-dir output.
|
||||
- 'source/**'
|
||||
- 'src/**'
|
||||
- 'package.json'
|
||||
- 'tsconfig.json'
|
||||
@@ -51,6 +54,18 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
# ── Resolve source directory ─────────────────────────────────────
|
||||
# Resolves the canonical source dir (e.g. 'source/' or 'src/') via the
|
||||
# shared composite action. Its 'source_dir' output has a trailing slash.
|
||||
# Note: the action lives on feature/ci-resolve-source-dir; this path
|
||||
# resolves once that branch merges.
|
||||
- name: Resolve source directory
|
||||
id: srcdir
|
||||
uses: ./.mokogit/actions/resolve-source-dir
|
||||
with:
|
||||
mokogit_token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||
strict: 'true'
|
||||
|
||||
- name: TypeScript compile check
|
||||
run: npx tsc --noEmit
|
||||
|
||||
@@ -68,17 +83,30 @@ jobs:
|
||||
- name: Generate tool inventory
|
||||
id: tools
|
||||
run: |
|
||||
TOOL_COUNT=$(grep -c "server\.tool(" src/index.ts || echo "0")
|
||||
# Resolve the entrypoint from the source dir output, fall back to
|
||||
# legacy src/index.ts if the resolved file is missing.
|
||||
SRC="${{ steps.srcdir.outputs.source_dir }}"
|
||||
INDEX="${SRC}index.ts"
|
||||
if [ ! -f "$INDEX" ]; then
|
||||
echo "::warning::resolved entrypoint $INDEX missing; falling back to src/index.ts"
|
||||
INDEX="src/index.ts"
|
||||
fi
|
||||
|
||||
# Fail loud on an unreadable entry file so it isn't silently
|
||||
# counted as 0 tools.
|
||||
[ -r "$INDEX" ] || { echo "::error::entry file unreadable"; exit 1; }
|
||||
|
||||
TOOL_COUNT=$(grep -c "server\.tool(" "$INDEX" || echo "0")
|
||||
echo "count=${TOOL_COUNT}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Extract tool names
|
||||
TOOL_LIST=$(grep -oE "'[a-z_]+'" src/index.ts | head -100 | tr -d "'" | sort -u)
|
||||
TOOL_LIST=$(grep -oE "'[a-z_]+'" "$INDEX" | head -100 | tr -d "'" | sort -u)
|
||||
echo "Tools registered: ${TOOL_COUNT}"
|
||||
|
||||
# Generate inventory for release notes
|
||||
echo "## Tool Inventory (${TOOL_COUNT} tools)" > /tmp/tool-inventory.md
|
||||
echo "" >> /tmp/tool-inventory.md
|
||||
grep -B0 -A1 "server\.tool(" src/index.ts | grep -oE "'[^']+'" | while IFS= read -r name; do
|
||||
grep -B0 -A1 "server\.tool(" "$INDEX" | grep -oE "'[^']+'" | while IFS= read -r name; do
|
||||
read -r desc 2>/dev/null || true
|
||||
CLEAN_NAME=$(echo "$name" | tr -d "'")
|
||||
CLEAN_DESC=$(echo "$desc" | tr -d "'" | sed 's/,$//')
|
||||
@@ -139,15 +167,6 @@ jobs:
|
||||
git rev-parse "$TAG" >/dev/null 2>&1 && TAG_EXISTS=true
|
||||
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# ── Release Artifact ─────────────────────────────────────────────
|
||||
- name: Package dist
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
REPO_NAME="${{ github.event.repository.name }}"
|
||||
tar -czf "/tmp/${REPO_NAME}-${VERSION}.tar.gz" -C dist .
|
||||
echo "artifact=/tmp/${REPO_NAME}-${VERSION}.tar.gz" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# ── Version Updates ──────────────────────────────────────────────
|
||||
- name: Set platform version
|
||||
if: >-
|
||||
@@ -208,6 +227,44 @@ jobs:
|
||||
git push origin "$TAG"
|
||||
fi
|
||||
|
||||
# ── Release Artifacts ────────────────────────────────────────────
|
||||
# Built AFTER the release commit/tag so both zips reflect the final
|
||||
# released tree. Mirrors the Joomla two-artifact model:
|
||||
# 1. full-repo zip — tracked files only (git archive excludes
|
||||
# .git/node_modules/dist automatically).
|
||||
# 2. source zip — just the resolved source subtree. The git
|
||||
# tree-ish path must NOT have a trailing slash, hence ${SRC%/}.
|
||||
- name: Package release artifacts
|
||||
id: package
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
REPO_NAME="${{ github.event.repository.name }}"
|
||||
|
||||
REPO_ZIP="/tmp/${REPO_NAME}-${VERSION}.zip"
|
||||
SOURCE_ZIP="/tmp/${REPO_NAME}-${VERSION}-source.zip"
|
||||
|
||||
# Full-repository "standard" zip (tracked files only).
|
||||
git archive --format=zip -o "$REPO_ZIP" HEAD
|
||||
|
||||
# Source-directory subtree zip.
|
||||
SRC="${{ steps.srcdir.outputs.source_dir }}"
|
||||
SRC="${SRC%/}"
|
||||
if [ -z "$SRC" ] || [ "$SRC" = "." ]; then
|
||||
echo "::error::source_dir did not resolve to a real subdirectory (got '${SRC}'); refusing to build a misleading source zip"
|
||||
exit 1
|
||||
fi
|
||||
git archive --format=zip -o "$SOURCE_ZIP" "HEAD:${SRC}"
|
||||
|
||||
# Fail closed if either archive is missing or empty.
|
||||
for z in "$REPO_ZIP" "$SOURCE_ZIP"; do
|
||||
[ -s "$z" ] || { echo "::error::archive $z missing or empty"; exit 1; }
|
||||
done
|
||||
|
||||
echo "repo_zip=${REPO_ZIP}" >> "$GITHUB_OUTPUT"
|
||||
echo "source_zip=${SOURCE_ZIP}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: GitHub Release
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
@@ -215,6 +272,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
MAJOR="${{ steps.version.outputs.major }}"
|
||||
@@ -223,8 +281,8 @@ jobs:
|
||||
REPO_NAME="${{ github.event.repository.name }}"
|
||||
|
||||
# Build release notes
|
||||
NOTES=$(php /tmp/mokostandards/api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
||||
NOTES=$(php /tmp/mokostandards/api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null || true)
|
||||
if [ -z "$NOTES" ]; then NOTES="Release ${VERSION}"; fi
|
||||
|
||||
{
|
||||
echo "$NOTES"
|
||||
@@ -241,20 +299,22 @@ jobs:
|
||||
|
||||
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
||||
|
||||
ARTIFACT="/tmp/${REPO_NAME}-${VERSION}.tar.gz"
|
||||
# Two artifacts: full-repo zip and source zip (from Package step).
|
||||
REPO_ZIP="${{ steps.package.outputs.repo_zip }}"
|
||||
SOURCE_ZIP="${{ steps.package.outputs.source_zip }}"
|
||||
|
||||
if [ -z "$EXISTING" ]; then
|
||||
gh release create "$RELEASE_TAG" \
|
||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||
--notes-file /tmp/release_notes.md \
|
||||
--target "$BRANCH" \
|
||||
"$ARTIFACT"
|
||||
"$REPO_ZIP" "$SOURCE_ZIP"
|
||||
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
gh release edit "$RELEASE_TAG" \
|
||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||
--notes-file /tmp/release_notes.md
|
||||
gh release upload "$RELEASE_TAG" "$ARTIFACT" --clobber 2>/dev/null || true
|
||||
gh release upload "$RELEASE_TAG" "$REPO_ZIP" "$SOURCE_ZIP" --clobber
|
||||
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
|
||||
@@ -45,6 +45,17 @@ env:
|
||||
GIT_REPO: ${{ vars.MOKOGIT_REPO || github.event.repository.name }}
|
||||
|
||||
jobs:
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
# NOTE (npm/MCP repos): This is the PHP/Joomla pre-release pipeline. It is
|
||||
# NOT a Node-aware release path — it has no setup-node / npm ci / npm build.
|
||||
# npm and MCP repos MUST NOT rely on this workflow for releasing; they use
|
||||
# 'npm-auto-release.yml' instead. The "Check platform eligibility" step
|
||||
# already sets proceed=false for non-Joomla platforms, so the PHP build
|
||||
# steps are skipped for npm repos. In addition, the PHP toolchain install
|
||||
# below is guarded on the ABSENCE of package.json so npm repos don't pull
|
||||
# in php-cli/composer. TODO(#35): once resolve-source-dir lands everywhere,
|
||||
# split this into platform-specific reusable workflows.
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
build:
|
||||
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
|
||||
runs-on: release
|
||||
@@ -76,7 +87,11 @@ jobs:
|
||||
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||
else
|
||||
echo Falling back to fresh clone
|
||||
if ! command -v composer > /dev/null 2>&1; then
|
||||
# Skip the PHP/composer toolchain install for npm/MCP repos
|
||||
# (detected via package.json). Those repos release through
|
||||
# npm-auto-release.yml and the Joomla-only steps below are gated
|
||||
# off via the eligibility check, so the toolchain is unneeded.
|
||||
if [ ! -f package.json ] && ! command -v composer > /dev/null 2>&1; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
|
||||
fi
|
||||
rm -rf /tmp/mokocli
|
||||
@@ -86,8 +101,23 @@ jobs:
|
||||
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
# Early clean-exit for npm/MCP repos: this is the Joomla pre-release
|
||||
# pipeline and does not apply. Detecting here (before any php call)
|
||||
# avoids invoking platform_detect.php on runners where php may be
|
||||
# absent (npm repos take the fresh-clone path that skips the PHP
|
||||
# toolchain install).
|
||||
- name: Detect npm/MCP repo
|
||||
id: npmcheck
|
||||
run: |
|
||||
if [ -f package.json ]; then
|
||||
echo "is_npm=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "is_npm=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
if: steps.npmcheck.outputs.is_npm != 'true'
|
||||
run: |
|
||||
# Auto-detect and update platform if not set in manifest
|
||||
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
|
||||
@@ -96,7 +126,18 @@ jobs:
|
||||
- name: Check platform eligibility (Joomla only)
|
||||
id: eligibility
|
||||
run: |
|
||||
# npm/MCP repos: Joomla pre-release does not apply — clean exit.
|
||||
if [ "${{ steps.npmcheck.outputs.is_npm }}" = "true" ]; then
|
||||
echo "proceed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::notice::npm/MCP repo — pre-release (Joomla) not applicable; use npm-auto-release.yml"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
# Do not silently no-op an eligible Joomla repo if detection failed.
|
||||
if [ -z "$PLATFORM" ]; then
|
||||
echo "::warning::platform detection returned empty"
|
||||
fi
|
||||
if [[ "$PLATFORM" == joomla* ]] || [[ "$PLATFORM" == "joomla" ]]; then
|
||||
echo "proceed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user