diff --git a/.github/version.yml b/.github/version.yml index 8ae5648..118599c 100644 --- a/.github/version.yml +++ b/.github/version.yml @@ -10,3 +10,12 @@ on: - kind: regexp file: setup/action.yml pattern: "(?<=default: ).*" + - kind: regexp + file: manifests/installer.yaml + pattern: "(?<=PackageVersion: ).*" + - kind: regexp + file: manifests/locale.en-US.yaml + pattern: "(?<=PackageVersion: ).*" + - kind: regexp + file: manifests/version.yaml + pattern: "(?<=PackageVersion: ).*" diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index a5d245c..0d71deb 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -168,3 +168,36 @@ jobs: push: false tags: optum/semver-cli:pr platforms: linux/amd64 + + winget-check: + runs-on: windows-latest + permissions: + contents: read + needs: + - check + steps: + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Install Dependencies + run: | + rm deno.lock + deno install --no-lock + + - name: Compile Windows Binary + run: | + deno compile --allow-run --allow-env --allow-read --allow-write -o bin/semver --target x86_64-pc-windows-msvc main.ts + + - name: Test Winget Package Script + shell: bash + run: | + chmod +x scripts/winget-package.sh + ./scripts/winget-package.sh + + - name: Test Winget Publish Script (Dry Run) + shell: bash + run: | + chmod +x scripts/winget-publish.sh + ./scripts/winget-publish.sh --dry-run diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d8e1627..06fd91d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -277,6 +277,57 @@ jobs: --head "${branch_name}" \ --base main + winget: + if: ${{ github.event_name == 'release' && github.event.release.prerelease == false }} + runs-on: windows-latest + needs: + - version + - assets + permissions: + contents: write + pull-requests: write + env: + GH_TOKEN: ${{ secrets.SEMVER_PUBLISH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.SEMVER_PUBLISH_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - name: Configure Git + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + gh auth setup-git + + - name: Package for Winget + env: + VERSION: ${{ needs.version.outputs.version }} + shell: bash + run: | + chmod +x scripts/winget-package.sh + ./scripts/winget-package.sh --release --version "${VERSION}" + + - name: Upload ZIP as Release Asset + shell: bash + run: | + VERSION="${{ needs.version.outputs.version }}" + ZIP_FILE="dist/winget/semver.x86_64-pc-windows-msvc.zip" + + if [ -f "$ZIP_FILE" ]; then + echo "Uploading $ZIP_FILE to release $VERSION" + gh release upload "${VERSION}" "$ZIP_FILE" --repo Optum/semver-cli --clobber + else + echo "Error: ZIP file not found at $ZIP_FILE" + exit 1 + fi + + - name: Publish to Winget + env: + VERSION: ${{ needs.version.outputs.version }} + shell: bash + run: | + chmod +x scripts/winget-publish.sh + ./scripts/winget-publish.sh --release + tags: if: ${{ github.event.release.prerelease == false }} runs-on: ubuntu-latest @@ -285,6 +336,7 @@ jobs: - assets - docker - homebrew + - winget permissions: contents: write id-token: write diff --git a/README.md b/README.md index 2943422..ea5873a 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,14 @@ brew install optum/tap/semver via [optum](https://github.com/Optum/homebrew-tap) +## Winget (Windows Package Manager) + +```sh +winget install Optum.semver-cli +``` + +via [microsoft/winget-pkgs](https://github.com/microsoft/winget-pkgs) + ## From Source Installation from source will require diff --git a/manifests/installer.yaml b/manifests/installer.yaml new file mode 100644 index 0000000..eb8b5d1 --- /dev/null +++ b/manifests/installer.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.6.0.schema.json + +PackageIdentifier: Optum.semver-cli +PackageVersion: __VERSION__ +InstallerType: zip +UpgradeBehavior: install +Commands: +- semver +ReleaseDate: __RELEASE_DATE__ +Installers: +- Architecture: x64 + NestedInstallerType: portable + NestedInstallerFiles: + - RelativeFilePath: semver.exe + PortableCommandAlias: semver + InstallerUrl: __INSTALLER_URL__ + InstallerSha256: __INSTALLER_SHA256__ +ManifestType: installer +ManifestVersion: 1.6.0 diff --git a/manifests/locale.en-US.yaml b/manifests/locale.en-US.yaml new file mode 100644 index 0000000..a151dbc --- /dev/null +++ b/manifests/locale.en-US.yaml @@ -0,0 +1,26 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.6.0.schema.json + +PackageIdentifier: Optum.semver-cli +PackageVersion: __VERSION__ +PackageLocale: en-US +Publisher: Optum +PublisherUrl: https://github.com/Optum +PublisherSupportUrl: https://github.com/Optum/semver-cli/issues +Author: Optum +PackageName: semver-cli +PackageUrl: https://github.com/Optum/semver-cli +License: Apache-2.0 +LicenseUrl: https://github.com/Optum/semver-cli/blob/HEAD/LICENSE +Copyright: Copyright (c) Optum +ShortDescription: A technology agnostic cli for common semantic versioning operations +Description: A technology agnostic cli for common semantic versioning operations. Built with Deno. +Moniker: semver +Tags: +- cli +- semver +- semantic-version +- versioning +- deno +ReleaseNotesUrl: https://github.com/Optum/semver-cli/releases/tag/__VERSION__ +ManifestType: defaultLocale +ManifestVersion: 1.6.0 diff --git a/manifests/version.yaml b/manifests/version.yaml new file mode 100644 index 0000000..c56adf6 --- /dev/null +++ b/manifests/version.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.6.0.schema.json + +PackageIdentifier: Optum.semver-cli +PackageVersion: __VERSION__ +DefaultLocale: en-US +ManifestType: version +ManifestVersion: 1.6.0 diff --git a/scripts/winget-package.sh b/scripts/winget-package.sh new file mode 100755 index 0000000..7f27791 --- /dev/null +++ b/scripts/winget-package.sh @@ -0,0 +1,185 @@ +#!/usr/bin/env bash +set -euo pipefail + +# winget-package.sh - Package semver-cli for winget +# Creates ZIP file and generates winget manifest files + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Default values +RELEASE_MODE=false +VERSION="" +PACKAGE_DIR="${REPO_ROOT}/dist/winget" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --release) + RELEASE_MODE=true + shift + ;; + --version) + VERSION="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--release] [--version VERSION]" + exit 1 + ;; + esac +done + +# Get version if not provided +if [ -z "$VERSION" ]; then + if [ -f "$REPO_ROOT/VERSION" ]; then + VERSION=$(cat "$REPO_ROOT/VERSION") + else + echo "Error: VERSION file not found and --version not provided" + exit 1 + fi +fi + +echo "Packaging semver-cli version ${VERSION} for winget" +echo "Release mode: ${RELEASE_MODE}" + +# Create output directory +mkdir -p "$PACKAGE_DIR" + +# Function to package from local build +package_local() { + echo "Packaging from local build..." + + # Check if binary exists + if [ ! -f "$REPO_ROOT/bin/semver.exe" ]; then + echo "Error: bin/semver.exe not found. Please build the Windows binary first." + exit 1 + fi + + # Create ZIP file + echo "Creating ZIP file..." + cd "$REPO_ROOT/bin" + zip -q "${PACKAGE_DIR}/semver.x86_64-pc-windows-msvc.zip" semver.exe + cd - > /dev/null + + echo "ZIP created: ${PACKAGE_DIR}/semver.x86_64-pc-windows-msvc.zip" +} + +# Function to package from GitHub release +package_release() { + echo "Downloading from GitHub release ${VERSION}..." + + # Download the Windows exe asset + asset_name="semver.x86_64-pc-windows-msvc.tar.gz" + echo "Downloading ${asset_name}..." + + if ! gh release download "${VERSION}" --pattern "${asset_name}" --dir "${PACKAGE_DIR}" --repo Optum/semver-cli 2>/dev/null; then + echo "Error: Failed to download ${asset_name} from release ${VERSION}" + echo "Make sure the release exists and contains the Windows binary" + exit 1 + fi + + # Extract the exe from tar.gz + echo "Extracting exe from tar.gz..." + cd "$PACKAGE_DIR" + tar -xzf "$asset_name" semver.exe + + # Create ZIP file + echo "Creating ZIP file..." + zip -q "semver.x86_64-pc-windows-msvc.zip" semver.exe + + # Clean up + rm -f "$asset_name" semver.exe + cd - > /dev/null + + echo "ZIP created: ${PACKAGE_DIR}/semver.x86_64-pc-windows-msvc.zip" +} + +# Package based on mode +if [ "$RELEASE_MODE" = true ]; then + package_release +else + package_local +fi + +# Calculate SHA256 +echo "Calculating SHA256..." +if command -v sha256sum &> /dev/null; then + SHA256=$(sha256sum "${PACKAGE_DIR}/semver.x86_64-pc-windows-msvc.zip" | cut -d' ' -f1) +elif command -v shasum &> /dev/null; then + SHA256=$(shasum -a 256 "${PACKAGE_DIR}/semver.x86_64-pc-windows-msvc.zip" | cut -d' ' -f1) +else + echo "Error: No SHA256 tool found (sha256sum or shasum)" + exit 1 +fi + +echo "SHA256: ${SHA256}" + +# Determine installer URL +if [ "$RELEASE_MODE" = true ]; then + INSTALLER_URL="https://github.com/Optum/semver-cli/releases/download/${VERSION}/semver.x86_64-pc-windows-msvc.zip" +else + # For local builds, we'll upload the ZIP as an asset later + INSTALLER_URL="__INSTALLER_URL__" +fi + +# Get release date +if [ "$RELEASE_MODE" = true ]; then + # Try to get from GitHub release + RELEASE_DATE=$(gh release view "${VERSION}" --repo Optum/semver-cli --json publishedAt --jq '.publishedAt' 2>/dev/null | cut -d'T' -f1 || date -u +%Y-%m-%d) +else + RELEASE_DATE=$(date -u +%Y-%m-%d) +fi + +echo "Release date: ${RELEASE_DATE}" + +# Generate manifest files +MANIFEST_DIR="${PACKAGE_DIR}/manifests/o/Optum/semver-cli/${VERSION}" +mkdir -p "$MANIFEST_DIR" + +echo "Generating manifest files in ${MANIFEST_DIR}..." + +# Copy and update installer manifest +sed -e "s|__VERSION__|${VERSION}|g" \ + -e "s|__RELEASE_DATE__|${RELEASE_DATE}|g" \ + -e "s|__INSTALLER_URL__|${INSTALLER_URL}|g" \ + -e "s|__INSTALLER_SHA256__|${SHA256}|g" \ + "${REPO_ROOT}/manifests/installer.yaml" > "${MANIFEST_DIR}/Optum.semver-cli.installer.yaml" + +# Copy and update locale manifest +sed -e "s|__VERSION__|${VERSION}|g" \ + "${REPO_ROOT}/manifests/locale.en-US.yaml" > "${MANIFEST_DIR}/Optum.semver-cli.locale.en-US.yaml" + +# Copy and update version manifest +sed -e "s|__VERSION__|${VERSION}|g" \ + "${REPO_ROOT}/manifests/version.yaml" > "${MANIFEST_DIR}/Optum.semver-cli.yaml" + +echo "Manifest files generated:" +ls -la "${MANIFEST_DIR}" + +# Output summary +cat << EOF + +================================================================= +Winget Package Created Successfully +================================================================= +Version: ${VERSION} +Package: ${PACKAGE_DIR}/semver.x86_64-pc-windows-msvc.zip +SHA256: ${SHA256} +Installer URL: ${INSTALLER_URL} +Manifests: ${MANIFEST_DIR} +================================================================= + +EOF + +# Save metadata for publish script +cat > "${PACKAGE_DIR}/metadata.env" << EOF +VERSION=${VERSION} +SHA256=${SHA256} +INSTALLER_URL=${INSTALLER_URL} +RELEASE_DATE=${RELEASE_DATE} +MANIFEST_DIR=${MANIFEST_DIR} +EOF + +echo "Metadata saved to ${PACKAGE_DIR}/metadata.env" diff --git a/scripts/winget-publish.sh b/scripts/winget-publish.sh new file mode 100755 index 0000000..dd26ff7 --- /dev/null +++ b/scripts/winget-publish.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +set -euo pipefail + +# winget-publish.sh - Publish semver-cli to winget repository +# Creates PR to microsoft/winget-pkgs repository + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Default values +DRY_RUN=false +RELEASE_MODE=false +PACKAGE_DIR="${REPO_ROOT}/dist/winget" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --dry-run) + DRY_RUN=true + shift + ;; + --release) + RELEASE_MODE=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--dry-run] [--release]" + exit 1 + ;; + esac +done + +echo "Publishing semver-cli to winget" +echo "Dry run: ${DRY_RUN}" +echo "Release mode: ${RELEASE_MODE}" + +# Load metadata from package step +if [ ! -f "${PACKAGE_DIR}/metadata.env" ]; then + echo "Error: metadata.env not found. Please run winget-package.sh first." + exit 1 +fi + +source "${PACKAGE_DIR}/metadata.env" + +echo "Version: ${VERSION}" +echo "Manifest directory: ${MANIFEST_DIR}" + +# Validate manifests using winget (if available and on Windows) +if command -v winget &> /dev/null; then + echo "Validating manifests with winget..." + if ! winget validate "${MANIFEST_DIR}"; then + echo "::error::Winget manifest validation failed" + exit 1 + fi + echo "✓ Validation successful!" +else + echo "⚠ winget command not available, skipping validation" +fi + +# If dry-run, stop here +if [ "$DRY_RUN" = true ]; then + echo "" + echo "================================================================= " + echo "DRY RUN MODE - Skipping PR creation" + echo "=================================================================" + echo "" + echo "Manifests are ready at: ${MANIFEST_DIR}" + echo "" + echo "To publish for real, run without --dry-run flag" + echo "" + + # Show what would be committed + echo "Files that would be published:" + ls -la "${MANIFEST_DIR}" + + echo "" + echo "Installer manifest:" + cat "${MANIFEST_DIR}/Optum.semver-cli.installer.yaml" + + echo "" + echo "Locale manifest:" + cat "${MANIFEST_DIR}/Optum.semver-cli.locale.en-US.yaml" + + echo "" + echo "Version manifest:" + cat "${MANIFEST_DIR}/Optum.semver-cli.yaml" + + exit 0 +fi + +# Real publish - create PR to microsoft/winget-pkgs +echo "" +echo "Creating PR to microsoft/winget-pkgs repository..." +echo "" + +# Check if gh is available +if ! command -v gh &> /dev/null; then + echo "Error: GitHub CLI (gh) is required for publishing" + exit 1 +fi + +# Clone the winget-pkgs repository +WINGET_REPO_DIR="${PACKAGE_DIR}/winget-pkgs-repo" +if [ -d "$WINGET_REPO_DIR" ]; then + echo "Removing existing winget-pkgs clone..." + rm -rf "$WINGET_REPO_DIR" +fi + +echo "Cloning microsoft/winget-pkgs..." +if ! gh repo clone microsoft/winget-pkgs "$WINGET_REPO_DIR" -- --depth=1; then + echo "Error: Failed to clone microsoft/winget-pkgs" + exit 1 +fi + +cd "$WINGET_REPO_DIR" + +# Create a new branch +BRANCH_NAME="Optum.semver-cli-${VERSION}" +echo "Creating branch: ${BRANCH_NAME}" +git checkout -b "${BRANCH_NAME}" + +# Copy manifests to the winget-pkgs repository +TARGET_DIR="manifests/o/Optum/semver-cli/${VERSION}" +mkdir -p "${TARGET_DIR}" +cp "${MANIFEST_DIR}"/* "${TARGET_DIR}/" + +echo "Copied manifests to ${TARGET_DIR}" + +# Show what will be committed +echo "" +echo "Files to be committed:" +git status --short + +# Commit the changes +git add "${TARGET_DIR}" +git commit -m "New version: Optum.semver-cli version ${VERSION}" + +# Push the branch +echo "Pushing branch to origin..." +if ! git push origin "${BRANCH_NAME}"; then + echo "Error: Failed to push branch" + exit 1 +fi + +# Create the PR +echo "Creating pull request..." + +PR_BODY="This PR adds Optum.semver-cli version ${VERSION} to the Windows Package Manager repository. + +**Package Information:** +- **Package Identifier**: Optum.semver-cli +- **Version**: ${VERSION} +- **Publisher**: Optum +- **License**: Apache-2.0 + +**Changes:** +- Added new package version ${VERSION} +- Installer type: zip (portable) +- Architecture: x64 + +This PR was automatically generated by the semver-cli release workflow. + +**Release Notes**: https://github.com/Optum/semver-cli/releases/tag/${VERSION}" + +if ! gh pr create \ + --repo microsoft/winget-pkgs \ + --title "New version: Optum.semver-cli version ${VERSION}" \ + --body "$PR_BODY" \ + --head "${BRANCH_NAME}" \ + --base master; then + echo "Error: Failed to create pull request" + exit 1 +fi + +echo "" +echo "=================================================================" +echo "✓ Successfully created PR to microsoft/winget-pkgs" +echo "=================================================================" +echo "" +echo "Version ${VERSION} has been submitted to winget!" +echo "" + +cd - > /dev/null