Skip to content

Commit 500f85b

Browse files
github-actions[bot]coisa
authored andcommitted
refactor: isolate GitAttributesCommand as standalone command
- Extract GitAttributesCommand from SyncCommand for standalone execution - SyncCommand now calls gitattributes via runCommand like gitignore - Register GitAttributesCommand in DevToolsCommandProvider - Update tests to include new command and dependencies
1 parent 63971a5 commit 500f85b

12 files changed

Lines changed: 540 additions & 9 deletions

File tree

.gitattributes

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,14 @@
77
/.gitignore export-ignore
88
/.gitmodules export-ignore
99
AGENTS.md export-ignore
10+
# << dev-tools:managed export-ignore
11+
/.github/ export-ignore
12+
/.vscode/ export-ignore
13+
/docs/ export-ignore
14+
/tests/ export-ignore
15+
/.editorconfig export-ignore
16+
/.gitattributes export-ignore
17+
/.gitignore export-ignore
18+
/.gitmodules export-ignore
19+
/README.md export-ignore
20+
# >> dev-tools:managed export-ignore

.github/wiki

Submodule wiki updated from 752f977 to 6ba2180

src/Command/SyncCommand.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ protected function configure(): void
4747
$this
4848
->setName('dev-tools:sync')
4949
->setDescription(
50-
'Installs and synchronizes dev-tools scripts, GitHub Actions workflows, and .editorconfig in the root project.'
50+
'Installs and synchronizes dev-tools scripts, GitHub Actions workflows, .editorconfig, and .gitattributes in the root project.'
5151
)
5252
->setHelp(
53-
'This command adds or updates dev-tools scripts in composer.json, copies reusable GitHub Actions workflows, and ensures .editorconfig is present and up to date.'
53+
'This command adds or updates dev-tools scripts in composer.json, copies reusable GitHub Actions workflows, ensures .editorconfig is present and up to date, and manages .gitattributes export-ignore rules.'
5454
);
5555
}
5656

@@ -75,6 +75,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
7575
$this->copyDependabotConfig();
7676
$this->addRepositoryWikiGitSubmodule();
7777
$this->runCommand('gitignore', $output);
78+
$this->runCommand('gitattributes', $output);
7879
$this->runCommand('skills', $output);
7980
$this->runCommand('license', $output);
8081

