Tailor

Tests

Tailor is a CLI application to help you maintain your extensions. Tailor talks with the TER REST API and enables you to register new keys, update extension information and publish new versions to the extension repository.

Contents

Prerequisites

The TER REST API can be accessed providing a personal access token. You can create such token either on https://extensions.typo3.org/ after you've logged in, or directly using Tailor.

Provide your credentials by either creating a .env file in the project root folder or setting environment variables through your system to this PHP script:

TYPO3_API_TOKEN=<your-token>
TYPO3_API_USERNAME=<your-t3o-username>
TYPO3_API_PASSWORD=<your-t3o-password>
Copied!

Example:

TYPO3_API_TOKEN="someToken" TYPO3_EXTENSION_KEY="ext_key" bin/tailor ter:details
Copied!

This will display the extension details for extension ext_key if someToken is valid (not expired/revoked and having at least the extension:read scope assigned).

Installation

Use Tailor as a dev dependency via composer of your extensions:

composer req --dev typo3/tailor
Copied!

Usage

All commands, requesting the TER API, provide the -r, --raw option. If set, the raw result will be returned. This can be used for further processing e.g. by using some JSON processor.

Most of the commands require an extension key to work with. There are multiple possibilities to provide an extension key. These are - in the order in which they are checked:

  • As argument, e.g. ./vendor/bin/tailor ter:details my_key
  • As environment variable, TYPO3_EXTENSION_KEY=my_key
  • In your composer.json, [extra][typo3/cms][extension-key] = 'my_key'

This means, even if you have an extension key defined globally, either as environment variable or in your composer.json, you can still run all commands for different extensions by adding the desired extension key as argument to the command.

Manage your personal access token

Use the ter:token:create command to create a new token:

./vendor/bin/tailor ter:token:create --name="token for my_extension" --extensions=my_extension
Copied!

The result will look like this:

Token type: bearer
Access token: eyJ0eXAOiEJKV1QiLCJhb
Refresh token: eyJ0eXMRxHRaF4hIVrEtu
Expires in: 604800
Scope: extension:read,extension:write
Extensions: my_extension
Copied!

As you can see, this will create an access token which is only valid for the extension my_extension. The scopes are set to extension:read,extension:write since this is the default if option --scope is not provided. The same applies to the expiration date which can be set with the option --expires.

If the token threatens to expire, refresh it with ter:token:refresh:

./vendor/bin/tailor ter:token:refresh eyJ0eXMRxHRaF4hIVrEtu
Copied!

This will generate new access and refresh tokens with the same options, initially set on creation.

To revoke an access token irretrievably, use ter:token:revoke:

./vendor/bin/tailor ter:token:revoke eyJ0eXAOiEJKV1QiLCJhb
Copied!

Register a new extension key

To register a new extension, use ter:register by providing your desired extension key as argument:

./vendor/bin/tailor ter:register my_extension
Copied!

This registers the key my_extension and returns following confirmation:

Key: my_extension
Owner: your_username
Copied!

Update the version in your extension files

Prior to publishing a new version, you have to update the version in your extensions ext_emconf.php file. This can be done using the set-version command.

./vendor/bin/tailor set-version 1.2.0
Copied!

If your extension also contains a Documentation/guides.xml or Documentation/Settings.cfg file, the command will also update the release and version information in it. You can disable this feature by either using --no-docs or by setting the environment variable TYPO3_DISABLE_DOCS_VERSION_UPDATE=1.

Publish a new version of an extension to TER

You can publish a new version of your extension using the ter:publish command. Therefore, provide the extension key and version number as arguments followed by the path to the extension directory or an artefact (a zipped version of your extension). The latter can be either local or a remote file.

Using --path:

./vendor/bin/tailor ter:publish 1.2.0 my_extension --path=/path/to/my_extension
Copied!

Using a local --artefact:

./vendor/bin/tailor ter:publish 1.2.0 my_extension --artefact=/path/to/any-zip-file/my_extension.zip
Copied!

Using a remote --artefact:

./vendor/bin/tailor ter:publish 1.2.0 my_extension --artefact=https://github.com/my-name/my_extension/archive/1.2.0.zip
Copied!

Using the root direcotry:

./vendor/bin/tailor ter:publish 1.2.0 my_extension
Copied!

If the extension key is defined as environment variable or in your composer.json, it can also be skipped. So using the current root directory the whole command simplifies to:

./vendor/bin/tailor ter:publish 1.2.0
Copied!

Create a local artefact of an extension

You can generate a local artefact of your extension using the create-artefact command. This will generate a zip archive ready to be uploaded to TER (which is not covered by this command, have a look at the ter:publish command instead).

Provide the version number and extension key as arguments followed by the path to the extension directory or an artefact (a zipped version of your extension). The latter can be either local or a remote file.

Using --path:

./vendor/bin/tailor create-artefact 1.2.0 my_extension --path=/path/to/my_extension
Copied!

