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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,19 @@ To update the tags:
./vendor/bin/tailor ter:update my_extension --tags=some-tag,another-tag
```

Tags are what people search the extension listing by, so they are worth
choosing for TER specifically. Every extension in TER is a TYPO3 extension,
which means terms such as `typo3`, `typo3-extension`, `extension`, `cms` or
`php` narrow nothing down there — even though the very same terms are what
make a package findable on GitHub or Packagist. Prefer tags describing what
the extension does. Tailor warns when it recognises such a term, but still
sends whatever you pass.

This matters most for automated publishing: because the whole list is
replaced on every call (see below), a pipeline that reuses one vocabulary
across registries will keep overwriting curated TER tags with terms that add
nothing.

Please use `./vendor/bin/tailor ter:update -h` to see the full
list of available options.

Expand Down
37 changes: 36 additions & 1 deletion src/Command/Extension/UpdateExtensionCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use TYPO3\Tailor\Command\AbstractClientRequestCommand;
use TYPO3\Tailor\Dto\Messages;
use TYPO3\Tailor\Dto\RequestConfiguration;
Expand Down Expand Up @@ -51,15 +52,49 @@ protected function configure(): void
->addOption('repository', '', InputOption::VALUE_OPTIONAL, 'Link to the repository')
->addOption('manual', '', InputOption::VALUE_OPTIONAL, 'Link to the external manual')
->addOption('paypal', '', InputOption::VALUE_OPTIONAL, 'Link to sponsoring page (paypal)')
->addOption('tags', '', InputOption::VALUE_OPTIONAL, 'Comma-separated list of tags');
->addOption(
'tags',
'',
InputOption::VALUE_OPTIONAL,
'Comma-separated list of tags. Replaces the existing list. Every extension in TER is a '
. 'TYPO3 extension, so terms like typo3, typo3-extension, extension or php add no '
. 'discoverability there - prefer tags describing what the extension does.'
);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->extensionKey = CommandHelper::getExtensionKeyFromInput($input);
$this->warnAboutTagsImpliedByTer($input, $output);

return parent::execute($input, $output);
}

/**
* TER replaces the whole tag list with what is sent, so a pipeline reusing
* one vocabulary across registries silently publishes terms that say
* nothing here. Point them out without changing what gets sent.
*/
private function warnAboutTagsImpliedByTer(InputInterface $input, OutputInterface $output): void
{
$tags = $input->getOption('tags');
if (!is_string($tags) || $tags === '') {
return;
}

$implied = CommandHelper::getTagsImpliedByTer($tags);
if ($implied === []) {
return;
}

(new SymfonyStyle($input, $output))->warning(sprintf(
'Every extension in TER is a TYPO3 extension, so %s %s no discoverability there. '
. 'Consider tags describing what the extension does instead.',
implode(', ', $implied),
count($implied) === 1 ? 'adds' : 'add'
));
}

protected function getRequestConfiguration(): RequestConfiguration
{
return new RequestConfiguration(
Expand Down
50 changes: 50 additions & 0 deletions src/Helper/CommandHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,31 @@
*/
final class CommandHelper
{
/**
* Terms every TER listing already implies. TER only lists TYPO3 extensions,
* so these narrow nothing down there — unlike on GitHub or Packagist, where
* the same terms are what make a package findable at all.
*
* Compared after stripping separators, so the spelling variants that occur
* in practice — "typo3 cms" in composer.json keywords, "typo3-cms-extension"
* in GitHub topics — are all recognised.
*
* @var string[]
*/
private const TAGS_IMPLIED_BY_TER = [
'cms',
'cmsextension',
'extension',
'extensions',
'php',
'ter',
'typo3',
'typo3cms',
'typo3cmsextension',
'typo3ext',
'typo3extension',
];

public static function getExtensionKeyFromInput(InputInterface $input): string
{
// 1. CLI argument has highest priority
Expand Down Expand Up @@ -52,4 +77,29 @@ public static function getExtensionKeyFromInput(InputInterface $input): string
1605706548
);
}

/**
* Returns those of the given tags that TER already implies, so the caller
* can point them out. Case and separators are ignored.
*
* @return string[]
*/
public static function getTagsImpliedByTer(string $tags): array
{
$implied = [];

foreach (explode(',', $tags) as $tag) {
$tag = trim($tag);
if ($tag === '') {
continue;
}

$normalized = strtolower((string)preg_replace('/[^a-z0-9]/i', '', $tag));
if (in_array($normalized, self::TAGS_IMPLIED_BY_TER, true)) {
$implied[] = $tag;
}
}

return $implied;
}
}
39 changes: 39 additions & 0 deletions tests/Unit/Helper/CommandHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace TYPO3\Tailor\Tests\Unit\Helper;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Input\ArrayInput;
Expand Down Expand Up @@ -98,4 +99,42 @@ public function getExtensionKeyFromInputReturnsExtensionKeyFromEnvironmentVariab
restore_error_handler();
}
}

/**
* @param string[] $expected
*/
#[Test]
#[DataProvider('tagsImpliedByTerDataProvider')]
public function getTagsImpliedByTerReturnsOnlyTermsTerAlreadyImplies(string $tags, array $expected): void
{
self::assertSame($expected, CommandHelper::getTagsImpliedByTer($tags));
}

/**
* @return array<string, array{0: string, 1: string[]}>
*/
public static function tagsImpliedByTerDataProvider(): array
{
return [
'empty input' => ['', []],
'only domain tags' => ['search,indexing,facets', []],
'single implied tag' => ['typo3,search', ['typo3']],
'several implied tags' => ['typo3,php,search,extension', ['typo3', 'php', 'extension']],
'case is ignored' => ['TYPO3,Extension', ['TYPO3', 'Extension']],
'surrounding whitespace' => [' typo3 , search ', ['typo3']],
'empty segments' => ['typo3,,search,', ['typo3']],
'substring is not a match' => ['typo3-solr,phpunit', []],
// Spelling variants observed in the wild: composer.json keywords
// favour "typo3 cms", GitHub topics "typo3-cms-extension".
'separator variants' => [
'typo3 cms,typo3-cms,typo3cms',
['typo3 cms', 'typo3-cms', 'typo3cms'],
],
'extension variants' => [
'typo3-cms-extension,typo3-extension,cms-extension',
['typo3-cms-extension', 'typo3-extension', 'cms-extension'],
],
'domain tag with separator survives' => ['e-commerce,tt_news', []],
];
}
}
Loading