Skip to content

Commit ec5362d

Browse files
authored
Merge branch 'main' into refactor/json-fs-consistency
2 parents 23a5aec + d30ddcf commit ec5362d

File tree

8 files changed

+124
-61
lines changed

8 files changed

+124
-61
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
## [1.0.1](https://github.com/tempestphp/tempest-framework/compare/v1.0.0..1.0.1) — 2025-06-27
5+
## [1.0.1](https://github.com/tempestphp/tempest-framework/compare/v1.0.0..v1.0.1) — 2025-06-27
66

77
### 🚀 Features
88

bin/release

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ use Composer\Semver\VersionParser;
1818
use Tempest\Console\Console;
1919
use Tempest\Console\ConsoleApplication;
2020
use Tempest\Console\Exceptions\InterruptException;
21+
use Tempest\Http\Status;
22+
use Tempest\HttpClient\HttpClient;
23+
use Tempest\Support\Json;
2124

2225
use function Tempest\get;
2326
use function Tempest\Support\arr;
@@ -156,11 +159,37 @@ function performPreReleaseChecks(string $remote, string $branch): void
156159
throw new Exception("You must be on the {$remote}/{$branch} branch to release.");
157160
}
158161

162+
if (null === Tempest\env('RELEASE_GITHUB_TOKEN')) {
163+
throw new Exception('`RELEASE_GITHUB_TOKEN` environment variable must be set to release.');
164+
}
165+
159166
if ($behindCount = trim(shell_exec("git rev-list HEAD..{$remote}/{$branch} --count") ?? '0') !== '0') {
160167
throw new Exception("Local branch is behind {$remote}/{$branch} by {$behindCount} commits. Please pull first.");
161168
}
162169
}
163170

171+
/**
172+
* Disables the protection ruleset, so the release branch can be pushed to without a pull request.
173+
*/
174+
function updateBranchProtection(bool $enabled): void
175+
{
176+
// https://github.com/tempestphp/tempest-framework/settings/rules/1879240
177+
$ruleset = '1879240';
178+
$token = Tempest\env('RELEASE_GITHUB_TOKEN');
179+
$url = "https://api.github.com/repos/tempestphp/tempest-framework/rulesets/{$ruleset}";
180+
181+
$httpClient = Tempest\get(HttpClient::class);
182+
$response = $httpClient->put(
183+
uri: $url,
184+
headers: ['Authorization' => "Bearer {$token}"],
185+
body: Json\encode(['enforcement' => $enabled ? 'active' : 'disabled'])
186+
);
187+
188+
if ($response->status !== Status::OK) {
189+
throw new Exception('Failed to update branch ruleset.');
190+
}
191+
}
192+
164193
/**
165194
* Gets the current version.
166195
*/
@@ -338,6 +367,9 @@ try {
338367
handler: fn () => updateChangelog($version),
339368
);
340369

370+
// Disable protection
371+
updateBranchProtection(enabled: false);
372+
341373
// Push tags
342374
$console->task(
343375
label: 'Releasing',
@@ -360,6 +392,9 @@ try {
360392
],
361393
);
362394