Using a local --artefact:

./vendor/bin/tailor create-artefact 1.2.0 my_extension --artefact=/path/to/any-zip-file/my_extension.zip
Copied!

Using a remote --artefact:

./vendor/bin/tailor create-artefact 1.2.0 my_extension --artefact=https://github.com/my-name/my_extension/archive/1.2.0.zip
Copied!

Using the root directory:

./vendor/bin/tailor create-artefact 1.2.0 my_extension
Copied!

If the extension key is defined as environment variable or in your composer.json, it can also be skipped. So using the current root directory the whole command simplifies to:

./vendor/bin/tailor create-artefact 1.2.0
Copied!

Update extension meta information

You can update the extension meta information, such as the composer name, or the associated tags with the ter:update command.

To update the composer name:

./vendor/bin/tailor ter:update my_extension --composer=vender/my_extension
Copied!

To update the tags:

./vendor/bin/tailor ter:update my_extension --tags=some-tag,another-tag
Copied!

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

Transfer ownership of an extension to another user

It's possible to transfer one of your extensions to another user. Therefore, use the ter:transfer command providing the extension key to be transferred and the TYPO3.org username of the recipient.

Since you won't have any access to the extension afterwards, the command asks for your confirmation before sending the order to the REST API.

./vendor/bin/tailor ter:transfer some_user my_extension
Copied!

This transfers the extension my_extension to the user some_user and returns following confirmation:

Key: my_extension
Owner: some_user
Copied!

Delete / abandon an extension

You can easily delete / abandon extensions with Tailor using the ter:delete command. This either removes the extension entirely or just abandons it if the extension still has public versions.

Since you won't have any access to the extension afterwards, the command asks for your confirmation before sending the order to the REST API.

./vendor/bin/tailor ter:delete my_extension
Copied!

This will delete / abandon the extension my_extension.

Find and filter extensions on TER

Tailor can't only be used for managing your extensions but also to find others. Therefore, use ter:find by adding some filters:

./vendor/bin/tailor ter:find
./vendor/bin/tailor ter:find --typo3-version=9
./vendor/bin/tailor ter:find --typo3-author=some_user
Copied!

First command will find all public extensions. The second and third one will only return extensions which match the filter. In this case being compatible with TYPO3 version 9 or owned by some_user.

To limit / paginate the result, you can use the options --page and --per_page:

./vendor/bin/tailor ter:find --page=3 --per_page=20
Copied!

Specific extension details

You can also request more details about a specific extension using the ter:details command:

./vendor/bin/tailor ter:details my_extension
Copied!

This will return details about the extension my_extension like the current version, the author, some meta information and more. Similar to the extension detail page on extension.typo3.org.

Specific extension version details

If you like to get details about a specific version of an extension, ter:version can be used:

./vendor/bin/tailor ter:version 1.0.0 my_extension
Copied!

This will return details about version 1.0.0 of extension my_extension.

Details for all versions of an extension

You can also get the details for all versions of an extension with ter:versions:

./vendor/bin/tailor ter:versions my_extension
Copied!

This will return the details for all version of the extension my_extension.

Publish a new version using tailor locally

Step 1: Update the version in your extension files

./vendor/bin/tailor set-version 1.5.0
Copied!

Step 2: Commit the changes and add a tag

git commit -am "[RELEASE] A new version was published"
git tag -a 1.5.0
Copied!

Step 3: Push this to your remote repository

git push origin --tags
Copied!

Step 4: Push this version to TER

./vendor/bin/tailor ter:publish 1.5.0
Copied!

Publish a new version using your CI

You can also integrate tailor into you GitHub workflow respectively your GitLab pipeline. Therefore, Step 1, Step 2 and Step 3 from the above example are the same. Step 4 could then be done by your integration.

Please have a look at the following examples describing how such integration could look like for GitHub workflows and GitLab pipelines.

Github actions workflow

The workflow will only be executed when pushing a new tag. This can either be done using Step 3 from above example or by creating a new GitHub release which will also add a new tag.

The workflow furthermore requires the GitHub secrets TYPO3_EXTENSION_KEY and TYPO3_API_TOKEN to be set. Add them at "Settings -> Secrets -> New repository secret".

The version is automatically fetched from the tag and validated to match the required pattern.

The commit message from Step 2 is used as the release comment. If it's empty, a static text will be used.

To see the following workflow in action, please have a look at the tailor_ext example extension.

name: publish
on:
  push:
    tags:
      - '*'
