Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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
2 changes: 0 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
27 changes: 18 additions & 9 deletions scripts/clean-install-smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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="$(php -r 'require $argv[1]; echo OilPriceAPI\Client::VERSION;' "$root_dir/src/Client.php")"

cleanup() {
if [[ -n "$server_pid" ]]; then
Expand All @@ -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);
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
26 changes: 24 additions & 2 deletions tests/PublicClaimsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,34 @@ 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);
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 ([
'/.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'),
Expand Down Expand Up @@ -290,7 +312,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');
Expand Down