Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 70 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ name: CI

on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:
Expand All @@ -11,14 +10,15 @@ concurrency:
group: ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read
permissions: {}

jobs:
quality:
name: Quality
runs-on: macos-26
timeout-minutes: 15
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -37,16 +37,24 @@ jobs:
unzip -j "${archive}" swiftlint -d "${RUNNER_TEMP}/swiftlint"
echo "${RUNNER_TEMP}/swiftlint" >> "${GITHUB_PATH}"
- name: Check Swift formatting
run: swift format lint --recursive --strict Sources Tests Package.swift
run: swift format lint --recursive --strict App Sources Tests Package.swift
- name: Run SwiftLint
run: |
test "$(swiftlint version)" = "0.65.0"
swiftlint lint --strict
- name: Test conventional subjects
run: bash Tests/Scripts/run-title-tests.sh
- name: Test version consistency
run: |
bash Tests/Scripts/run-version-consistency-tests.sh
bash scripts/check-version-consistency.sh

tests:
name: Tests
runs-on: macos-26
timeout-minutes: 15
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -71,6 +79,8 @@ jobs:
name: Build
runs-on: macos-26
timeout-minutes: 15
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -90,15 +100,69 @@ jobs:
run: swift package resolve
- name: Test DMG packaging
run: bash Tests/Scripts/run-dmg-packaging-tests.sh
- name: Build debug configuration
run: swift build
- name: Build release configuration
run: swift build -c release

app:
name: App
runs-on: macos-26
timeout-minutes: 20
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Restore Xcode dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
.build/ci/DerivedData/SourcePackages
~/Library/Caches/org.swift.swiftpm
key: xcode-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Package.resolved', 'project.yml') }}
restore-keys: xcode-${{ runner.os }}-${{ runner.arch }}-
- name: Install XcodeGen
env:
XCODEGEN_SHA256: 4d9e34b62172d645eed6457cac13fc222569974098ef4ee9c3368bedf0196806
XCODEGEN_VERSION: 2.46.0
run: |
archive="${RUNNER_TEMP}/xcodegen.zip"
curl --fail --location --silent --show-error \
--output "${archive}" \
"https://github.com/yonaskolb/XcodeGen/releases/download/${XCODEGEN_VERSION}/xcodegen.zip"
echo "${XCODEGEN_SHA256} ${archive}" | shasum -a 256 --check
unzip -q "${archive}" -d "${RUNNER_TEMP}"
echo "${RUNNER_TEMP}/xcodegen/bin" >> "${GITHUB_PATH}"
- name: Generate Xcode project
run: xcodegen generate
- name: Test application
run: >-
xcodebuild -quiet -project Clamshell.xcodeproj
-scheme ClamshellApp -configuration Debug
-destination "platform=macOS"
-derivedDataPath .build/ci/DerivedData
CODE_SIGNING_ALLOWED=NO test
- name: Test control extension
run: >-
xcodebuild -quiet -project Clamshell.xcodeproj
-scheme ClamshellControl -configuration Debug
-destination "platform=macOS"
-derivedDataPath .build/ci/DerivedData
CODE_SIGNING_ALLOWED=NO test
- name: Build release application
run: >-
xcodebuild -quiet -project Clamshell.xcodeproj
-scheme ClamshellApp -configuration Release
-derivedDataPath .build/ci/DerivedData
CODE_SIGNING_ALLOWED=NO build

workflows:
name: Workflow lint
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read
permissions: {}

jobs:
analyze:
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ name: PR

on:
pull_request:
branches: [main]
types: [opened, edited, reopened, synchronize]

concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: true

permissions:
contents: read
permissions: {}

jobs:
title:
name: Title
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- name: Check out trusted base
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -45,6 +45,8 @@ jobs:
name: Commits
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- name: Check out pull request
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
45 changes: 45 additions & 0 deletions Tests/Scripts/run-title-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash
set -euo pipefail

repository_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
readonly repository_root
readonly validator="$repository_root/scripts/check-conventional-subject.sh"

fail() {
echo "FAIL: $*" >&2
exit 1
}

assert_accepts() {
local subject="$1"
"$validator" "$subject" >/dev/null 2>&1 ||
fail "expected subject to pass: $subject"
}

assert_rejects() {
local subject="$1"
if "$validator" "$subject" >/dev/null 2>&1; then
fail "expected subject to fail: $subject"
fi
}

run_tests() {
[[ -x "$validator" ]] || fail "missing executable validator: $validator"

local verb
for verb in feat fix docs test build ci refactor perf style chore revert; do
assert_accepts "$verb(test-area): describe the change"
done

assert_rejects "feat: omit the area"
assert_rejects "feature(cli): use an unsupported verb"
assert_rejects "feat(CLI): use an upper-case area"
assert_rejects "feat(cli): end with a full stop."
assert_rejects "feat(cli): "
assert_rejects "feat(cli):"
assert_rejects " feat(cli): start with whitespace"

echo "All conventional subject tests passed."
}

