diff --git a/.github/scripts/check-versions.py b/.github/scripts/check-versions.py new file mode 100644 index 0000000..844c80e --- /dev/null +++ b/.github/scripts/check-versions.py @@ -0,0 +1,88 @@ +"""Check that SDK versions and documented installation versions agree.""" + +import argparse +from pathlib import Path +import re +from typing import Optional + + +POD_NAMES = ( + "SimlaCore", + "SimlaOnlineConsultant", + "SimlaPushNotificationExtension", + "SimlaPushService", + "SimlaRecommendations", + "SimlaSDKConfiguration", + "SimlaSQLiteStorage", + "SimlaTracker", +) + +NUMBER = r"(?:0|[1-9][0-9]*)" +PRERELEASE = r"(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)" +SEMVER = ( + rf"{NUMBER}\.{NUMBER}\.{NUMBER}" + rf"(?:-{PRERELEASE}(?:\.{PRERELEASE})*)?" + r"(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?" +) + + +def validate(root: Path, tag: Optional[str] = None) -> list[str]: + if tag is not None and re.fullmatch(SEMVER, tag) is None: + return [f"Release tag must be a semantic version without a 'v' prefix; got {tag!r}."] + podspecs = {f"{name}.podspec" for name in POD_NAMES} + podspecs.update(path.name for path in root.glob("*.podspec")) + fields = [ + (name, "podspec version", r"^\s*s\.version\s*=\s*['\"]([^'\"]+)['\"]", False) + for name in sorted(podspecs) + ] + for name, label in ( + ("Sources/Core/Session/SessionRuntime.swift", "SessionRuntime.version"), + ("Sources/PushService/PushService+Version.swift", "PushService.version"), + ): + fields.append((name, label, r'\bstatic\s+let\s+version\s*=\s*"([^"]+)"', False)) + for language in ("en", "ru"): + name = f"docs/{language}/sdk.md" + fields.extend([ + (name, "SPM example", r"Exact Version\s*→\s*([^*\r\n]+)", True), + (name, "CocoaPods example", r":tag\s*=>\s*['\"]([^'\"]+)['\"]", True), + ]) + + errors = [] + versions = [] + for name, label, pattern, multiple in fields: + path = root / name + if not path.is_file(): + errors.append(f"{name}: missing file ({label}).") + continue + content = path.read_text(encoding="utf-8") + matches = list(re.finditer(pattern, content, re.MULTILINE)) + if not matches or (not multiple and len(matches) != 1): + expected = "at least one" if multiple else "exactly one" + errors.append(f"{name}: expected {expected} {label}, found {len(matches)}.") + for match in matches: + version = match.group(1).strip() + line = content.count("\n", 0, match.start(1)) + 1 + versions.append((name, line, label, version)) + + if tag is not None: + errors.extend( + f"{name}:{line}: {label} = {version!r}, expected release tag {tag!r}." + for name, line, label, version in versions if version != tag + ) + elif len({version for _, _, _, version in versions}) > 1: + errors.append("SDK versions do not match:") + errors.extend( + f"{name}:{line}: {label} = {version!r}" + for name, line, label, version in versions + ) + return errors + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--tag', help='Also require every version to match this release tag.') + args = parser.parse_args() + errors = validate(Path(__file__).resolve().parents[2], args.tag) + if errors: + raise SystemExit("Version check failed:\n" + "\n".join(errors)) + print("All podspec, Swift and documented installation versions match.") diff --git a/.github/scripts/prepare-pod-build.rb b/.github/scripts/prepare-sdk-build.rb similarity index 52% rename from .github/scripts/prepare-pod-build.rb rename to .github/scripts/prepare-sdk-build.rb index ff53ff4..d27c9ae 100644 --- a/.github/scripts/prepare-pod-build.rb +++ b/.github/scripts/prepare-sdk-build.rb @@ -1,7 +1,19 @@ require 'fileutils' +require 'optparse' require 'pathname' require 'xcodeproj' +options = { manager: 'pod' } +OptionParser.new do |parser| + parser.on('--manager MANAGER', %w[pod spm]) { |value| options[:manager] = value } + parser.on('--git URL') { |value| options[:git] = value } + parser.on('--tag VERSION') { |value| options[:tag] = value } +end.parse! +abort 'Pass --git and --tag together.' if options.key?(:git) != options.key?(:tag) +if options[:manager] == 'spm' && !options.key?(:git) + abort 'SPM integration builds require --git and --tag.' +end + sdk_path = Pathname.new(ARGV.fetch(0)).expand_path build_path = Pathname.new(ARGV.fetch(1)).expand_path FileUtils.mkdir_p(build_path) @@ -64,31 +76,60 @@ SWIFT app.add_file_references([project.main_group.new_file('App.swift')]) notification_extension.add_file_references([project.main_group.new_file('NotificationService.swift')]) + +if options[:manager] == 'spm' + package = project.new(Xcodeproj::Project::Object::XCRemoteSwiftPackageReference) + package.repositoryURL = options.fetch(:git) + package.requirement = { 'kind' => 'exactVersion', 'version' => options.fetch(:tag) } + project.root_object.package_references << package + { + app => %w[Core OnlineConsultant Tracker Recommendations PushService PushNotificationExtension], + notification_extension => %w[PushNotificationExtension] + }.each do |target, products| + products.each do |name| + product = project.new(Xcodeproj::Project::Object::XCSwiftPackageProductDependency) + product.package = package + product.product_name = name + target.package_product_dependencies << product + link = project.new(Xcodeproj::Project::Object::PBXBuildFile) + link.product_ref = product + target.frameworks_build_phase.files << link + end + end +end + project.save scheme = Xcodeproj::XCScheme.new scheme.add_build_target(app) scheme.set_launch_target(app) scheme.save_as(project.path, 'PodBuild', true) -File.write(build_path.join('Podfile'), <<~PODS) - source 'https://cdn.cocoapods.org/' - platform :ios, '15.0' - use_frameworks! :linkage => :static +if options[:manager] == 'pod' + sdk_source = if options.key?(:git) + { git: options.fetch(:git), tag: options.fetch(:tag) } + else + { path: sdk_path.to_s } + end + File.write(build_path.join('Podfile'), <<~PODS) + source 'https://cdn.cocoapods.org/' + platform :ios, '15.0' + use_frameworks! :linkage => :static - sdk_path = #{sdk_path.to_s.dump} - target 'PodBuild' do - %w[ - SimlaCore SimlaOnlineConsultant SimlaTracker SimlaRecommendations - SimlaPushService SimlaPushNotificationExtension SimlaSDKConfiguration SimlaSQLiteStorage - ].each do |name| - pod name, :path => sdk_path + sdk_source = #{sdk_source.inspect} + target 'PodBuild' do + %w[ + SimlaCore SimlaOnlineConsultant SimlaTracker SimlaRecommendations + SimlaPushService SimlaPushNotificationExtension SimlaSDKConfiguration SimlaSQLiteStorage + ].each do |name| + pod name, sdk_source + end end - end - target 'NotificationService' do - pod 'SimlaSDKConfiguration', :path => sdk_path - pod 'SimlaSQLiteStorage', :path => sdk_path - pod 'SimlaPushNotificationExtension', :path => sdk_path - end -PODS -puts "CocoaPods build project: #{build_path}" + target 'NotificationService' do + pod 'SimlaSDKConfiguration', sdk_source + pod 'SimlaSQLiteStorage', sdk_source + pod 'SimlaPushNotificationExtension', sdk_source + end + PODS +end +puts "#{options[:manager]} integration build project: #{build_path}" diff --git a/.github/scripts/publish-release.sh b/.github/scripts/publish-release.sh new file mode 100644 index 0000000..660c58c --- /dev/null +++ b/.github/scripts/publish-release.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${RELEASE_TAG:?Missing release tag}" +: "${EXPECTED_COMMIT:?Missing verified commit}" +: "${SDK_REPOSITORY_URL:?Missing SDK repository URL}" +: "${GITHUB_REPOSITORY:?Missing GitHub repository}" + +# An annotated tag has its own object ID; compare its peeled commit instead. +remote_commit="$(git ls-remote --exit-code "$SDK_REPOSITORY_URL" \ + "refs/tags/$RELEASE_TAG" "refs/tags/$RELEASE_TAG^{}" | + awk '$2 ~ /\^\{\}$/ { peeled = $1; next } { direct = $1 } END { print peeled ? peeled : direct }')" +if [[ "$remote_commit" != "$EXPECTED_COMMIT" ]]; then + echo "Release tag $RELEASE_TAG no longer points to verified commit $EXPECTED_COMMIT (found $remote_commit)." >&2 + exit 1 +fi + +release_args=(--verify-tag --generate-notes --title "Release $RELEASE_TAG") +version_without_metadata="${RELEASE_TAG%%+*}" +if [[ "$version_without_metadata" == *-* ]]; then + release_args+=(--prerelease --latest=false) +fi + +gh release create "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" "${release_args[@]}" diff --git a/.github/scripts/test_check_versions.py b/.github/scripts/test_check_versions.py new file mode 100644 index 0000000..765dbb5 --- /dev/null +++ b/.github/scripts/test_check_versions.py @@ -0,0 +1,138 @@ +import importlib.util +from pathlib import Path +import tempfile +import unittest + + +spec = importlib.util.spec_from_file_location( + "check_versions", Path(__file__).with_name("check-versions.py") +) +check_versions = importlib.util.module_from_spec(spec) +spec.loader.exec_module(check_versions) + + +class VersionCheckTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + self.files = { + f"{name}.podspec": "Pod::Spec.new do |s|\n s.version = '1.2.3'\nend\n" + for name in ( + "SimlaCore", "SimlaOnlineConsultant", "SimlaPushNotificationExtension", + "SimlaPushService", "SimlaRecommendations", "SimlaSDKConfiguration", + "SimlaSQLiteStorage", "SimlaTracker", + ) + } + self.files.update({ + "Sources/Core/Session/SessionRuntime.swift": + 'nonisolated package static let version = "1.2.3"\n', + "Sources/PushService/PushService+Version.swift": + 'static let version = "1.2.3"\n', + "docs/en/sdk.md": "**Exact Version → 1.2.3**\n:tag => '1.2.3'\n", + "docs/ru/sdk.md": "**Exact Version → 1.2.3**\n:tag => '1.2.3'\n", + }) + for name, content in self.files.items(): + self.write(name, content) + + def write(self, name, content): + path = self.root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + + def test_matching_versions_pass(self): + self.assertEqual(check_versions.validate(self.root), []) + + def test_changing_all_versions_together_passes(self): + for name, content in self.files.items(): + self.write(name, content.replace("1.2.3", "2.0.0")) + self.assertEqual(check_versions.validate(self.root), []) + + def test_each_of_the_fourteen_version_fields_can_fail_independently(self): + for name, content in self.files.items(): + parts = content.split("1.2.3") + for index in range(len(parts) - 1): + with self.subTest(file=name, occurrence=index): + changed = parts[0] + for occurrence, part in enumerate(parts[1:]): + changed += ("1.2.2" if occurrence == index else "1.2.3") + part + self.write(name, changed) + errors = check_versions.validate(self.root) + self.assertIn("SDK versions do not match:", errors) + line = content.count("\n", 0, content.index("1.2.3")) + 1 + if name.startswith("docs/") and index == 1: + line = 2 + self.assertTrue(any( + error.startswith(f"{name}:{line}:") and "'1.2.2'" in error + for error in errors + ), errors) + self.write(name, content) + + def test_missing_required_files_fail(self): + for name, content in self.files.items(): + with self.subTest(file=name): + (self.root / name).unlink() + errors = check_versions.validate(self.root) + self.assertTrue(any( + error.startswith(f"{name}: missing file") for error in errors + ), errors) + self.write(name, content) + + def test_missing_each_documentation_example_fails(self): + for language in ("en", "ru"): + name = f"docs/{language}/sdk.md" + for retained_line in self.files[name].splitlines(keepends=True): + with self.subTest(file=name, retained=retained_line): + self.write(name, retained_line) + errors = check_versions.validate(self.root) + self.assertTrue(any( + error.startswith(name + ":") and "found 0" in error + for error in errors + ), errors) + self.write(name, self.files[name]) + + def test_missing_and_duplicate_constant_fail(self): + name = "Sources/PushService/PushService+Version.swift" + for content, count in (("", 0), (self.files[name] * 2, 2)): + with self.subTest(count=count): + self.write(name, content) + self.assertTrue(any( + error.startswith(name + ":") and f"found {count}" in error + for error in check_versions.validate(self.root) + )) + + def test_additional_examples_are_checked(self): + name = "docs/en/sdk.md" + self.write(name, self.files[name] + ":tag => '1.2.2'\n") + errors = check_versions.validate(self.root) + self.assertIn(f"{name}:3: CocoaPods example = '1.2.2'", errors) + + def test_additional_podspecs_are_checked(self): + self.write("SimlaNewModule.podspec", "s.version = '1.2.2'\n") + self.assertIn( + "SimlaNewModule.podspec:1: podspec version = '1.2.2'", + check_versions.validate(self.root), + ) + + def test_matching_release_tag_passes(self): + self.assertEqual(check_versions.validate(self.root, "1.2.3"), []) + + def test_versions_can_agree_but_mismatch_the_release_tag(self): + errors = check_versions.validate(self.root, "1.2.4") + self.assertEqual(len(errors), 14) + self.assertTrue(all("expected release tag '1.2.4'" in error for error in errors)) + + def test_invalid_release_tag_names_fail(self): + for tag in ("v1.2.3", "1.2", "01.2.3", "1.2.3-01", "release/1.2.3"): + with self.subTest(tag=tag): + self.assertIn("semantic version", check_versions.validate(self.root, tag)[0]) + + def test_prerelease_tag_is_supported(self): + tag = "1.2.3-rc.1" + for name, content in self.files.items(): + self.write(name, content.replace("1.2.3", tag)) + self.assertEqual(check_versions.validate(self.root, tag), []) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/scripts/test_publish_release.py b/.github/scripts/test_publish_release.py new file mode 100644 index 0000000..af9b9c5 --- /dev/null +++ b/.github/scripts/test_publish_release.py @@ -0,0 +1,92 @@ +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + + +SCRIPT = Path(__file__).with_name("publish-release.sh") + + +class PublishReleaseTests(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + tools = self.root / "bin" + tools.mkdir() + self.gh_args = self.root / "gh-args" + stubs = { + "git": '#!/bin/sh\nprintf "%s\\n" "$TEST_REMOTE_REFS"\nexit "$TEST_GIT_EXIT"\n', + "gh": '#!/bin/sh\nprintf "%s\\n" "$@" > "$TEST_GH_ARGS"\nexit "$TEST_GH_EXIT"\n', + } + for name, content in stubs.items(): + path = tools / name + path.write_text(content, encoding="utf-8") + path.chmod(0o755) + self.commit = "1" * 40 + self.env = { + **os.environ, + "PATH": str(tools) + os.pathsep + os.environ["PATH"], + "RELEASE_TAG": "1.2.3", + "EXPECTED_COMMIT": self.commit, + "SDK_REPOSITORY_URL": "https://github.com/example/sdk.git", + "GITHUB_REPOSITORY": "example/sdk", + "TEST_REMOTE_REFS": self.commit + "\trefs/tags/1.2.3", + "TEST_GH_ARGS": str(self.gh_args), + "TEST_GIT_EXIT": "0", + "TEST_GH_EXIT": "0", + } + + def run_publish(self): + return subprocess.run( + ["bash", str(SCRIPT)], env=self.env, capture_output=True, text=True, check=False + ) + + def test_verified_lightweight_tag_publishes_with_expected_arguments(self): + result = self.run_publish() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(self.gh_args.read_text().splitlines(), [ + "release", "create", "1.2.3", "--repo", "example/sdk", + "--verify-tag", "--generate-notes", "--title", "Release 1.2.3", + ]) + + def test_annotated_tag_compares_peeled_commit(self): + self.env["TEST_REMOTE_REFS"] = ( + "2" * 40 + "\trefs/tags/1.2.3\n" + self.commit + "\trefs/tags/1.2.3^{}" + ) + result = self.run_publish() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertTrue(self.gh_args.is_file()) + + def test_changed_tag_does_not_publish(self): + self.env["TEST_REMOTE_REFS"] = "2" * 40 + "\trefs/tags/1.2.3" + result = self.run_publish() + self.assertNotEqual(result.returncode, 0) + self.assertIn("no longer points to verified commit", result.stderr) + self.assertFalse(self.gh_args.exists()) + + def test_missing_remote_tag_does_not_publish(self): + self.env.update(TEST_REMOTE_REFS="", TEST_GIT_EXIT="2") + result = self.run_publish() + self.assertNotEqual(result.returncode, 0) + self.assertFalse(self.gh_args.exists()) + + def test_prerelease_does_not_become_latest(self): + self.env.update( + RELEASE_TAG="1.2.3-rc.1", + TEST_REMOTE_REFS=self.commit + "\trefs/tags/1.2.3-rc.1", + ) + result = self.run_publish() + self.assertEqual(result.returncode, 0, result.stderr) + args = self.gh_args.read_text().splitlines() + self.assertIn("--prerelease", args) + self.assertIn("--latest=false", args) + + def test_publication_failure_is_not_hidden(self): + self.env["TEST_GH_EXIT"] = "7" + self.assertEqual(self.run_publish().returncode, 7) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 598504d..6e8eb06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,15 @@ name: CI on: - push: - branches: [main] pull_request: workflow_dispatch: + workflow_call: permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: @@ -37,6 +36,13 @@ jobs: with: persist-credentials: false + - name: Test SDK version validation + if: ${{ matrix.kind == 'spm' }} + run: python3 -B -m unittest discover -s .github/scripts -p 'test_*.py' -v + + - name: Check SDK version consistency + run: python3 .github/scripts/check-versions.py + - name: Show Xcode version run: xcodebuild -version @@ -106,7 +112,7 @@ jobs: if: ${{ matrix.kind == 'pod' }} run: | pod --version - ruby .github/scripts/prepare-pod-build.rb \ + ruby .github/scripts/prepare-sdk-build.rb \ "$GITHUB_WORKSPACE" "$RUNNER_TEMP/SimlaPodBuild" - name: Install local SDK pods @@ -142,8 +148,8 @@ jobs: ${{ runner.temp }}/pod-build.log hosted_tests: - name: Tests / TestHost - needs: builds + name: Tests / Push notifications (iOS Simulator) + needs: module_tests runs-on: macos-26 timeout-minutes: 20 steps: @@ -177,7 +183,7 @@ jobs: if: ${{ !cancelled() }} uses: actions/upload-artifact@v7 with: - name: tests-TestHost + name: tests-push-notifications-ios-simulator retention-days: 7 if-no-files-found: ignore path: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8eb728d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,153 @@ +name: Release + +on: + push: + tags: ['**'] + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: true + +jobs: + version: + name: Release version + if: ${{ !github.event.deleted }} + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + commit: ${{ steps.source.outputs.commit }} + steps: + - name: Check out tagged commit + uses: actions/checkout@v7 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Verify tag belongs to the default branch + id: source + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + shell: bash + run: | + set -euo pipefail + if ! git merge-base --is-ancestor HEAD "origin/$DEFAULT_BRANCH"; then + echo "Release tag must point to a commit from $DEFAULT_BRANCH." >&2 + exit 1 + fi + echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + - name: Test release validation + run: python3 -B -m unittest discover -s .github/scripts -p 'test_*.py' -v + + - name: Validate SDK versions against tag + env: + RELEASE_TAG: ${{ github.ref_name }} + run: python3 .github/scripts/check-versions.py --tag "$RELEASE_TAG" + + ci: + name: Release CI + needs: version + uses: ./.github/workflows/ci.yml + + integration: + name: Release integration / ${{ matrix.manager }} + needs: version + runs-on: macos-26 + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + manager: [spm, pod] + env: + DEVELOPER_DIR: /Applications/Xcode_26.5.app/Contents/Developer + COCOAPODS_DISABLE_STATS: 'true' + SDK_REPOSITORY_URL: https://github.com/${{ github.repository }}.git + RELEASE_TAG: ${{ github.ref_name }} + PACKAGE_MANAGER: ${{ matrix.manager }} + steps: + - name: Check out tagged commit + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Show tool versions + run: | + xcodebuild -version + pod --version + + - name: Prepare application and extension using the Git tag + run: | + ruby .github/scripts/prepare-sdk-build.rb \ + "$GITHUB_WORKSPACE" "$RUNNER_TEMP/SimlaReleaseBuild" \ + --manager "$PACKAGE_MANAGER" \ + --git "$SDK_REPOSITORY_URL" --tag "$RELEASE_TAG" + + - name: Install CocoaPods dependencies from Git + if: ${{ matrix.manager == 'pod' }} + shell: bash + run: | + set -euo pipefail + cd "$RUNNER_TEMP/SimlaReleaseBuild" + pod install 2>&1 | tee "$RUNNER_TEMP/release-pod-install.log" + + - name: Build application and extension + shell: bash + run: | + set -euo pipefail + build_path="$RUNNER_TEMP/SimlaReleaseBuild" + if [[ "$PACKAGE_MANAGER" == "spm" ]]; then + container=(-project "$build_path/PodBuild.xcodeproj") + else + container=(-workspace "$build_path/PodBuild.xcworkspace") + fi + xcodebuild \ + "${container[@]}" \ + -scheme PodBuild \ + -configuration Release \ + -destination 'generic/platform=iOS' \ + -derivedDataPath "$RUNNER_TEMP/SimlaReleaseDerivedData" \ + -clonedSourcePackagesDirPath "$RUNNER_TEMP/SimlaReleaseSourcePackages" \ + -resultBundlePath "$RUNNER_TEMP/ReleaseIntegration.xcresult" \ + build CODE_SIGNING_ALLOWED=NO \ + 2>&1 | tee "$RUNNER_TEMP/release-build.log" + + - name: Upload integration diagnostics + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v7 + with: + name: release-integration-${{ matrix.manager }} + retention-days: 7 + if-no-files-found: ignore + path: | + ${{ runner.temp }}/release-pod-install.log + ${{ runner.temp }}/release-build.log + ${{ runner.temp }}/ReleaseIntegration.xcresult + ${{ runner.temp }}/SimlaReleaseBuild/Podfile + ${{ runner.temp }}/SimlaReleaseBuild/Podfile.lock + ${{ runner.temp }}/SimlaReleaseBuild/PodBuild.xcodeproj/project.pbxproj + ${{ runner.temp }}/SimlaReleaseBuild/PodBuild.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved + + publish: + name: Publish GitHub Release + needs: [version, ci, integration] + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + steps: + - name: Check out verified commit + uses: actions/checkout@v7 + with: + ref: ${{ needs.version.outputs.commit }} + persist-credentials: false + + - name: Publish release for the verified tag + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.ref_name }} + EXPECTED_COMMIT: ${{ needs.version.outputs.commit }} + SDK_REPOSITORY_URL: https://github.com/${{ github.repository }}.git + run: bash .github/scripts/publish-release.sh diff --git a/SimlaCore.podspec b/SimlaCore.podspec index 6a2c80d..3df8a9b 100644 --- a/SimlaCore.podspec +++ b/SimlaCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SimlaCore' - s.version = '0.1.0' + s.version = '1.0.0' s.summary = 'Shared configuration, sessions and networking for the Simla iOS SDK.' s.homepage = 'https://github.com/simla-tech/MobileSdkIOS' s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } diff --git a/SimlaOnlineConsultant.podspec b/SimlaOnlineConsultant.podspec index e9dcea9..15f5721 100644 --- a/SimlaOnlineConsultant.podspec +++ b/SimlaOnlineConsultant.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SimlaOnlineConsultant' - s.version = '0.1.0' + s.version = '1.0.0' s.summary = 'Chat interface for the Simla iOS SDK.' s.homepage = 'https://github.com/simla-tech/MobileSdkIOS' s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } diff --git a/SimlaPushNotificationExtension.podspec b/SimlaPushNotificationExtension.podspec index 09bef2d..a28e010 100644 --- a/SimlaPushNotificationExtension.podspec +++ b/SimlaPushNotificationExtension.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SimlaPushNotificationExtension' - s.version = '0.1.0' + s.version = '1.0.0' s.summary = 'Notification service extension support for the Simla iOS SDK.' s.homepage = 'https://github.com/simla-tech/MobileSdkIOS' s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } diff --git a/SimlaPushService.podspec b/SimlaPushService.podspec index e48359b..5c662c5 100644 --- a/SimlaPushService.podspec +++ b/SimlaPushService.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SimlaPushService' - s.version = '0.1.0' + s.version = '1.0.0' s.summary = 'Push notification registration and handling for the Simla iOS SDK.' s.homepage = 'https://github.com/simla-tech/MobileSdkIOS' s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } diff --git a/SimlaRecommendations.podspec b/SimlaRecommendations.podspec index 4a7daa5..54e2136 100644 --- a/SimlaRecommendations.podspec +++ b/SimlaRecommendations.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SimlaRecommendations' - s.version = '0.1.0' + s.version = '1.0.0' s.summary = 'Product recommendations for the Simla iOS SDK.' s.homepage = 'https://github.com/simla-tech/MobileSdkIOS' s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } diff --git a/SimlaSDKConfiguration.podspec b/SimlaSDKConfiguration.podspec index aafde6b..aca82bf 100644 --- a/SimlaSDKConfiguration.podspec +++ b/SimlaSDKConfiguration.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SimlaSDKConfiguration' - s.version = '0.1.0' + s.version = '1.0.0' s.summary = 'Shared backend configuration and HTTP observation for the Simla iOS SDK.' s.homepage = 'https://github.com/simla-tech/MobileSdkIOS' s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } diff --git a/SimlaSQLiteStorage.podspec b/SimlaSQLiteStorage.podspec index c983392..2f0a1ac 100644 --- a/SimlaSQLiteStorage.podspec +++ b/SimlaSQLiteStorage.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SimlaSQLiteStorage' - s.version = '0.1.0' + s.version = '1.0.0' s.summary = 'Shared SQLite storage for the Simla iOS SDK.' s.homepage = 'https://github.com/simla-tech/MobileSdkIOS' s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } diff --git a/SimlaTracker.podspec b/SimlaTracker.podspec index f49e4be..6b5c1e3 100644 --- a/SimlaTracker.podspec +++ b/SimlaTracker.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SimlaTracker' - s.version = '0.1.0' + s.version = '1.0.0' s.summary = 'Event tracking and persistent event storage for the Simla iOS SDK.' s.homepage = 'https://github.com/simla-tech/MobileSdkIOS' s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } diff --git a/Sources/Core/Session/SessionRuntime.swift b/Sources/Core/Session/SessionRuntime.swift index 7c37cd6..731df23 100644 --- a/Sources/Core/Session/SessionRuntime.swift +++ b/Sources/Core/Session/SessionRuntime.swift @@ -4,7 +4,7 @@ import Foundation package actor SessionRuntime { /// Semantic version of the connect module bundled with this package. - nonisolated package static let version = "0.1.0" + nonisolated package static let version = "1.0.0" /// Runtime configuration used for connect requests. nonisolated package let config: SessionConfig diff --git a/Sources/PushService/PushService+Version.swift b/Sources/PushService/PushService+Version.swift index 42de1c1..e33e2a8 100644 --- a/Sources/PushService/PushService+Version.swift +++ b/Sources/PushService/PushService+Version.swift @@ -2,5 +2,5 @@ import Foundation public extension PushService { /// Semantic version of the push service module bundled with this package. - static let version = "0.1.0" + static let version = "1.0.0" } diff --git a/TestHost/README.md b/TestHost/README.md index e1edd79..57debfa 100644 --- a/TestHost/README.md +++ b/TestHost/README.md @@ -23,6 +23,6 @@ xcodebuild test \ Тесты выполняются последовательно, поскольку используют общие настройки SDK и системные категории уведомлений. Обычный запуск схемы пакета `ConsultantSdk-Package` сохраняется; сценарии, требующие приложения, в нём пропускаются. -В CI задача `Tests / TestHost` собирает приложение и запускает его тесты на симуляторе. +В CI задача `Tests / Push notifications (iOS Simulator)` собирает приложение и запускает его тесты на симуляторе. Обычные тесты модулей выполняются в отдельных задачах `Tests / <модуль>`. -Логи и результат запуска хостовых тестов сохраняются в артефакте `tests-TestHost`. +Логи и результат запуска хостовых тестов сохраняются в артефакте `tests-push-notifications-ios-simulator`. diff --git a/docs/en/sdk.md b/docs/en/sdk.md index 2502ef2..87f98bc 100644 --- a/docs/en/sdk.md +++ b/docs/en/sdk.md @@ -52,7 +52,7 @@ In Xcode, select **File → Add Package Dependencies** and enter: https://github.com/simla-tech/MobileSdkIOS.git ``` -Select **Exact Version → 0.1.0** for a specific release or **Branch → main** to install from the branch. +Select **Exact Version → 1.0.0** for a specific release or **Branch → main** to install from the branch. Add `Core` and the required SDK products to your application target. For example, chat requires `Core` and `OnlineConsultant`. @@ -79,7 +79,7 @@ This configuration installs the SDK from the `main` branch. To install a specifi ```ruby sdk_source = { :git => 'https://github.com/simla-tech/MobileSdkIOS.git', - :tag => '0.1.0' + :tag => '1.0.0' } ``` diff --git a/docs/ru/sdk.md b/docs/ru/sdk.md index d3f12ef..3cf314c 100644 --- a/docs/ru/sdk.md +++ b/docs/ru/sdk.md @@ -54,7 +54,7 @@ Swift-модуль / CocoaPods - Назначение https://github.com/simla-tech/MobileSdkIOS.git ``` -Выберите **Exact Version → 0.1.0** для конкретного выпуска или **Branch → main** для подключения из ветки. +Выберите **Exact Version → 1.0.0** для конкретного выпуска или **Branch → main** для подключения из ветки. Добавьте продукт `Core` и необходимые продукты SDK к таргету приложения. Например, для чата — `Core` и `OnlineConsultant`. @@ -81,7 +81,7 @@ sdk_source = { ```ruby sdk_source = { :git => 'https://github.com/simla-tech/MobileSdkIOS.git', - :tag => '0.1.0' + :tag => '1.0.0' } ```