395+
// Re-enable protection
396+
updateBranchProtection(enabled: true);
397+
363398
$console->success(sprintf(
364399
'Released <em>%1$s</em>. The <href="https://github.com/tempestphp/tempest-framework/releases/tag/%1$s">GitHub release</href> will be created automatically in a few seconds.',
365400
$tag,

packages/core/src/Kernel.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
interface Kernel
1010
{
11-
public const string VERSION = '1.0.1';
11+
public const string VERSION = '1.0.3';
1212

1313
public string $root {
1414
get;

packages/core/src/Kernel/LoadDiscoveryClasses.php

Lines changed: 73 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44

55
namespace Tempest\Core\Kernel;
66

7-
use FilesystemIterator;
8-
use RecursiveDirectoryIterator;
9-
use RecursiveIteratorIterator;
10-
use SplFileInfo;
117
use Tempest\Container\Container;
128
use Tempest\Core\DiscoveryCache;
139
use Tempest\Core\DiscoveryCacheStrategy;
@@ -92,69 +88,85 @@ private function buildDiscovery(string $discoveryClass): Discovery
9288
}
9389

9490
foreach ($this->kernel->discoveryLocations as $location) {
95-
if ($this->shouldSkipLocation($location)) {
96-
continue;
97-
}
91+
$this->discoverPath($discovery, $location, $location->path);
92+
}
9893

99-
$directories = new RecursiveDirectoryIterator($location->path, FilesystemIterator::UNIX_PATHS | FilesystemIterator::SKIP_DOTS);
100-
$files = new RecursiveIteratorIterator($directories);
94+
return $discovery;
95+
}
10196

102-
/** @var SplFileInfo $file */
103-
foreach ($files as $file) {
104-
$fileName = $file->getFilename();
97+
private function discoverPath(Discovery $discovery, DiscoveryLocation $location, string $path): void
98+
{
99+
if ($this->shouldSkipLocation($location)) {
100+
return;
101+
}
105102

106-
if ($fileName === '') {
107-
continue;
108-
}
103+
$input = realpath($path);
109104

110-
if ($fileName === '.') {
111-
continue;
112-
}
105+
if ($input === false) {
106+
return;
107+
}
113108

114-
if ($fileName === '..') {
115-
continue;
116-
}
109+
// Make sure the path is not marked for skipping
110+
if ($this->shouldSkipBasedOnConfig($input)) {
111+
return;
112+
}
117113

118-
$input = $file->getRealPath();
114+
// Directories are scanned recursively
115+
if (is_dir($input)) {
116+
if ($this->shouldSkipDirectory($input)) {
117+
return;
118+
}
119119

120-
if ($this->shouldSkipBasedOnConfig($input)) {
120+
foreach (scandir($input) as $subPath) {
121+
if ($subPath === '.' || $subPath === '..') {
121122
continue;
122123
}
123124

124-
// We assume that any PHP file that starts with an uppercase letter will be a class
125-
if ($file->getExtension() === 'php' && ucfirst($fileName) === $fileName) {
126-
$className = $location->toClassName($file->getPathname());
127-
128-
// Discovery errors (syntax errors, missing imports, etc.)
129-
// are ignored when they happen in vendor files,
130-
// but they are allowed to be thrown in project code
131-
if ($location->isVendor()) {
132-
try {
133-
$input = new ClassReflector($className);
134-
} catch (Throwable) { // @mago-expect best-practices/no-empty-catch-clause
135-
}
136-
} elseif (class_exists($className)) {
137-
$input = new ClassReflector($className);
138-
}
139-
}
125+
$this->discoverPath($discovery, $location, "{$input}/{$subPath}");
126+
}
140127

141-
if ($this->shouldSkipBasedOnConfig($input)) {
142-
continue;
143-
}
128+
return;
129+
}
144130

145-
if ($input instanceof ClassReflector) {
146-
// If the input is a class, we'll call `discover`
147-
if (! $this->shouldSkipDiscoveryForClass($discovery, $input)) {
148-
$discovery->discover($location, $input);
149-
}
150-
} elseif ($discovery instanceof DiscoversPath) {
151-
// If the input is NOT a class, AND the discovery class can discover paths, we'll call `discoverPath`
152-
$discovery->discoverPath($location, $input);
131+
$pathInfo = pathinfo($input);
132+
$extension = $pathInfo['extension'] ?? null;
133+
$fileName = $pathInfo['filename'] ?: null;
134+
135+
// We assume that any PHP file that starts with an uppercase letter will be a class
136+
if ($extension === 'php' && ucfirst($fileName) === $fileName) {
137+
$className = $location->toClassName($input);
138+
139+
// Discovery errors (syntax errors, missing imports, etc.)
140+
// are ignored when they happen in vendor files,
141+
// but they are allowed to be thrown in project code
142+
if ($location->isVendor()) {
143+
try {
144+
$input = new ClassReflector($className);
145+
} catch (Throwable $e) { // @mago-expect best-practices/no-empty-catch-clause
153146
}
147+
} elseif (class_exists($className)) {
148+
$input = new ClassReflector($className);
154149
}
155150
}
156151

157-
return $discovery;
152+
// If the input is a class, we'll try to discover it
153+
if ($input instanceof ClassReflector) {
154+
// Check whether the class should be skipped
155+
if ($this->shouldSkipBasedOnConfig($input)) {
156+
return;
157+
}
158+
159+
// Check whether this class is marked with `#[SkipDiscovery]`
160+
if ($this->shouldSkipDiscoveryForClass($discovery, $input)) {
161+
return;
162+
}
163+
164+
$discovery->discover($location, $input);
165+
} elseif ($discovery instanceof DiscoversPath) {
166+
// If the input is NOT a class, AND the discovery class can discover paths, we'll call `discoverPath`
167+
// Note that we've already checked whether the path was marked for skipping earlier in this method
168+
$discovery->discoverPath($location, $input);
169+
}
158170
}
159171

160172
/**
@@ -212,4 +224,14 @@ private function shouldSkipLocation(DiscoveryLocation $location): bool
212224
DiscoveryCacheStrategy::PARTIAL => $location->isVendor(),
213225
};
214226
}
227+
228+
/**
229+
* Check whether a given directory should be skipped
230+
*/
231+
private function shouldSkipDirectory(string $path): bool
232+
{
233+
$directory = pathinfo($path, PATHINFO_BASENAME);
234+
235+
return $directory === 'node_modules';
236+
}
215237
}

packages/discovery/src/DiscoveryLocation.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@
66

77
final readonly class DiscoveryLocation
88
{
9+
public string $namespace;
10+
public string $path;
11+
912
public function __construct(
10-
public string $namespace,
11-
public string $path,
12-
) {}
13+
string $namespace,
14+
string $path,
15+
) {
16+
$this->namespace = $namespace;
17+
$this->path = realpath(rtrim($path, '\\/'));
18+
}
1319

1420
public function isVendor(): bool
1521
{
@@ -18,12 +24,10 @@ public function isVendor(): bool
1824

1925
public function toClassName(string $path): string
2026
{
21-
$pathWithoutSlashes = rtrim($this->path, '\\/');
22-
2327
// Try to create a PSR-compliant class name from the path
2428
return str_replace(
2529
[
26-
$pathWithoutSlashes,
30+
$this->path,
2731
'/',
2832
'\\\\',
2933
'.php',

packages/mapper/src/Mappers/JsonToArrayMapper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
{
1212
public function canMap(mixed $from, mixed $to): bool
1313
{
14-
return is_string($from) && Json\is_valid($from);
14+
return false;
1515
}
1616

1717
public function map(mixed $from, mixed $to): array

packages/vite-plugin-tempest/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "vite-plugin-tempest",
33
"type": "module",
4-
"version": "1.0.1",
4+
"version": "1.0.3",
55
"author": "Enzo Innocenzi",
66
"license": "MIT",
77
"sideEffects": false,

tests/Integration/Mapper/MapperTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use Tempest\DateTime\DateTime;
99
use Tempest\DateTime\DateTimeInterface;
1010
use Tempest\Mapper\Exceptions\MappingValuesWereMissing;
11+
use Tempest\Mapper\Mappers\ArrayToObjectMapper;
12+
use Tempest\Mapper\Mappers\JsonToObjectMapper;
1113
use Tempest\Mapper\Mappers\ObjectToArrayMapper;
1214
use Tests\Tempest\Fixtures\Modules\Books\Models\Author;
1315
use Tests\Tempest\Fixtures\Modules\Books\Models\AuthorType;

0 commit comments

Comments
 (0)