src/Composer/Capability/DevToolsCommandProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use FastForward\DevTools\Command\CopyLicenseCommand;
2525
use FastForward\DevTools\Command\DependenciesCommand;
2626
use FastForward\DevTools\Command\DocsCommand;
27+
use FastForward\DevTools\Command\GitAttributesCommand;
2728
use FastForward\DevTools\Command\GitIgnoreCommand;
2829
use FastForward\DevTools\Command\PhpDocCommand;
2930
use FastForward\DevTools\Command\RefactorCommand;
@@ -62,6 +63,7 @@ public function getCommands()
6263
new WikiCommand(),
6364
new SyncCommand(),
6465
new GitIgnoreCommand(),
66+
new GitAttributesCommand(),
6567
new SkillsCommand(),
6668
new CopyLicenseCommand(),
6769
];
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of fast-forward/dev-tools.
7+
*
8+
* This source file is subject to the license bundled
9+
* with this source code in the file LICENSE.
10+
*
11+
* @copyright Copyright (c) 2026 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
12+
* @license https://opensource.org/licenses/MIT MIT License
13+
*
14+
* @see https://github.com/php-fast-forward/dev-tools
15+
* @see https://github.com/php-fast-forward
16+
* @see https://datatracker.ietf.org/doc/html/rfc2119
17+
*/
18+
19+
namespace FastForward\DevTools\GitAttributes;
20+
21+
/**
22+
* Provides the canonical list of candidate paths for export-ignore rules.
23+
*
24+
* This class defines the baseline set of files and directories that should
25+
* typically be excluded from Composer package archives. The list is organized
26+
* into folders and files groups for deterministic ordering.
27+
*/
28+
final class CandidateProvider implements CandidateProviderInterface
29+
{
30+
/**
31+
* @return list<string> Folders that are candidates for export-ignore
32+
*/
33+
public function folders(): array
34+
{
35+
return [
36+
'/.github/',
37+
'/.idea/',
38+
'/.vscode/',
39+
'/benchmarks/',
40+
'/build/',
41+
'/coverage/',
42+
'/docs/',
43+
'/examples/',
44+
'/fixtures/',
45+
'/scripts/',
46+
'/tests/',
47+
'/tools/',
48+
];
49+
}
50+
51+
/**
52+
* @return list<string> Files that are candidates for export-ignore
53+
*/
54+
public function files(): array
55+
{
56+
return [
57+
'/.editorconfig',
58+
'/.gitattributes',
59+
'/.gitignore',
60+
'/.gitmodules',
61+
'/CODE_OF_CONDUCT.md',
62+
'/CONTRIBUTING.md',
63+
'/Makefile',
64+
'/phpunit.xml.dist',
65+
'/README.md',
66+
];
67+
}
68+
69+
/**
70+
* Returns all candidates as a combined list with folders first, then files.
71+
*
72+
* @return list<string> All candidates in deterministic order
73+
*/
74+
public function all(): array
75+
{
76+
return [...$this->folders(), ...$this->files()];
77+
}
78+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of fast-forward/dev-tools.
7+
*
8+
* This source file is subject to the license bundled
9+
* with this source code in the file LICENSE.
10+
*
11+
* @copyright Copyright (c) 2026 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
12+
* @license https://opensource.org/licenses/MIT MIT License
13+
*
14+
* @see https://github.com/php-fast-forward/dev-tools
15+
* @see https://github.com/php-fast-forward
16+
* @see https://datatracker.ietf.org/doc/html/rfc2119
17+
*/
18+
19+
namespace FastForward\DevTools\GitAttributes;
20+
21+
/**
22+
* Provides the canonical list of candidate paths for export-ignore rules.
23+
*
24+
* This interface defines the contract for classes that provide the baseline
25+
* set of files and directories that should typically be excluded from
26+
* Composer package archives.
27+
*/
28+
interface CandidateProviderInterface
29+
{
30+
/**
31+
* Returns the list of folder paths that are candidates for export-ignore.
32+
*
33+
* @return list<string> Folder paths in canonical form (e.g., "/.github/")
34+
*/
35+
public function folders(): array;
36+
37+
/**
38+
* Returns the list of file paths that are candidates for export-ignore.
39+
*
40+
* @return list<string> File paths in canonical form (e.g., "/.editorconfig")
41+
*/
42+
public function files(): array;
43+
44+
/**
45+
* Returns all candidates as a combined list with folders first, then files.
46+
*
47+
* @return list<string> All candidates in deterministic order
48+
*/
49+
public function all(): array;
50+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of fast-forward/dev-tools.
7+
*
8+
* This source file is subject to the license bundled
9+
* with this source code in the file LICENSE.
10+
*
11+
* @copyright Copyright (c) 2026 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
12+
* @license https://opensource.org/licenses/MIT MIT License
13+
*
14+
* @see https://github.com/php-fast-forward/dev-tools
15+
* @see https://github.com/php-fast-forward
16+
* @see https://datatracker.ietf.org/doc/html/rfc2119
17+
*/
18+
19+
namespace FastForward\DevTools\GitAttributes;
20+
21+
use Symfony\Component\Filesystem\Filesystem;
22+
23+
/**
24+
* Checks the existence of files and directories in a given base path.
25+
*
26+
* This class determines which candidate paths from the canonical list
27+
* actually exist in the target repository, enabling selective export-ignore rules.
28+
*/
29+
final readonly class ExistenceChecker implements ExistenceCheckerInterface
30+
{
31+
private string $basePath;
32+
33+
/**
34+
* @param string $basePath The base directory to check paths against
35+
* @param Filesystem $filesystem
36+
*/
37+
public function __construct(
38+
string $basePath,
39+
private Filesystem $filesystem = new Filesystem()
40+
) {
41+
$this->basePath = rtrim($basePath, '/');
42+
}
43+
44+
/**
45+
* Checks if a path exists as a file or directory.
46+
*
47+
* @param string $path The path to check (e.g., "/.github/" or "/.editorconfig")
48+
*
49+
* @return bool True if the path exists as a file or directory
50+
*/
51+
public function exists(string $path): bool
52+
{
53+
$fullPath = $this->basePath . $path;
54+
55+
return $this->filesystem->exists($fullPath);
56+
}
57+
58+
/**
59+
* Filters a list of paths to only those that exist.
60+
*
61+
* @param list<string> $paths The paths to filter
62+
*
63+
* @return list<string> Only the paths that exist
64+
*/
65+
public function filterExisting(array $paths): array
66+
{
67+
return array_values(array_filter($paths, $this->exists(...)));
68+
}
69+
70+
/**
71+
* Checks if a path is a directory.
72+
*
73+
* @param string $path The path to check (e.g., "/.github/")
74+
*
75+
* @return bool True if the path exists and is a directory
76+
*/
77+
public function isDirectory(string $path): bool
78+
{
79+
$fullPath = $this->basePath . $path;
80+
81+
return is_dir($fullPath);
82+
}
83+
84+
/**
85+
* Checks if a path is a file.
86+
*
87+
* @param string $path The path to check (e.g., "/.editorconfig")
88+
*
89+
* @return bool True if the path exists and is a file
90+
*/
91+
public function isFile(string $path): bool
92+
{
93+
$fullPath = $this->basePath . $path;
94+
95+
return is_file($fullPath);
96+
}
97+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of fast-forward/dev-tools.
7+
*
8+
* This source file is subject to the license bundled
9+
* with this source code in the file LICENSE.
10+
*
11+
* @copyright Copyright (c) 2026 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
12+
* @license https://opensource.org/licenses/MIT MIT License
13+
*
14+
* @see https://github.com/php-fast-forward/dev-tools
15+
* @see https://github.com/php-fast-forward
16+
* @see https://datatracker.ietf.org/doc/html/rfc2119
17+
*/
18+
19+
namespace FastForward\DevTools\GitAttributes;
20+
21+
/**
22+
* Checks the existence of files and directories in a given base path.
23+
*
24+
* This interface defines the contract for determining which candidate
25+
* paths actually exist in the target repository.
26+
*/
27+
interface ExistenceCheckerInterface
28+
{
29+
/**
30+
* Checks if a path exists as a file or directory.
31+
*
32+
* @param string $path The path to check (e.g., "/.github/" or "/.editorconfig")
33+
*
34+
* @return bool True if the path exists as a file or directory
35+
*/
36+
public function exists(string $path): bool;
37+
38+
/**
39+
* Filters a list of paths to only those that exist.
40+
*
41+
* @param list<string> $paths The paths to filter
42+
*
43+
* @return list<string> Only the paths that exist
44+
*/
45+
public function filterExisting(array $paths): array;
46+
47+
/**
48+
* Checks if a path is a directory.
49+
*
50+
* @param string $path The path to check (e.g., "/.github/")
51+
*
52+
* @return bool True if the path exists and is a directory
53+
*/
54+
public function isDirectory(string $path): bool;
55+
56+
/**
57+
* Checks if a path is a file.
58+
*
59+
* @param string $path The path to check (e.g., "/.editorconfig")
60+
*
61+
* @return bool True if the path exists and is a file
62+
*/
63+
public function isFile(string $path): bool;
64+
}

0 commit comments

Comments
 (0)