From d2d8af7a75fd2d61a62a4186d5eb73678f337c1b Mon Sep 17 00:00:00 2001 From: Karl Waldman Date: Tue, 11 Aug 2026 12:20:09 -0400 Subject: [PATCH 1/2] Match Packagist distribution in package smoke --- .gitattributes | 9 +++++++++ CHANGELOG.md | 9 +++++++++ scripts/clean-install-smoke.sh | 27 ++++++++++++++++++--------- src/Client.php | 2 +- tests/PublicClaimsTest.php | 22 ++++++++++++++++++++-- 5 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4d825e5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +/.gitattributes export-ignore +/.github export-ignore +/.gitignore export-ignore +/.phpunit.result.cache export-ignore +/composer.lock export-ignore +/phpunit.xml.dist export-ignore +/scripts export-ignore +/tests export-ignore +/vendor export-ignore diff --git a/CHANGELOG.md b/CHANGELOG.md index 68f90cb..2cfb96a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2.1.2 (2026-08-11) + +### Fixed + +- Make the tested package match Packagist's GitHub distribution by excluding + development-only workflows, tests, and validation tools through Git archive + attributes. The clean-install gate now scans that exact archive shape and + rejects a stale packaged SDK version. + ## 2.1.1 (2026-08-11) ### Fixed diff --git a/scripts/clean-install-smoke.sh b/scripts/clean-install-smoke.sh index d57d9e8..00e008a 100755 --- a/scripts/clean-install-smoke.sh +++ b/scripts/clean-install-smoke.sh @@ -4,7 +4,7 @@ set -euo pipefail root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" tmp_dir="$(mktemp -d)" server_pid="" -sdk_version="${SDK_VERSION:-2.1.1}" +sdk_version="${SDK_VERSION:-2.1.2}" cleanup() { if [[ -n "$server_pid" ]]; then @@ -16,15 +16,19 @@ cleanup() { trap cleanup EXIT mkdir -p "$tmp_dir/artifacts" "$tmp_dir/consumer" -COMPOSER_ROOT_VERSION="$sdk_version" composer archive \ - --working-dir="$root_dir" \ - --format=zip \ - --dir="$tmp_dir/artifacts" \ - --file=oilpriceapi \ - --no-interaction \ - --quiet archive_path="$tmp_dir/artifacts/oilpriceapi.zip" -[[ -s "$archive_path" ]] || { echo "Composer archive was not created" >&2; exit 1; } +( + cd "$root_dir" + git archive --format=zip --worktree-attributes --output="$archive_path" HEAD +) +[[ -s "$archive_path" ]] || { echo "GitHub-shaped archive was not created" >&2; exit 1; } + +for dev_only in .gitattributes .github .gitignore .phpunit.result.cache composer.lock phpunit.xml.dist scripts tests vendor; do + if unzip -Z1 "$archive_path" | grep -Eq "^${dev_only}(/|$)"; then + echo "GitHub-shaped archive includes development-only path: $dev_only" >&2 + exit 1 + fi +done package_json="$(php -r ' $package = json_decode(file_get_contents($argv[1]), true, flags: JSON_THROW_ON_ERROR); @@ -39,6 +43,11 @@ export COMPOSER_ROOT_VERSION=1.0.0 composer init --name=oilpriceapi/example-smoke --no-interaction --quiet composer config --quiet repositories.oilpriceapi "$package_json" composer require "oilpriceapi/oilpriceapi:$sdk_version" --no-interaction --prefer-dist --no-progress --quiet +installed_version="$(php -r 'require "vendor/autoload.php"; echo OilPriceAPI\Client::VERSION;')" +[[ "$installed_version" = "$sdk_version" ]] || { + echo "Installed Client::VERSION $installed_version does not match $sdk_version" >&2 + exit 1 +} php "$root_dir/scripts/validate-public-claims.php" \ "$tmp_dir/consumer/vendor/oilpriceapi/oilpriceapi" quickstart="$tmp_dir/consumer/vendor/oilpriceapi/oilpriceapi/examples/quickstart.php" diff --git a/src/Client.php b/src/Client.php index 47eb801..99c565b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -28,7 +28,7 @@ */ final class Client { - public const VERSION = '2.1.1'; + public const VERSION = '2.1.2'; public const DEFAULT_BASE_URL = 'https://api.oilpriceapi.com'; public const DEFAULT_TIMEOUT = 10.0; public const DEFAULT_MAX_RETRIES = 3; diff --git a/tests/PublicClaimsTest.php b/tests/PublicClaimsTest.php index 52e641e..34e70e5 100644 --- a/tests/PublicClaimsTest.php +++ b/tests/PublicClaimsTest.php @@ -68,12 +68,30 @@ public function testFutureComposerTextFilesAndQuotaAliasesCannotBypassDiscovery( } } - public function testPackagedSmokeScansTheExactInstalledComposerArchive(): void + public function testPackagedSmokeScansTheExactGitHubDistribution(): void { $root = dirname(__DIR__); $smoke = (string) file_get_contents($root . '/scripts/clean-install-smoke.sh'); self::assertStringContainsString('validate-public-claims.php', $smoke); self::assertStringContainsString('vendor/oilpriceapi/oilpriceapi', $smoke); + self::assertStringContainsString('git archive --format=zip --worktree-attributes', $smoke); + self::assertStringNotContainsString('composer archive', $smoke); + self::assertStringContainsString('Client::VERSION', $smoke); + + $attributes = (string) file_get_contents($root . '/.gitattributes'); + foreach ([ + '/.gitattributes', + '/.github', + '/.gitignore', + '/.phpunit.result.cache', + '/composer.lock', + '/phpunit.xml.dist', + '/scripts', + '/tests', + '/vendor', + ] as $devOnlyPath) { + self::assertStringContainsString($devOnlyPath . ' export-ignore', $attributes); + } $composer = json_decode( (string) file_get_contents($root . '/composer.json'), @@ -290,7 +308,7 @@ public function testCanonicalDeveloperContractIsDiscoverable(): void ); self::assertSame('oilpriceapi/oilpriceapi', $composer['name']); self::assertSame('>=8.1', $composer['require']['php']); - self::assertSame('2.1.1', Client::VERSION); + self::assertSame('2.1.2', Client::VERSION); self::assertSame('https://api.oilpriceapi.com', Client::DEFAULT_BASE_URL); $readme = (string) file_get_contents($root . '/README.md'); From fe91169abcf9882b49bdd2b9b4c35600a74de855 Mon Sep 17 00:00:00 2001 From: Karl Waldman Date: Tue, 11 Aug 2026 12:22:49 -0400 Subject: [PATCH 2/2] Derive PHP package smoke version from source --- .github/workflows/test.yml | 2 -- scripts/clean-install-smoke.sh | 2 +- tests/PublicClaimsTest.php | 4 ++++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b085374..7711239 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,8 +54,6 @@ jobs: coverage: none - name: Install exact Composer archive and run recovery fixtures - env: - SDK_VERSION: ${{ startsWith(github.ref, 'refs/tags/v') && github.ref_name || '2.1.1' }} run: ./scripts/clean-install-smoke.sh live: diff --git a/scripts/clean-install-smoke.sh b/scripts/clean-install-smoke.sh index 00e008a..9a8ed67 100755 --- a/scripts/clean-install-smoke.sh +++ b/scripts/clean-install-smoke.sh @@ -4,7 +4,7 @@ set -euo pipefail root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" tmp_dir="$(mktemp -d)" server_pid="" -sdk_version="${SDK_VERSION:-2.1.2}" +sdk_version="$(php -r 'require $argv[1]; echo OilPriceAPI\Client::VERSION;' "$root_dir/src/Client.php")" cleanup() { if [[ -n "$server_pid" ]]; then diff --git a/tests/PublicClaimsTest.php b/tests/PublicClaimsTest.php index 34e70e5..dcb117a 100644 --- a/tests/PublicClaimsTest.php +++ b/tests/PublicClaimsTest.php @@ -77,6 +77,10 @@ public function testPackagedSmokeScansTheExactGitHubDistribution(): void self::assertStringContainsString('git archive --format=zip --worktree-attributes', $smoke); self::assertStringNotContainsString('composer archive', $smoke); self::assertStringContainsString('Client::VERSION', $smoke); + self::assertStringContainsString('$root_dir/src/Client.php', $smoke); + + $workflow = (string) file_get_contents($root . '/.github/workflows/test.yml'); + self::assertStringNotContainsString('SDK_VERSION:', $workflow); $attributes = (string) file_get_contents($root . '/.gitattributes'); foreach ([