jobs:
  publish:
    name: Publish new version to TER
    # use folliwing if tags begins with `v`
    # if: startsWith(github.ref, 'refs/tags/v')
    if: startsWith(github.ref, 'refs/tags/')
    runs-on: ubuntu-20.04
    env:
      TYPO3_EXTENSION_KEY: ${{ secrets.TYPO3_EXTENSION_KEY }}
      TYPO3_API_TOKEN: ${{ secrets.TYPO3_API_TOKEN }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Check tag
        run: |
          # use ^refs/tags/v[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$ when tag is prefixed with v
          if ! [[ ${{ github.ref }} =~ ^refs/tags/[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$ ]]; then
            exit 1
          fi

      - name: Get version
        id: get-version
        run: echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV

      - name: Get comment
        id: get-comment
        run: |
          # If tag begins with `v` use:
          # readonly local comment=$(git tag -l v${{ env.version }} --format '%(contents))
          readonly local comment=$(git tag -l ${{ env.version }} --format '%(contents))

          if [[ -z "${comment// }" ]]; then
            echo "comment=Released version ${{ env.version }} of ${{ env.TYPO3_EXTENSION_KEY }}" >> $GITHUB_ENV
          else
            {
              echo 'comment<<EOF'
              echo "$comment"
              echo EOF
            } >> "$GITHUB_ENV"
          fi

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 7.4
          extensions: intl, mbstring, json, zip, curl
          tools: composer:v2

      - name: Install tailor
        run: composer global require typo3/tailor --prefer-dist --no-progress --no-suggest

      - name: Publish to TER
        run: php ~/.composer/vendor/bin/tailor ter:publish --comment "${{ env.comment }}" ${{ env.version }}
Copied!
  1. The regular expression in step Check tag should be:
^refs/tags/v[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$
Copied!
  1. The output format in step Get version should be:
${GITHUB_REF#refs/tags/v}
Copied!
  1. The variable declaration in step Get comment should be:
$(git tag -l v${{ env.version }} --format '%(contents))
Copied!

GitHub actions from TYPO3 community

Additionally, to further simplify your workflow, you can also use the typo3-uploader-ter GitHub action from TYPO3 community member Tomas Norre. For more information about the usage, please refer to the corresponding README.

GitLab pipeline

The job will only be executed when pushing a new tag. The upload comment is taken from the message in the tag.

The job furthermore requires the GitLab variables TYPO3_EXTENSION_KEY and TYPO3_API_TOKEN to be set.

The variable CI_COMMIT_TAG is set by GitLab automatically.

"Publish new version to TER":
  stage: release
  image: composer:2
  only:
    - tags
  before_script:
    - composer global require typo3/tailor
  script:
    - >
      if [ -n "$CI_COMMIT_TAG" ] && [ -n "$TYPO3_API_TOKEN" ] && [ -n "$TYPO3_EXTENSION_KEY" ]; then
        echo -e "Preparing upload of release ${CI_COMMIT_TAG} to TER\n"
        # Cleanup before we upload
        git reset --hard HEAD && git clean -fx
        # Upload
        TAG_MESSAGE=`git tag -n10 -l $CI_COMMIT_TAG | sed 's/^[0-9.]*[ ]*//g'`
        echo "Uploading release ${CI_COMMIT_TAG} to TER"
        /tmp/vendor/bin/tailor ter:publish --comment "$TAG_MESSAGE" "$CI_COMMIT_TAG" "$TYPO3_EXTENSION_KEY"
      fi;
Copied!

Exclude paths from packaging

A couple of directories and files are excluded from packaging by default. You can find the configuration in conf/ExcludeFromPackaging.php.

If you like, you can also use a custom configuration. Just add the path to your custom configuration file to the environment variable TYPO3_EXCLUDE_FROM_PACKAGING. This file must return an array with the keys directories and files on root level.

Overview of all available commands

Commands Arguments Options Description
set-version version --path--no-docs Update the version in extension files
ter:delete extensionkey   Delete an extension.
ter:details extensionkey   Fetch details about an extension.
ter:find   --page--per-page--author--typo3-version Fetch a list of extensions from TER.
ter:publish versionextensionkey --path--artefact--comment Publishes a new version of an extension to TER.
create-artefact versionextensionkey --path--artefact Create an artefact file (zip archive) of an extension.
ter:register extensionkey   Register a new extension key in TER.
ter:token:create   --name--expires--scope--extensions Request an access token for the TER.
ter:token:refresh token   Refresh an access token for the TER.
ter:token:revoke token   Revoke an access token for the TER.
ter:transfer usernameextensionkey   Transfer ownership of an extension key.
ter:update extensionkey --composer--issues--repository--manual--paypal--tags Update extension meta information.
ter:version versionextensionkey   Fetch details about an extension version.
ter:versions extensionkey   Fetch details for all versions of an extension.

General options for all commands

  • -r, --raw Return result as raw object (e.g. json) - Only for commands, requesting the TER API
  • -h, --help Display help message
  • -q, --quiet Do not output any message
  • -v, --version Display the CLI applications' version
  • -n, --no-interaction Do not ask any interactive question
  • -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
  • --ansi Force ANSI output
  • --no-ansi Disable ANSI output

Author & License

Created by Benni Mack and Oliver Bartsch.

MIT License, see LICENSE