diff --git a/.gitea/workflows/release-nuget.yml b/.gitea/workflows/release-nuget.yml index 4551ce4..1bcc6fa 100644 --- a/.gitea/workflows/release-nuget.yml +++ b/.gitea/workflows/release-nuget.yml @@ -4,94 +4,267 @@ on: release: types: [published] workflow_dispatch: + inputs: + package_version: + description: Optional SemVer package version. Defaults to a timestamped dev prerelease. + type: string + required: false + push_to_baget: + description: Push to internal BaGet (nuget.sabp.ir) + type: boolean + default: true + push_to_nuget_org: + description: Also push to nuget.org (manual opt-in only) + type: boolean + default: false env: DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - NUGET_SOURCE_URL: https://nuget.sabp.ir/v3/index.json + HARBOR_REGISTRY: reg.sabp.ir + SDK_IMAGE: reg.sabp.ir/sufi-chain/dotnet-sdk-build:10.0.202 + GIT_HOST: git.sabp.ir + REPOSITORY: ${{ gitea.repository }} + REF_NAME: ${{ gitea.ref_name }} + RELEASE_TAG: ${{ gitea.event.release.tag_name }} + INPUT_PACKAGE_VERSION: ${{ inputs.package_version }} + INPUT_PUSH_TO_BAGET: ${{ inputs.push_to_baget }} + INPUT_PUSH_TO_NUGET_ORG: ${{ inputs.push_to_nuget_org }} + BAGET_SOURCE_URL: https://nuget.sabp.ir/v3/index.json + NUGET_ORG_SOURCE_URL: https://api.nuget.org/v3/index.json NUGET_PUSH_PARALLELISM: 4 PACKAGE_OUTPUT: ./artifacts/nuget PACKAGE_PROJECTS: src/SufiChain.SufiBlazor/SufiChain.SufiBlazor.csproj - # TODO: Add demo packages here when they are ready for release. - RELEASE_TAG: ${{ gitea.event.release.tag_name }} - REF_NAME: ${{ gitea.ref_name }} - REPOSITORY: ${{ gitea.repository }} + ROOT_SLNX: SufiChain.SufiBlazor.slnx + WORKSPACE_DIR: ${{ github.workspace }} + +concurrency: + group: release-nuget-${{ gitea.ref_name }} + cancel-in-progress: false jobs: pack-and-push: runs-on: ubuntu-latest - container: - image: mcr-mirror.liara.ir/dotnet/sdk:10.0.202 - steps: - - name: Checkout - shell: bash + - name: Check runner prerequisites + shell: sh run: | - set -euo pipefail - git clone --depth 1 https://git.sabp.ir/sufi-chain/sufi-blazor.git . + set -eu + if ! command -v docker >/dev/null 2>&1; then + echo "docker CLI is required on the runner." >&2 + echo "Map ubuntu-latest to docker:27-cli and mount /var/run/docker.sock into act_runner." >&2 + exit 1 + fi + if ! docker version >/dev/null 2>&1; then + echo "docker CLI exists, but cannot reach Docker daemon." >&2 + echo "Check Docker service and /var/run/docker.sock mount/permissions for the Gitea runner." >&2 + exit 1 + fi - - name: Set package version - shell: bash + - name: Login to Harbor + shell: sh + env: + HARBOR_USERNAME: ${{ secrets.HARBOR_USERNAME }} + HARBOR_PASSWORD: ${{ secrets.HARBOR_PASSWORD }} run: | - set -euo pipefail + set -eu + if [ -z "${HARBOR_USERNAME:-}" ] || [ -z "${HARBOR_PASSWORD:-}" ]; then + echo "HARBOR_USERNAME and HARBOR_PASSWORD secrets are required." >&2 + exit 1 + fi + echo "$HARBOR_PASSWORD" | docker login "$HARBOR_REGISTRY" \ + -u "$HARBOR_USERNAME" \ + --password-stdin + + - name: Checkout + shell: sh + env: + GITEATOKEN: ${{ secrets.GITEATOKEN }} + run: | + set -eu + workspace="${WORKSPACE_DIR:-${GITHUB_WORKSPACE:-$PWD}}" + cd "$workspace" + echo "Using workspace: $workspace" + + if [ -f "$ROOT_SLNX" ]; then + echo "Repository already checked out by the runner ($ROOT_SLNX)." + exit 0 + fi + + echo "Runner workspace is empty; cloning with SDK container." + CLONE_URL="https://${GIT_HOST}/${REPOSITORY}.git" + if [ -n "${GITEATOKEN:-}" ]; then + CLONE_URL="$(printf '%s' "$CLONE_URL" | sed "s#^https://#https://x-access-token:${GITEATOKEN}@#")" + fi + export CLONE_URL + container_id="$(docker run -d \ + -w /src \ + -e REF_NAME \ + -e CLONE_URL \ + "$SDK_IMAGE" \ + sleep 3600)" + trap 'docker rm -f "$container_id" >/dev/null 2>&1 || true' EXIT INT TERM + docker exec "$container_id" sh -c ' + set -eu + if [ -n "${REF_NAME:-}" ]; then + git clone --depth 1 --branch "$REF_NAME" "$CLONE_URL" /src + else + git clone --depth 1 "$CLONE_URL" /src + fi + test -f "'"$ROOT_SLNX"'" + ' + docker cp "${container_id}:/src/." "${workspace}/" + docker rm -f "$container_id" + trap - EXIT INT TERM + + if [ ! -f "$ROOT_SLNX" ]; then + echo "Checkout did not produce $ROOT_SLNX under $workspace" >&2 + find "$workspace" -maxdepth 2 -type f -name '*.slnx' 2>/dev/null | head -20 >&2 || true + exit 1 + fi + + - name: Resolve package version + id: version + shell: sh + run: | + set -eu if [ -n "${RELEASE_TAG:-}" ]; then version="$RELEASE_TAG" + elif [ -n "${INPUT_PACKAGE_VERSION:-}" ]; then + version="$INPUT_PACKAGE_VERSION" else version="0.0.0-dev.$(date +%Y%m%d%H%M%S)" fi version="${version#v}" - echo "VERSION=$version" >> "$GITHUB_ENV" - echo "Package version: $version" + if ! printf '%s' "$version" | grep -Eq '^[0-9]+([.][0-9]+){2}(-[0-9A-Za-z][0-9A-Za-z.-]*)?([+][0-9A-Za-z][0-9A-Za-z.-]*)?$'; then + echo "Invalid NuGet package version: $version" >&2 + echo "Use SemVer like 1.2.3, 1.2.3-beta.1, or v1.2.3." >&2 + exit 1 + fi + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "Resolved version: $version" - - name: Restore selected projects - shell: bash + - name: Restore, build and pack packages + shell: sh + env: + VERSION: ${{ steps.version.outputs.version }} run: | - set -euo pipefail - for project in $PACKAGE_PROJECTS; do - dotnet restore "$project" \ - --verbosity minimal \ - -p:NuGetAudit=false - done - - - name: Build and pack selected projects - shell: bash - run: | - set -euo pipefail - mkdir -p "$PACKAGE_OUTPUT" - for project in $PACKAGE_PROJECTS; do - dotnet build "$project" \ - --configuration Release \ - --no-restore \ - --verbosity minimal \ - -m \ - -p:PackageVersion="$VERSION" \ - -p:ContinuousIntegrationBuild=true \ - -p:BuildInParallel=true \ - -p:PackageOutputPath="$PWD/$PACKAGE_OUTPUT" \ - -p:GeneratePackageOnBuild=true - done + set -eu + if [ -z "${VERSION:-}" ]; then + echo "Package version was not resolved (steps.version.outputs.version is empty)." >&2 + exit 1 + fi + workspace="${WORKSPACE_DIR:-${GITHUB_WORKSPACE:-$PWD}}" + cd "$workspace" + echo "Using workspace: $workspace" + package_dir="${workspace}/${PACKAGE_OUTPUT#./}" + container_id="$(docker run -d \ + -v "${workspace}:/src" \ + -w /src \ + -e VERSION \ + -e ROOT_SLNX \ + -e PACKAGE_PROJECTS \ + -e DOTNET_NOLOGO \ + -e DOTNET_CLI_TELEMETRY_OPTOUT \ + -e DOTNET_SKIP_FIRST_TIME_EXPERIENCE \ + "$SDK_IMAGE" \ + sleep 3600)" + trap 'docker rm -f "$container_id" >/dev/null 2>&1 || true' EXIT INT TERM + docker exec "$container_id" sh -c ' + set -eu + rm -rf /src/artifacts/nuget + mkdir -p /src/artifacts/nuget + for project in $PACKAGE_PROJECTS; do + dotnet restore "$project" \ + --verbosity minimal \ + -p:NuGetAudit=false + dotnet build "$project" \ + --configuration Release \ + --no-restore \ + --verbosity minimal \ + -m \ + -p:PackageVersion="$VERSION" \ + -p:ContinuousIntegrationBuild=true \ + -p:BuildInParallel=true \ + -p:PackageOutputPath="/src/artifacts/nuget" \ + -p:GeneratePackageOnBuild=true + done + ' + rm -rf "$package_dir" + mkdir -p "$package_dir" + docker cp "${container_id}:/src/artifacts/nuget/." "$package_dir/" + docker rm -f "$container_id" + trap - EXIT INT TERM + package_count="$(find "$package_dir" -type f -name '*.nupkg' ! -name '*.symbols.nupkg' | wc -l | tr -d ' ')" + if [ "$package_count" = "0" ]; then + echo "No NuGet packages were produced in $package_dir." >&2 + exit 1 + fi + echo "Packed $package_count package(s) into $package_dir." - name: Push packages to BaGet - shell: bash + if: ${{ gitea.event_name == 'release' || inputs.push_to_baget == true }} + shell: sh env: NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} run: | - set -euo pipefail + set -eu if [ -z "${NUGET_API_KEY:-}" ]; then - echo "NUGET_API_KEY secret is required." + echo "NUGET_API_KEY secret is required." >&2 exit 1 fi + workspace="${WORKSPACE_DIR:-${GITHUB_WORKSPACE:-$PWD}}" + cd "$workspace" + docker run --rm \ + -v "${workspace}:/src" \ + -w /src \ + -e NUGET_API_KEY \ + -e BAGET_SOURCE_URL \ + -e NUGET_PUSH_PARALLELISM \ + -e PACKAGE_OUTPUT \ + "$SDK_IMAGE" \ + sh -c ' + set -eu + package_dir="/src/${PACKAGE_OUTPUT#./}" + find "$package_dir" -type f -name "*.nupkg" ! -name "*.symbols.nupkg" -print0 | \ + xargs -0 -n 1 -P "$NUGET_PUSH_PARALLELISM" sh -c '\'' + dotnet nuget push "$1" \ + --source "$BAGET_SOURCE_URL" \ + --api-key "$NUGET_API_KEY" \ + --skip-duplicate + '\'' sh + ' - mapfile -t packages < <(find "$PACKAGE_OUTPUT" -type f -name '*.nupkg' ! -name '*.symbols.nupkg' | sort) - if [ ${#packages[@]} -eq 0 ]; then - echo "No NuGet packages were produced." + - name: Push packages to nuget.org + if: ${{ gitea.event_name == 'workflow_dispatch' && inputs.push_to_nuget_org == true && inputs.package_version != '' }} + shell: sh + env: + NUGET_ORG_API_KEY: ${{ secrets.NUGET_ORG_API_KEY }} + run: | + set -eu + if [ -z "${NUGET_ORG_API_KEY:-}" ]; then + echo "NUGET_ORG_API_KEY secret is required for nuget.org push." >&2 exit 1 fi - - printf '%s\0' "${packages[@]}" | xargs -0 -n 1 -P "$NUGET_PUSH_PARALLELISM" sh -c ' - dotnet nuget push "$1" \ - --source "$NUGET_SOURCE_URL" \ - --api-key "$NUGET_API_KEY" \ - --skip-duplicate - ' sh + workspace="${WORKSPACE_DIR:-${GITHUB_WORKSPACE:-$PWD}}" + cd "$workspace" + docker run --rm \ + -v "${workspace}:/src" \ + -w /src \ + -e NUGET_ORG_API_KEY \ + -e NUGET_ORG_SOURCE_URL \ + -e NUGET_PUSH_PARALLELISM \ + -e PACKAGE_OUTPUT \ + "$SDK_IMAGE" \ + sh -c ' + set -eu + package_dir="/src/${PACKAGE_OUTPUT#./}" + find "$package_dir" -type f -name "*.nupkg" ! -name "*.symbols.nupkg" -print0 | \ + xargs -0 -n 1 -P "$NUGET_PUSH_PARALLELISM" sh -c '\'' + dotnet nuget push "$1" \ + --source "$NUGET_ORG_SOURCE_URL" \ + --api-key "$NUGET_ORG_API_KEY" \ + --skip-duplicate + '\'' sh + ' diff --git a/Directory.Build.props b/Directory.Build.props index a99abd8..86c719a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,10 +1,9 @@ - - + Sufi Chain Intelligent Solutions - SufiChain Team + Sufi Chain Team SufiBlazor Copyright © Sufi Chain Intelligent Solutions $([System.DateTime]::Now.Year) https://git.sabp.ir/sufi-chain/sufi-blazor diff --git a/NuGet.Config b/NuGet.Config index cb582e9..f0cd68a 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,11 +1,7 @@ - + - - - - - - - - + + + + diff --git a/SufiChain.SufiBlazor.slnx b/SufiChain.SufiBlazor.slnx index 163e89e..080701f 100644 --- a/SufiChain.SufiBlazor.slnx +++ b/SufiChain.SufiBlazor.slnx @@ -1,7 +1,9 @@ + + - + diff --git a/common.props b/common.props index 0ba9147..b97a0ef 100644 --- a/common.props +++ b/common.props @@ -8,8 +8,8 @@ - - + + All runtime; build; native; contentfiles; analyzers diff --git a/src/SufiChain.SufiBlazor.Demo.Localization/SufiChain.SufiBlazor.Demo.Localization.csproj b/src/SufiChain.SufiBlazor.Demo.Localization/SufiChain.SufiBlazor.Demo.Localization.csproj index 9a78657..e59c4ee 100644 --- a/src/SufiChain.SufiBlazor.Demo.Localization/SufiChain.SufiBlazor.Demo.Localization.csproj +++ b/src/SufiChain.SufiBlazor.Demo.Localization/SufiChain.SufiBlazor.Demo.Localization.csproj @@ -1,4 +1,4 @@ - + latest @@ -16,13 +16,13 @@ - - - + + + - + diff --git a/src/SufiChain.SufiBlazor.Demo/SufiChain.SufiBlazor.Demo.csproj b/src/SufiChain.SufiBlazor.Demo/SufiChain.SufiBlazor.Demo.csproj index 49194c5..51fddf5 100644 --- a/src/SufiChain.SufiBlazor.Demo/SufiChain.SufiBlazor.Demo.csproj +++ b/src/SufiChain.SufiBlazor.Demo/SufiChain.SufiBlazor.Demo.csproj @@ -20,11 +20,12 @@ + + - diff --git a/src/SufiChain.SufiBlazor/SufiChain.SufiBlazor.csproj b/src/SufiChain.SufiBlazor/SufiChain.SufiBlazor.csproj index da41966..5869552 100644 --- a/src/SufiChain.SufiBlazor/SufiChain.SufiBlazor.csproj +++ b/src/SufiChain.SufiBlazor/SufiChain.SufiBlazor.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -22,8 +22,8 @@ - - + + diff --git a/versions.props b/versions.props deleted file mode 100644 index 4788f51..0000000 --- a/versions.props +++ /dev/null @@ -1,20 +0,0 @@ - - - - latest - - - - 0.0.0-alpha.1.0 - 10.3.0 - 10.0.2 - 10.0.2 - 10.0.2 - - - - 3.3.1 - 6.5.3 - - -