├── CODEOWNERS ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── lint-grammar.yml │ ├── _lint-docs.yml │ ├── lint-workflows.yml │ ├── tests-unit-php.yml │ ├── lint-php.yml │ ├── coding-standards-php.yml │ ├── static-analysis-php.yml │ ├── static-analysis-js.yml │ ├── tests-unit-js.yml │ ├── automatic-release.yml │ ├── test-playwright.yml │ ├── wp-scripts-lint.yml │ ├── build-assets-compilation.yml │ ├── ddev-playwright.yml │ ├── build-plugin-archive.yml │ ├── build-and-push-assets.yml │ └── build-and-distribute.yml ├── package.json ├── docs ├── lint-workflows.md ├── assets-compilation.md ├── wp-scripts.md ├── automatic-release.md ├── js.md ├── test-playwright.md ├── archive-creation.md ├── ddev-playwright.md ├── php.md ├── build-and-distribute.md └── build-and-push-assets.md ├── templates └── automatic-release │ └── release.config.js ├── README.md └── LICENSE /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @inpsyde/delivery-heroes 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## PhpStorm 2 | .idea/ 3 | 4 | ## npm 5 | /node_modules/ 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "@wordpress/scripts": "^29.0.0" 5 | }, 6 | "scripts": { 7 | "lint:md": "wp-scripts lint-md-docs" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/lint-grammar.yml: -------------------------------------------------------------------------------- 1 | name: Lint Grammar 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | lint-grammar: 8 | timeout-minutes: 5 9 | runs-on: ubuntu-latest 10 | steps: 11 | - run: echo "Foobar" 12 | -------------------------------------------------------------------------------- /.github/workflows/_lint-docs.yml: -------------------------------------------------------------------------------- 1 | name: Lint documentation 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.md' 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | wp-scripts-lint: 12 | uses: inpsyde/reusable-workflows/.github/workflows/wp-scripts-lint.yml@main 13 | with: 14 | LINT_TOOLS: '["md-docs"]' 15 | -------------------------------------------------------------------------------- /docs/lint-workflows.md: -------------------------------------------------------------------------------- 1 | # Lint GitHub Actions workflows 2 | 3 | This workflow runs [actionlint](https://github.com/rhysd/actionlint). It does so by executing the 4 | linter inside a Docker container using the official `actionlint` Docker image. 5 | 6 | **Example:** 7 | 8 | ```yml 9 | name: Lint GitHub Actions workflows 10 | on: 11 | pull_request: 12 | jobs: 13 | lint-workflows: 14 | uses: inpsyde/reusable-workflows/.github/workflows/lint-workflows.yml@main 15 | ``` 16 | -------------------------------------------------------------------------------- /.github/workflows/lint-workflows.yml: -------------------------------------------------------------------------------- 1 | name: Lint GitHub Actions workflows 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.yml' 7 | pull_request: 8 | workflow_dispatch: 9 | workflow_call: 10 | 11 | jobs: 12 | actionlint: 13 | timeout-minutes: 5 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up problem matchers for actionlint 20 | run: | 21 | curl -s -o actionlint-matcher.json https://raw.githubusercontent.com/rhysd/actionlint/main/.github/actionlint-matcher.json 22 | echo "::add-matcher::${GITHUB_WORKSPACE}/actionlint-matcher.json" 23 | 24 | - name: Run actionlint 25 | uses: docker://rhysd/actionlint:latest 26 | with: 27 | args: -color -pyflakes= -shellcheck= 28 | -------------------------------------------------------------------------------- /templates/automatic-release/release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "branches": [ 3 | "main", 4 | "next", 5 | { 6 | "name": "beta", 7 | "prerelease": true 8 | }, 9 | { 10 | "name": "alpha", 11 | "prerelease": true 12 | } 13 | ], 14 | "plugins": [ 15 | "@semantic-release/commit-analyzer", 16 | "@semantic-release/release-notes-generator", 17 | "@semantic-release/changelog", 18 | ["@semantic-release/npm", { 19 | "tarballDir": "release", 20 | "npmPublish": false 21 | }], 22 | ["@semantic-release/exec", { 23 | "prepareCmd": "[ -f index.php ] || [ -f style.css ] && sed -i \"s/Version:.*/Version: ${nextRelease.version}/\" index.php style.css || true", 24 | }], 25 | "@semantic-release/github", 26 | ["@semantic-release/git", { 27 | "assets": ["CHANGELOG.md", "package-lock.json", "package.json", "composer.json", "style.css", "index.php"], 28 | "message": "chore(release): \${nextRelease.version} [skip ci]\n\n\${nextRelease.notes}" 29 | }] 30 | ], 31 | "preset": "angular", 32 | "tagFormat": "${version}" 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/tests-unit-php.yml: -------------------------------------------------------------------------------- 1 | name: Unit tests PHP 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | PHP_VERSION: 7 | description: PHP version with which the scripts are executed. 8 | default: '8.2' 9 | required: false 10 | type: string 11 | COMPOSER_ARGS: 12 | description: Set of arguments passed to Composer. 13 | default: '--prefer-dist' 14 | required: false 15 | type: string 16 | PHPUNIT_ARGS: 17 | description: Set of arguments passed to PHPUnit. 18 | default: '--coverage-text' 19 | required: false 20 | type: string 21 | secrets: 22 | COMPOSER_AUTH_JSON: 23 | description: Authentication for privately hosted packages and repositories as a JSON formatted object. 24 | required: false 25 | ENV_VARS: 26 | description: Additional environment variables as a JSON formatted object. 27 | required: false 28 | 29 | jobs: 30 | tests-unit-php: 31 | timeout-minutes: 5 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | 37 | - name: Set up custom environment variables 38 | env: 39 | ENV_VARS: ${{ secrets.ENV_VARS }} 40 | if: ${{ env.ENV_VARS }} 41 | uses: actions/github-script@v7 42 | with: 43 | script: | 44 | JSON 45 | .parse(process.env.ENV_VARS) 46 | .forEach(envVar => core.exportVariable(envVar.name, envVar.value)); 47 | 48 | - name: Set up PHP 49 | uses: shivammathur/setup-php@v2 50 | with: 51 | php-version: ${{ inputs.PHP_VERSION }} 52 | 53 | - name: Set up problem matchers for PHPUnit 54 | run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 55 | 56 | - name: Install Composer dependencies 57 | uses: ramsey/composer-install@v3 58 | env: 59 | COMPOSER_AUTH: '${{ secrets.COMPOSER_AUTH_JSON }}' 60 | with: 61 | composer-options: ${{ inputs.COMPOSER_ARGS }} 62 | 63 | - name: Run PHPUnit 64 | run: ./vendor/bin/phpunit ${{ inputs.PHPUNIT_ARGS }} 65 | -------------------------------------------------------------------------------- /.github/workflows/lint-php.yml: -------------------------------------------------------------------------------- 1 | name: Lint PHP 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | PHP_VERSION: 7 | description: PHP version with which the scripts are executed. 8 | default: '8.2' 9 | required: false 10 | type: string 11 | COMPOSER_ARGS: 12 | description: Set of arguments passed to Composer. 13 | default: '--prefer-dist' 14 | required: false 15 | type: string 16 | LINT_ARGS: 17 | description: Set of arguments passed to PHP Parallel Lint. 18 | default: '. -e php --colors --show-deprecated' 19 | required: false 20 | type: string 21 | COMPOSER_DEPS_INSTALL: 22 | description: Whether or not to install Composer dependencies before linting. 23 | type: boolean 24 | default: false 25 | required: false 26 | secrets: 27 | COMPOSER_AUTH_JSON: 28 | description: Authentication for privately hosted packages and repositories as a JSON formatted object. 29 | required: false 30 | ENV_VARS: 31 | description: Additional environment variables as a JSON formatted object. 32 | required: false 33 | 34 | jobs: 35 | lint-php: 36 | timeout-minutes: 5 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v4 41 | 42 | - name: Set up custom environment variables 43 | env: 44 | ENV_VARS: ${{ secrets.ENV_VARS }} 45 | if: ${{ env.ENV_VARS }} 46 | uses: actions/github-script@v7 47 | with: 48 | script: | 49 | JSON 50 | .parse(process.env.ENV_VARS) 51 | .forEach(envVar => core.exportVariable(envVar.name, envVar.value)); 52 | 53 | - name: Set up PHP 54 | uses: shivammathur/setup-php@v2 55 | with: 56 | php-version: ${{ inputs.PHP_VERSION }} 57 | tools: cs2pr, parallel-lint 58 | coverage: none 59 | 60 | - name: Install Composer dependencies 61 | if: ${{ inputs.COMPOSER_DEPS_INSTALL }} 62 | uses: ramsey/composer-install@v3 63 | env: 64 | COMPOSER_AUTH: '${{ secrets.COMPOSER_AUTH_JSON }}' 65 | with: 66 | composer-options: ${{ inputs.COMPOSER_ARGS }} 67 | 68 | - name: Run PHP lint check 69 | run: parallel-lint ${{ inputs.LINT_ARGS }} --checkstyle | cs2pr 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Syde Reusable Workflows 2 | 3 | ## Introduction to GitHub Actions 4 | 5 | With [GitHub Actions](https://github.com/features/actions), you can create custom workflows for the 6 | software development lifecycle directly in your GitHub repository. These workflows consist of 7 | different tasks, called actions, that can be executed automatically when certain events occur. 8 | 9 | At Syde, we use GitHub Actions for a wide range of tasks. From various quality assurance tests ( 10 | e.g., static analysis checks, PHPUnit tests, etc.), to asset (pre)compilation 11 | with [Composer Asset Compiler](https://github.com/inpsyde/composer-asset-compiler), release 12 | generation, deployments (CI/CD), and container registry management: all automatable, recurring tasks 13 | are performed in GitHub Actions. 14 | 15 | ## About reusable workflows 16 | 17 | To avoid code duplication of GitHub Actions workflow files across thousands of repositories, we 18 | utilize [reusable workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows). 19 | This allows us to DRY (don't repeat yourself) configurations so we don't have to copy and paste 20 | workflows from one repository to another. 21 | 22 | ## Calling reusable workflows 23 | 24 | In the calling workflow file, use the `uses` property to specify the location and version of a 25 | reusable workflow file to run as a job. 26 | 27 | ```yml 28 | name: {Job name} 29 | on: 30 | pull_request: 31 | jobs: 32 | {topic}-{workflow}: 33 | uses: inpsyde/reusable-workflows/.github/workflows/{topic}-{workflow}.yml@main 34 | ``` 35 | 36 | Please note that the individual workflows have different (optional) parameters that you can pass 37 | either as `input`s or `secret`s. To learn more, consult the documentation of the individual workflow 38 | groups here: 39 | 40 | * Assets linting and formatting with [`@wordpress/scripts`](./docs/wp-scripts.md) 41 | * Linting, formatting, and testing tools for [PHP](./docs/php.md) 42 | * [Linting GitHub Actions workflow files](./docs/lint-workflows.md) 43 | * Unit tests for [JavaScript](./docs/js.md) 44 | * Assets compilation with [Composer Asset Compiler](./docs/assets-compilation.md) or 45 | the [Build and push](./docs/build-and-push-assets.md) approach 46 | * [Create plugin archive](./docs/archive-creation.md) 47 | * [Automatic release](./docs/automatic-release.md) 48 | * [DDEV Playwright](./docs/ddev-playwright.md) 49 | 50 | **Note:** 51 | 52 | Workflow files prefixed with `_` are specific to the repository and cannot be reused. 53 | -------------------------------------------------------------------------------- /.github/workflows/coding-standards-php.yml: -------------------------------------------------------------------------------- 1 | name: Coding standards analysis PHP 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | PHP_VERSION: 7 | description: PHP version with which the scripts are executed. 8 | default: '8.2' 9 | required: false 10 | type: string 11 | COMPOSER_ARGS: 12 | description: Set of arguments passed to Composer. 13 | default: '--prefer-dist' 14 | required: false 15 | type: string 16 | PHPCS_ARGS: 17 | description: Set of arguments passed to PHP_CodeSniffer. 18 | default: '--report-full --report-checkstyle=./phpcs-report.xml' 19 | required: false 20 | type: string 21 | CS2PR_ARGS: 22 | description: Set of arguments passed to cs2pr. 23 | default: '--graceful-warnings ./phpcs-report.xml' 24 | required: false 25 | type: string 26 | secrets: 27 | COMPOSER_AUTH_JSON: 28 | description: Authentication for privately hosted packages and repositories as a JSON formatted object. 29 | required: false 30 | ENV_VARS: 31 | description: Additional environment variables as a JSON formatted object. 32 | required: false 33 | 34 | jobs: 35 | coding-standards-php: 36 | timeout-minutes: 5 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v4 41 | 42 | - name: Set up custom environment variables 43 | env: 44 | ENV_VARS: ${{ secrets.ENV_VARS }} 45 | if: ${{ env.ENV_VARS }} 46 | uses: actions/github-script@v7 47 | with: 48 | script: | 49 | JSON 50 | .parse(process.env.ENV_VARS) 51 | .forEach(envVar => core.exportVariable(envVar.name, envVar.value)); 52 | 53 | - name: Set up PHP 54 | uses: shivammathur/setup-php@v2 55 | with: 56 | php-version: ${{ inputs.PHP_VERSION }} 57 | tools: composer, cs2pr 58 | coverage: none 59 | 60 | - name: Validate composer.json and composer.lock 61 | run: composer validate 62 | 63 | - name: Install Composer dependencies 64 | uses: ramsey/composer-install@v3 65 | env: 66 | COMPOSER_AUTH: '${{ secrets.COMPOSER_AUTH_JSON }}' 67 | with: 68 | composer-options: ${{ inputs.COMPOSER_ARGS }} 69 | 70 | - name: Run PHP_CodeSniffer 71 | run: ./vendor/bin/phpcs ${{ inputs.PHPCS_ARGS }} 72 | 73 | - name: Annotate PHP_CodeSniffer report 74 | if: ${{ always() }} 75 | run: cs2pr ${{ inputs.CS2PR_ARGS }} 76 | -------------------------------------------------------------------------------- /.github/workflows/static-analysis-php.yml: -------------------------------------------------------------------------------- 1 | name: Static code analysis PHP 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | PHP_VERSION: 7 | description: PHP version with which the scripts are executed. 8 | default: '8.2' 9 | required: false 10 | type: string 11 | COMPOSER_ARGS: 12 | description: Set of arguments passed to Composer. 13 | default: '--prefer-dist' 14 | required: false 15 | type: string 16 | PSALM_ARGS: 17 | description: Set of arguments passed to Psalm. 18 | default: '--output-format=github --no-cache' 19 | required: false 20 | type: string 21 | PHPSTAN_ARGS: 22 | description: Set of arguments passed to PHPStan. 23 | default: '--no-progress --memory-limit=1G' 24 | required: false 25 | type: string 26 | secrets: 27 | COMPOSER_AUTH_JSON: 28 | description: Authentication for privately hosted packages and repositories as a JSON formatted object. 29 | required: false 30 | ENV_VARS: 31 | description: Additional environment variables as a JSON formatted object. 32 | required: false 33 | 34 | jobs: 35 | static-analysis-php: 36 | timeout-minutes: 5 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v4 41 | 42 | - name: Set up custom environment variables 43 | env: 44 | ENV_VARS: ${{ secrets.ENV_VARS }} 45 | if: ${{ env.ENV_VARS }} 46 | uses: actions/github-script@v7 47 | with: 48 | script: | 49 | JSON 50 | .parse(process.env.ENV_VARS) 51 | .forEach(envVar => core.exportVariable(envVar.name, envVar.value)); 52 | 53 | - name: Set up PHP 54 | uses: shivammathur/setup-php@v2 55 | with: 56 | php-version: ${{ inputs.PHP_VERSION }} 57 | tools: composer, cs2pr 58 | coverage: none 59 | 60 | - name: Validate composer.json and composer.lock 61 | run: composer validate 62 | 63 | - name: Install Composer dependencies 64 | uses: ramsey/composer-install@v3 65 | env: 66 | COMPOSER_AUTH: '${{ secrets.COMPOSER_AUTH_JSON }}' 67 | with: 68 | composer-options: ${{ inputs.COMPOSER_ARGS }} 69 | 70 | - name: Run Psalm 71 | if: ${{ hashFiles('psalm.xml', 'psalm.xml.dist') != '' }} 72 | run: ./vendor/bin/psalm --php-version="${{ inputs.PHP_VERSION }}" ${{ inputs.PSALM_ARGS }} 73 | 74 | - name: Run PHPStan 75 | if: ${{ hashFiles('phpstan.dist.neon', 'phpstan.neon', 'phpstan.neon.dist') != '' }} 76 | run: ./vendor/bin/phpstan ${{ inputs.PHPSTAN_ARGS }} 77 | -------------------------------------------------------------------------------- /docs/assets-compilation.md: -------------------------------------------------------------------------------- 1 | # Assets compilation 2 | 3 | > [!CAUTION] 4 | > This workflow is deprecated and will be removed soon. Use `.github/workflows/build-and-distribute.yml` ([documentation](build-and-distribute.md)) instead. 5 | 6 | This workflow utilizes 7 | the [Composer Asset Compiler](https://github.com/inpsyde/composer-asset-compiler) to compile assets. 8 | For details, refer 9 | to [Pre-compilation](https://github.com/inpsyde/composer-asset-compiler#pre-compilation). 10 | 11 | **Simplest possible example:** 12 | 13 | ```yml 14 | name: Assets compilation 15 | on: 16 | schedule: 17 | - cron: '0 0 * * 0' 18 | jobs: 19 | assets-compilation: 20 | uses: inpsyde/reusable-workflows/.github/workflows/build-assets-compilation.yml@main 21 | ``` 22 | 23 | ## Configuration parameters 24 | 25 | ### Inputs 26 | 27 | | Name | Default | Description | 28 | |-----------------------|---------------------------------|-----------------------------------------------------------------| 29 | | `NODE_OPTIONS` | `''` | Space-separated list of command-line Node options | 30 | | `NODE_VERSION` | `18` | Node version with which the assets will be compiled | 31 | | `NPM_REGISTRY_DOMAIN` | `'https://npm.pkg.github.com/'` | Domain of the private npm registry | 32 | | `PHP_VERSION` | `'8.2'` | PHP version with which the assets compilation is to be executed | 33 | | `COMPOSER_ARGS` | `'--prefer-dist'` | Set of arguments passed to Composer | 34 | | `COMPILE_ASSETS_ARGS` | `'-v --env=root'` | Set of arguments passed to Composer Asset Compiler | 35 | 36 | ### Secrets 37 | 38 | | Name | Description | 39 | |-----------------------|------------------------------------------------------------------------------------------| 40 | | `COMPOSER_AUTH_JSON` | Authentication for privately hosted packages and repositories as a JSON formatted object | 41 | | `NPM_REGISTRY_TOKEN` | Authentication for the private npm registry | 42 | | `GITHUB_USER_EMAIL` | Email address for the GitHub user configuration | 43 | | `GITHUB_USER_NAME` | Username for the GitHub user configuration | 44 | | `GITHUB_USER_SSH_KEY` | Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME` | 45 | | `ENV_VARS` | Additional environment variables as a JSON formatted object | 46 | 47 | **Example with configuration parameters:** 48 | 49 | ```yml 50 | name: Assets compilation 51 | on: 52 | schedule: 53 | - cron: '0 0 * * 0' 54 | jobs: 55 | assets-compilation: 56 | uses: inpsyde/reusable-workflows/.github/workflows/build-assets-compilation.yml@main 57 | secrets: 58 | COMPOSER_AUTH_JSON: ${{ secrets.COMPOSER_AUTH_JSON }} 59 | NPM_REGISTRY_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 60 | ENV_VARS: >- 61 | [{"name":"EXAMPLE_USERNAME", "value":"${{ secrets.USERNAME }}"}] 62 | with: 63 | COMPILE_ASSETS_ARGS: '-vv --env=root' 64 | NPM_REGISTRY_DOMAIN: 'https://registry.example.com/' 65 | NODE_VERSION: 14 66 | ``` 67 | -------------------------------------------------------------------------------- /docs/wp-scripts.md: -------------------------------------------------------------------------------- 1 | # Reusable workflows – `@wordpress/scripts` 2 | 3 | ## Lint 4 | 5 | This workflow runs [ESLint](https://eslint.org/), [Stylelint](https://stylelint.io/), 6 | and [markdownlint](https://github.com/DavidAnson/markdownlint) wrapped in the [ 7 | `@wordpress/scripts`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) 8 | library. It does so by executing the `wp-scripts` binary in the `./node_modules/.bin/` folder. 9 | 10 | **Simplest possible example:** 11 | 12 | ```yml 13 | name: Static code analysis assets 14 | on: 15 | push: 16 | jobs: 17 | wp-scripts-lint: 18 | uses: inpsyde/reusable-workflows/.github/workflows/wp-scripts-lint.yml@main 19 | ``` 20 | 21 | ### Configuration parameters 22 | 23 | #### Inputs 24 | 25 | | Name | Default | Description | 26 | |-------------------------|---------------------------------|-------------------------------------------------------| 27 | | `NODE_OPTIONS` | `''` | Space-separated list of command-line Node options | 28 | | `NODE_VERSION` | `18` | Node version with which the assets will be compiled | 29 | | `NPM_REGISTRY_DOMAIN` | `'https://npm.pkg.github.com/'` | Domain of the private npm registry | 30 | | `LINT_TOOLS` | `'["js", "style", "md-docs"]'` | Array of checks to be executed by @wordpress/scripts | 31 | | `ESLINT_ARGS` | `''` | Set of arguments passed to `wp-script lint-js` | 32 | | `STYLELINT_ARGS` | `''` | Set of arguments passed to `wp-script lint-style` | 33 | | `MARKDOWNLINT_ARGS` | `''` | Set of arguments passed to `wp-script lint-md-docs` | 34 | | `PACKAGE_JSONLINT_ARGS` | `''` | Set of arguments passed to `wp-scripts lint-pkg-json` | 35 | 36 | > :information_source: **By default, "pkg-json" is not part of the `LINT_TOOLS` input.** 37 | > :information_source: **The `--formatter github` flag is hardcoded into the `wp-script lint-style` 38 | command; it must not be passed via `STYLELINT_ARGS`.** 39 | 40 | #### Secrets 41 | 42 | | Name | Description | 43 | |-----------------------|------------------------------------------------------------------------------| 44 | | `NPM_REGISTRY_TOKEN` | Authentication for the private npm registry | 45 | | `GITHUB_USER_EMAIL` | Email address for the GitHub user configuration | 46 | | `GITHUB_USER_NAME` | Username for the GitHub user configuration | 47 | | `GITHUB_USER_SSH_KEY` | Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME` | 48 | | `ENV_VARS` | Additional environment variables as a JSON formatted object | 49 | 50 | **Example with configuration parameters:** 51 | 52 | ```yml 53 | name: Static code analysis assets 54 | on: 55 | pull_request: 56 | jobs: 57 | wp-scripts-lint-js: 58 | uses: inpsyde/reusable-workflows/.github/workflows/wp-scripts-lint-js.yml@main 59 | secrets: 60 | NPM_REGISTRY_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 61 | ENV_VARS: >- 62 | [{"name":"EXAMPLE_USERNAME", "value":"${{ secrets.USERNAME }}"}] 63 | with: 64 | NODE_VERSION: 18 65 | ESLINT_ARGS: '-o eslint_report.json -f json' 66 | STYLELINT_ARGS: '"./resources/**/*.scss"' 67 | ``` 68 | 69 | --- 70 | **Note:** 71 | 72 | Stylelint [requires quotes](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/#lint-style) 73 | around file glob patterns. 74 | 75 | --- 76 | -------------------------------------------------------------------------------- /.github/workflows/static-analysis-js.yml: -------------------------------------------------------------------------------- 1 | name: Static code analysis JavaScript 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | NPM_REGISTRY_DOMAIN: 7 | description: Domain of the private npm registry. 8 | default: https://npm.pkg.github.com/ 9 | required: false 10 | type: string 11 | NODE_VERSION: 12 | description: Node version with which the static analysis is to be executed. 13 | default: 18 14 | required: false 15 | type: string 16 | NODE_OPTIONS: 17 | description: Space-separated list of command-line Node options. 18 | type: string 19 | default: '' 20 | required: false 21 | secrets: 22 | NPM_REGISTRY_TOKEN: 23 | description: Authentication for the private npm registry. 24 | required: false 25 | GITHUB_USER_EMAIL: 26 | description: Email address for the GitHub user configuration. 27 | required: false 28 | GITHUB_USER_NAME: 29 | description: Username for the GitHub user configuration. 30 | required: false 31 | GITHUB_USER_SSH_KEY: 32 | description: Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME`. 33 | required: false 34 | ENV_VARS: 35 | description: Additional environment variables as a JSON formatted object. 36 | required: false 37 | 38 | jobs: 39 | static-analysis-js: 40 | timeout-minutes: 5 41 | runs-on: ubuntu-latest 42 | env: 43 | NODE_CACHE_MODE: '' 44 | steps: 45 | - name: Checkout 46 | uses: actions/checkout@v4 47 | 48 | - name: Set up custom environment variables 49 | env: 50 | ENV_VARS: ${{ secrets.ENV_VARS }} 51 | if: ${{ env.ENV_VARS }} 52 | uses: actions/github-script@v7 53 | with: 54 | script: | 55 | JSON 56 | .parse(process.env.ENV_VARS) 57 | .forEach(envVar => core.exportVariable(envVar.name, envVar.value)); 58 | 59 | - name: Set up SSH 60 | env: 61 | GITHUB_USER_SSH_KEY: ${{ secrets.GITHUB_USER_SSH_KEY }} 62 | if: ${{ env.GITHUB_USER_SSH_KEY != '' }} 63 | uses: webfactory/ssh-agent@v0.9.1 64 | with: 65 | ssh-private-key: ${{ env.GITHUB_USER_SSH_KEY }} 66 | 67 | - name: Set up Git 68 | env: 69 | GITHUB_USER_EMAIL: ${{ secrets.GITHUB_USER_EMAIL }} 70 | GITHUB_USER_NAME: ${{ secrets.GITHUB_USER_NAME }} 71 | if: ${{ env.GITHUB_USER_EMAIL != '' && env.GITHUB_USER_NAME != '' }} 72 | run: | 73 | git config --global user.email "${{ env.GITHUB_USER_EMAIL }}" 74 | git config --global user.name "${{ env.GITHUB_USER_NAME }}" 75 | 76 | - name: Set up node cache mode 77 | run: | 78 | if [ -f "${GITHUB_WORKSPACE}/package-lock.json" ] || [ -f "${GITHUB_WORKSPACE}/npm-shrinkwrap.json" ]; then 79 | echo "NODE_CACHE_MODE=npm" >> $GITHUB_ENV 80 | else 81 | echo "No lock files found" 82 | fi 83 | 84 | - name: Set up node 85 | uses: actions/setup-node@v4 86 | env: 87 | NODE_OPTIONS: ${{ inputs.NODE_OPTIONS }} 88 | NODE_AUTH_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 89 | with: 90 | node-version: ${{ inputs.NODE_VERSION }} 91 | registry-url: ${{ inputs.NPM_REGISTRY_DOMAIN }} 92 | cache: ${{ env.NODE_CACHE_MODE }} 93 | 94 | - name: Install dependencies 95 | env: 96 | ARGS: ${{ env.NODE_CACHE_MODE == 'npm' && 'ci' || 'install' }} 97 | run: ${{ format('npm {0}', env.ARGS) }} 98 | 99 | - name: Run tsc 100 | run: ./node_modules/.bin/tsc --noEmit --skipLibCheck --pretty false 101 | -------------------------------------------------------------------------------- /docs/automatic-release.md: -------------------------------------------------------------------------------- 1 | # Automatic release 2 | 3 | This workflow utilizes [semantic-release](https://github.com/semantic-release/semantic-release) to 4 | create a package release. Note that you must stick to 5 | their [commit message convention](https://github.com/semantic-release/semantic-release#commit-message-format) 6 | to use it. 7 | 8 | You can provide a `release.config.js` file in your repository to create a custom release that uses 9 | the following semantic-release plugins: 10 | 11 | - [git](https://github.com/semantic-release/git) 12 | - [npm](https://github.com/semantic-release/npm) 13 | - [exec](https://github.com/semantic-release/exec) 14 | 15 | Otherwise, the workflow will create the release with 16 | a [standard set of configurations](../templates/automatic-release/release.config.js), updating the 17 | version in the following files: 18 | 19 | - `CHANGELOG.md` 20 | - `composer.json` 21 | - `package-lock.json` 22 | - `package.json` 23 | - `style.css` or the main plugin file (automatically discovered by the workflow) 24 | 25 | By default, every push to the `main` and `next` branches will release a stable version, and every 26 | push to the `alpha` and `beta` branches will create a pre-release version. If you want to use a 27 | different configuration, please provide your custom `release.config.js` file. 28 | 29 | **Simplest possible example:** 30 | 31 | ```yml 32 | name: Release 33 | on: 34 | push: 35 | branches: 36 | - main 37 | - beta 38 | - alpha 39 | jobs: 40 | release: 41 | uses: inpsyde/reusable-workflows/.github/workflows/automatic-release.yml@main 42 | ``` 43 | 44 | ## Configuration parameters 45 | 46 | ### Inputs 47 | 48 | | Name | Required | Default | Description | 49 | |-----------------------|----------|---------------------------------|------------------------------------| 50 | | `NPM_REGISTRY_DOMAIN` | false | `'https://npm.pkg.github.com/'` | Domain of the private npm registry | 51 | 52 | ### Secrets 53 | 54 | | Name | Required | Default | Description | 55 | |------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------| 56 | | `NPM_REGISTRY_TOKEN` | false | `''` | Authentication for the private npm registry | 57 | | `GITHUB_USER_EMAIL` | false | `''` | Email address for the GitHub user configuration | 58 | | `GITHUB_USER_NAME` | false | `''` | Username for the GitHub user configuration | 59 | | `GITHUB_USER_SSH_KEY` | false | `''` | Private SSH key associated with the GitHub user for the token passed as `GITHUB_USER_TOKEN` | 60 | | `GITHUB_USER_SSH_PUBLIC_KEY` | false | `''` | Public SSH key associated with the GitHub user for the token passed as `GITHUB_USER_TOKEN` | 61 | | `GITHUB_USER_TOKEN` | false | `''` | Authentication token with write permission needed by the release bot (falls back to `GITHUB_TOKEN`) | 62 | 63 | **Example with configuration parameters:** 64 | 65 | ```yml 66 | name: Release 67 | on: 68 | push: 69 | branches: 70 | - main 71 | - alpha 72 | jobs: 73 | release: 74 | uses: inpsyde/reusable-workflows/.github/workflows/automatic-release.yml@main 75 | secrets: 76 | GITHUB_USER_TOKEN: ${{ secrets.DEPLOYBOT_REPO_READ_WRITE_TOKEN }} 77 | ``` 78 | 79 | **Example with custom GitHub user and signed commits using SSH key:** 80 | 81 | ```yml 82 | name: Release 83 | on: 84 | push: 85 | branches: 86 | - main 87 | - alpha 88 | jobs: 89 | release: 90 | uses: inpsyde/reusable-workflows/.github/workflows/automatic-release.yml@main 91 | secrets: 92 | GITHUB_USER_EMAIL: ${{ secrets.DEPLOYBOT_EMAIL }} 93 | GITHUB_USER_NAME: ${{ secrets.DEPLOYBOT_USER }} 94 | GITHUB_USER_SSH_KEY: ${{ secrets.DEPLOYBOT_SSH_PRIVATE_KEY }} 95 | GITHUB_USER_SSH_PUBLIC_KEY: ${{ secrets.DEPLOYBOT_SSH_PUBLIC_KEY }} 96 | GITHUB_USER_TOKEN: ${{ secrets.DEPLOYBOT_REPO_READ_WRITE_TOKEN }} 97 | ``` 98 | -------------------------------------------------------------------------------- /.github/workflows/tests-unit-js.yml: -------------------------------------------------------------------------------- 1 | name: Unit tests JavaScript 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | NPM_REGISTRY_DOMAIN: 7 | description: Domain of the private npm registry. 8 | default: https://npm.pkg.github.com/ 9 | required: false 10 | type: string 11 | NODE_VERSION: 12 | description: Node version with which the unit tests are to be executed. 13 | default: 18 14 | required: false 15 | type: string 16 | JEST_ARGS: 17 | description: Set of arguments passed to Jest. 18 | default: '--reporters=default --reporters=github-actions' 19 | required: false 20 | type: string 21 | PACKAGE_MANAGER: 22 | description: Package manager with which the dependencies should be installed (`npm` or `yarn`). 23 | default: 'npm' 24 | required: false 25 | type: string 26 | NODE_OPTIONS: 27 | description: Space-separated list of command-line Node options. 28 | type: string 29 | default: '' 30 | required: false 31 | secrets: 32 | NPM_REGISTRY_TOKEN: 33 | description: Authentication for the private npm registry. 34 | required: false 35 | GITHUB_USER_EMAIL: 36 | description: Email address for the GitHub user configuration. 37 | required: false 38 | GITHUB_USER_NAME: 39 | description: Username for the GitHub user configuration. 40 | required: false 41 | GITHUB_USER_SSH_KEY: 42 | description: Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME`. 43 | required: false 44 | ENV_VARS: 45 | description: Additional environment variables as a JSON formatted object. 46 | required: false 47 | 48 | jobs: 49 | tests-unit-js: 50 | timeout-minutes: 5 51 | runs-on: ubuntu-latest 52 | env: 53 | NODE_CACHE_MODE: '' 54 | steps: 55 | - name: PACKAGE_MANAGER deprecation warning 56 | if: ${{ inputs.PACKAGE_MANAGER != '' }} 57 | run: | 58 | if [ "${{ inputs.PACKAGE_MANAGER }}" == 'npm' ]; then 59 | echo "::warning::The PACKAGE_MANAGER input is deprecated and will be removed soon. Please remove it. The workflow already uses npm by default." 60 | else 61 | echo "::warning::The PACKAGE_MANAGER input is deprecated and will be removed soon. Please update your workflow to use npm." 62 | fi 63 | 64 | - name: Checkout 65 | uses: actions/checkout@v4 66 | 67 | - name: Set up custom environment variables 68 | env: 69 | ENV_VARS: ${{ secrets.ENV_VARS }} 70 | if: ${{ env.ENV_VARS }} 71 | uses: actions/github-script@v7 72 | with: 73 | script: | 74 | JSON 75 | .parse(process.env.ENV_VARS) 76 | .forEach(envVar => core.exportVariable(envVar.name, envVar.value)); 77 | 78 | - name: Set up SSH 79 | env: 80 | GITHUB_USER_SSH_KEY: ${{ secrets.GITHUB_USER_SSH_KEY }} 81 | if: ${{ env.GITHUB_USER_SSH_KEY != '' }} 82 | uses: webfactory/ssh-agent@v0.9.1 83 | with: 84 | ssh-private-key: ${{ env.GITHUB_USER_SSH_KEY }} 85 | 86 | - name: Set up Git 87 | env: 88 | GITHUB_USER_EMAIL: ${{ secrets.GITHUB_USER_EMAIL }} 89 | GITHUB_USER_NAME: ${{ secrets.GITHUB_USER_NAME }} 90 | if: ${{ env.GITHUB_USER_EMAIL != '' && env.GITHUB_USER_NAME != '' }} 91 | run: | 92 | git config --global user.email "${{ env.GITHUB_USER_EMAIL }}" 93 | git config --global user.name "${{ env.GITHUB_USER_NAME }}" 94 | 95 | - name: Set up node cache mode 96 | run: | 97 | if [ "${{ inputs.PACKAGE_MANAGER }}" == 'npm' ] && { [ -f "${GITHUB_WORKSPACE}/package-lock.json" ] || [ -f "${GITHUB_WORKSPACE}/npm-shrinkwrap.json" ]; }; then 98 | echo "NODE_CACHE_MODE=npm" >> $GITHUB_ENV 99 | elif [ "${{ inputs.PACKAGE_MANAGER }}" == 'yarn' ] && [ -f "${GITHUB_WORKSPACE}/yarn.lock" ]; then 100 | echo "NODE_CACHE_MODE=yarn" >> $GITHUB_ENV 101 | else 102 | echo "No lock files found or unknown package manager" 103 | fi 104 | 105 | - name: Set up node 106 | uses: actions/setup-node@v4 107 | env: 108 | NODE_OPTIONS: ${{ inputs.NODE_OPTIONS }} 109 | NODE_AUTH_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 110 | with: 111 | node-version: ${{ inputs.NODE_VERSION }} 112 | registry-url: ${{ inputs.NPM_REGISTRY_DOMAIN }} 113 | cache: ${{ env.NODE_CACHE_MODE }} 114 | 115 | - name: Install dependencies 116 | env: 117 | ARGS: ${{ env.NODE_CACHE_MODE == 'yarn' && '--frozen-lockfile' || env.NODE_CACHE_MODE == 'npm' && 'ci' || 'install' }} 118 | run: ${{ format('{0} {1}', inputs.PACKAGE_MANAGER, env.ARGS) }} 119 | 120 | - name: Run Jest 121 | run: ./node_modules/.bin/jest ${{ inputs.JEST_ARGS }} 122 | -------------------------------------------------------------------------------- /docs/js.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Reusable workflows – JavaScript 4 | 5 | ## Unit tests JavaScript 6 | 7 | This workflow runs [Jest](https://jestjs.io/). It does so by executing the binary in the 8 | `./node_modules/.bin/` folder. 9 | 10 | **Simplest possible example:** 11 | 12 | ```yml 13 | name: Unit tests JavaScript 14 | on: 15 | pull_request: 16 | jobs: 17 | tests-unit-js: 18 | uses: inpsyde/reusable-workflows/.github/workflows/tests-unit-js.yml@main 19 | ``` 20 | 21 | ### Configuration parameters 22 | 23 | #### Inputs 24 | 25 | | Name | Default | Description | 26 | |-----------------------|----------------------------------------------------|-----------------------------------------------------------| 27 | | `NPM_REGISTRY_DOMAIN` | `'https://npm.pkg.github.com/'` | Domain of the private npm registry | 28 | | `NODE_VERSION` | `18` | Node version with which the unit tests are to be executed | 29 | | `JEST_ARGS` | `'--reporters=default --reporters=github-actions'` | Set of arguments passed to Jest | 30 | 31 | **Note**: The default `github-actions` reporter requires Jest 28 or higher. 32 | 33 | #### Secrets 34 | 35 | | Name | Description | 36 | |-----------------------|------------------------------------------------------------------------------| 37 | | `NPM_REGISTRY_TOKEN` | Authentication for the private npm registry | 38 | | `GITHUB_USER_EMAIL` | Email address for the GitHub user configuration | 39 | | `GITHUB_USER_NAME` | Username for the GitHub user configuration | 40 | | `GITHUB_USER_SSH_KEY` | Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME` | 41 | | `ENV_VARS` | Additional environment variables as a JSON formatted object | 42 | 43 | **Example with configuration parameters:** 44 | 45 | ```yml 46 | name: Unit tests JavaScript 47 | on: 48 | pull_request: 49 | jobs: 50 | tests-unit-js: 51 | uses: inpsyde/reusable-workflows/.github/workflows/tests-unit-js.yml@main 52 | secrets: 53 | NPM_REGISTRY_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 54 | ENV_VARS: >- 55 | [{"name":"EXAMPLE_USERNAME", "value":"${{ secrets.USERNAME }}"}] 56 | with: 57 | NODE_VERSION: 14 58 | JEST_ARGS: 'my-test --reporters=jest-junit --coverage' 59 | ``` 60 | 61 | ## Static analysis JavaScript 62 | 63 | This workflow runs 64 | the [TypeScript compiler](https://www.typescriptlang.org/docs/handbook/compiler-options.html) with 65 | the `--noEmit` argument. It does so by executing the `tsc` binary in the `./node_modules/.bin/` 66 | folder. 67 | 68 | **Simplest possible example:** 69 | 70 | ```yml 71 | name: Static code analysis JavaScript 72 | on: 73 | push: 74 | jobs: 75 | static-analysis-js: 76 | uses: inpsyde/reusable-workflows/.github/workflows/static-analysis-js.yml@main 77 | ``` 78 | 79 | ### Configuration parameters 80 | 81 | #### Inputs 82 | 83 | | Name | Default | Description | 84 | |-----------------------|---------------------------------|-----------------------------------------------------| 85 | | `NODE_OPTIONS` | `''` | Space-separated list of command-line Node options | 86 | | `NODE_VERSION` | `18` | Node version with which the assets will be compiled | 87 | | `NPM_REGISTRY_DOMAIN` | `'https://npm.pkg.github.com/'` | Domain of the private npm registry | 88 | 89 | #### Secrets 90 | 91 | | Name | Description | 92 | |-----------------------|------------------------------------------------------------------------------| 93 | | `NPM_REGISTRY_TOKEN` | Authentication for the private npm registry | 94 | | `GITHUB_USER_EMAIL` | Email address for the GitHub user configuration | 95 | | `GITHUB_USER_NAME` | Username for the GitHub user configuration | 96 | | `GITHUB_USER_SSH_KEY` | Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME` | 97 | | `ENV_VARS` | Additional environment variables as a JSON formatted object | 98 | 99 | **Example with configuration parameters:** 100 | 101 | ```yml 102 | name: Static code analysis JavaScript 103 | on: 104 | pull_request: 105 | jobs: 106 | static-analysis-js: 107 | uses: inpsyde/reusable-workflows/.github/workflows/static-analysis-js.yml@main 108 | secrets: 109 | NPM_REGISTRY_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 110 | ENV_VARS: >- 111 | [{"name":"EXAMPLE_USERNAME", "value":"${{ secrets.USERNAME }}"}] 112 | with: 113 | NODE_VERSION: 18 114 | ``` 115 | -------------------------------------------------------------------------------- /docs/test-playwright.md: -------------------------------------------------------------------------------- 1 | # Playwright test 2 | 3 | This workflow executes Playwright-based tests in a controlled and isolated environment via GitHub Actions. 4 | 5 | The workflow can: 6 | 7 | - execute a building step, both for node and PHP environments (if the PHP version is provided and a `composer.json` file is present) 8 | - create an environment variables file named `.env.ci` dedicated to the test step; load this file using `dotenv-ci` directly in your test script, e.g., `./node_modules/.bin/dotenv -e .env.ci -- npm run e2e` 9 | - execute the tests using Playwright 10 | - upload the artifacts 11 | 12 | **Simplest possible example:** 13 | 14 | ```yml 15 | name: E2E Testing 16 | 17 | on: 18 | workflow_dispatch: 19 | jobs: 20 | e2e-playwright: 21 | uses: inpsyde/reusable-workflows/.github/workflows/test-playwright.yml@main 22 | with: 23 | ARTIFACT_PATH: './artifacts' 24 | SCRIPT_NAME: 'ci-test-e2e' 25 | ``` 26 | 27 | ## Configuration parameters 28 | 29 | ### Inputs 30 | 31 | | Name | Default | Description | 32 | |---------------------------------|---------------------------------|---------------------------------------------------------------------------------------------------| 33 | | `ARTIFACT_INCLUDE_HIDDEN_FILES` | `false` | Whether to include hidden files in the artifact | 34 | | `ARTIFACT_NAME` | `'artifact'` | Name for the artifact | 35 | | `ARTIFACT_OVERWRITE` | `false` | Determine if an artifact with a matching name will be deleted before a new one is uploaded or not | 36 | | `ARTIFACT_PATH` | | A file, directory or wildcard pattern that describes what to upload | 37 | | `ARTIFACT_RETENTION_DAYS` | `30` | Duration after which artifact will expire in day | 38 | | `COMPOSER_DEPS_INSTALL` | `false` | Whether to install Composer dependencies | 39 | | `NODE_VERSION` | `18` | Node version with which the node script will be executed | 40 | | `NPM_REGISTRY_DOMAIN` | `'https://npm.pkg.github.com/'` | Domain of the private npm registry | 41 | | `PHP_VERSION` | `'8.2'` | PHP version with which the dependencies are installed | 42 | | `PLAYWRIGHT_BROWSER_ARGS` | `'--with-deps'` | Set of arguments passed to `npx playwright install` | 43 | | `SCRIPT_NAME` | | The name of a custom script to run the tests | 44 | 45 | ### Secrets 46 | 47 | | Name | Description | 48 | |-----------------------|------------------------------------------------------------------------------------------| 49 | | `ENV_FILE_DATA` | Additional environment variables for the tests | 50 | | `COMPOSER_AUTH_JSON` | Authentication for privately hosted packages and repositories as a JSON formatted object | 51 | | `NPM_REGISTRY_TOKEN` | Authentication for the private npm registry | 52 | | `GITHUB_USER_EMAIL` | Email address for the GitHub user configuration | 53 | | `GITHUB_USER_NAME` | Username for the GitHub user configuration | 54 | | `GITHUB_USER_SSH_KEY` | Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME` | 55 | 56 | **Example with configuration parameters:** 57 | 58 | ```yml 59 | name: E2E Testing 60 | 61 | on: 62 | workflow_dispatch: 63 | jobs: 64 | e2e-playwright: 65 | uses: inpsyde/reusable-workflows/.github/workflows/test-playwright.yml@main 66 | strategy: 67 | matrix: 68 | php: [ '8.2', '8.3' ] 69 | with: 70 | ARTIFACT_PATH: | 71 | artifacts/* 72 | playwright-report/ 73 | ARTIFACT_INCLUDE_HIDDEN_FILES: true 74 | SCRIPT_NAME: 'ci-test-e2e' 75 | COMPOSER_DEPS_INSTALL: true 76 | PHP_VERSION: ${{ matrix.php }} 77 | NODE_VERSION: 20 78 | PLAYWRIGHT_BROWSER_ARGS: 'chromium --with-deps' 79 | secrets: 80 | ENV_FILE_DATA: ${{ secrets.ENV_FILE_DATA }} 81 | COMPOSER_AUTH_JSON: '${{ secrets.PACKAGIST_AUTH_JSON }}' 82 | GITHUB_USER_EMAIL: ${{ secrets.DEPLOYBOT_EMAIL }} 83 | GITHUB_USER_NAME: ${{ secrets.DEPLOYBOT_USER }} 84 | GITHUB_USER_SSH_KEY: ${{ secrets.DEPLOYBOT_SSH_PRIVATE_KEY }} 85 | NPM_REGISTRY_TOKEN: ${{ secrets.DEPLOYBOT_PACKAGES_READ_ACCESS_TOKEN}} 86 | ``` 87 | 88 | **Example of secrets:** 89 | 90 | For `ENV_FILE_DATA`: 91 | 92 | ```SHELL 93 | TEST_EXEC_KEY=YOUR-KEY 94 | WP_BASE_URL=https://example.com 95 | ``` 96 | -------------------------------------------------------------------------------- /docs/archive-creation.md: -------------------------------------------------------------------------------- 1 | # Create plugin archive 2 | 3 | > [!CAUTION] 4 | > This workflow is deprecated and will be removed soon. Use `.github/workflows/build-and-distribute.yml` ([documentation](build-and-distribute.md)) instead. 5 | 6 | This action can be used to create plugin archives in a controlled and isolated environment via 7 | GitHub Actions. 8 | 9 | To achieve that, the reusable workflow: 10 | 11 | 1. Installs dependencies (including dev-dependencies) defined in `composer.json` 12 | 2. Executes [Composer Asset Compiler](https://github.com/inpsyde/composer-asset-compiler) if 13 | configured by the package 14 | 3. Executes [WordPress Translation Downloader](https://github.com/inpsyde/wp-translation-downloader) 15 | if configured by the package 16 | 4. Executes [PHP-Scoper](https://github.com/humbug/php-scoper) if configured by the package 17 | 5. Executes [Rector](https://github.com/rectorphp/rector) if configured by the package 18 | 6. Re-installs dependencies without dev-dependencies 19 | 7. Sets current commit hash and plugin version in the plugin's main file 20 | 8. Runs `wp dist-archive` to create the final archive (with builtin support for a `.distignore` 21 | file) 22 | 9. Uploads it as an artifact for download or further processing 23 | 24 | ## Simple usage example 25 | 26 | ```yml 27 | name: Create release package 28 | on: 29 | workflow_dispatch: 30 | inputs: 31 | PACKAGE_VERSION: 32 | description: 'Package Version' 33 | required: true 34 | jobs: 35 | create_archive: 36 | uses: inpsyde/reusable-workflows/.github/workflows/build-plugin-archive.yml@main 37 | secrets: 38 | COMPOSER_AUTH_JSON: ${{ secrets.PACKAGIST_AUTH_JSON }} 39 | with: 40 | PLUGIN_MAIN_FILE: my-plugin.php 41 | PLUGIN_VERSION: ${{ inputs.PACKAGE_VERSION }} 42 | PRE_SCRIPT: | 43 | echo 'hello world!'; 44 | 45 | ``` 46 | 47 | ## Configuration parameters 48 | 49 | ### Inputs 50 | 51 | | Name | Default | Description | 52 | |-----------------------|---------------------------------------------------------------|------------------------------------------------------------------------------------------------| 53 | | `NODE_OPTIONS` | `''` | Space-separated list of command-line Node options | 54 | | `NODE_VERSION` | `18` | Node version with which the assets will be compiled | 55 | | `NPM_REGISTRY_DOMAIN` | `'https://npm.pkg.github.com/'` | Domain of the private npm registry | 56 | | `COMPOSER_ARGS` | `'--no-dev --no-scripts --prefer-dist --optimize-autoloader'` | Set of arguments passed to Composer when gathering production dependencies | 57 | | `PHP_VERSION` | `'8.2'` | PHP version to use when gathering production dependencies | 58 | | `PHP_VERSION_BUILD` | `'8.2'` | PHP version to use when executing build tools | 59 | | `ARCHIVE_NAME` | `''` | The name of the zip archive (falls back to the repository name) | 60 | | `PLUGIN_MAIN_FILE` | `'index.php'` | The name of the main plugin file | 61 | | `PLUGIN_FOLDER_NAME` | `''` | The name of the plugin folder (falls back to the archive name, if set, or the repository name) | 62 | | `PLUGIN_VERSION` | - | The new plugin version | 63 | | `PRE_SCRIPT` | `''` | Run custom shell code before creating the release archive | 64 | | `COMPILE_ASSETS_ARGS` | `'-v --env=root'` | Set of arguments passed to Composer Asset Compiler | 65 | 66 | #### A note on `PLUGIN_VERSION` 67 | 68 | The workflow will accept any arbitrary string and will use it without validation or sanitization. 69 | Adding this would mean reduced flexibility at increased complexity. Adding 70 | standardization/validation is encouraged but should take place in the controlling workflow, where 71 | the conventions and requirements of the project/team/client are known. 72 | 73 | ## Secrets 74 | 75 | | Name | Description | 76 | |----------------------|------------------------------------------------------------------------------------------| 77 | | `COMPOSER_AUTH_JSON` | Authentication for privately hosted packages and repositories as a JSON formatted object | 78 | | `NPM_REGISTRY_TOKEN` | Authentication for the private npm registry | 79 | | `ENV_VARS` | Additional environment variables as a JSON formatted object | 80 | -------------------------------------------------------------------------------- /.github/workflows/automatic-release.yml: -------------------------------------------------------------------------------- 1 | name: Automatic Release 2 | on: 3 | workflow_call: 4 | inputs: 5 | NPM_REGISTRY_DOMAIN: 6 | description: Domain of the private npm registry. 7 | default: https://npm.pkg.github.com/ 8 | required: false 9 | type: string 10 | secrets: 11 | NPM_REGISTRY_TOKEN: 12 | description: Authentication for the private npm registry. 13 | required: false 14 | GITHUB_USER_EMAIL: 15 | description: Email address for the GitHub user configuration. 16 | required: false 17 | GITHUB_USER_NAME: 18 | description: Username for the GitHub user configuration. 19 | required: false 20 | GITHUB_USER_SSH_KEY: 21 | description: Private SSH key associated with the GitHub user for the token passed as `GITHUB_USER_TOKEN`. 22 | required: false 23 | GITHUB_USER_SSH_PUBLIC_KEY: 24 | description: Public SSH key associated with the GitHub user for the token passed as `GITHUB_USER_TOKEN`. 25 | required: false 26 | GITHUB_USER_TOKEN: 27 | description: Authentication token with write permission needed by the release bot (falls back to `GITHUB_TOKEN`). 28 | required: false 29 | 30 | jobs: 31 | release: 32 | name: Release 33 | timeout-minutes: 5 34 | runs-on: ubuntu-latest 35 | env: 36 | HAS_CONFIG: false 37 | steps: 38 | - name: Fetch semantic-release Node version 39 | uses: actions/checkout@v4 40 | with: 41 | repository: semantic-release/semantic-release 42 | sparse-checkout: | 43 | package.json 44 | sparse-checkout-cone-mode: false 45 | path: semantic-release-repo 46 | 47 | - name: Set up node 48 | uses: actions/setup-node@v4 49 | env: 50 | NODE_AUTH_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 51 | with: 52 | node-version-file: semantic-release-repo/package.json 53 | registry-url: ${{ inputs.NPM_REGISTRY_DOMAIN }} 54 | 55 | - name: Install dependencies 56 | run: | 57 | npm i -g @semantic-release/changelog \ 58 | @semantic-release/git \ 59 | @semantic-release/npm \ 60 | @semantic-release/exec \ 61 | semantic-release \ 62 | conventional-changelog-conventionalcommits 63 | 64 | - name: Checkout 65 | uses: actions/checkout@v4 66 | with: 67 | fetch-depth: 0 68 | persist-credentials: false 69 | ssh-key: ${{ secrets.GITHUB_USER_SSH_KEY }} 70 | 71 | - name: Set up SSH 72 | env: 73 | GITHUB_USER_SSH_KEY: ${{ secrets.GITHUB_USER_SSH_KEY }} 74 | if: ${{ env.GITHUB_USER_SSH_KEY != '' }} 75 | uses: webfactory/ssh-agent@v0.9.1 76 | with: 77 | ssh-private-key: ${{ env.GITHUB_USER_SSH_KEY }} 78 | 79 | - name: Set up signing commits 80 | env: 81 | GITHUB_USER_SSH_PUBLIC_KEY: ${{ secrets.GITHUB_USER_SSH_PUBLIC_KEY }} 82 | if: ${{ env.GITHUB_USER_SSH_PUBLIC_KEY != '' }} 83 | run: | 84 | : # Create empty SSH private key file so Git does not complain. 85 | touch "${{ runner.temp }}/signingkey" 86 | echo "${{ env.GITHUB_USER_SSH_PUBLIC_KEY }}" > "${{ runner.temp }}/signingkey.pub" 87 | git config --global commit.gpgsign true 88 | git config --global gpg.format ssh 89 | git config --global user.signingkey "${{ runner.temp }}/signingkey.pub" 90 | 91 | - name: Check presence of release.config.js 92 | run: | 93 | HAS_CONFIG=$(test -f "release.config.js" && echo true || echo false) 94 | echo "Configuration file release.config.js found: $HAS_CONFIG" 95 | echo "HAS_CONFIG=$HAS_CONFIG" >> $GITHUB_ENV 96 | 97 | - name: Checkout the workflow repository if release.config.js file is not provided 98 | if: ${{ env.HAS_CONFIG == 'false' }} 99 | uses: actions/checkout@v4 100 | with: 101 | repository: inpsyde/reusable-workflows 102 | path: workflow-repo 103 | 104 | - name: Add and customize release.config.js file if not provided 105 | if: ${{ env.HAS_CONFIG == 'false' }} 106 | run: | 107 | cp ${GITHUB_WORKSPACE}/workflow-repo/templates/automatic-release/release.config.js . 108 | FILE=$(find . -maxdepth 1 -type f -name '*.php' -exec grep -l 'Plugin Name:' {} + | xargs -I{} basename {}) || true 109 | [ -n "$FILE" ] && sed "s/index\.php/$FILE/g" -i release.config.js || true 110 | 111 | - name: Remove the workflow repository 112 | if: ${{ env.HAS_CONFIG == 'false' }} 113 | run: | 114 | rm -rf workflow-repo 115 | 116 | - name: Set up release environment variables 117 | env: 118 | GITHUB_USER_EMAIL: ${{ secrets.GITHUB_USER_EMAIL }} 119 | GITHUB_USER_NAME: ${{ secrets.GITHUB_USER_NAME }} 120 | run: | 121 | ${{ env.GITHUB_USER_EMAIL != '' }} && echo "GIT_AUTHOR_EMAIL=${{ env.GITHUB_USER_EMAIL }}" >> $GITHUB_ENV || true 122 | ${{ env.GITHUB_USER_NAME != '' }} && echo "GIT_AUTHOR_NAME=${{ env.GITHUB_USER_NAME }}" >> $GITHUB_ENV || true 123 | ${{ env.GITHUB_USER_EMAIL != '' }} && echo "GIT_COMMITTER_EMAIL=${{ env.GITHUB_USER_EMAIL }}" >> $GITHUB_ENV || true 124 | ${{ env.GITHUB_USER_NAME != '' }} && echo "GIT_COMMITTER_NAME=${{ env.GITHUB_USER_NAME }}" >> $GITHUB_ENV || true 125 | 126 | - name: Release 127 | env: 128 | GITHUB_TOKEN: ${{ secrets.GITHUB_USER_TOKEN != '' && secrets.GITHUB_USER_TOKEN || secrets.GITHUB_TOKEN }} 129 | run: npx semantic-release 130 | 131 | - name: Delete signing key files 132 | env: 133 | GITHUB_USER_SSH_PUBLIC_KEY: ${{ secrets.GITHUB_USER_SSH_PUBLIC_KEY }} 134 | if: ${{ always() && env.GITHUB_USER_SSH_PUBLIC_KEY != '' }} 135 | run: | 136 | rm -f "${{ runner.temp }}/signingkey" 137 | rm -f "${{ runner.temp }}/signingkey.pub" 138 | -------------------------------------------------------------------------------- /.github/workflows/test-playwright.yml: -------------------------------------------------------------------------------- 1 | name: Playwright test 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | ARTIFACT_INCLUDE_HIDDEN_FILES: 7 | description: Whether to include hidden files in the artifact. 8 | type: boolean 9 | default: false 10 | required: false 11 | ARTIFACT_NAME: 12 | description: Name for the artifact. 13 | type: string 14 | default: 'artifact' 15 | required: false 16 | ARTIFACT_OVERWRITE: 17 | description: Determine if an artifact with a matching name will be deleted before a new one is uploaded or not. 18 | type: boolean 19 | default: false 20 | required: false 21 | ARTIFACT_PATH: 22 | description: A file, directory or wildcard pattern that describes what to upload. 23 | type: string 24 | required: true 25 | ARTIFACT_RETENTION_DAYS: 26 | description: Duration after which artifact will expire in day. 27 | type: number 28 | default: 30 29 | required: false 30 | COMPOSER_DEPS_INSTALL: 31 | description: Whether to install Composer dependencies. 32 | type: boolean 33 | default: false 34 | required: false 35 | NODE_VERSION: 36 | description: Node version with which the node script will be executed. 37 | default: 18 38 | required: false 39 | type: string 40 | NPM_REGISTRY_DOMAIN: 41 | description: Domain of the private npm registry. 42 | default: https://npm.pkg.github.com/ 43 | required: false 44 | type: string 45 | PHP_VERSION: 46 | description: PHP version with which the dependencies are installed. 47 | default: '8.2' 48 | required: false 49 | type: string 50 | PLAYWRIGHT_BROWSER_ARGS: 51 | description: Set of arguments passed to `npx playwright install`. 52 | default: '--with-deps' 53 | required: false 54 | type: string 55 | SCRIPT_NAME: 56 | description: The name of a custom script to run the tests. 57 | required: true 58 | type: string 59 | secrets: 60 | ENV_FILE_DATA: 61 | description: Additional environment variables for the tests. 62 | required: false 63 | GITHUB_USER_EMAIL: 64 | description: Email address for the GitHub user configuration. 65 | required: false 66 | GITHUB_USER_NAME: 67 | description: Username for the GitHub user configuration. 68 | required: false 69 | GITHUB_USER_SSH_KEY: 70 | description: Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME`. 71 | required: false 72 | NPM_REGISTRY_TOKEN: 73 | description: Authentication for the private npm registry. 74 | required: false 75 | COMPOSER_AUTH_JSON: 76 | description: Authentication for privately hosted packages and repositories as a JSON formatted object. 77 | required: false 78 | 79 | jobs: 80 | run-playwright-test: 81 | timeout-minutes: 60 82 | runs-on: ubuntu-latest 83 | env: 84 | PHP_CHECK: false 85 | NODE_CACHE_MODE: '' 86 | steps: 87 | - name: Checkout 88 | uses: actions/checkout@v4 89 | 90 | - name: Set up SSH 91 | env: 92 | GITHUB_USER_SSH_KEY: ${{ secrets.GITHUB_USER_SSH_KEY }} 93 | if: ${{ env.GITHUB_USER_SSH_KEY != '' }} 94 | uses: webfactory/ssh-agent@v0.9.1 95 | with: 96 | ssh-private-key: ${{ env.GITHUB_USER_SSH_KEY }} 97 | 98 | - name: Set up Git 99 | env: 100 | GITHUB_USER_EMAIL: ${{ secrets.GITHUB_USER_EMAIL }} 101 | GITHUB_USER_NAME: ${{ secrets.GITHUB_USER_NAME }} 102 | if: ${{ env.GITHUB_USER_EMAIL != '' && env.GITHUB_USER_NAME != '' }} 103 | run: | 104 | git config --global user.email "${{ env.GITHUB_USER_EMAIL }}" 105 | git config --global user.name "${{ env.GITHUB_USER_NAME }}" 106 | 107 | - name: Set up node cache mode 108 | run: | 109 | if [ -f "${GITHUB_WORKSPACE}/package-lock.json" ] || [ -f "${GITHUB_WORKSPACE}/npm-shrinkwrap.json" ]; then 110 | echo "NODE_CACHE_MODE=npm" >> $GITHUB_ENV 111 | else 112 | echo "No lock files found or unknown package manager" 113 | fi 114 | 115 | - name: Set up PHP 116 | if: ${{ inputs.COMPOSER_DEPS_INSTALL }} 117 | uses: shivammathur/setup-php@v2 118 | env: 119 | COMPOSER_AUTH: '${{ secrets.COMPOSER_AUTH_JSON }}' 120 | with: 121 | php-version: ${{ inputs.PHP_VERSION }} 122 | tools: composer 123 | coverage: none 124 | 125 | - name: Install Composer dependencies 126 | if: ${{ inputs.COMPOSER_DEPS_INSTALL }} 127 | uses: ramsey/composer-install@v3 128 | with: 129 | composer-options: '--prefer-dist' 130 | 131 | - name: Set up node 132 | uses: actions/setup-node@v4 133 | env: 134 | NODE_AUTH_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 135 | with: 136 | node-version: ${{ inputs.NODE_VERSION }} 137 | registry-url: ${{ inputs.NPM_REGISTRY_DOMAIN }} 138 | cache: ${{ env.NODE_CACHE_MODE }} 139 | 140 | - name: Install dependencies 141 | run: npm ${{ env.NODE_CACHE_MODE == 'npm' && 'ci' || 'install' }} 142 | 143 | - name: Install Playwright dependencies 144 | run: | 145 | npx playwright install ${{ inputs.PLAYWRIGHT_BROWSER_ARGS }} 146 | 147 | - name: Run script for test 148 | continue-on-error: true 149 | run: | 150 | touch .env.ci 151 | echo "${{ secrets.ENV_FILE_DATA }}" >> .env.ci 152 | # Ensure .env.ci is deleted on exit 153 | trap 'rm -f .env.ci' EXIT 154 | 155 | npm run ${{ inputs.SCRIPT_NAME }} 156 | 157 | - name: Upload artifact 158 | uses: actions/upload-artifact@v4 159 | with: 160 | name: ${{ inputs.ARTIFACT_NAME }} 161 | path: | 162 | ${{ inputs.ARTIFACT_PATH }} 163 | overwrite: ${{ inputs.ARTIFACT_OVERWRITE }} 164 | include-hidden-files: ${{ inputs.ARTIFACT_INCLUDE_HIDDEN_FILES }} 165 | retention-days: ${{ inputs.ARTIFACT_RETENTION_DAYS }} 166 | -------------------------------------------------------------------------------- /.github/workflows/wp-scripts-lint.yml: -------------------------------------------------------------------------------- 1 | name: WP Scripts lint 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | NODE_OPTIONS: 7 | description: Space-separated list of command-line Node options. 8 | type: string 9 | default: '' 10 | required: false 11 | NODE_VERSION: 12 | description: Node version with which the static code analysis is to be executed. 13 | default: 18 14 | required: false 15 | type: string 16 | NPM_REGISTRY_DOMAIN: 17 | description: Domain of the private npm registry. 18 | default: https://npm.pkg.github.com/ 19 | required: false 20 | type: string 21 | PACKAGE_MANAGER: 22 | description: Package manager with which the dependencies should be installed (`npm` or `yarn`). 23 | default: 'npm' 24 | required: false 25 | type: string 26 | LINT_TOOLS: 27 | description: Array of checks to be executed by @wordpress/scripts. 28 | # [!] Note: "pkg-json" is not included by default. This is currently internally reviewed. 29 | default: '["js", "style", "md-docs"]' 30 | required: false 31 | type: string 32 | ESLINT_ARGS: 33 | description: Set of arguments passed to `wp-scripts lint-js`. 34 | default: '' 35 | required: false 36 | type: string 37 | STYLELINT_ARGS: 38 | description: Set of arguments passed to `wp-scripts lint-style`. 39 | default: '' 40 | required: false 41 | type: string 42 | MARKDOWNLINT_ARGS: 43 | description: Set of arguments passed to `wp-scripts lint-md-docs`. 44 | default: '' 45 | required: false 46 | type: string 47 | PACKAGE_JSONLINT_ARGS: 48 | description: Set of arguments passed to `wp-scripts lint-pkg-json`. 49 | default: '' 50 | required: false 51 | type: string 52 | secrets: 53 | NPM_REGISTRY_TOKEN: 54 | description: Authentication for the private npm registry. 55 | required: false 56 | GITHUB_USER_EMAIL: 57 | description: Email address for the GitHub user configuration. 58 | required: false 59 | GITHUB_USER_NAME: 60 | description: Username for the GitHub user configuration. 61 | required: false 62 | GITHUB_USER_SSH_KEY: 63 | description: Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME`. 64 | required: false 65 | ENV_VARS: 66 | description: Additional environment variables as a JSON formatted object. 67 | required: false 68 | 69 | jobs: 70 | static-analysis-assets: 71 | timeout-minutes: 5 72 | runs-on: ubuntu-latest 73 | env: 74 | NODE_CACHE_MODE: '' 75 | steps: 76 | - name: PACKAGE_MANAGER deprecation warning 77 | if: ${{ inputs.PACKAGE_MANAGER != '' }} 78 | run: | 79 | if [ "${{ inputs.PACKAGE_MANAGER }}" == 'npm' ]; then 80 | echo "::warning::The PACKAGE_MANAGER input is deprecated and will be removed soon. Please remove it. The workflow already uses npm by default." 81 | else 82 | echo "::warning::The PACKAGE_MANAGER input is deprecated and will be removed soon. Please update your workflow to use npm." 83 | fi 84 | 85 | - name: Checkout 86 | uses: actions/checkout@v4 87 | 88 | - name: Set up custom environment variables 89 | env: 90 | ENV_VARS: ${{ secrets.ENV_VARS }} 91 | if: ${{ env.ENV_VARS }} 92 | uses: actions/github-script@v7 93 | with: 94 | script: | 95 | JSON 96 | .parse(process.env.ENV_VARS) 97 | .forEach(envVar => core.exportVariable(envVar.name, envVar.value)); 98 | 99 | - name: Set up SSH 100 | env: 101 | GITHUB_USER_SSH_KEY: ${{ secrets.GITHUB_USER_SSH_KEY }} 102 | if: ${{ env.GITHUB_USER_SSH_KEY != '' }} 103 | uses: webfactory/ssh-agent@v0.9.1 104 | with: 105 | ssh-private-key: ${{ env.GITHUB_USER_SSH_KEY }} 106 | 107 | - name: Set up Git 108 | env: 109 | GITHUB_USER_EMAIL: ${{ secrets.GITHUB_USER_EMAIL }} 110 | GITHUB_USER_NAME: ${{ secrets.GITHUB_USER_NAME }} 111 | if: ${{ env.GITHUB_USER_EMAIL != '' && env.GITHUB_USER_NAME != '' }} 112 | run: | 113 | git config --global user.email "${{ env.GITHUB_USER_EMAIL }}" 114 | git config --global user.name "${{ env.GITHUB_USER_NAME }}" 115 | 116 | - name: Set up node cache mode 117 | run: | 118 | if [ "${{ inputs.PACKAGE_MANAGER }}" == 'npm' ] && { [ -f "${GITHUB_WORKSPACE}/package-lock.json" ] || [ -f "${GITHUB_WORKSPACE}/npm-shrinkwrap.json" ]; }; then 119 | echo "NODE_CACHE_MODE=npm" >> $GITHUB_ENV 120 | elif [ "${{ inputs.PACKAGE_MANAGER }}" == 'yarn' ] && [ -f "${GITHUB_WORKSPACE}/yarn.lock" ]; then 121 | echo "NODE_CACHE_MODE=yarn" >> $GITHUB_ENV 122 | else 123 | echo "No lock files found or unknown package manager" 124 | fi 125 | 126 | - name: Set up node 127 | uses: actions/setup-node@v4 128 | env: 129 | NODE_OPTIONS: ${{ inputs.NODE_OPTIONS }} 130 | NODE_AUTH_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 131 | with: 132 | node-version: ${{ inputs.NODE_VERSION }} 133 | registry-url: ${{ inputs.NPM_REGISTRY_DOMAIN }} 134 | cache: ${{ env.NODE_CACHE_MODE }} 135 | 136 | - name: Install dependencies 137 | env: 138 | ARGS: ${{ env.NODE_CACHE_MODE == 'yarn' && '--frozen-lockfile' || env.NODE_CACHE_MODE == 'npm' && 'ci' || 'install' }} 139 | run: ${{ format('{0} {1}', inputs.PACKAGE_MANAGER, env.ARGS) }} 140 | 141 | - name: Lint script files 142 | if: ${{ contains(fromJSON(inputs.LINT_TOOLS), 'js') }} 143 | run: ./node_modules/.bin/wp-scripts lint-js ${{ inputs.ESLINT_ARGS }} 144 | 145 | - name: Lint style files 146 | if: ${{ contains(fromJSON(inputs.LINT_TOOLS), 'style') }} 147 | run: ./node_modules/.bin/wp-scripts lint-style ${{ inputs.STYLELINT_ARGS }} --formatter github 148 | 149 | - name: Lint markdown files 150 | if: ${{ contains(fromJSON(inputs.LINT_TOOLS), 'md-docs') }} 151 | run: ./node_modules/.bin/wp-scripts lint-md-docs ${{ inputs.MARKDOWNLINT_ARGS }} 152 | 153 | - name: Lint `package.json` files 154 | if: ${{ contains(fromJSON(inputs.LINT_TOOLS), 'pkg-json') }} 155 | run: ./node_modules/.bin/wp-scripts lint-pkg-json ${{ inputs.PACKAGE_JSONLINT_ARGS }} 156 | -------------------------------------------------------------------------------- /docs/ddev-playwright.md: -------------------------------------------------------------------------------- 1 | # Run Playwright tests using the project's DDEV setup 2 | 3 | This reusable workflow: 4 | 5 | 1. Launches DDEV. 6 | 2. Runs the provided command such as `ddev orchestrate` 7 | from [inpsyde/ddev-wordpress-plugin-template](https://github.com/inpsyde/ddev-wordpress-plugin-template) 8 | to set up the environment (install WP, plugins, ...). 9 | 3. If the Ngrok auth token is provided, it sets up Ngrok. Ngrok can be needed if some third-party 10 | service, e.g., webhooks, must access the website. 11 | 1. Launches Ngrok, using `vendor/bin/ddev-share` 12 | from [inpsyde/ddev-tools](https://github.com/inpsyde/ddev-tools) by default. 13 | 2. Saves the URL to the specified env variable, `BASEURL` by default. 14 | 4. Installs Playwright and its dependencies via the provided command, such as 15 | `yarn install && yarn playwright install --with-deps` or `ddev pw-install-host` 16 | from [inpsyde/ddev-wordpress-plugin-template](https://github.com/inpsyde/ddev-wordpress-plugin-template). 17 | 5. Runs Playwright tests via the provided command, such as `yarn playwright test` or 18 | `ddev pw-host test` 19 | from [inpsyde/ddev-wordpress-plugin-template](https://github.com/inpsyde/ddev-wordpress-plugin-template). 20 | 21 | It is possible to add any env variables for the "host" and for DDEV. 22 | 23 | The "host" env variables (`ENV_VARS`) can be used in the tests (Playwright runs outside of DDEV), 24 | such as user credentials or credit card numbers. To keep things simple and avoid passing hundreds of 25 | variables, consider using an `.env` file, and commit `.env.example` with usable defaults for 26 | parameters that do not have to be secret, then add `cp .env.example .env` in 27 | `PLAYWRIGHT_INSTALL_CMD` (`ddev pw-install-host` already includes it). 28 | 29 | Use the DDEV env variables (`DDEV_ENV_VARS`) for debug flags of your project needed for the tests to 30 | run properly (e.g., using placeholder product images or non-live mode). 31 | 32 | Also, it is possible to change some DDEV config values, such as the PHP version (`PHP_VERSION`). 33 | 34 | ## Usage example 35 | 36 | ```yml 37 | name: Run Playwright tests via DDEV 38 | on: 39 | push: 40 | jobs: 41 | ddev-playwright: 42 | uses: inpsyde/reusable-workflows/.github/workflows/ddev-playwright.yml@main 43 | secrets: 44 | COMPOSER_AUTH_JSON: ${{ secrets.PACKAGIST_AUTH_JSON }} 45 | NGROK_AUTH_TOKEN: ${{ secrets.NGROK_AUTH_TOKEN }} 46 | SSH_KEY: ${{ secrets.DEPLOYBOT_SSH_PRIVATE_KEY }} 47 | ENV_VARS: >- 48 | [ 49 | {"name": "SOME_USERNAME", "value": "${{ secrets.SOME_USERNAME }}"}, 50 | {"name": "SOME_PASSWORD", "value": "${{ secrets.SOME_PASSWORD }}"} 51 | ] 52 | DDEV_ENV_VARS: >- 53 | [ 54 | {"name": "SOME_DEBUG_FLAG", "value": "true"} 55 | ] 56 | with: 57 | DDEV_ORCHESTRATE_CMD: ddev orchestrate 58 | PLAYWRIGHT_INSTALL_CMD: ddev pw-install-host 59 | PLAYWRIGHT_RUN_CMD: ddev pw-host test 60 | ``` 61 | 62 | ## Configuration parameters 63 | 64 | ### Inputs 65 | 66 | | Name | Default | Description | 67 | |--------------------------|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 68 | | `PHP_VERSION` | `''` | PHP version which will override the version set in the DDEV config | 69 | | `NODE_VERSION` | - | Node version which will override the version set in the DDEV config | 70 | | `DDEV_ORCHESTRATE_CMD` | `''` | The command for setting up the DDEV website, such as `ddev orchestrate` from [inpsyde/ddev-wordpress-plugin-template](https://github.com/inpsyde/ddev-wordpress-plugin-template) | 71 | | `PLAYWRIGHT_INSTALL_CMD` | `''` | The command for installing Playwright and its deps, such as `yarn install && yarn playwright install --with-deps` or `ddev pw-install-host` from [inpsyde/ddev-wordpress-plugin-template](https://github.com/inpsyde/ddev-wordpress-plugin-template) | 72 | | `PLAYWRIGHT_RUN_CMD` | `''` | The command for running Playwright tests, such as `yarn playwright test` or `ddev pw-host test` from [inpsyde/ddev-wordpress-plugin-template](https://github.com/inpsyde/ddev-wordpress-plugin-template) | 73 | | `PLAYWRIGHT_DIR` | `'tests/Playwright'` | The path to the Playwright project | 74 | | `NGROK_START_CMD` | `'vendor/bin/ddev-share'` | The command for starting Ngrok, such as `ddev-share` from [inpsyde/ddev-tools](https://github.com/inpsyde/ddev-tools) | 75 | | `BASEURL_ENV_NAME` | `'BASEURL'` | The name of the env variable with the base URL for Playwright, used for overwriting it with the URL from Ngrok | 76 | 77 | ## Secrets 78 | 79 | | Name | Description | 80 | |----------------------|------------------------------------------------------------------------------------------| 81 | | `COMPOSER_AUTH_JSON` | Authentication for privately hosted packages and repositories as a JSON formatted object | 82 | | `NPM_REGISTRY_TOKEN` | Authentication for the private npm registry | 83 | | `NGROK_AUTH_TOKEN` | Ngrok auth token; skips the installation of Ngrok if not provided | 84 | | `ENV_VARS` | Additional environment variables as a JSON formatted object | 85 | | `DDEV_ENV_VARS` | Additional environment variables for DDEV as a JSON formatted object | 86 | | `SSH_KEY` | Private SSH key to be used to reach remote destinations | 87 | | `SSH_KNOWN_HOSTS` | SSH hosts to be set in the `known_hosts` file | 88 | -------------------------------------------------------------------------------- /.github/workflows/build-assets-compilation.yml: -------------------------------------------------------------------------------- 1 | name: "[DEPRECATED] Assets compilation" 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | NODE_OPTIONS: 7 | description: Space-separated list of command-line Node options. 8 | type: string 9 | default: '' 10 | required: false 11 | NODE_VERSION: 12 | description: Node version with which the static code analysis is to be executed. 13 | default: 18 14 | required: false 15 | type: string 16 | NPM_REGISTRY_DOMAIN: 17 | description: Domain of the private npm registry. 18 | default: https://npm.pkg.github.com/ 19 | required: false 20 | type: string 21 | PACKAGE_MANAGER: 22 | description: Package manager with which the dependencies should be installed (`npm` or `yarn`). 23 | default: 'npm' 24 | required: false 25 | type: string 26 | PHP_VERSION: 27 | description: PHP version with which the assets compilation is to be executed. 28 | default: '8.2' 29 | required: false 30 | type: string 31 | COMPOSER_ARGS: 32 | description: Set of arguments passed to Composer. 33 | default: '--prefer-dist' 34 | required: false 35 | type: string 36 | COMPILE_ASSETS_ARGS: 37 | description: Set of arguments passed to Composer Asset Compiler. 38 | default: '-v --env=root' 39 | required: false 40 | type: string 41 | secrets: 42 | COMPOSER_AUTH_JSON: 43 | description: Authentication for privately hosted packages and repositories as a JSON formatted object. 44 | required: false 45 | NPM_REGISTRY_TOKEN: 46 | description: Authentication for the private npm registry. 47 | required: false 48 | GITHUB_USER_EMAIL: 49 | description: Email address for the GitHub user configuration. 50 | required: false 51 | GITHUB_USER_NAME: 52 | description: Username for the GitHub user configuration. 53 | required: false 54 | GITHUB_USER_SSH_KEY: 55 | description: Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME`. 56 | required: false 57 | ENV_VARS: 58 | description: Additional environment variables as a JSON formatted object. 59 | required: false 60 | 61 | jobs: 62 | assets-compilation: 63 | timeout-minutes: 5 64 | runs-on: ubuntu-latest 65 | env: 66 | NODE_CACHE_MODE: '' 67 | steps: 68 | - name: Deprecation warning 69 | run: echo "::warning::This workflow is deprecated and will be removed soon. Use .github/workflows/build-and-distribute.yml instead." 70 | 71 | - name: PACKAGE_MANAGER deprecation warning 72 | if: ${{ inputs.PACKAGE_MANAGER != '' }} 73 | run: | 74 | if [ "${{ inputs.PACKAGE_MANAGER }}" == 'npm' ]; then 75 | echo "::warning::The PACKAGE_MANAGER input is deprecated and will be removed soon. Please remove it. The workflow already uses npm by default." 76 | else 77 | echo "::warning::The PACKAGE_MANAGER input is deprecated and will be removed soon. Please update your workflow to use npm." 78 | fi 79 | 80 | - name: Checkout 81 | uses: actions/checkout@v4 82 | 83 | - name: Set up custom environment variables 84 | env: 85 | ENV_VARS: ${{ secrets.ENV_VARS }} 86 | if: ${{ env.ENV_VARS }} 87 | uses: actions/github-script@v7 88 | with: 89 | script: | 90 | JSON 91 | .parse(process.env.ENV_VARS) 92 | .forEach(envVar => core.exportVariable(envVar.name, envVar.value)); 93 | 94 | - name: Set up SSH 95 | env: 96 | GITHUB_USER_SSH_KEY: ${{ secrets.GITHUB_USER_SSH_KEY }} 97 | if: ${{ env.GITHUB_USER_SSH_KEY != '' }} 98 | uses: webfactory/ssh-agent@v0.9.1 99 | with: 100 | ssh-private-key: ${{ env.GITHUB_USER_SSH_KEY }} 101 | 102 | - name: Set up Git 103 | env: 104 | GITHUB_USER_EMAIL: ${{ secrets.GITHUB_USER_EMAIL }} 105 | GITHUB_USER_NAME: ${{ secrets.GITHUB_USER_NAME }} 106 | if: ${{ env.GITHUB_USER_EMAIL != '' && env.GITHUB_USER_NAME != '' }} 107 | run: | 108 | git config --global user.email "${{ env.GITHUB_USER_EMAIL }}" 109 | git config --global user.name "${{ env.GITHUB_USER_NAME }}" 110 | 111 | - name: Set up node cache mode 112 | run: | 113 | if [ "${{ inputs.PACKAGE_MANAGER }}" == 'npm' ] && { [ -f "${GITHUB_WORKSPACE}/package-lock.json" ] || [ -f "${GITHUB_WORKSPACE}/npm-shrinkwrap.json" ]; }; then 114 | echo "NODE_CACHE_MODE=npm" >> $GITHUB_ENV 115 | elif [ "${{ inputs.PACKAGE_MANAGER }}" == 'yarn' ] && [ -f "${GITHUB_WORKSPACE}/yarn.lock" ]; then 116 | echo "NODE_CACHE_MODE=yarn" >> $GITHUB_ENV 117 | else 118 | echo "No lock files found or unknown package manager" 119 | fi 120 | 121 | - name: Set up node 122 | uses: actions/setup-node@v4 123 | env: 124 | NODE_OPTIONS: ${{ inputs.NODE_OPTIONS }} 125 | NODE_AUTH_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 126 | with: 127 | node-version: ${{ inputs.NODE_VERSION }} 128 | registry-url: ${{ inputs.NPM_REGISTRY_DOMAIN }} 129 | cache: ${{ env.NODE_CACHE_MODE }} 130 | 131 | - name: Set up PHP 132 | uses: shivammathur/setup-php@v2 133 | with: 134 | php-version: ${{ inputs.PHP_VERSION }} 135 | tools: composer 136 | coverage: none 137 | 138 | - name: Install Composer dependencies 139 | uses: ramsey/composer-install@v3 140 | env: 141 | COMPOSER_AUTH: '${{ secrets.COMPOSER_AUTH_JSON }}' 142 | with: 143 | composer-options: ${{ inputs.COMPOSER_ARGS }} 144 | 145 | - name: Set environment variables [DEV] 146 | if: ${{ !contains(github.ref, 'refs/tags/') }} 147 | run: | 148 | echo "ASSETS_HASH=$(composer assets-hash)" >> $GITHUB_ENV 149 | # We set "development" here to align with webpack's "mode" 150 | # to be used in Composer Asset Compiler as a placeholder variable. 151 | # @link https://webpack.js.org/configuration/mode/ 152 | echo "BUILD_ENV=development" >> $GITHUB_ENV 153 | 154 | - name: Set environment variables [PROD] 155 | if: ${{ contains(github.ref, 'refs/tags/') }} 156 | run: | 157 | echo "TAG_NAME=$(echo ${GITHUB_REF#refs/*/})" >> $GITHUB_ENV 158 | echo "BUILD_ENV=production" >> $GITHUB_ENV 159 | echo "::notice::The ENCORE_ENV variable is deprecated and will be removed soon. If you use it, please change it to BUILD_ENV." 160 | echo "ENCORE_ENV=production" >> $GITHUB_ENV 161 | 162 | - name: Compile assets 163 | env: 164 | COMPOSER_AUTH: '${{ secrets.COMPOSER_AUTH_JSON }}' 165 | run: composer compile-assets ${{ inputs.COMPILE_ASSETS_ARGS }} 166 | 167 | - name: Upload assets artifact [DEV] 168 | uses: actions/upload-artifact@v4 169 | if: ${{ !contains(github.ref, 'refs/tags/') }} 170 | with: 171 | name: assets-${{ env.ASSETS_HASH }} 172 | path: assets 173 | overwrite: true 174 | include-hidden-files: true 175 | 176 | - name: Zip assets folder [PROD] 177 | uses: montudor/action-zip@v1 178 | if: ${{ contains(github.ref, 'refs/tags/') }} 179 | with: 180 | args: zip -qq -r assets-${{ env.TAG_NAME }}.zip assets 181 | 182 | - name: Upload release attachment [PROD] 183 | if: ${{ contains(github.ref, 'refs/tags/') }} 184 | uses: svenstaro/upload-release-action@v2 185 | with: 186 | repo_token: ${{ secrets.GITHUB_TOKEN }} 187 | file: assets-${{ env.TAG_NAME }}.zip 188 | tag: ${{ github.ref }} 189 | # Release description fallback which will not be enforced due "overwrite: false" (default). 190 | body: | 191 | # ${{ env.TAG_NAME }} 192 | Compiled assets available in `assets-${{ env.TAG_NAME }}.zip`. 193 | -------------------------------------------------------------------------------- /.github/workflows/ddev-playwright.yml: -------------------------------------------------------------------------------- 1 | name: Run Playwright tests using DDEV 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | PHP_VERSION: 7 | description: PHP version which will override the version set in the DDEV config. 8 | required: false 9 | type: string 10 | NODE_VERSION: 11 | description: Node version which will override the version set in the DDEV config. 12 | required: false 13 | type: string 14 | DDEV_ORCHESTRATE_CMD: 15 | description: The command for setting up the DDEV website, such as `ddev orchestrate` from inpsyde/ddev-wordpress-plugin-template. 16 | required: false 17 | type: string 18 | PLAYWRIGHT_INSTALL_CMD: 19 | description: The command for installing Playwright and its dependencies, such as `npm install && npx playwright install --with-deps` or `ddev pw-install-host` from inpsyde/ddev-wordpress-plugin-template. 20 | required: true 21 | type: string 22 | PLAYWRIGHT_RUN_CMD: 23 | description: The command for running Playwright tests, such as `npx playwright test` or `ddev pw-host test` from inpsyde/ddev-wordpress-plugin-template. 24 | required: true 25 | type: string 26 | PLAYWRIGHT_DIR: 27 | description: The path to the Playwright project. 28 | required: false 29 | default: 'tests/Playwright' 30 | type: string 31 | NGROK_START_CMD: 32 | default: 'vendor/bin/ddev-share' 33 | description: The command for starting Ngrok, such as `ddev-share` from inpsyde/ddev-tools. 34 | required: false 35 | type: string 36 | BASEURL_ENV_NAME: 37 | default: 'BASEURL' 38 | description: The name of the env variable with the base URL for Playwright, used for overwriting it with the URL from Ngrok. 39 | required: false 40 | type: string 41 | secrets: 42 | COMPOSER_AUTH_JSON: 43 | description: Authentication for privately hosted packages and repositories as a JSON formatted object. 44 | required: false 45 | NPM_REGISTRY_TOKEN: 46 | description: Authentication for the private npm registry. 47 | required: false 48 | NGROK_AUTH_TOKEN: 49 | description: Ngrok auth token; skips the installation of Ngrok if not provided. 50 | required: false 51 | ENV_VARS: 52 | description: Additional environment variables as a JSON formatted object. 53 | required: false 54 | DDEV_ENV_VARS: 55 | description: Additional environment variables for DDEV as a JSON formatted object. 56 | required: false 57 | SSH_KEY: 58 | description: Private SSH key to be used to reach remote destinations. 59 | required: false 60 | SSH_KNOWN_HOSTS: 61 | description: SSH hosts to be set in the `known_hosts` file. 62 | required: false 63 | 64 | jobs: 65 | ddev-playwright: 66 | timeout-minutes: 30 67 | runs-on: ubuntu-latest 68 | env: 69 | NGROK: ${{ secrets.NGROK_AUTH_TOKEN != '' }} 70 | outputs: 71 | artifact: ${{ steps.set-artifact-name.outputs.artifact }} 72 | steps: 73 | - name: Checkout 74 | uses: actions/checkout@v4 75 | 76 | - name: Set up custom environment variables 77 | env: 78 | ENV_VARS: ${{ secrets.ENV_VARS }} 79 | if: env.ENV_VARS 80 | uses: actions/github-script@v7 81 | with: 82 | script: | 83 | JSON 84 | .parse(process.env.ENV_VARS) 85 | .forEach(envVar => core.exportVariable(envVar.name, envVar.value)); 86 | 87 | - name: Set up SSH keys 88 | env: 89 | SSH_KEY: ${{ secrets.SSH_KEY }} 90 | if: ${{ env.SSH_KEY != '' }} 91 | run: | 92 | mkdir -p .ddev/homeadditions/.ssh 93 | echo "${{ env.SSH_KEY }}" > .ddev/homeadditions/.ssh/id_rsa 94 | echo "StrictHostKeyChecking=accept-new" > .ddev/homeadditions/.ssh/config 95 | echo "CheckHostIP=no" >> .ddev/homeadditions/.ssh/config 96 | echo "HashKnownHosts=no" >> .ddev/homeadditions/.ssh/config 97 | chmod 700 .ddev/homeadditions/.ssh 98 | chmod 600 .ddev/homeadditions/.ssh/id_rsa .ddev/homeadditions/.ssh/config 99 | 100 | - name: Set up SSH knows hosts 101 | env: 102 | SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }} 103 | if: ${{ env.SSH_KNOWN_HOSTS != '' }} 104 | run: | 105 | echo "${{ env.SSH_KNOWN_HOSTS }}" > .ddev/homeadditions/.ssh/known_hosts 106 | 107 | - name: Set up DDEV 108 | uses: ddev/github-action-setup-ddev@v1 109 | with: 110 | autostart: false 111 | 112 | - name: Add CI env var inside DDEV 113 | run: ddev config --web-environment-add="CI=true" 114 | 115 | - name: Set up DDEV environment variables 116 | env: 117 | ENV_VARS: ${{ secrets.DDEV_ENV_VARS }} 118 | if: env.ENV_VARS 119 | uses: actions/github-script@v7 120 | with: 121 | script: | 122 | JSON 123 | .parse(process.env.ENV_VARS) 124 | .forEach(envVar => { 125 | core.setSecret(envVar.name); 126 | core.setSecret(envVar.value); 127 | exec.exec('ddev config --web-environment-add="' + envVar.name + '=' + envVar.value + '"'); 128 | }); 129 | 130 | - name: Add COMPOSER_AUTH into DDEV 131 | env: 132 | COMPOSER_AUTH: '${{ secrets.COMPOSER_AUTH_JSON }}' 133 | if: env.COMPOSER_AUTH 134 | run: echo '${{ env.COMPOSER_AUTH }}' > $GITHUB_WORKSPACE/auth.json 135 | 136 | - name: Add NODE_AUTH_TOKEN into DDEV 137 | env: 138 | NPM_REGISTRY_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 139 | if: env.NPM_REGISTRY_TOKEN 140 | run: | 141 | cat < $GITHUB_WORKSPACE/.npmrc 142 | //npm.pkg.github.com/:_authToken=${{ env.NPM_REGISTRY_TOKEN }} 143 | @inpsyde:registry=https://npm.pkg.github.com/ 144 | EOF 145 | 146 | - name: Configure DDEV PHP version 147 | if: inputs.PHP_VERSION 148 | run: ddev config --php-version ${{ inputs.PHP_VERSION }} 149 | 150 | - name: Configure DDEV Node.js version 151 | if: inputs.NODE_VERSION 152 | run: ddev config --nodejs-version ${{ inputs.NODE_VERSION }} 153 | 154 | - name: Start DDEV 155 | run: ddev start 156 | 157 | - name: Orchestrate DDEV 158 | if: inputs.DDEV_ORCHESTRATE_CMD 159 | run: ${{ inputs.DDEV_ORCHESTRATE_CMD }} 160 | 161 | - name: Install Ngrok 162 | if: env.NGROK != 'false' 163 | run: curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null && echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | sudo tee /etc/apt/sources.list.d/ngrok.list && sudo apt update && sudo apt install ngrok 164 | 165 | - name: Add Ngrok auth token 166 | env: 167 | NGROK_AUTH_TOKEN: ${{ secrets.NGROK_AUTH_TOKEN }} 168 | if: env.NGROK != 'false' 169 | run: ngrok config add-authtoken ${{ env.NGROK_AUTH_TOKEN }} 170 | 171 | - name: Install Playwright 172 | run: ${{ inputs.PLAYWRIGHT_INSTALL_CMD }} 173 | 174 | - name: Start Ngrok 175 | if: env.NGROK != 'false' 176 | run: ${{ inputs.NGROK_START_CMD }} 177 | 178 | - name: Get Ngrok URL and save in env 179 | if: env.NGROK != 'false' 180 | run: echo "${{ inputs.BASEURL_ENV_NAME }}=https://$( curl http://127.0.0.1:4040/api/tunnels | jq -r '.tunnels[0].public_url' | awk -F'^http[s]?://' '{print $2}' )" >> $GITHUB_ENV 181 | 182 | - name: Run Playwright 183 | run: ${{ inputs.PLAYWRIGHT_RUN_CMD }} 184 | 185 | - name: Set artifact name 186 | id: set-artifact-name 187 | run: echo "artifact=playwright-report" >> $GITHUB_OUTPUT 188 | 189 | - name: Upload artifact 190 | uses: actions/upload-artifact@v4 191 | with: 192 | name: ${{ steps.set-artifact-name.outputs.artifact }} 193 | path: | 194 | ${{ inputs.PLAYWRIGHT_DIR }}/playwright-report/* 195 | ${{ inputs.PLAYWRIGHT_DIR }}/test-results/* 196 | ${{ inputs.PLAYWRIGHT_DIR }}/artifacts/test-results/* 197 | overwrite: true 198 | include-hidden-files: true 199 | -------------------------------------------------------------------------------- /docs/php.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Reusable workflows – PHP 4 | 5 | ## Coding standards analysis 6 | 7 | This workflow runs [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer). It does so by 8 | executing the binary in the `./vendor/bin/` folder. 9 | 10 | **Simplest possible example:** 11 | 12 | ```yml 13 | name: Coding standards analysis PHP 14 | on: 15 | push: 16 | pull_request: 17 | jobs: 18 | coding-standards-analysis-php: 19 | uses: inpsyde/reusable-workflows/.github/workflows/coding-standards-php.yml@main 20 | ``` 21 | 22 | ### Configuration parameters 23 | 24 | #### Inputs 25 | 26 | | Name | Default | Description | 27 | |-----------------|----------------------------------------------------------|-------------------------------------------------| 28 | | `PHP_VERSION` | `'8.2'` | PHP version with which the scripts are executed | 29 | | `COMPOSER_ARGS` | `'--prefer-dist'` | Set of arguments passed to Composer | 30 | | `PHPCS_ARGS` | `'--report-full --report-checkstyle=./phpcs-report.xml'` | Set of arguments passed to PHP_CodeSniffer | 31 | | `CS2PR_ARGS` | `'--graceful-warnings ./phpcs-report.xml'` | Set of arguments passed to cs2pr | 32 | 33 | #### Secrets 34 | 35 | | Name | Description | 36 | |----------------------|------------------------------------------------------------------------------------------| 37 | | `COMPOSER_AUTH_JSON` | Authentication for privately hosted packages and repositories as a JSON formatted object | 38 | | `ENV_VARS` | Additional environment variables as a JSON formatted object | 39 | 40 | **Example with configuration parameters:** 41 | 42 | ```yml 43 | name: Coding standards analysis PHP 44 | on: 45 | push: 46 | pull_request: 47 | jobs: 48 | coding-standards-analysis-php: 49 | uses: inpsyde/reusable-workflows/.github/workflows/coding-standards-php.yml@main 50 | secrets: 51 | COMPOSER_AUTH_JSON: ${{ secrets.COMPOSER_AUTH_JSON }} 52 | ENV_VARS: >- 53 | [{"name":"EXAMPLE_USERNAME", "value":"${{ secrets.USERNAME }}"}] 54 | with: 55 | PHPCS_ARGS: '--report=summary' 56 | ``` 57 | 58 | **Note**: Coding _standards_ analysis should only be performed with the highest supported PHP 59 | version. Use the [Lint PHP](#lint-php) workflow to check code _compatibility_ with multiple PHP 60 | versions. 61 | 62 | ## Static code analysis 63 | 64 | This workflow runs either [Psalm](https://psalm.dev/) or [PHPStan](https://phpstan.org/), or both, based on the existence of a supported configuration file in your repository root folder. 65 | 66 | It does so by executing the binary in the `./vendor/bin/` folder. 67 | 68 | **Supported configuration files:** 69 | 70 | | Tool | Files | 71 | |---------|----------------------------------------------------------| 72 | | Psalm | `psalm.xml`, `psalm.xml.dist` | 73 | | PHPStan | `phpstan.dist.neon`, `phpstan.neon`, `phpstan.neon.dist` | 74 | 75 | **Simplest possible example:** 76 | 77 | ```yml 78 | name: Static code analysis PHP 79 | on: 80 | push: 81 | pull_request: 82 | jobs: 83 | static-code-analysis-php: 84 | uses: inpsyde/reusable-workflows/.github/workflows/static-analysis-php.yml@main 85 | ``` 86 | 87 | ### Configuration parameters 88 | 89 | #### Inputs 90 | 91 | | Name | Default | Description | 92 | |-----------------|---------------------------------------|-------------------------------------------------| 93 | | `PHP_VERSION` | `'8.2'` | PHP version with which the scripts are executed | 94 | | `COMPOSER_ARGS` | `'--prefer-dist'` | Set of arguments passed to Composer | 95 | | `PSALM_ARGS` | `'--output-format=github --no-cache'` | Set of arguments passed to Psalm | 96 | | `PHPSTAN_ARGS` | `'--no-progress --memory-limit=1G'` | Set of arguments passed to PHPStan | 97 | 98 | #### Secrets 99 | 100 | | Name | Description | 101 | |----------------------|------------------------------------------------------------------------------------------| 102 | | `COMPOSER_AUTH_JSON` | Authentication for privately hosted packages and repositories as a JSON formatted object | 103 | | `ENV_VARS` | Additional environment variables as a JSON formatted object | 104 | 105 | **Example with configuration parameters:** 106 | 107 | ```yml 108 | name: Static code analysis PHP 109 | on: 110 | push: 111 | pull_request: 112 | jobs: 113 | static-code-analysis-php: 114 | uses: inpsyde/reusable-workflows/.github/workflows/static-analysis-php.yml@main 115 | secrets: 116 | COMPOSER_AUTH_JSON: ${{ secrets.COMPOSER_AUTH_JSON }} 117 | ENV_VARS: >- 118 | [{"name":"EXAMPLE_USERNAME", "value":"${{ secrets.USERNAME }}"}] 119 | with: 120 | PSALM_ARGS: '--threads=3' 121 | ``` 122 | 123 | **Note**: Static code analysis can only be performed with a specific PHP version and not in a PHP 124 | matrix, as it should always be tested with the highest PHP version in use. To check compatibility 125 | with multiple PHP versions, use the [Lint PHP](#lint-php) workflow. 126 | 127 | ## Unit tests PHP 128 | 129 | This workflow runs [PHPUnit](https://phpunit.de/). It does so by executing the binary in the 130 | `./vendor/bin/` folder. 131 | 132 | **Simplest possible example:** 133 | 134 | ```yml 135 | name: Unit tests PHP 136 | on: 137 | push: 138 | pull_request: 139 | jobs: 140 | tests-unit-php: 141 | uses: inpsyde/reusable-workflows/.github/workflows/tests-unit-php.yml@main 142 | ``` 143 | 144 | ### Configuration parameters 145 | 146 | #### Inputs 147 | 148 | | Name | Default | Description | 149 | |-----------------|---------------------|-------------------------------------------------| 150 | | `PHP_VERSION` | `'8.2'` | PHP version with which the scripts are executed | 151 | | `COMPOSER_ARGS` | `'--prefer-dist'` | Set of arguments passed to Composer | 152 | | `PHPUNIT_ARGS` | `'--coverage-text'` | Set of arguments passed to PHPUnit | 153 | 154 | #### Secrets 155 | 156 | | Name | Description | 157 | |----------------------|------------------------------------------------------------------------------------------| 158 | | `COMPOSER_AUTH_JSON` | Authentication for privately hosted packages and repositories as a JSON formatted object | 159 | | `ENV_VARS` | Additional environment variables as a JSON formatted object | 160 | 161 | **Example with configuration parameters:** 162 | 163 | ```yml 164 | name: Unit tests PHP 165 | on: 166 | push: 167 | pull_request: 168 | jobs: 169 | tests-unit-php: 170 | strategy: 171 | matrix: 172 | php: [ "8.1", "8.2", "8.3" ] 173 | uses: inpsyde/reusable-workflows/.github/workflows/tests-unit-php.yml@main 174 | with: 175 | PHP_VERSION: ${{ matrix.php }} 176 | PHPUNIT_ARGS: '--coverage-text --debug' 177 | ``` 178 | 179 | ## Lint PHP 180 | 181 | This workflow runs [PHP Parallel Lint](https://github.com/php-parallel-lint/PHP-Parallel-Lint). 182 | 183 | **Simplest possible example:** 184 | 185 | ```yml 186 | name: Lint PHP 187 | on: 188 | push: 189 | pull_request: 190 | jobs: 191 | lint-php: 192 | uses: inpsyde/reusable-workflows/.github/workflows/lint-php.yml@main 193 | ``` 194 | 195 | ### Configuration parameters 196 | 197 | #### Inputs 198 | 199 | | Name | Default | Description | 200 | |-------------------------|-----------------------------------------|----------------------------------------------------------------| 201 | | `PHP_VERSION` | `'8.2'` | PHP version with which the scripts are executed | 202 | | `COMPOSER_ARGS` | `'--prefer-dist'` | Set of arguments passed to Composer | 203 | | `LINT_ARGS` | `'-e php --colors --show-deprecated .'` | Set of arguments passed to PHP Parallel Lint | 204 | | `COMPOSER_DEPS_INSTALL` | `false` | Whether or not to install Composer dependencies before linting | 205 | 206 | #### Secrets 207 | 208 | | Name | Description | 209 | |----------------------|------------------------------------------------------------------------------------------| 210 | | `COMPOSER_AUTH_JSON` | Authentication for privately hosted packages and repositories as a JSON formatted object | 211 | | `ENV_VARS` | Additional environment variables as a JSON formatted object | 212 | 213 | **Example with configuration parameters:** 214 | 215 | ```yml 216 | name: Lint PHP 217 | on: 218 | push: 219 | pull_request: 220 | jobs: 221 | lint-php: 222 | uses: inpsyde/reusable-workflows/.github/workflows/lint-php.yml@main 223 | secrets: 224 | COMPOSER_AUTH_JSON: ${{ secrets.COMPOSER_AUTH_JSON }} 225 | ENV_VARS: >- 226 | [{"name":"EXAMPLE_USERNAME", "value":"${{ secrets.USERNAME }}"}] 227 | with: 228 | PHP_VERSION: '8.1' 229 | LINT_ARGS: '. --exclude vendor' 230 | COMPOSER_DEPS_INSTALL: true 231 | ``` 232 | 233 | **Example with `PHP_VERSION` in matrix:** 234 | 235 | ```yml 236 | name: Lint PHP 237 | on: 238 | push: 239 | pull_request: 240 | jobs: 241 | lint-php: 242 | strategy: 243 | matrix: 244 | php: [ "8.1", "8.2", "8.3" ] 245 | uses: inpsyde/reusable-workflows/.github/workflows/lint-php.yml@main 246 | with: 247 | PHP_VERSION: ${{ matrix.php }} 248 | ``` 249 | -------------------------------------------------------------------------------- /.github/workflows/build-plugin-archive.yml: -------------------------------------------------------------------------------- 1 | name: "[DEPRECATED] Create plugin archive" 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | NODE_OPTIONS: 7 | description: Space-separated list of command-line Node options. 8 | type: string 9 | default: '' 10 | required: false 11 | NODE_VERSION: 12 | description: Node version with which the assets will be compiled. 13 | default: 18 14 | required: false 15 | type: string 16 | NPM_REGISTRY_DOMAIN: 17 | description: Domain of the private npm registry. 18 | default: https://npm.pkg.github.com/ 19 | required: false 20 | type: string 21 | PACKAGE_MANAGER: 22 | description: Package manager with which the dependencies should be installed (`npm` or `yarn`). 23 | default: 'npm' 24 | required: false 25 | type: string 26 | COMPOSER_ARGS: 27 | description: Set of arguments passed to Composer when gathering production dependencies. 28 | default: '--no-dev --prefer-dist --optimize-autoloader' 29 | required: false 30 | type: string 31 | PHP_VERSION: 32 | description: PHP version to use when gathering production dependencies. 33 | default: '8.2' 34 | required: false 35 | type: string 36 | PHP_VERSION_BUILD: 37 | description: PHP version to use when executing build tools. 38 | default: '8.2' 39 | required: false 40 | type: string 41 | ARCHIVE_NAME: 42 | description: The name of the zip archive (falls back to the repository name). 43 | default: '' 44 | required: false 45 | type: string 46 | PLUGIN_MAIN_FILE: 47 | description: The name of the main plugin file. 48 | required: false 49 | default: 'index.php' 50 | type: string 51 | PLUGIN_FOLDER_NAME: 52 | description: The name of the plugin folder (falls back to the archive name, if set, or the repository name). 53 | required: false 54 | default: '' 55 | type: string 56 | PLUGIN_VERSION: 57 | description: The new plugin version. 58 | required: true 59 | type: string 60 | PRE_SCRIPT: 61 | description: Run custom shell code before creating the release archive. 62 | default: '' 63 | required: false 64 | type: string 65 | COMPILE_ASSETS_ARGS: 66 | description: Set of arguments passed to Composer Asset Compiler. 67 | default: '-v --env=root' 68 | required: false 69 | type: string 70 | secrets: 71 | COMPOSER_AUTH_JSON: 72 | description: Authentication for privately hosted packages and repositories as a JSON formatted object. 73 | required: false 74 | NPM_REGISTRY_TOKEN: 75 | description: Authentication for the private npm registry. 76 | required: false 77 | ENV_VARS: 78 | description: Additional environment variables as a JSON formatted object. 79 | required: false 80 | outputs: 81 | artifact: 82 | description: The name of the generated release artifact 83 | value: ${{ jobs.create-plugin-archive.outputs.artifact }} 84 | 85 | jobs: 86 | checkout-dependencies: 87 | name: Install production dependencies 88 | timeout-minutes: 5 89 | runs-on: ubuntu-latest 90 | outputs: 91 | artifact: ${{ steps.set-artifact-name.outputs.artifact }} 92 | env: 93 | ENV_VARS: ${{ secrets.ENV_VARS }} 94 | # Disables symlinking of local path repositories. 95 | # During development, symlinking is preferable. 96 | # In resulting builds, you will likely want to ship the actual install location and remove the source directory. 97 | COMPOSER_MIRROR_PATH_REPOS: 1 98 | steps: 99 | - name: Deprecation warning 100 | run: echo "::warning::This workflow is deprecated and will be removed soon. Use .github/workflows/build-and-distribute.yml instead." 101 | 102 | - name: Checkout 103 | uses: actions/checkout@v4 104 | 105 | - name: Set up custom environment variables 106 | if: ${{ env.ENV_VARS }} 107 | uses: actions/github-script@v7 108 | with: 109 | script: | 110 | JSON 111 | .parse(process.env.ENV_VARS) 112 | .forEach(envVar => core.exportVariable(envVar.name, envVar.value)); 113 | 114 | - name: Set up PHP 115 | uses: shivammathur/setup-php@v2 116 | with: 117 | php-version: ${{ inputs.PHP_VERSION }} 118 | tools: wp-cli 119 | 120 | - name: Install Composer dependencies without dev dependencies 121 | uses: ramsey/composer-install@v3 122 | env: 123 | COMPOSER_AUTH: '${{ secrets.COMPOSER_AUTH_JSON }}' 124 | with: 125 | composer-options: ${{ inputs.COMPOSER_ARGS }} 126 | 127 | - name: Set artifact name 128 | id: set-artifact-name 129 | run: echo "artifact=interim-deps" >> $GITHUB_OUTPUT 130 | 131 | - name: Upload artifact 132 | uses: actions/upload-artifact@v4 133 | with: 134 | name: ${{ steps.set-artifact-name.outputs.artifact }} 135 | path: | 136 | ./* 137 | !./.git 138 | !./.ddev 139 | !./.github 140 | include-hidden-files: true 141 | 142 | run-build-tools: 143 | name: Process build steps 144 | timeout-minutes: 10 145 | runs-on: ubuntu-latest 146 | needs: checkout-dependencies 147 | env: 148 | NODE_CACHE_MODE: '' 149 | outputs: 150 | artifact: ${{ steps.set-artifact-name.outputs.artifact }} 151 | steps: 152 | - name: PACKAGE_MANAGER deprecation warning 153 | if: ${{ inputs.PACKAGE_MANAGER != '' }} 154 | run: | 155 | if [ "${{ inputs.PACKAGE_MANAGER }}" == 'npm' ]; then 156 | echo "::warning::The PACKAGE_MANAGER input is deprecated and will be removed soon. Please remove it. The workflow already uses npm by default." 157 | else 158 | echo "::warning::The PACKAGE_MANAGER input is deprecated and will be removed soon. Please update your workflow to use npm." 159 | fi 160 | 161 | - name: Download Artifact 162 | uses: actions/download-artifact@v4 163 | with: 164 | name: ${{ needs.checkout-dependencies.outputs.artifact }} 165 | 166 | - name: Set up PHP 167 | uses: shivammathur/setup-php@v2 168 | with: 169 | php-version: ${{ inputs.PHP_VERSION_BUILD }} 170 | tools: rector, php-scoper, sniccowp/php-scoper-wordpress-excludes, composer/installers, inpsyde/composer-assets-compiler, inpsyde/wp-translation-downloader 171 | 172 | - name: Set up node cache mode 173 | run: | 174 | if [ "${{ inputs.PACKAGE_MANAGER }}" == 'npm' ] && { [ -f "${GITHUB_WORKSPACE}/package-lock.json" ] || [ -f "${GITHUB_WORKSPACE}/npm-shrinkwrap.json" ]; }; then 175 | echo "NODE_CACHE_MODE=npm" >> $GITHUB_ENV 176 | elif [ "${{ inputs.PACKAGE_MANAGER }}" == 'yarn' ] && [ -f "${GITHUB_WORKSPACE}/yarn.lock" ]; then 177 | echo "NODE_CACHE_MODE=yarn" >> $GITHUB_ENV 178 | else 179 | echo "No lock files found or unknown package manager" 180 | fi 181 | 182 | - name: Set up node 183 | uses: actions/setup-node@v4 184 | env: 185 | NODE_OPTIONS: ${{ inputs.NODE_OPTIONS }} 186 | NODE_AUTH_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 187 | with: 188 | node-version: ${{ inputs.NODE_VERSION }} 189 | registry-url: ${{ inputs.NPM_REGISTRY_DOMAIN }} 190 | cache: ${{ env.NODE_CACHE_MODE }} 191 | 192 | - name: Run Composer Asset Compiler 193 | run: | 194 | composer compile-assets ${{ inputs.COMPILE_ASSETS_ARGS }} 195 | 196 | - name: Run WordPress Translation Downloader 197 | run: | 198 | composer wp-translation-downloader:download 199 | 200 | - name: Run Rector 201 | if: ${{ hashFiles('rector.php') != '' }} 202 | run: | 203 | rector 204 | 205 | - name: Run PHP-Scoper ensuring isolated file autoloading 206 | if: ${{ hashFiles('scoper.inc.php') != '' }} 207 | run: | 208 | php-scoper add-prefix --force --output-dir=build 209 | composer --working-dir=build dump-autoload -o 210 | 211 | # Appends the Git commit SHA to the Composer autoload cache key to ensure unique identification for prefixed files. 212 | # Prevents Composer from skipping autoloaded files due to hash collisions based on relative paths. 213 | sed -i "s/'__composer_autoload_files'/\'__composer_autoload_files_${{ github.sha }}'/g" "build/vendor/composer/autoload_real.php" 214 | 215 | - name: Move code to the `build/` directory 216 | if: ${{ hashFiles('scoper.inc.php') == '' }} 217 | run: | 218 | shopt -s extglob dotglob 219 | mkdir build 220 | mv !(build) build 221 | 222 | - name: Set artifact name 223 | id: set-artifact-name 224 | run: echo "artifact=interim-built" >> $GITHUB_OUTPUT 225 | 226 | - name: Upload artifact 227 | uses: actions/upload-artifact@v4 228 | with: 229 | name: ${{ steps.set-artifact-name.outputs.artifact }} 230 | path: | 231 | build/ 232 | !build/**/node_modules 233 | include-hidden-files: true 234 | 235 | create-plugin-archive: 236 | name: Create build archive 237 | timeout-minutes: 5 238 | runs-on: ubuntu-latest 239 | needs: run-build-tools 240 | env: 241 | ARCHIVE_NAME: ${{ inputs.ARCHIVE_NAME }} 242 | PLUGIN_FOLDER_NAME: ${{ inputs.PLUGIN_FOLDER_NAME }} 243 | GIT_SHA: ${{ github.sha }} 244 | PLUGIN_VERSION: ${{ inputs.PLUGIN_VERSION }} 245 | outputs: 246 | artifact: ${{ steps.set-artifact-name.outputs.artifact }} 247 | steps: 248 | - name: Download Artifact 249 | uses: actions/download-artifact@v4 250 | with: 251 | name: ${{ needs.run-build-tools.outputs.artifact }} 252 | 253 | - name: Set up plugin folder name 254 | id: plugin-folder-name 255 | run: echo "plugin-folder-name=${PLUGIN_FOLDER_NAME:-${ARCHIVE_NAME:-${{ github.event.repository.name }}}}" >> $GITHUB_OUTPUT 256 | 257 | - name: Set up archive name 258 | id: plugin-data 259 | run: echo "archive-name=${ARCHIVE_NAME:-${{ github.event.repository.name }}}" >> $GITHUB_OUTPUT 260 | 261 | - name: Add commit hash to plugin header 262 | run: 'sed -Ei "s/SHA: .*/SHA: ${GIT_SHA}/g" ${{ inputs.PLUGIN_MAIN_FILE }}' 263 | 264 | - name: Set plugin version header 265 | run: 'sed -Ei "s/Version: .*/Version: ${PLUGIN_VERSION}/g" ${{ inputs.PLUGIN_MAIN_FILE }}' 266 | 267 | - name: Set up PHP 268 | uses: shivammathur/setup-php@v2 269 | with: 270 | php-version: ${{ inputs.PHP_VERSION_BUILD }} 271 | tools: wp-cli 272 | 273 | - name: Install dist-archive command 274 | run: wp package install wp-cli/dist-archive-command 275 | 276 | - name: Execute custom code before archive creation 277 | run: | 278 | ${{ inputs.PRE_SCRIPT }} 279 | 280 | - name: Run WP-CLI command 281 | run: | 282 | wp dist-archive . ./archive.zip --plugin-dirname=${{ steps.plugin-folder-name.outputs.plugin-folder-name }} 283 | 284 | # GitHub Action artifacts would otherwise produce a zip within a zip 285 | - name: Unzip archive to dist/ 286 | run: unzip archive.zip -d dist 287 | 288 | - name: Set artifact name 289 | id: set-artifact-name 290 | run: echo "artifact=${{ steps.plugin-data.outputs.archive-name }}" >> $GITHUB_OUTPUT 291 | 292 | - name: Upload artifact 293 | uses: actions/upload-artifact@v4 294 | with: 295 | name: ${{ steps.set-artifact-name.outputs.artifact }} 296 | path: ./dist/* 297 | include-hidden-files: true 298 | -------------------------------------------------------------------------------- /docs/build-and-distribute.md: -------------------------------------------------------------------------------- 1 | # Build & Distribute 2 | 3 | This action can be used to build plugin and theme archives and push them to corresponding build branches in a controlled and isolated environment via GitHub Actions. 4 | 5 | To achieve that, the reusable workflow: 6 | 7 | 1. Inspects the origin branch and determines the correlating build branch (strips `dev/` prefix) 8 | 2. Installs dependencies (including dev-dependencies) defined in `composer.json` 9 | 3. Installs Node.js dependencies and compiles assets via `npm run build` 10 | 4. Updates version information in plugin/theme headers and `package.json` 11 | 5. Executes [WordPress Translation Downloader](https://github.com/inpsyde/wp-translation-downloader) if configured by the package 12 | 6. Executes [PHP-Scoper](https://github.com/humbug/php-scoper) if configured by the package 13 | 7. Applies `.distignore` file filtering if present 14 | 8. Commits and pushes the build artifact to the determined build branch 15 | 9. Uploads the build as a GitHub Actions artifact for download 16 | 17 | ## Branch naming convention 18 | 19 | The workflow implements a branch scheme where development branches are prefixed with `dev/` and build branches contain the compiled assets: 20 | 21 | - **`dev/main`** → **`main`** (stable production code with assets, where releases are created) 22 | - **`dev/ABC-123`** → **`ABC-123`** (feature branch with compiled assets) 23 | - **`dev/feature/user-auth`** → **`feature/user-auth`** (feature branch with compiled assets) 24 | - **`dev/hotfix/critical-bug`** → **`hotfix/critical-bug`** (hotfix branch with compiled assets) 25 | 26 | This approach keeps source code separate from build artifacts while maintaining a clear relationship between development and build branches. The `main` branch (without `dev/` prefix) serves as the stable production branch where releases are created. 27 | 28 | ## Version handling 29 | 30 | If no `PACKAGE_VERSION` is provided, the workflow automatically: 31 | 32 | 1. Fetches the latest tag from the repository 33 | 2. Strips the `dev/` prefix from the branch name and normalizes it to be semver-compatible 34 | 3. Creates a pre-release version like `1.2.3-main` or `2.0.0-abc-123` 35 | 4. Falls back to `0.0.0-{branch}` if no tags exist 36 | 37 | **Examples:** 38 | 39 | - `dev/main` with latest tag `1.2.3` → `1.2.3-main` 40 | - `dev/ABC-123` with latest tag `1.2.3` → `1.2.3-abc-123` 41 | - `dev/feature/user-auth` with latest tag `2.0.0` → `2.0.0-feature-user-auth` 42 | 43 | This ensures every build has a unique, meaningful version identifier that traces back to both the base release and the source branch. 44 | 45 | ## Simple usage example 46 | 47 | ### WordPress Plugin/Theme or PHP Library 48 | 49 | For PHP-based projects with `composer.json`: 50 | 51 | ```yml 52 | name: Build and push assets 53 | on: 54 | push: 55 | branches: [ 'dev/main', 'dev/feature/**' ] 56 | workflow_dispatch: 57 | inputs: 58 | PACKAGE_VERSION: 59 | description: 'Package Version' 60 | required: false 61 | jobs: 62 | build-and-distribute: 63 | uses: inpsyde/reusable-workflows/.github/workflows/build-and-distribute.yml@main 64 | secrets: 65 | COMPOSER_AUTH_JSON: ${{ secrets.PACKAGIST_AUTH_JSON }} 66 | GITHUB_USER_EMAIL: ${{ secrets.DEPLOYBOT_EMAIL }} 67 | GITHUB_USER_NAME: ${{ secrets.DEPLOYBOT_USER }} 68 | GITHUB_USER_SSH_KEY: ${{ secrets.DEPLOYBOT_SSH_PRIVATE_KEY }} 69 | GITHUB_USER_SSH_PUBLIC_KEY: ${{ secrets.DEPLOYBOT_SSH_PUBLIC_KEY }} 70 | with: 71 | PACKAGE_VERSION: ${{ inputs.PACKAGE_VERSION }} 72 | ``` 73 | 74 | ### Frontend-only Library 75 | 76 | For JavaScript-only projects without `composer.json`, you can omit PHP-related secrets: 77 | 78 | ```yml 79 | name: Build and push assets 80 | on: 81 | push: 82 | branches: [ 'dev/main', 'dev/feature/**' ] 83 | workflow_dispatch: 84 | inputs: 85 | PACKAGE_VERSION: 86 | description: 'Package Version' 87 | required: false 88 | jobs: 89 | build-and-distribute: 90 | uses: inpsyde/reusable-workflows/.github/workflows/build-and-distribute.yml@main 91 | secrets: 92 | NPM_REGISTRY_TOKEN: ${{ secrets.NPM_TOKEN }} 93 | GITHUB_USER_EMAIL: ${{ secrets.DEPLOYBOT_EMAIL }} 94 | GITHUB_USER_NAME: ${{ secrets.DEPLOYBOT_USER }} 95 | GITHUB_USER_SSH_KEY: ${{ secrets.DEPLOYBOT_SSH_PRIVATE_KEY }} 96 | GITHUB_USER_SSH_PUBLIC_KEY: ${{ secrets.DEPLOYBOT_SSH_PUBLIC_KEY }} 97 | with: 98 | PACKAGE_VERSION: ${{ inputs.PACKAGE_VERSION }} 99 | NODE_VERSION: 20 100 | ``` 101 | 102 | **Note**: For frontend-only projects, the workflow automatically skips all PHP-related steps including: 103 | 104 | - PHP setup 105 | - Composer dependency installation 106 | - `composer.json` version updates 107 | - WordPress Translation Downloader 108 | - PHP-Scoper 109 | 110 | ## Configuration parameters 111 | 112 | ### Inputs 113 | 114 | | Name | Default | Description | 115 | |-----------------------|--------------------------------------------------|----------------------------------------------------------------------------------------------------------------------| 116 | | `NODE_OPTIONS` | `''` | Space-separated list of command-line Node options | 117 | | `NODE_VERSION` | `18` | Node version with which the assets will be compiled | 118 | | `NPM_REGISTRY_DOMAIN` | `'https://npm.pkg.github.com/'` | Domain of the private npm registry | 119 | | `PHP_VERSION` | `'8.2'` | PHP version with which the PHP tools are to be executed | 120 | | `PHP_TOOLS` | `''` | PHP tools supported by shivammathur/setup-php to be installed | 121 | | `COMPOSER_ARGS` | `'--no-dev --prefer-dist --optimize-autoloader'` | Set of arguments passed to Composer when gathering production dependencies | 122 | | `PACKAGE_NAME` | `''` | The name of the package (falls back to the repository name) | 123 | | `PACKAGE_VERSION` | `''` | The new package version. If not provided, will use latest tag version with branch name as pre-release identifier | 124 | | `PRE_SCRIPT` | `''` | Run custom shell code before creating the release archive | 125 | | `BUILT_BRANCH_NAME` | `''` | Override the automatic build branch naming (defaults to stripping `dev/` prefix from origin branch) | 126 | 127 | 128 | #### A note on `BUILT_BRANCH_NAME` 129 | 130 | By default, the workflow strips the `dev/` prefix from the origin branch to determine the build branch. You can override this behavior by providing a custom `BUILT_BRANCH_NAME`. This is useful for specific branching strategies or when you need to maintain compatibility with existing build processes. 131 | 132 | ## Secrets 133 | 134 | | Name | Description | 135 | |------------------------------|------------------------------------------------------------------------------------------| 136 | | `COMPOSER_AUTH_JSON` | Authentication for privately hosted packages and repositories as a JSON formatted object | 137 | | `NPM_REGISTRY_TOKEN` | Authentication for the private npm registry | 138 | | `GITHUB_USER_EMAIL` | Email address for the GitHub user configuration | 139 | | `GITHUB_USER_NAME` | Username for the GitHub user configuration | 140 | | `GITHUB_USER_SSH_KEY` | Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME` | 141 | | `GITHUB_USER_SSH_PUBLIC_KEY` | Public SSH key associated with the GitHub user passed as `GITHUB_USER_NAME` | 142 | | `ENV_VARS` | Additional environment variables as a JSON formatted object | 143 | 144 | ### Example with configuration parameters 145 | 146 | #### Recommendations 147 | 148 | - Set up your repository's default branch to be `dev/main` to follow the new branching convention 149 | - Ensure your `package.json` includes a `build` script for asset compilation 150 | - Use `.distignore` to exclude development files from the final build 151 | - Consider using [path filters](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-including-paths) to avoid unnecessary builds when only documentation changes 152 | - Use [concurrency settings](https://docs.github.com/en/actions/using-jobs/using-concurrency) to prevent conflicts when multiple pushes occur rapidly 153 | 154 | ```yml 155 | name: Build and push assets 156 | on: 157 | push: 158 | branches: [ 'dev/main', 'dev/develop', 'dev/feature/**', 'dev/hotfix/**' ] 159 | paths: 160 | - 'src/**' 161 | - 'assets/**' 162 | - 'package.json' 163 | - 'composer.json' 164 | - '.github/workflows/build-and-distribute.yml' 165 | workflow_dispatch: 166 | inputs: 167 | CUSTOM_PACKAGE_VERSION: 168 | description: 'Custom Package Version (skip auto-detection of pre-release version)' 169 | required: false 170 | CUSTOM_BUILD_BRANCH: 171 | description: 'Custom build branch name' 172 | required: false 173 | 174 | concurrency: 175 | group: ${{ github.workflow }}-${{ github.ref }} 176 | cancel-in-progress: true 177 | 178 | jobs: 179 | build-and-distribute: 180 | uses: inpsyde/reusable-workflows/.github/workflows/build-and-distribute.yml@main 181 | secrets: 182 | COMPOSER_AUTH_JSON: ${{ secrets.PACKAGIST_AUTH_JSON }} 183 | NPM_REGISTRY_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 184 | GITHUB_USER_EMAIL: ${{ secrets.DEPLOYBOT_EMAIL }} 185 | GITHUB_USER_NAME: ${{ secrets.DEPLOYBOT_USER }} 186 | GITHUB_USER_SSH_KEY: ${{ secrets.DEPLOYBOT_SSH_PRIVATE_KEY }} 187 | GITHUB_USER_SSH_PUBLIC_KEY: ${{ secrets.DEPLOYBOT_SSH_PUBLIC_KEY }} 188 | ENV_VARS: >- 189 | [{"name":"BUILD_ENV", "value":"production"}] 190 | with: 191 | PACKAGE_VERSION: ${{ inputs.CUSTOM_PACKAGE_VERSION }} 192 | BUILT_BRANCH_NAME: ${{ inputs.CUSTOM_BUILD_BRANCH }} 193 | NODE_VERSION: 20 194 | PHP_VERSION: '8.3' 195 | PRE_SCRIPT: | 196 | echo "Starting custom build process..." 197 | ``` 198 | 199 | ## Build process details 200 | 201 | ### Project Type Detection 202 | 203 | The workflow automatically detects the project type and adjusts the build process accordingly: 204 | 205 | **PHP-based projects** (WordPress plugins/themes or PHP libraries): 206 | 207 | - Detected by the presence of `composer.json` 208 | - Includes PHP setup, Composer dependency installation, and PHP-specific tooling 209 | - Updates version in `composer.json` 210 | 211 | **Frontend-only projects** (JavaScript libraries): 212 | 213 | - No `composer.json` present 214 | - Skips PHP-related steps entirely 215 | - Only processes Node.js dependencies and asset compilation 216 | 217 | This makes the workflow flexible enough to handle both full-stack WordPress projects and frontend-only library packages. 218 | 219 | ### Version Management 220 | 221 | The workflow handles version information for both plugins and themes: 222 | 223 | - Updates `Version:` header in the main plugin file 224 | - Updates `SHA:` header with the current commit hash 225 | - Updates version in `package.json` and `composer.json` 226 | 227 | ### Asset Compilation 228 | 229 | The workflow expects a `build` script in your `package.json`: 230 | 231 | ```json 232 | { 233 | "scripts": { 234 | "build": "wp-scripts build" 235 | } 236 | } 237 | ``` 238 | 239 | ### PHP-Scoper Integration 240 | 241 | If a `scoper.inc.php` file is present, the workflow will: 242 | 243 | 1. Run PHP-Scoper to prefix all PHP dependencies 244 | 2. Rebuild the autoloader for the scoped dependencies 245 | 3. Ensure unique autoload cache keys to prevent conflicts 246 | 247 | ### Distignore Support 248 | 249 | If a `.distignore` file is present, the workflow will: 250 | 251 | 1. Replace `.gitignore` with `.distignore` for the build process 252 | 2. Remove all files and directories listed in `.distignore` 253 | 3. Clean up any untracked files that match the ignore patterns 254 | 255 | This is particularly useful for excluding source files, tests, and development tools from the final build. 256 | 257 | ## Artifact Output 258 | 259 | The workflow produces two outputs: 260 | 261 | 1. **Build Branch**: The compiled code pushed to the build branch (e.g., `dev/main` → `main`) 262 | 2. **GitHub Artifact**: A downloadable archive named `{package-name}-{version}` containing the build (without `.git` folder) 263 | 264 | ## Migration from previous workflows 265 | 266 | If you're migrating from a previous build workflow, consider these changes: 267 | 268 | 1. **Update branch triggers**: Change from `main` to `dev/main` and `feature/**` to `dev/feature/**` 269 | 2. **Update default branch**: Set your repository's default branch to `dev/main` 270 | 3. **Update Composer constraints**: If you use branch-specific Composer constraints, update them to reference the new build branches (e.g., `dev-main` instead of `dev-dev/main`) 271 | 4. **Update deployment targets**: Update any deployment scripts or configurations that reference the old branch names 272 | 273 | The new branching convention provides clearer separation between development and production-ready code, with the `main` branch always containing the latest stable build artifacts ready for release. 274 | -------------------------------------------------------------------------------- /.github/workflows/build-and-push-assets.yml: -------------------------------------------------------------------------------- 1 | name: "[DEPRECATED] Build and push assets" 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | NODE_OPTIONS: 7 | description: Space-separated list of command-line Node options. 8 | type: string 9 | default: '' 10 | required: false 11 | NODE_VERSION: 12 | description: Node version with which the static code analysis is to be executed. 13 | default: 18 14 | required: false 15 | type: string 16 | NPM_REGISTRY_DOMAIN: 17 | description: Domain of the private npm registry. 18 | default: https://npm.pkg.github.com/ 19 | required: false 20 | type: string 21 | PACKAGE_MANAGER: 22 | description: Package manager with which the dependencies should be installed (`npm` or `yarn`). 23 | default: 'npm' 24 | required: false 25 | type: string 26 | WORKING_DIRECTORY: 27 | description: Working directory path. 28 | default: './' 29 | required: false 30 | type: string 31 | COMPILE_SCRIPT_PROD: 32 | description: Script added to "npm run" or "yarn" to build production assets. 33 | type: string 34 | default: 'build' 35 | required: false 36 | COMPILE_SCRIPT_DEV: 37 | description: Script added to "npm run" or "yarn" to build development assets. 38 | type: string 39 | default: 'build:dev' 40 | required: false 41 | MODE: 42 | description: Mode for compiling assets (`prod` or `dev`) 43 | default: '' 44 | required: false 45 | type: string 46 | ASSETS_TARGET_PATHS: 47 | description: Space-separated list of target directory paths for compiled assets. 48 | default: './assets' 49 | required: false 50 | type: string 51 | ASSETS_TARGET_FILES: 52 | description: Space-separated list of target file paths for compiled assets. 53 | default: '' 54 | required: false 55 | type: string 56 | BUILT_BRANCH_NAME: 57 | description: Sets the target branch for pushing assets on the `branch` event. 58 | type: string 59 | default: '' 60 | required: false 61 | RELEASE_BRANCH_NAME: 62 | description: On tag events, target branch where compiled assets are pushed and the tag is moved to. 63 | type: string 64 | default: '' 65 | required: false 66 | PHP_VERSION: 67 | description: PHP version with which the PHP tools are to be executed. 68 | default: '8.2' 69 | required: false 70 | type: string 71 | PHP_TOOLS: 72 | description: PHP tools supported by shivammathur/setup-php to be installed. 73 | default: '' 74 | required: false 75 | type: string 76 | secrets: 77 | NPM_REGISTRY_TOKEN: 78 | description: Authentication for the private npm registry. 79 | required: false 80 | GITHUB_USER_EMAIL: 81 | description: Email address for the GitHub user configuration. 82 | required: true 83 | GITHUB_USER_NAME: 84 | description: Username for the GitHub user configuration. 85 | required: true 86 | GITHUB_USER_SSH_KEY: 87 | description: Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME`. 88 | required: false 89 | GITHUB_USER_SSH_PUBLIC_KEY: 90 | description: Public SSH key associated with the GitHub user passed as `GITHUB_USER_NAME`. 91 | required: false 92 | ENV_VARS: 93 | description: Additional environment variables as a JSON formatted object. 94 | required: false 95 | jobs: 96 | checks: 97 | runs-on: ubuntu-latest 98 | outputs: 99 | is_development_branch_last_commit: ${{ github.sha == steps.detect_development_branch_last_commit.outputs.development_branch_last_commit && 'yes' || 'no' }} 100 | is_moved_tag: ${{ (github.ref_type == 'tag' && contains(steps.detect_tag_annotation.outputs.tag_annotation, env.MOVED_TAG_ANNOTATION_PATTERN)) && 'yes' || 'no' }} 101 | steps: 102 | - name: Deprecation warning 103 | run: echo "::warning::This workflow is deprecated and will be removed soon. Use .github/workflows/build-and-distribute.yml instead." 104 | 105 | - name: Checkout repository 106 | uses: actions/checkout@v4 107 | with: 108 | fetch-depth: 0 109 | 110 | - name: Checkout development branch 111 | # it is not possible to get tag annotation from detached state https://github.com/actions/runner/issues/712 112 | run: | 113 | git checkout ${{ github.event.repository.default_branch }} 114 | 115 | - name: Find last commit 116 | id: detect_development_branch_last_commit 117 | run: | 118 | echo "development_branch_last_commit=$(git --no-pager log -n 1 --pretty=tformat:'%H')" >> "$GITHUB_OUTPUT" 119 | 120 | - name: Find tag message 121 | id: detect_tag_annotation 122 | if: ${{ github.ref_type == 'tag' }} 123 | run: | 124 | git fetch --tags --force # Retrieve annotated tags https://github.com/actions/checkout/issues/290 125 | echo "tag_annotation=$(git --no-pager tag -l --format='%(contents:subject)' ${{ github.ref_name }})" >> "$GITHUB_OUTPUT" 126 | 127 | - name: Prepare moved tag annotation pattern 128 | run: | 129 | echo "MOVED_TAG_ANNOTATION_PATTERN=// Released by ${{ github.workflow }}" >> $GITHUB_ENV 130 | 131 | compile-assets: 132 | needs: checks 133 | if: ${{ github.ref_type == 'branch' || (github.ref_type == 'tag' && needs.checks.outputs.is_moved_tag == 'no') }} 134 | defaults: 135 | run: 136 | working-directory: ${{ inputs.WORKING_DIRECTORY }} 137 | timeout-minutes: 10 138 | runs-on: ubuntu-latest 139 | env: 140 | NODE_CACHE_MODE: '' 141 | COMPILE_SCRIPT: '' 142 | TAG_NAME: '' # we'll override if the push is for tag 143 | TAG_BRANCH_NAME: '' # we'll override if the push is for tag 144 | NO_CHANGES: '' # we'll override if no changes to commit 145 | steps: 146 | - name: PACKAGE_MANAGER deprecation warning 147 | if: ${{ inputs.PACKAGE_MANAGER != '' }} 148 | run: | 149 | if [ "${{ inputs.PACKAGE_MANAGER }}" == 'npm' ]; then 150 | echo "::warning::The PACKAGE_MANAGER input is deprecated and will be removed soon. Please remove it. The workflow already uses npm by default." 151 | else 152 | echo "::warning::The PACKAGE_MANAGER input is deprecated and will be removed soon. Please update your workflow to use npm." 153 | fi 154 | 155 | - name: Checkout 156 | uses: actions/checkout@v4 157 | with: 158 | fetch-depth: 0 159 | ssh-key: ${{ secrets.GITHUB_USER_SSH_KEY }} 160 | 161 | - name: Set up SSH 162 | env: 163 | GITHUB_USER_SSH_KEY: ${{ secrets.GITHUB_USER_SSH_KEY }} 164 | if: ${{ env.GITHUB_USER_SSH_KEY != '' }} 165 | uses: webfactory/ssh-agent@v0.9.1 166 | with: 167 | ssh-private-key: ${{ env.GITHUB_USER_SSH_KEY }} 168 | 169 | - name: Set up Git 170 | run: | 171 | git config --global user.email "${{ secrets.GITHUB_USER_EMAIL }}" 172 | git config --global user.name "${{ secrets.GITHUB_USER_NAME }}" 173 | git config --global advice.addIgnoredFile false 174 | git config --global push.autoSetupRemote true 175 | 176 | - name: Set up signing commits 177 | env: 178 | GITHUB_USER_SSH_PUBLIC_KEY: ${{ secrets.GITHUB_USER_SSH_PUBLIC_KEY }} 179 | if: ${{ env.GITHUB_USER_SSH_PUBLIC_KEY != '' }} 180 | run: | 181 | : # Create empty SSH private key file so Git does not complain. 182 | touch "${{ runner.temp }}/signingkey" 183 | echo "${{ env.GITHUB_USER_SSH_PUBLIC_KEY }}" > "${{ runner.temp }}/signingkey.pub" 184 | git config --global commit.gpgsign true 185 | git config --global gpg.format ssh 186 | git config --global user.signingkey "${{ runner.temp }}/signingkey.pub" 187 | 188 | - name: Set up custom environment variables 189 | env: 190 | ENV_VARS: ${{ secrets.ENV_VARS }} 191 | if: ${{ env.ENV_VARS }} 192 | uses: actions/github-script@v7 193 | with: 194 | script: | 195 | JSON 196 | .parse(process.env.ENV_VARS) 197 | .forEach(envVar => core.exportVariable(envVar.name, envVar.value)); 198 | 199 | - name: Set compile script 200 | run: | 201 | if [ "${{ inputs.MODE }}" == 'dev' ]; then 202 | echo "COMPILE_SCRIPT=${{ inputs.COMPILE_SCRIPT_DEV }}" >> $GITHUB_ENV 203 | elif [ "${{ inputs.MODE }}" == 'prod' ]; then 204 | echo "COMPILE_SCRIPT=${{ inputs.COMPILE_SCRIPT_PROD }}" >> $GITHUB_ENV 205 | elif [ ${{ contains(github.ref, 'refs/tags/') }} ]; then 206 | echo "COMPILE_SCRIPT=${{ inputs.COMPILE_SCRIPT_PROD }}" >> $GITHUB_ENV 207 | else 208 | echo "COMPILE_SCRIPT=${{ inputs.COMPILE_SCRIPT_DEV }}" >> $GITHUB_ENV 209 | fi 210 | 211 | - name: Set branch environment variables 212 | if: ${{ github.ref_type == 'branch' }} 213 | run: | 214 | echo "BUILT_BRANCH_NAME=${{ inputs.BUILT_BRANCH_NAME && inputs.BUILT_BRANCH_NAME || github.ref_name }}" >> $GITHUB_ENV 215 | 216 | - name: Set tag environment variables 217 | if: ${{ github.ref_type == 'tag' }} 218 | run: | 219 | echo "TAG_NAME=$(echo ${GITHUB_REF#refs/*/})" >> $GITHUB_ENV 220 | echo "RELEASE_BRANCH_ENABLED=${{ (needs.checks.outputs.is_development_branch_last_commit == 'yes' && inputs.RELEASE_BRANCH_NAME != '') && 'yes' || 'no' }}" >> $GITHUB_ENV 221 | 222 | - name: Checkout and merge the built branch 223 | if: ${{ github.ref_type == 'branch' }} 224 | run: | 225 | git show-ref -q refs/remotes/origin/${{ env.BUILT_BRANCH_NAME }} && git checkout ${{ env.BUILT_BRANCH_NAME }} || git checkout -b ${{ env.BUILT_BRANCH_NAME }} 226 | git merge ${{ github.ref_name }} 227 | 228 | - name: Git pull on re-run 229 | if: ${{ (github.run_attempt > 1) && (github.ref_type != 'tag') }} 230 | run: git show-ref -q refs/remotes/origin/$(git branch --show-current) && git pull || true 231 | 232 | - name: Checkout and merge the release branch 233 | if: ${{ github.ref_type == 'tag' && env.RELEASE_BRANCH_ENABLED == 'yes' }} 234 | run: | 235 | git checkout ${{ github.event.repository.default_branch }} 236 | git show-ref -q refs/remotes/origin/${{ inputs.RELEASE_BRANCH_NAME }} && git checkout ${{ inputs.RELEASE_BRANCH_NAME }} || git checkout -b ${{ inputs.RELEASE_BRANCH_NAME }} 237 | git merge ${{ github.event.repository.default_branch }} 238 | 239 | - name: Checkout temporary tag branch 240 | if: ${{ github.ref_type == 'tag' && env.RELEASE_BRANCH_ENABLED == 'no' }} 241 | run: | 242 | git checkout -b bot/compiled-assets/${{ github.sha }} 243 | echo "TAG_BRANCH_NAME=bot/compiled-assets/${{ github.sha }}" >> $GITHUB_ENV 244 | 245 | - name: Prepare directories 246 | run: | 247 | mkdir -p ${{ inputs.ASSETS_TARGET_PATHS }} 248 | 249 | - name: Set up node cache mode 250 | run: | 251 | if [ "${{ inputs.PACKAGE_MANAGER }}" == 'npm' ] && { [ -f "${GITHUB_WORKSPACE}/package-lock.json" ] || [ -f "${GITHUB_WORKSPACE}/npm-shrinkwrap.json" ]; }; then 252 | echo "NODE_CACHE_MODE=npm" >> $GITHUB_ENV 253 | elif [ "${{ inputs.PACKAGE_MANAGER }}" == 'yarn' ] && [ -f "${GITHUB_WORKSPACE}/yarn.lock" ]; then 254 | echo "NODE_CACHE_MODE=yarn" >> $GITHUB_ENV 255 | else 256 | echo "No lock files found or unknown package manager" 257 | fi 258 | 259 | - name: Set up node 260 | uses: actions/setup-node@v4 261 | env: 262 | NODE_OPTIONS: ${{ inputs.NODE_OPTIONS }} 263 | NODE_AUTH_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 264 | with: 265 | node-version: ${{ inputs.NODE_VERSION }} 266 | registry-url: ${{ inputs.NPM_REGISTRY_DOMAIN }} 267 | cache: ${{ env.NODE_CACHE_MODE }} 268 | 269 | - name: Set up PHP 270 | if: ${{ inputs.PHP_TOOLS }} 271 | uses: shivammathur/setup-php@v2 272 | with: 273 | php-version: ${{ inputs.PHP_VERSION }} 274 | tools: ${{ inputs.PHP_TOOLS }} 275 | coverage: none 276 | 277 | - name: Install dependencies 278 | env: 279 | ARGS: ${{ env.NODE_CACHE_MODE == 'yarn' && '--frozen-lockfile' || env.NODE_CACHE_MODE == 'npm' && 'ci' || 'install' }} 280 | run: ${{ format('{0} {1}', inputs.PACKAGE_MANAGER, env.ARGS) }} 281 | 282 | - name: Compile assets 283 | run: ${{ inputs.PACKAGE_MANAGER == 'yarn' && 'yarn' || 'npm run' }} ${{ env.COMPILE_SCRIPT }} 284 | 285 | - name: Git add, commit 286 | run: | 287 | declare -a TARGET_DIRECTORY_PATHS_ARRAY=(${{ inputs.ASSETS_TARGET_PATHS }}) 288 | for path in "${TARGET_DIRECTORY_PATHS_ARRAY[@]}"; do git add -f "${path}/*"; done 289 | declare -a TARGET_FILES_PATHS_ARRAY=(${{ inputs.ASSETS_TARGET_FILES }}) 290 | for path in "${TARGET_FILES_PATHS_ARRAY[@]}"; do [[ -f "$path" ]] && git add -f "${path}"; done 291 | git add -A 292 | git commit -m "[BOT] Add compiled assets for #${{ github.ref }}" --no-verify || ((echo "NO_CHANGES=yes" >> $GITHUB_ENV) && (echo "No changes to commit")) 293 | 294 | - name: Git push for branch 295 | if: ${{ github.ref_type == 'branch' }} 296 | run: git push 297 | 298 | - name: Git push for tag 299 | if: ${{ github.ref_type == 'tag' && (env.NO_CHANGES != 'yes' || env.RELEASE_BRANCH_ENABLED == 'yes') }} 300 | run: git push 301 | 302 | - name: Move tag 303 | if: ${{ github.ref_type == 'tag' && (env.NO_CHANGES != 'yes' || env.RELEASE_BRANCH_ENABLED == 'yes') }} 304 | run: | 305 | git tag -d ${{ env.TAG_NAME }} 306 | git push origin :refs/tags/${{ env.TAG_NAME }} 307 | git tag -a -m "[RELEASE] ${{ github.sha }} // Released by ${{ github.workflow }}" ${{ env.TAG_NAME }} 308 | git push origin --tags 309 | 310 | - name: Delete temporary tag branch 311 | if: ${{ always() && env.TAG_BRANCH_NAME != '' && env.NO_CHANGES != 'yes' }} 312 | run: | 313 | git checkout --detach 314 | git branch -d ${{ env.TAG_BRANCH_NAME }} 315 | git push origin --delete ${{ env.TAG_BRANCH_NAME }} 316 | 317 | - name: Delete signing key files 318 | env: 319 | GITHUB_USER_SSH_PUBLIC_KEY: ${{ secrets.GITHUB_USER_SSH_PUBLIC_KEY }} 320 | if: ${{ always() && env.GITHUB_USER_SSH_PUBLIC_KEY != '' }} 321 | run: | 322 | rm -f "${{ runner.temp }}/signingkey" 323 | rm -f "${{ runner.temp }}/signingkey.pub" 324 | -------------------------------------------------------------------------------- /.github/workflows/build-and-distribute.yml: -------------------------------------------------------------------------------- 1 | name: Build and distribute package 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | NODE_OPTIONS: 7 | description: Space-separated list of command-line Node options. 8 | type: string 9 | default: '' 10 | required: false 11 | NODE_VERSION: 12 | description: Node version to be used. 13 | default: 18 14 | required: false 15 | type: string 16 | NPM_REGISTRY_DOMAIN: 17 | description: Domain of the private npm registry. 18 | default: https://npm.pkg.github.com/ 19 | required: false 20 | type: string 21 | PHP_VERSION: 22 | description: PHP version with which the PHP tools are to be executed. 23 | default: '8.2' 24 | required: false 25 | type: string 26 | PHP_TOOLS: 27 | description: PHP tools supported by shivammathur/setup-php to be installed. 28 | default: '' 29 | required: false 30 | type: string 31 | COMPOSER_ARGS: 32 | description: Set of arguments passed to Composer. 33 | default: '--no-dev --prefer-dist --optimize-autoloader' 34 | required: false 35 | type: string 36 | PACKAGE_NAME: 37 | description: The name of the package (falls back to the repository name). 38 | required: false 39 | default: '' 40 | type: string 41 | PACKAGE_VERSION: 42 | description: The new package version. If not provided, will use latest release version with branch name as pre-release identifier. 43 | required: false 44 | type: string 45 | PRE_SCRIPT: 46 | description: Run custom shell code before creating the release archive. 47 | default: '' 48 | required: false 49 | type: string 50 | BUILT_BRANCH_NAME: 51 | description: Sets the target branch for pushing assets. If not provided, strips 'dev/' prefix from origin branch. 52 | type: string 53 | default: '' 54 | required: false 55 | 56 | secrets: 57 | COMPOSER_AUTH_JSON: 58 | description: Authentication for privately hosted packages and repositories as a JSON formatted object. 59 | required: false 60 | NPM_REGISTRY_TOKEN: 61 | description: Authentication for the private npm registry. 62 | required: false 63 | GITHUB_USER_EMAIL: 64 | description: Email address for the GitHub user configuration. 65 | required: true 66 | GITHUB_USER_NAME: 67 | description: Username for the GitHub user configuration. 68 | required: true 69 | GITHUB_USER_SSH_KEY: 70 | description: Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME`. 71 | required: true 72 | GITHUB_USER_SSH_PUBLIC_KEY: 73 | description: Public SSH key associated with the GitHub user passed as `GITHUB_USER_NAME`. 74 | required: true 75 | ENV_VARS: 76 | description: Additional environment variables as a JSON formatted object. 77 | required: false 78 | 79 | jobs: 80 | build-and-distribute: 81 | timeout-minutes: 10 82 | runs-on: ubuntu-latest 83 | # Used to check whether there are changes to commit 84 | env: 85 | HAS_GIT_CHANGES: '' 86 | # Disables symlinking of local "path" repositories during package installation. 87 | # During development, symlinking is preferable. 88 | # In production builds, we ship the actual install location and remove the source directory. 89 | COMPOSER_MIRROR_PATH_REPOS: 1 90 | PACKAGE_VERSION: ${{ inputs.PACKAGE_VERSION }} # Will be overridden by determine package version step 91 | PACKAGE_NAME: ${{ inputs.PACKAGE_NAME && inputs.PACKAGE_NAME || github.event.repository.name }} 92 | steps: 93 | - name: Checkout 94 | uses: actions/checkout@v4 95 | with: 96 | fetch-depth: 0 97 | ssh-key: ${{ secrets.GITHUB_USER_SSH_KEY }} 98 | 99 | - name: Set up SSH 100 | uses: webfactory/ssh-agent@v0.9.0 101 | with: 102 | ssh-private-key: ${{ secrets.GITHUB_USER_SSH_KEY }} 103 | 104 | - name: Set up Git 105 | run: | 106 | git config --global user.email "${{ secrets.GITHUB_USER_EMAIL }}" 107 | git config --global user.name "${{ secrets.GITHUB_USER_NAME }}" 108 | git config --global advice.addIgnoredFile false 109 | git config --global push.autoSetupRemote true 110 | 111 | - name: Set up signing commits 112 | run: | 113 | : # Create empty SSH private key file so Git does not complain. 114 | touch "${{ runner.temp }}/signingkey" 115 | echo "${{ secrets.GITHUB_USER_SSH_PUBLIC_KEY }}" > "${{ runner.temp }}/signingkey.pub" 116 | git config --global commit.gpgsign true 117 | git config --global gpg.format ssh 118 | git config --global user.signingkey "${{ runner.temp }}/signingkey.pub" 119 | 120 | - name: Set up custom environment variables 121 | env: 122 | ENV_VARS: ${{ secrets.ENV_VARS }} 123 | if: ${{ env.ENV_VARS }} 124 | uses: actions/github-script@v7 125 | with: 126 | script: | 127 | JSON 128 | .parse(process.env.ENV_VARS) 129 | .forEach(envVar => core.exportVariable(envVar.name, envVar.value)); 130 | 131 | - name: Determine package version 132 | if: ${{ !inputs.PACKAGE_VERSION }} 133 | env: 134 | GITHUB_TOKEN: ${{ secrets.GITHUB_USER_SSH_KEY }} 135 | run: | 136 | # Fetch latest public release 137 | LATEST_RELEASE=$(git for-each-ref --sort=-creatordate --count=1 --format='%(refname:short)' refs/tags || echo "") 138 | 139 | if [ -z "$LATEST_RELEASE" ]; then 140 | echo "No releases found, using default version 0.0.0." 141 | LATEST_RELEASE="0.0.0" 142 | else 143 | echo "Latest release found: $LATEST_RELEASE" 144 | fi 145 | 146 | # Get short SHA (first 7 characters) 147 | SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) 148 | echo "Short SHA: $SHORT_SHA" 149 | 150 | # Strip 'dev/' prefix from branch name for version string (if it exists) 151 | ORIGIN_BRANCH="${{ github.ref_name }}" 152 | VERSION_BRANCH="${ORIGIN_BRANCH#dev/}" 153 | 154 | # Normalize branch name for semver pre-release identifier 155 | # Replace invalid characters with hyphens and convert to lowercase 156 | NORMALIZED_BRANCH=$(echo "$VERSION_BRANCH" | sed 's/[^a-zA-Z0-9-]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g' | tr '[:upper:]' '[:lower:]') 157 | 158 | # Construct semver with pre-release identifier using short SHA 159 | PACKAGE_VERSION="${LATEST_RELEASE}+${NORMALIZED_BRANCH}.${SHORT_SHA}" 160 | echo "Generated package version: $PACKAGE_VERSION" 161 | 162 | echo "PACKAGE_VERSION=$PACKAGE_VERSION" >> $GITHUB_ENV 163 | 164 | - name: Inspect origin branch and determine build branch 165 | run: | 166 | ORIGIN_BRANCH="${{ github.ref_name }}" 167 | echo "Origin branch: $ORIGIN_BRANCH" 168 | 169 | # Branch scheme: 170 | # dev/main -> main (stable production code with assets, where releases are created) 171 | # dev/ABC-123 -> ABC-123 (feature branch with compiled assets) 172 | # dev/xyz -> xyz (any other branch) 173 | 174 | # Determine the build branch name 175 | if [ -n "${{ inputs.BUILT_BRANCH_NAME }}" ]; then 176 | BUILD_BRANCH="${{ inputs.BUILT_BRANCH_NAME }}" 177 | else 178 | # Strip 'dev/' prefix from origin branch to get build branch (if it exists) 179 | BUILD_BRANCH="${ORIGIN_BRANCH#dev/}" 180 | fi 181 | 182 | echo "Build branch: $BUILD_BRANCH" 183 | echo "BUILT_BRANCH_NAME=$BUILD_BRANCH" >> $GITHUB_ENV 184 | 185 | - name: Validate branch strategy 186 | run: | 187 | ORIGIN_BRANCH="${{ github.ref_name }}" 188 | 189 | # Check if we would commit to the same branch as the origin 190 | if [ "$ORIGIN_BRANCH" = "${{ env.BUILT_BRANCH_NAME }}" ]; then 191 | echo "❌ ERROR: Build artifacts would be committed to the same branch as the source code!" 192 | echo "" 193 | echo "This workflow is designed to keep source code and build artifacts separate." 194 | echo "You have two options:" 195 | echo "" 196 | echo "1. Use a 'dev/' prefixed branch (e.g., 'dev/main', 'dev/feature-branch')" 197 | echo " - Source: dev/main → Build artifacts: main" 198 | echo " - Source: dev/ABC-123 → Build artifacts: ABC-123" 199 | echo "" 200 | echo "2. Provide an explicit BUILT_BRANCH_NAME input to specify where build artifacts should go." 201 | echo "" 202 | echo "Current branch: $ORIGIN_BRANCH" 203 | echo "Target build branch: ${{ env.BUILT_BRANCH_NAME }}" 204 | echo "" 205 | exit 1 206 | fi 207 | 208 | echo "✅ Branch strategy validated:" 209 | echo " Source branch: $ORIGIN_BRANCH" 210 | echo " Build branch: ${{ env.BUILT_BRANCH_NAME }}" 211 | 212 | - name: Checkout the build branch with clean slate 213 | run: | 214 | # Check if build branch exists remotely 215 | if git show-ref -q refs/remotes/origin/${{ env.BUILT_BRANCH_NAME }}; then 216 | echo "Build branch exists, checking out and cleaning..." 217 | git checkout ${{ env.BUILT_BRANCH_NAME }} 218 | 219 | # Clean everything except .git directory for a fresh start 220 | git rm -rf . || true 221 | git clean -fxd 222 | echo "✅ Cleaned existing build branch for fresh build" 223 | else 224 | echo "Build branch doesn't exist, creating new branch..." 225 | git checkout -b ${{ env.BUILT_BRANCH_NAME }} 226 | echo "✅ Created new build branch" 227 | fi 228 | 229 | # Copy all files from source branch 230 | git checkout ${{ github.ref_name }} -- . 231 | echo "✅ Copied source files to build branch" 232 | 233 | - name: Check for composer.json 234 | id: check-composer 235 | run: | 236 | if [ -f "composer.json" ]; then 237 | echo "Composer project detected (composer.json exists)" 238 | echo "HAS_COMPOSER=true" >> $GITHUB_ENV 239 | else 240 | echo "No composer.json found - skipping PHP-related steps" 241 | echo "HAS_COMPOSER=false" >> $GITHUB_ENV 242 | fi 243 | 244 | - name: Set up PHP 245 | if: ${{ env.HAS_COMPOSER == 'true' }} 246 | uses: shivammathur/setup-php@v2 247 | with: 248 | php-version: ${{ inputs.PHP_VERSION }} 249 | tools: php-scoper, sniccowp/php-scoper-wordpress-excludes, composer/installers, inpsyde/wp-translation-downloader, ${{ inputs.PHP_TOOLS }} 250 | coverage: none 251 | 252 | - name: Install Composer dependencies 253 | if: ${{ env.HAS_COMPOSER == 'true' }} 254 | uses: ramsey/composer-install@v3 255 | env: 256 | COMPOSER_AUTH: '${{ secrets.COMPOSER_AUTH_JSON }}' 257 | with: 258 | composer-options: ${{ inputs.COMPOSER_ARGS }} 259 | 260 | - name: Set up node 261 | uses: actions/setup-node@v4 262 | env: 263 | NODE_OPTIONS: ${{ inputs.NODE_OPTIONS }} 264 | NODE_AUTH_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }} 265 | with: 266 | node-version: ${{ inputs.NODE_VERSION }} 267 | registry-url: ${{ inputs.NPM_REGISTRY_DOMAIN }} 268 | cache: npm 269 | 270 | - name: Install dependencies 271 | run: npm ci 272 | 273 | - name: Determine package type 274 | run: | 275 | # Check for WordPress theme (style.css with Theme Name header) 276 | if [ -f "style.css" ] && grep -q "Theme Name:" style.css; then 277 | echo "Package type: WordPress Theme" 278 | echo "PACKAGE_TYPE=wordpress-theme" >> $GITHUB_ENV 279 | # Check for WordPress plugin (PHP file with Plugin Name header) 280 | else 281 | # Find the first PHP file with "Plugin Name:" header 282 | PLUGIN_FILE=$(grep -rl --include "*.php" "Plugin Name:" . 2>/dev/null | head -n1) 283 | if [ -n "$PLUGIN_FILE" ]; then 284 | echo "Package type: WordPress Plugin" 285 | echo "Plugin file found: $PLUGIN_FILE" 286 | echo "PACKAGE_TYPE=wordpress-plugin" >> $GITHUB_ENV 287 | echo "PLUGIN_MAIN_FILE=$PLUGIN_FILE" >> $GITHUB_ENV 288 | # Fallback to generic library 289 | else 290 | echo "Package type: Library" 291 | echo "PACKAGE_TYPE=library" >> $GITHUB_ENV 292 | fi 293 | fi 294 | 295 | - name: Prepare WordPress Plugin 296 | if: ${{ env.PACKAGE_TYPE == 'wordpress-plugin' }} 297 | run: | 298 | PLUGIN_FILE="${{ env.PLUGIN_MAIN_FILE }}" 299 | echo "Updating plugin file: $PLUGIN_FILE" 300 | sed -Ei "s/Version: .*/Version: ${{ env.PACKAGE_VERSION }}/g" "$PLUGIN_FILE" 301 | sed -Ei "s/SHA: .*/SHA: ${{ github.sha }}/g" "$PLUGIN_FILE" 302 | 303 | - name: Prepare WordPress Theme 304 | if: ${{ env.PACKAGE_TYPE == 'wordpress-theme' }} 305 | run: | 306 | # Update theme style.css file 307 | echo "Updating theme style.css." 308 | sed -Ei "s/Version: .*/Version: ${{ env.PACKAGE_VERSION }}/g" style.css 309 | sed -Ei "s/SHA: .*/SHA: ${{ github.sha }}/g" style.css 310 | 311 | - name: Update version in package.json 312 | run: npm version ${{ env.PACKAGE_VERSION }} --no-git-tag-version --allow-same-version 313 | 314 | - name: Update version in composer.json 315 | if: ${{ env.HAS_COMPOSER == 'true' }} 316 | run: | 317 | echo "Updating composer.json version to ${{ env.PACKAGE_VERSION }}." 318 | jq --arg version "${{ env.PACKAGE_VERSION }}" '.version = $version' composer.json > composer.json.tmp && mv composer.json.tmp composer.json 319 | echo "✅ Updated composer.json version field." 320 | 321 | - name: Execute custom code before archive creation 322 | run: | 323 | ${{ inputs.PRE_SCRIPT }} 324 | 325 | - name: Compile assets 326 | run: npm run build 327 | 328 | - name: Run WordPress Translation Downloader 329 | if: ${{ env.HAS_COMPOSER == 'true' }} 330 | run: composer wp-translation-downloader:download 331 | 332 | - name: Run PHP-Scoper 333 | if: ${{ env.HAS_COMPOSER == 'true' && hashFiles('scoper.inc.php') != '' }} 334 | # The sed call appends the Git commit SHA to the Composer autoload cache key to ensure unique identification for prefixed files. 335 | # This prevents Composer from skipping autoloaded files due to hash collisions based on relative paths. 336 | run: | 337 | php-scoper add-prefix --force --output-dir=build 338 | composer --working-dir=build dump-autoload -o 339 | sed -i "s/'__composer_autoload_files'/\'__composer_autoload_files_${{ github.sha }}'/g" "build/vendor/composer/autoload_real.php" 340 | rsync -av ./build/ . && rm -rf ./build/ 341 | 342 | - name: Apply .distignore file 343 | if: ${{ hashFiles('.distignore') != '' }} 344 | # Configure git to (gracefully) use .distignore file during packaging instead of the regular .gitignore file. 345 | # Then clean up all files possibly mentioned in .distignore (e.g., source files). 346 | run: | 347 | find . -name ".gitignore" -delete 348 | git config core.excludesFile .distignore 349 | git rm -rf --cached . 350 | git add . 351 | git clean -Xdf 352 | 353 | - name: Git add, commit, and push 354 | run: | 355 | git add -A 356 | git commit -m "[BOT] Add build artifact from ${{ github.ref_name }} -> ${{ env.BUILT_BRANCH_NAME }}." --no-verify || ((echo "HAS_GIT_CHANGES=yes" >> $GITHUB_ENV) && (echo "No changes to commit.")) 357 | if [ "${{ env.HAS_GIT_CHANGES }}" != "yes" ]; then 358 | git push 359 | echo "Build artifact pushed to branch: ${{ env.BUILT_BRANCH_NAME }}" 360 | fi 361 | 362 | # Copy the compiled package into a subdirectory to ensure a stable install path in WordPress 363 | - name: Prepare artifact with package directory structure 364 | run: | 365 | # Create package directory for artifact 366 | mkdir -p "./artifact-staging/${{ env.PACKAGE_NAME }}" 367 | 368 | # Copy all files to the package directory (excluding .git) 369 | rsync -av --exclude='.git' --exclude='./artifact-staging' ./ "./artifact-staging/${{ env.PACKAGE_NAME }}/" 370 | 371 | echo "✅ Prepared artifact with package structure:" 372 | echo " Package name: ${{ env.PACKAGE_NAME }}" 373 | echo " Artifact structure: artifact-staging/${{ env.PACKAGE_NAME }}/" 374 | 375 | - name: Upload artifact 376 | uses: actions/upload-artifact@v4 377 | with: 378 | name: ${{ env.PACKAGE_NAME }}-${{ env.PACKAGE_VERSION }} 379 | path: ./artifact-staging/ 380 | include-hidden-files: true 381 | compression-level: 9 382 | 383 | - name: Delete signing key files 384 | if: ${{ always() }} 385 | run: | 386 | rm -f "${{ runner.temp }}/signingkey" 387 | rm -f "${{ runner.temp }}/signingkey.pub" 388 | -------------------------------------------------------------------------------- /docs/build-and-push-assets.md: -------------------------------------------------------------------------------- 1 | # Build and push assets 2 | 3 | > [!CAUTION] 4 | > This workflow is deprecated and will be removed soon. Use `.github/workflows/build-and-distribute.yml` ([documentation](build-and-distribute.md)) instead. 5 | 6 | This action can be used to build assets in a controlled and isolated environment via GitHub Actions. 7 | 8 | To achieve that, the reusable workflow: 9 | 10 | 1. installs dependencies defined in `package.json` 11 | 2. builds the package's assets via a build script (see below) 12 | 3. pushes compiled assets back to the repository (to the same branch or a defined branch) 13 | 14 | ## Where are assets stored 15 | 16 | Two inputs can be used to define branches as assets storage: `BUILT_BRANCH_NAME` and 17 | `RELEASE_BRANCH_NAME`. 18 | 19 | `BUILT_BRANCH_NAME` is used only for `branch` events. If defined, compiled assets will be stored in 20 | the branch of this name. For example, if `BUILT_BRANCH_NAME` is set to 21 | `${{ github.ref_name }}-built`, when pushing to the `main` branch, compiled assets will be stored in 22 | the `main-built` branch (the branch will be created if it does not exist). 23 | 24 | `RELEASE_BRANCH_NAME` is only used for tag events. If defined and the tag being pushed points to the 25 | latest commit of the default branch of the GitHub repository, compiled assets will be pushed to the 26 | branch of this name, and the tag will be moved there (the branch will be created if it does not 27 | exist). 28 | 29 | The main benefit of using `BUILT_BRANCH_NAME` is not to pollute the main development branch with 30 | commits containing compiled assets. With `RELEASE_BRANCH_NAME`, you can gain linear tag history by 31 | always tagging only the latest commit from the main development branch. 32 | 33 | ## Build script 34 | 35 | In step *2* above, the assets are "built", whatever that means for a package. For maximum 36 | flexibility, the workflow relies on a "script" to be defined in `package.json`. There are two 37 | possible building scripts: a "*dev*" script which is executed on regular pushes to branches, and a " 38 | *prod*" script, which is executed when a tag is pushed. 39 | 40 | By default, the two scripts are `encore dev` and `encore prod`, but can be configured 41 | via [inputs](#inputs). 42 | 43 | ## Notes on the "tag" workflow 44 | 45 | When a tag is pushed, an additional step is added to the 3-step workflow above: the **now pushed tag 46 | is moved** to point to the commit that contains the compiled assets. 47 | 48 | ## Recommendations for consuming packages 49 | 50 | - The consuming packages compiled assets' target folder(s) must be **git-ignored** and marked as 51 | `linguist-generated` in `.gitattributes`. 52 | - The calling workflows should 53 | use ["concurrency" settings](https://docs.github.com/en/actions/using-jobs/using-concurrency) to 54 | avoid conflicts when a push happens before the current workflow is not completed. 55 | - It is recommended for calling workflows to 56 | use ["paths" settings](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-including-paths) 57 | to avoid running the workflow when no asset sources are changed. However, it should not be used 58 | for built branches and release branch strategies because the sync should happen on every push. 59 | 60 | ## Simple usage example 61 | 62 | ```yml 63 | name: Build and push assets 64 | 65 | on: 66 | workflow_dispatch: 67 | push: 68 | tags: [ '*' ] 69 | branches: 70 | - '*' 71 | - '!*-built' # exclude jobs.build-assets.with.BUILT_BRANCH_NAME 72 | 73 | # Don't include paths if BUILT_BRANCH_NAME or RELEASE_BRANCH_NAME are defined 74 | paths: 75 | - '**workflows/build-and-push-assets.yml' # the workflow file itself 76 | - '**.ts' 77 | - '**.scss' 78 | - '**.js' 79 | - '**package.json' 80 | - '**tsconfig.json' 81 | - '**yarn.lock' 82 | 83 | concurrency: 84 | group: ${{ github.workflow }}-${{ github.ref }} 85 | 86 | jobs: 87 | build-assets: 88 | uses: inpsyde/reusable-workflows/.github/workflows/build-and-push-assets.yml@main 89 | with: 90 | BUILT_BRANCH_NAME: ${{ github.ref_name }}-built # Optionally, to push compiled assets to built branch 91 | secrets: 92 | GITHUB_USER_EMAIL: ${{ secrets.DEPLOYBOT_EMAIL }} 93 | GITHUB_USER_NAME: ${{ secrets.DEPLOYBOT_USER }} 94 | NPM_REGISTRY_TOKEN: ${{ secrets.DEPLOYBOT_PACKAGES_READ_ACCESS_TOKEN }} 95 | ``` 96 | 97 | This is not the simplest possible example, but it showcases all the recommendations. 98 | 99 | **Note**: Do not set `cancel-in-progress: true` to the `concurrency` setting because it interrupts 100 | the workflow. 101 | 102 | ## Configuration parameters 103 | 104 | ### Inputs 105 | 106 | | Name | Default | Description | 107 | |-----------------------|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------| 108 | | `NODE_OPTIONS` | `''` | Space-separated list of command-line Node options | 109 | | `NODE_VERSION` | `18` | Node version with which the assets will be compiled | 110 | | `NPM_REGISTRY_DOMAIN` | `'https://npm.pkg.github.com/'` | Domain of the private npm registry | 111 | | `WORKING_DIRECTORY` | `'./'` | Working directory path | 112 | | `COMPILE_SCRIPT_PROD` | `'build'` | Script added to `npm run` or `yarn` to build production assets | 113 | | `COMPILE_SCRIPT_DEV` | `'build:dev'` | Script added to `npm run` or `yarn` to build development assets | 114 | | `MODE` | `''` | Mode for compiling assets (`prod` or `dev`) | 115 | | `ASSETS_TARGET_PATHS` | `'./assets'` | Space-separated list of target directory paths for compiled assets | 116 | | `ASSETS_TARGET_FILES` | `''` | Space-separated list of target file paths for compiled assets | 117 | | `BUILT_BRANCH_NAME` | `''` | Sets the target branch for pushing assets on the `branch` event | 118 | | `RELEASE_BRANCH_NAME` | `''` | On tag events, target branch where compiled assets are pushed and the tag is moved to | 119 | | `PHP_VERSION` | `'8.2'` | PHP version with which the PHP tools are to be executed | 120 | | `PHP_TOOLS` | `''` | PHP tools supported by [shivammathur/setup-php](https://github.com/shivammathur/setup-php#wrench-tools-support) to be installed | 121 | 122 | ## Secrets 123 | 124 | | Name | Description | 125 | |------------------------------|------------------------------------------------------------------------------| 126 | | `NPM_REGISTRY_TOKEN` | Authentication for the private npm registry | 127 | | `GITHUB_USER_EMAIL` | Email address for the GitHub user configuration | 128 | | `GITHUB_USER_NAME` | Username for the GitHub user configuration | 129 | | `GITHUB_USER_SSH_KEY` | Private SSH key associated with the GitHub user passed as `GITHUB_USER_NAME` | 130 | | `GITHUB_USER_SSH_PUBLIC_KEY` | Public SSH key associated with the GitHub user passed as `GITHUB_USER_NAME` | 131 | | `ENV_VARS` | Additional environment variables as a JSON formatted object | 132 | 133 | **Example with signed commits using SSH key:** 134 | 135 | ```yml 136 | name: Build and push assets 137 | 138 | on: 139 | workflow_dispatch: 140 | push: 141 | tags: [ '*' ] 142 | branches: 143 | - '*' 144 | - '!*-built' # exclude jobs.build-assets.with.BUILT_BRANCH_NAME 145 | - '!release' # exclude jobs.build-assets.with.RELEASE_BRANCH_NAME 146 | # Don't include paths if BUILT_BRANCH_NAME or RELEASE_BRANCH_NAME are defined 147 | paths: 148 | - '**workflows/build-and-push-assets.yml' # the workflow file itself 149 | - '**.ts' 150 | - '**.scss' 151 | - '**.js' 152 | - '**package.json' 153 | - '**tsconfig.json' 154 | - '**yarn.lock' 155 | 156 | concurrency: 157 | group: ${{ github.workflow }}-${{ github.ref }} 158 | 159 | jobs: 160 | build-assets: 161 | uses: inpsyde/reusable-workflows/.github/workflows/build-and-push-assets.yml@main 162 | with: 163 | BUILT_BRANCH_NAME: ${{ github.ref_name }}-built # Optionally, to push compiled assets to built branch 164 | RELEASE_BRANCH_NAME: release # Optionally, to move tags to release branch 165 | secrets: 166 | GITHUB_USER_EMAIL: ${{ secrets.DEPLOYBOT_EMAIL }} 167 | GITHUB_USER_NAME: ${{ secrets.DEPLOYBOT_USER }} 168 | GITHUB_USER_SSH_KEY: ${{ secrets.DEPLOYBOT_SSH_PRIVATE_KEY }} 169 | GITHUB_USER_SSH_PUBLIC_KEY: ${{ secrets.DEPLOYBOT_SSH_PUBLIC_KEY }} 170 | NPM_REGISTRY_TOKEN: ${{ secrets.DEPLOYBOT_PACKAGES_READ_ACCESS_TOKEN }} 171 | ``` 172 | 173 | ## FAQ 174 | 175 | > Isn't it bad practice to push compiled assets into version control? 176 | 177 | This is the only supported way by [Composer](https://github.com/composer/packagist/issues/903). 178 | 179 | --- 180 | 181 | > What happens to outdated or invalid compiled assets? 182 | 183 | The reusable workflow does not remove previously compiled assets that may cause conflicts. This is 184 | the responsibility of the consuming workflow or build process. For example, when using Webpack 185 | Encore, something like the following can be used to clean up previously created assets and avoid 186 | conflicts: 187 | 188 | ```js 189 | Encore.cleanupOutputBeforeBuild(['*.js', '*.css']) 190 | ``` 191 | 192 | --- 193 | 194 | > Can I decide when to run `COMPILE_SCRIPT_PROD` or `COMPILE_SCRIPT_DEV`? 195 | 196 | Use the `inputs.MODE` and set it to `dev` or `prod`. Depending on the value, the corresponding 197 | script will be executed. When left empty, the default logic is applied. 198 | 199 | The following table provides an overview when `COMPILE_SCRIPT_DEV` or `COMPILE_SCRIPT_PROD` is used: 200 | 201 | | MODE | scenario | script | 202 | |--------|--------------------|-----------------------| 203 | | `''` | push to branch | `COMPILE_SCRIPT_DEV` | 204 | | `''` | create release/tag | `COMPILE_SCRIPT_PROD` | 205 | | `dev` | *not evaluated* | `COMPILE_SCRIPT_DEV` | 206 | | `prod` | *not evaluated* | `COMPILE_SCRIPT_PROD` | 207 | 208 | **Example:** I want to push to a branch `production` and "production"-ready assets should be 209 | compiled: 210 | 211 | ```yaml 212 | name: Build and push assets 213 | on: 214 | push: 215 | jobs: 216 | build-assets: 217 | uses: inpsyde/reusable-workflows/.github/workflows/build-and-push-assets.yml@main 218 | with: 219 | MODE: ${{ github.ref_type == 'branch' && github.ref_name == 'production' && 'prod' || '' }} 220 | ``` 221 | 222 | > Can I have multiple output folders for my package? What about files? 223 | 224 | Yes, `inputs.ASSETS_TARGET_PATHS` and `inputs.ASSETS_TARGET_FILES` accept multiple space-separated 225 | paths for directories and files, respectively. 226 | 227 | ```yaml 228 | name: Build and push assets 229 | on: 230 | push: 231 | jobs: 232 | build-assets: 233 | uses: inpsyde/reusable-workflows/.github/workflows/build-and-push-assets.yml@main 234 | with: 235 | ASSETS_TARGET_PATHS: "./assets ./modules/Foo/assets ./modules/Bar/assets" 236 | ASSETS_TARGET_FILES: "./my-generated-file.txt ./LICENSE" 237 | secrets: 238 | GITHUB_USER_EMAIL: ${{ secrets.DEPLOYBOT_EMAIL }} 239 | GITHUB_USER_NAME: ${{ secrets.DEPLOYBOT_USER }} 240 | ENV_VARS: >- 241 | [{"name":"EXAMPLE_USERNAME", "value":"${{ secrets.USERNAME }}"}] 242 | ``` 243 | 244 | --- 245 | 246 | > Will I have merge conflicts during PRs merging if I don't use `BUILT_BRANCH_NAME`? 247 | 248 | No, if you follow the recommendations in this document you shouldn't. 249 | 250 | When compiled assets are `.gitignore`d, they are ignored by GitHub, even if the PR's assets conflict 251 | with the base branch's assets. And when the PR is merged, the assets silently overwrite what is in 252 | the base branch. This may not be correct, but it is not relevant: When merging, the workflow is run 253 | again and, re-compiling assets to their correct status. 254 | 255 | Also, GitHub will not even show the compiled assets in the PR if the assets are marked 256 | as `linguist-generated` in `.gitattributes`, so the process is completely invisible to users. 257 | 258 | --- 259 | 260 | > What happens if I push before the current workflow is completed? 261 | 262 | By following recommendations, nothing bad. The 263 | recommended [concurrency settings](https://docs.github.com/en/actions/using-jobs/using-concurrency) 264 | will make sure that GitHub stops processing the incomplete workflow, and starts a new workflow as 265 | soon as the new commit is pushed. In the end, as far as the workflow is concerned, it would be the 266 | same as the two commits would have been made as a single commit including both. 267 | 268 | --- 269 | 270 | > Does the workflow mess up the git history or add noise to it? How do we know which "compilation" 271 | > commit belongs to which "real" commit? 272 | 273 | As a side effect of using the 274 | recommended [concurrency settings](https://docs.github.com/en/actions/using-jobs/using-concurrency) 275 | , the git history will be linear. The compilation commit would normally refer to the previous 276 | commit, whatever that is. In the case of cherry-picking or another non-linear branch merging, this " 277 | linearity" could be compromised. For this reason, the workflow adds to the commit message the commit 278 | hash that triggered the compilation. 279 | 280 | As for the "noise", it will indeed be there. However, considering that all workflow commit messages 281 | start with the prefix `[BOT]`, it would be quite easy to ignore them without any cognitive effort. 282 | 283 | By defining `BUILT_BRANCH_NAME`, you keep commits containing compiled assets separated in the built 284 | branch. 285 | 286 | --- 287 | 288 | > When using commit-precise Composer version constraints like `dev-master#a1bcde`, is there a risk 289 | > of referencing a commit that has no compiled assets? 290 | 291 | Yes. However, commit-accurate version constraints are not recommended (especially in production), 292 | are usually temporary, and are objectively rare. And in the unlikely event that we need to maintain 293 | a particular commit hash, we can choose the commit hash wisely. 294 | 295 | --- 296 | 297 | > What happens when I create a release via the GitHub UI? 298 | 299 | When creating the release for an existing tag, you just need to wait until the workflow is complete 300 | before creating a release. This will ensure that the release points to the "moved" tag. 301 | 302 | Unfortunately, creating a release via the GitHub UI for a non-existing tag is **incompatible** with 303 | this workflow. In this case, GitHub would first create a tag and then associate the release with it. 304 | However, this tag creation would not trigger the workflow. Consequently, the release points to a tag 305 | that does not contain any assets. And even if the workflow is configured to run when a release is 306 | published, the workflow will fail because there is no "current branch" on release, so the workflow 307 | would try to push a commit made in the "detached HEAD" state, which would fail. 308 | 309 | Theoretically, it is possible to make the workflow 100% release-compatible via the UI. However, the 310 | complexity required to do so was not deemed worthwhile. 311 | 312 | --- 313 | 314 | > I use the `git+ssh` protocol for dependencies in `package.json`. How can I use it with this 315 | > workflow? 316 | 317 | The workflow supports a private SSH key passed via the `GITHUB_USER_SSH_KEY` secret. 318 | 319 | By passing a key associated with the GitHub user defined in the required `GITHUB_USER_NAME`, the 320 | workflow can install these packages. 321 | 322 | Please note that in such cases it is a good practice not to use a "personal" GitHub user, but an 323 | *ad-hoc* "bot" user with an *ad-hoc* private SSH key used only for the scope. 324 | 325 | --- 326 | 327 | > What version should I use when requiring the package with Composer? 328 | 329 | For tags, the pushed tag name is always used. 330 | 331 | For branches, it depends on the `BUILT_BRANCH_NAME` input value. For example, when 332 | `BUILT_BRANCH_NAME` 333 | is `${{ github.ref_name}}-built` and the branch triggering the workflow is `main`, the built branch 334 | name will resolve 335 | to `main-built`. In this case, require the `dev-main-built` branch in `composer.json`. 336 | 337 | --- 338 | 339 | > `BUILT_BRANCH_NAME` configuration example 340 | 341 | ```yaml 342 | BUILT_BRANCH_NAME: "${{ (github.ref_name == 'dev-main' && 'main' || (github.ref_name == 'dev-beta' && 'beta' || (github.ref_name == 'dev-alpha' && 'alpha' || '') ) ) }}" 343 | ``` 344 | 345 | The logic in the example above will behave like this: 346 | 347 | - If `github.ref_name` is equal to `dev-main`, the value of `BUILT_BRANCH_NAME` will be `main` 348 | - If `github.ref_name` is equal to `dev-beta`, the value of `BUILT_BRANCH_NAME` will be `beta` 349 | - If `github.ref_name` is equal to `dev-alpha`, the value of `BUILT_BRANCH_NAME` will be `alpha` 350 | - If none of the above conditions are met, the value of `BUILT_BRANCH_NAME` will be `''`, which is 351 | the default 352 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------