run_tests
170 changes: 170 additions & 0 deletions Tests/Scripts/run-version-consistency-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#!/bin/bash
set -euo pipefail

repository_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
readonly repository_root
readonly version_library="$repository_root/scripts/lib/version-consistency.sh"
temporary_root=""

cleanup() {
[[ -n "$temporary_root" ]] || return 0
rm -rf "$temporary_root" || true
}

fail() {
echo "FAIL: $*" >&2
exit 1
}

assert_contains() {
local expected="$1"
local actual="$2"

[[ "$actual" == *"$expected"* ]] ||
fail "expected output to contain '$expected', got: $actual"
}

assert_fails() {
local expected="$1"
shift

local output
if output="$("$@" 2>&1)"; then
fail "expected command to fail: $*"
fi
assert_contains "$expected" "$output"
}

create_fixture() {
local name="$1"
local fixture="$temporary_root/$name"

mkdir -p \
"$fixture/App/ClamshellApp" \
"$fixture/App/ClamshellControl" \
"$fixture/Sources/ClamshellCore"
printf '0.1.0\n' >"$fixture/version.txt"
cat >"$fixture/Sources/ClamshellCore/BuildVersion.swift" <<'SWIFT'
public enum BuildVersion {
// x-release-please-version
public static let current = "0.1.0"
}
SWIFT
cat >"$fixture/project.yml" <<'YAML'
settings:
base:
# x-release-please-version
MARKETING_VERSION: "0.1.0"
YAML
cat >"$fixture/.release-please-manifest.json" <<'JSON'
{".":"0.1.0"}
JSON
cat >"$fixture/release-please-config.json" <<'JSON'
{
"packages": {
".": {
"release-type": "simple",
"extra-files": [
{
"type": "generic",
"path": "Sources/ClamshellCore/BuildVersion.swift"
},
{
"type": "generic",
"path": "project.yml"
}
]
}
}
}
JSON
cat >"$fixture/App/ClamshellApp/Info.plist" <<'PLIST'
<plist version="1.0">
<dict>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
</dict>
</plist>
PLIST
cp \
"$fixture/App/ClamshellApp/Info.plist" \
"$fixture/App/ClamshellControl/Info.plist"
printf '%s\n' "$fixture"
}

run_tests() {
[[ -f "$version_library" ]] || fail "missing $version_library"
# shellcheck source=/dev/null
source "$version_library"

temporary_root="$(mktemp -d "${TMPDIR:-/tmp}/clamshellctl-version-tests.XXXXXX")"
trap cleanup EXIT

local aligned_fixture
aligned_fixture="$(create_fixture aligned)"
check_version_consistency "$aligned_fixture" >/dev/null ||
fail "an aligned fixture failed validation"

local cli_drift_fixture
cli_drift_fixture="$(create_fixture cli-drift)"
sed -i '' 's/current = "0.1.0"/current = "0.2.0"/' \
"$cli_drift_fixture/Sources/ClamshellCore/BuildVersion.swift"
assert_fails \
"CLI version must be 0.1.0: 0.2.0" \
check_version_consistency \
"$cli_drift_fixture"

local app_drift_fixture
app_drift_fixture="$(create_fixture app-drift)"
sed -i '' 's/MARKETING_VERSION: "0.1.0"/MARKETING_VERSION: "0.2.0"/' \
"$app_drift_fixture/project.yml"
assert_fails \
"app version must be 0.1.0: 0.2.0" \
check_version_consistency \
"$app_drift_fixture"

local manifest_drift_fixture
manifest_drift_fixture="$(create_fixture manifest-drift)"
printf '{".":"0.2.0"}\n' \
>"$manifest_drift_fixture/.release-please-manifest.json"
assert_fails \
"release manifest version must be 0.1.0: 0.2.0" \
check_version_consistency \
"$manifest_drift_fixture"

local static_plist_fixture
static_plist_fixture="$(create_fixture static-plist)"
sed -i '' "s/\$(MARKETING_VERSION)/0.1.0/" \
"$static_plist_fixture/App/ClamshellControl/Info.plist"
assert_fails \
"control extension version must use \$(MARKETING_VERSION)" \
check_version_consistency \
"$static_plist_fixture"

local release_config_fixture
release_config_fixture="$(create_fixture release-config)"
jq \
'.packages["."]."extra-files"
|= map(select((if type == "string" then . else .path end) != "project.yml"))' \
"$release_config_fixture/release-please-config.json" \
>"$release_config_fixture/release-please-config.updated.json"
mv \
"$release_config_fixture/release-please-config.updated.json" \
"$release_config_fixture/release-please-config.json"
assert_fails \
"release configuration must update project.yml" \
check_version_consistency \
"$release_config_fixture"

local malformed_fixture
malformed_fixture="$(create_fixture malformed)"
printf '01.0.0\n' >"$malformed_fixture/version.txt"
assert_fails \
"version.txt must use MAJOR.MINOR.PATCH" \
check_version_consistency \
"$malformed_fixture"

echo "All version consistency tests passed."
}

run_tests
Loading