diff --git a/.github/scripts/calculate-package-version.sh b/.github/scripts/calculate-package-version.sh new file mode 100644 index 0000000..a6281ea --- /dev/null +++ b/.github/scripts/calculate-package-version.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +set -euo pipefail + +current_version="${1:-}" +published_versions_file="${2:-}" + +if [[ ! "${current_version}" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then + echo "Expected a stable major.minor.patch source version, found '${current_version}'." >&2 + exit 1 +fi + +if [[ ! -f "${published_versions_file}" ]]; then + echo "Published-version index '${published_versions_file}' does not exist." >&2 + exit 1 +fi + +major="${BASH_REMATCH[1]}" +minor="${BASH_REMATCH[2]}" +source_patch="$((10#${BASH_REMATCH[3]}))" + +published_patch="$( + jq --exit-status --raw-output \ + --arg major "${major}" \ + --arg minor "${minor}" \ + ' + if (.versions | type) != "array" then + error("Expected a versions array in the NuGet version index.") + else + [ + .versions[] + | select(type == "string") + | select(test("^[0-9]+\\.[0-9]+\\.[0-9]+$")) + | split(".") + | select(.[0] == $major and .[1] == $minor) + | .[2] + | tonumber + ] + | max // -1 + end + ' \ + "${published_versions_file}" +)" + +base_patch="${source_patch}" +if (( published_patch > base_patch )); then + base_patch="${published_patch}" +fi + +printf '%s.%s.%s\n' "${major}" "${minor}" "$((base_patch + 1))" diff --git a/.github/scripts/pack-nuget.sh b/.github/scripts/pack-nuget.sh new file mode 100644 index 0000000..5f06045 --- /dev/null +++ b/.github/scripts/pack-nuget.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -euo pipefail + +output_directory="${1:-}" +package_version="${2:-}" +project="Magic.IndexedDb/Magic.IndexedDb.csproj" + +if [[ -z "${output_directory}" ]]; then + echo "Usage: pack-nuget.sh OUTPUT_DIRECTORY [PACKAGE_VERSION]" >&2 + exit 1 +fi + +if [[ -z "${package_version}" ]]; then + package_version="$(sed -nE 's:.*([^<]+).*:\1:p' "${project}")" +fi + +if [[ ! "${package_version}" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then + echo "Expected a stable major.minor.patch package version, found '${package_version}'." >&2 + exit 1 +fi + +dotnet pack "${project}" \ + --configuration Release \ + --no-restore \ + --output "${output_directory}" \ + -p:Version="${package_version}" \ + -p:PackageVersion="${package_version}" \ + -p:ContinuousIntegrationBuild=true \ + -p:GeneratePackageOnBuild=false diff --git a/.github/scripts/test-calculate-package-version.sh b/.github/scripts/test-calculate-package-version.sh new file mode 100644 index 0000000..e438b12 --- /dev/null +++ b/.github/scripts/test-calculate-package-version.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script_directory="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +test_directory="$(mktemp -d)" +trap 'rm -rf "${test_directory}"' EXIT +test_number=0 + +assert_version() { + local current_version="${1}" + local published_versions="${2}" + local expected_version="${3}" + local versions_file="${test_directory}/versions-$((++test_number)).json" + local actual_version + + printf '%s\n' "${published_versions}" > "${versions_file}" + actual_version="$( + bash "${script_directory}/calculate-package-version.sh" \ + "${current_version}" \ + "${versions_file}" + )" + + if [[ "${actual_version}" != "${expected_version}" ]]; then + echo "Expected ${expected_version}, got ${actual_version}." >&2 + exit 1 + fi +} + +assert_failure() { + local current_version="${1}" + local published_versions="${2}" + local versions_file="${test_directory}/versions-$((++test_number)).json" + + printf '%s\n' "${published_versions}" > "${versions_file}" + if bash "${script_directory}/calculate-package-version.sh" \ + "${current_version}" \ + "${versions_file}" > /dev/null 2>&1; then + echo "Expected version calculation to fail for '${current_version}'." >&2 + exit 1 + fi +} + +assert_version "2.0.2" '{"versions":["1.0.12","2.0.1","2.0.2"]}' "2.0.3" +assert_version "2.0.2" '{"versions":["2.0.2","2.0.3"]}' "2.0.4" +assert_version "2.0.7" '{"versions":["2.0.3"]}' "2.0.8" +assert_version "2.1.0" '{"versions":["2.0.99","2.1.0-alpha1"]}' "2.1.1" +assert_version "2.0.2" '{"versions":["2.0.999-alpha1","3.0.0"]}' "2.0.3" +assert_failure "2.0" '{"versions":["2.0.2"]}' +assert_failure "02.0.2" '{"versions":["2.0.2"]}' +assert_failure "2.0.2" '{"unexpected":[]}' + +echo "Package-version tests passed." diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml index a5e088d..bc19518 100644 --- a/.github/workflows/publish-nuget.yml +++ b/.github/workflows/publish-nuget.yml @@ -54,17 +54,22 @@ jobs: shell: bash run: | current_version="$(sed -nE 's:.*([^<]+).*:\1:p' Magic.IndexedDb/Magic.IndexedDb.csproj)" - - if [[ ! "${current_version}" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then - echo "Expected a stable major.minor.patch Version in Magic.IndexedDb.csproj, found '${current_version}'." >&2 - exit 1 - fi - - major="${BASH_REMATCH[1]}" - minor="${BASH_REMATCH[2]}" - patch="${BASH_REMATCH[3]}" - next_patch="$((10#${patch} + GITHUB_RUN_NUMBER))" - package_version="${major}.${minor}.${next_patch}" + published_versions="${RUNNER_TEMP}/magic-indexeddb-versions.json" + curl \ + --fail \ + --silent \ + --show-error \ + --retry 3 \ + --retry-all-errors \ + --connect-timeout 10 \ + --max-time 30 \ + --output "${published_versions}" \ + https://api.nuget.org/v3-flatcontainer/magic.indexeddb/index.json + package_version="$( + bash .github/scripts/calculate-package-version.sh \ + "${current_version}" \ + "${published_versions}" + )" echo "package_version=${package_version}" >> "${GITHUB_OUTPUT}" echo "Publishing Magic.IndexedDb ${package_version} from ${GITHUB_SHA}." @@ -74,13 +79,9 @@ jobs: - name: Pack run: >- - dotnet pack Magic.IndexedDb/Magic.IndexedDb.csproj - --configuration Release - --no-restore - --output artifacts - -p:Version=${{ steps.version.outputs.package_version }} - -p:PackageVersion=${{ steps.version.outputs.package_version }} - -p:ContinuousIntegrationBuild=true + bash .github/scripts/pack-nuget.sh + artifacts + ${{ steps.version.outputs.package_version }} - name: Upload package artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 diff --git a/.github/workflows/validate-dotnet.yml b/.github/workflows/validate-dotnet.yml index 027f6a8..7c283d4 100644 --- a/.github/workflows/validate-dotnet.yml +++ b/.github/workflows/validate-dotnet.yml @@ -39,22 +39,11 @@ jobs: - name: Restore unit tests run: dotnet restore Magic.IndexedDb.UnitTests/Magic.IndexedDb.UnitTests.csproj - - name: Run unit and contract tests - run: >- - dotnet test Magic.IndexedDb.UnitTests/Magic.IndexedDb.UnitTests.csproj - --configuration Release - --no-restore - --logger "trx;LogFileName=core-validation.trx" - --results-directory artifacts/test-results + - name: Test package version calculation + run: bash .github/scripts/test-calculate-package-version.sh - - name: Pack the NuGet artifact - run: >- - dotnet pack Magic.IndexedDb/Magic.IndexedDb.csproj - --configuration Release - --no-restore - --output artifacts/package - -p:ContinuousIntegrationBuild=true - -p:GeneratePackageOnBuild=false + - name: Pack the NuGet artifact from a clean build state + run: bash .github/scripts/pack-nuget.sh artifacts/package - name: Verify package contents shell: bash @@ -66,6 +55,14 @@ jobs: unzip -Z1 "${package}" | grep -Fx wizardHatIcon.png unzip -Z1 "${package}" | grep -F 'staticwebassets/' + - name: Run unit and contract tests + run: >- + dotnet test Magic.IndexedDb.UnitTests/Magic.IndexedDb.UnitTests.csproj + --configuration Release + --no-restore + --logger "trx;LogFileName=core-validation.trx" + --results-directory artifacts/test-results + - name: Upload test results and package if: ${{ always() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md index 1ada523..d5060a2 100644 --- a/docs/contributing/testing.md +++ b/docs/contributing/testing.md @@ -49,6 +49,16 @@ Every pull request runs: Pushes to `master` run the same workflows again. A push to `release` starts `publish-nuget.yml`, which calls both validation workflows and does not build or publish the NuGet package until every validation job succeeds. This makes the release run a final independent gate even when the same commit passed on `master`. +Core validation packages the library before the unit-test build so the package check starts without cached build manifests. Validation and release publishing use the same `.github/scripts/pack-nuget.sh` entry point. + +Release versions are based on the checked-in stable version line and the versions already present on NuGet. The workflow publishes one patch above whichever matching version is newer. Failed or retried workflow runs therefore do not consume or skip package versions. Version calculation fails safely if NuGet's version index cannot be read or validated. + +The version-selection regression tests can be run locally: + +```bash +bash .github/scripts/test-calculate-package-version.sh +``` + The macOS WebKit job is valuable coverage for Apple's browser engine, but Playwright's WebKit build is not the branded Safari application and is not an iPhone or iPad device. Real Safari and iOS device coverage requires a separate device service or owned Apple test hardware; it should be added when credentials and a stable device-testing provider are available. ## Test design rules