├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ └── watch.yml ├── Dockerfile ├── README.md ├── action.yml └── entrypoint.sh /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Trigger rebuild 2 | 3 | on: 4 | schedule: 5 | - cron: '03 */4 * * *' 6 | 7 | jobs: 8 | trigger: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Trigger build on dockerhub 13 | run: | 14 | sudo apt-get install curl 15 | curl -L -X POST https://hub.docker.com/api/build/v1/source/bae8da83-86af-432b-81c5-99883ad5dfe4/trigger/c0360b1f-9d0c-4b51-9431-b9b541137052/call/ 16 | -------------------------------------------------------------------------------- /.github/workflows/watch.yml: -------------------------------------------------------------------------------- 1 | name: Watch 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '30 */4 * * *' 7 | push: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | docker: 13 | name: Push tagged docker image 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | packages: write 18 | 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v4 22 | with: 23 | ref: master 24 | 25 | - name: Update Dockerfile with latest version 26 | id: fetch_version 27 | run: | 28 | last=$(curl -s 'https://packagist.org/packages/vimeo/psalm.json?v=latest'|jq '[.package.versions[]|select(.version|test("^\\d+\\.\\d+\\.\\d+$"))|.version]|max_by(.|[splits("[.]")]|map(tonumber))' | tr -d '"') 29 | echo "Last Psalm version is $last" 30 | echo "last=$last" >> $GITHUB_OUTPUT 31 | 32 | sed -i -re "s/require vimeo\/psalm/require vimeo\/psalm:$last/" Dockerfile 33 | cat Dockerfile 34 | 35 | - name: Docker login 36 | uses: docker/login-action@v3 37 | with: 38 | registry: ghcr.io 39 | username: ${{ github.repository_owner }} 40 | password: ${{ secrets.GITHUB_TOKEN }} 41 | 42 | - name: Build images 43 | run: | 44 | last=$(curl -s 'https://packagist.org/packages/vimeo/psalm.json?v=latest'|jq '[.package.versions[]|select(.version|test("^\\d+\\.\\d+\\.\\d+$"))|.version]|max_by(.|[splits("[.]")]|map(tonumber))' | tr -d '"') 45 | docker build -t ghcr.io/psalm/psalm-github-actions:$last -t ghcr.io/psalm/psalm-github-actions:latest . 46 | 47 | - name: Publish 48 | run: | 49 | last=$(curl -s 'https://packagist.org/packages/vimeo/psalm.json?v=latest'|jq '[.package.versions[]|select(.version|test("^\\d+\\.\\d+\\.\\d+$"))|.version]|max_by(.|[splits("[.]")]|map(tonumber))' | tr -d '"') 50 | docker push ghcr.io/psalm/psalm-github-actions:$last 51 | docker push ghcr.io/psalm/psalm-github-actions:latest 52 | 53 | workflow-keepalive: 54 | if: github.event_name == 'schedule' 55 | runs-on: ubuntu-latest 56 | permissions: 57 | actions: write 58 | steps: 59 | - uses: liskin/gh-workflow-keepalive@v1.2.1 60 | with: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ghcr.io/danog/psalm:latest 2 | 3 | # Satisfy Psalm's quest for a composer autoloader (with a symlink that disappears once a volume is mounted at /app) 4 | 5 | RUN ln -s /composer/vendor/ /app/vendor 6 | 7 | # Add entrypoint script 8 | 9 | COPY ./entrypoint.sh /entrypoint.sh 10 | RUN chmod +x /entrypoint.sh 11 | 12 | # Package container 13 | 14 | WORKDIR "/app" 15 | ENTRYPOINT ["/entrypoint.sh"] 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Psalm Github action 2 | 3 | Run Psalm as a github action. 4 | 5 | ```yaml 6 | name: Psalm Static analysis 7 | 8 | on: [push, pull_request] 9 | 10 | jobs: 11 | psalm: 12 | name: Psalm 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | 18 | - name: Psalm 19 | uses: docker://ghcr.io/psalm/psalm-github-actions 20 | 21 | ``` 22 | 23 | ## Specify Psalm version 24 | 25 | You can also specify a version (after 3.14.2). 26 | 27 | ```diff 28 | - name: Psalm 29 | - uses: docker://ghcr.io/psalm/psalm-github-actions 30 | + uses: docker://ghcr.io/psalm/psalm-github-actions:5.7.7 31 | ``` 32 | 33 | ## Use Security Analysis 34 | 35 | Psalm supports [Security Analysis](https://psalm.dev/docs/security_analysis/). You can use this config to show security analysis reports: 36 | 37 | ```diff 38 | - name: Psalm 39 | uses: docker://ghcr.io/psalm/psalm-github-actions 40 | + with: 41 | + security_analysis: true 42 | ``` 43 | 44 | ### Send security output to GitHub Security tab 45 | 46 | GitHub also allows you to [send security issues to a separate part of the site](https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/sarif-support-for-code-scanning) that can be restricted to members of your team. 47 | 48 | Use the following config: 49 | 50 | ```diff 51 | - name: Psalm 52 | uses: docker://ghcr.io/psalm/psalm-github-actions 53 | + with: 54 | + security_analysis: true 55 | + report_file: results.sarif 56 | + - name: Upload Security Analysis results to GitHub 57 | + uses: github/codeql-action/upload-sarif@v2 58 | + with: 59 | + sarif_file: results.sarif 60 | ``` 61 | 62 | ## Customising Composer 63 | 64 | Specify `composer_require_dev: true` to install dev dependencies and `composer_ignore_platform_reqs: true` in order to ignore platform requirements. 65 | 66 | These are both set to false by default. 67 | 68 | ```diff 69 | - name: Psalm 70 | uses: docker://ghcr.io/psalm/psalm-github-actions 71 | + with: 72 | + composer_require_dev: true 73 | + composer_ignore_platform_reqs: true 74 | ``` 75 | 76 | ### Use relative dir 77 | 78 | If your composer file is not in the directory, you can specify the relative directory. 79 | 80 | Use the following config: 81 | 82 | ```diff 83 | - name: Psalm 84 | uses: docker://ghcr.io/psalm/psalm-github-actions 85 | + with: 86 | + relative_dir: ./subdir 87 | ``` 88 | 89 | 90 | Auth for private composer repositories 91 | ------------------------------- 92 | If you have private composer dependencies, SSH authentication must be used. Generate an SSH key pair for this purpose and add it to your private repository's configuration, preferable with only read-only privileges. On Github for instance, this can be done by using [deploy keys][deploy-keys]. 93 | 94 | Add the key pair to your project using [Github Secrets][secrets], and pass them into this action by using the `ssh_key` and `ssh_key_pub` inputs. If your private repository is stored on another server than github.com, you also need to pass the domain via `ssh_domain`. 95 | 96 | Example: 97 | 98 | ```yaml 99 | jobs: 100 | build: 101 | 102 | ... 103 | 104 | - name: Psalm 105 | uses: docker://ghcr.io/psalm/psalm-github-actions 106 | with: 107 | ssh_key: ${{ secrets.SOME_PRIVATE_KEY }} 108 | ssh_key_pub: ${{ secrets.SOME_PUBLIC_KEY }} 109 | # Optional: 110 | ssh_domain: my-own-github.com 111 | ``` 112 | 113 | github.com, gitlab.com and bitbucket.org are automatically added to the list of SSH known hosts. You can provide your own domain via `ssh_domain` input. 114 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/articles/metadata-syntax-for-github-actions 2 | 3 | author: 'muglug' 4 | 5 | branding: 6 | icon: 'check' 7 | color: 'blue' 8 | 9 | description: 'Run Psalm via GitHub Actions' 10 | 11 | name: 'Psalm – Static Analysis for PHP' 12 | 13 | inputs: 14 | security_analysis: 15 | required: false 16 | default: 'false' 17 | description: 'Whether or not to use Psalm’s security analysis' 18 | show_info: 19 | required: false 20 | default: 'false' 21 | description: 'Whether or not to show non-exception parser findings' 22 | report_file: 23 | required: false 24 | default: '' 25 | description: 'File for Psalm’s output' 26 | composer_require_dev: 27 | required: false 28 | default: 'false' 29 | description: 'Whether or not Composer installs dev packages' 30 | composer_ignore_platform_reqs: 31 | required: false 32 | default: 'false' 33 | description: 'Whether or not the --ignore-platform-reqs flag is passed to Composer' 34 | ssh_key: 35 | description: The private key contents to use for private repositories 36 | required: false 37 | ssh_key_pub: 38 | description: The public key contents to use for private repositories 39 | required: false 40 | ssh_domain: 41 | description: The domain to gather SSH public keys for (automatic for github.com, gitlab.com, bitbucket.org) 42 | required: false 43 | relative_dir: 44 | description: If your composer file is not in the directory, you can specify the relative directory. 45 | required: false 46 | php_version: 47 | description: The PHP version to run Psalm against 48 | required: false 49 | default: '' 50 | 51 | runs: 52 | using: 'docker' 53 | image: 'docker://ghcr.io/psalm/psalm-github-actions' 54 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -l 2 | set -e 3 | 4 | TAINT_ANALYSIS="" 5 | if [ "$INPUT_SECURITY_ANALYSIS" = "true" ]; then 6 | TAINT_ANALYSIS="--taint-analysis" 7 | fi 8 | 9 | REPORT="" 10 | if [ ! -z "$INPUT_REPORT_FILE" ]; then 11 | REPORT="--report=$INPUT_REPORT_FILE" 12 | fi 13 | 14 | SHOW_INFO="" 15 | if [ "$INPUT_SHOW_INFO" = "true" ]; then 16 | SHOW_INFO="--show-info=true" 17 | fi 18 | 19 | PHP_VERSION="" 20 | if [ -n "$INPUT_PHP_VERSION" ]; then 21 | PHP_VERSION="--php-version=$INPUT_PHP_VERSION" 22 | fi 23 | 24 | if [ -n "$INPUT_SSH_KEY" ] 25 | then 26 | echo "::group::Keys setup for private repositories" 27 | 28 | echo "Keyscan:" 29 | mkdir -p /tmp/.ssh 30 | ssh-keyscan -t rsa github.com >> /tmp/.ssh/known_hosts 31 | ssh-keyscan -t rsa gitlab.com >> /tmp/.ssh/known_hosts 32 | ssh-keyscan -t rsa bitbucket.org >> /tmp/.ssh/known_hosts 33 | 34 | if [ -n "$INPUT_SSH_DOMAIN" ] 35 | then 36 | ssh-keyscan -t rsa "$INPUT_SSH_DOMAIN" >> /tmp/.ssh/known_hosts 37 | fi 38 | echo "Installing keys:" 39 | 40 | echo "$INPUT_SSH_KEY" > /tmp/.ssh/action_rsa 41 | echo "$INPUT_SSH_KEY_PUB" > /tmp/.ssh/action_rsa.pub 42 | chmod 600 /tmp/.ssh/action_rsa 43 | 44 | echo "Private key hash:" 45 | md5sum /tmp/.ssh/action_rsa 46 | echo "Public key hash:" 47 | md5sum /tmp/.ssh/action_rsa.pub 48 | 49 | echo "[core]" >> ~/.gitconfig 50 | echo "sshCommand = \"ssh -i /tmp/.ssh/action_rsa -o UserKnownHostsFile=/tmp/.ssh/known_hosts\"" >> ~/.gitconfig 51 | 52 | echo "OK" 53 | echo "::endgroup::" 54 | else 55 | echo "No private keys supplied" 56 | fi 57 | 58 | if [ -n "$INPUT_RELATIVE_DIR" ] 59 | then 60 | if [ -d "$INPUT_RELATIVE_DIR" ]; then 61 | echo "changing directory into $INPUT_RELATIVE_DIR" 62 | cd "$INPUT_RELATIVE_DIR" 63 | else 64 | echo "given relative_dir not existing" 65 | exit 1 66 | fi 67 | fi 68 | 69 | if test -f "composer.json"; then 70 | IGNORE_PLATFORM_REQS="" 71 | if [ "$CHECK_PLATFORM_REQUIREMENTS" = "false" ] || [ "$INPUT_COMPOSER_IGNORE_PLATFORM_REQS" = "true" ]; then 72 | IGNORE_PLATFORM_REQS="--ignore-platform-reqs" 73 | fi 74 | 75 | NO_DEV="--no-dev" 76 | if [ "$REQUIRE_DEV" = "true" ] || [ "$INPUT_COMPOSER_REQUIRE_DEV" = "true" ]; then 77 | NO_DEV="" 78 | fi 79 | 80 | COMPOSER_COMMAND="composer install --no-scripts --no-progress $NO_DEV $IGNORE_PLATFORM_REQS" 81 | echo "::group::$COMPOSER_COMMAND" 82 | $COMPOSER_COMMAND 83 | echo "::endgroup::" 84 | else 85 | echo "composer.json not found in repo, skipping Composer installation" 86 | fi 87 | 88 | /composer/vendor/bin/psalm --version 89 | /composer/vendor/bin/psalm --force-jit --output-format=github $TAINT_ANALYSIS $REPORT $SHOW_INFO $PHP_VERSION $* 90 | --------------------------------------------------------------------------------