├── .editorconfig ├── .env.example ├── .envrc ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── autolabel.yml │ ├── automerge.yml │ ├── ccb-ticket.twig │ ├── ci.yml │ ├── create-ccb-ticket.sh │ ├── integration-tests.yml │ ├── jira.yml │ ├── mutation.yml │ ├── publish-release.yml │ ├── release-drafter.yml │ ├── release.yml │ └── upload-s3.yml ├── .gitignore ├── .phplint.yml ├── .phpstorm.meta.php ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── assets ├── acquia-spec.json └── acsf-spec.json ├── bin ├── acli └── acli.bat ├── box.json ├── box.json.md ├── codecov.yml ├── composer.json ├── composer.lock ├── config ├── dev │ └── services.yml ├── from_d7_config.json ├── from_d7_recommendations.json └── prod │ └── services.yml ├── docker ├── Dockerfile └── docker-entrypoint.sh ├── grumphp.yml ├── infection.json5 ├── phpcs.xml.dist ├── phpstan.neon.dist ├── phpunit.xml.dist ├── src ├── AcsfApi │ ├── AcsfClient.php │ ├── AcsfClientService.php │ ├── AcsfConnector.php │ ├── AcsfConnectorFactory.php │ └── AcsfCredentials.php ├── ApiCredentialsInterface.php ├── Application.php ├── Attribute │ ├── RequireAuth.php │ ├── RequireLocalDb.php │ └── RequireRemoteDb.php ├── CloudApi │ ├── AccessTokenConnector.php │ ├── ClientService.php │ ├── CloudCredentials.php │ └── ConnectorFactory.php ├── Command │ ├── Acsf │ │ ├── AcsfCommandFactory.php │ │ ├── AcsfListCommand.php │ │ └── AcsfListCommandBase.php │ ├── Api │ │ ├── ApiBaseCommand.php │ │ ├── ApiCommandFactory.php │ │ ├── ApiCommandHelper.php │ │ ├── ApiListCommand.php │ │ └── ApiListCommandBase.php │ ├── App │ │ ├── AppOpenCommand.php │ │ ├── AppVcsInfo.php │ │ ├── From │ │ │ ├── Composer │ │ │ │ └── ProjectBuilder.php │ │ │ ├── Configuration.php │ │ │ ├── JsonResourceParserTrait.php │ │ │ ├── Recommendation │ │ │ │ ├── AbandonmentRecommendation.php │ │ │ │ ├── DefinedRecommendation.php │ │ │ │ ├── NoRecommendation.php │ │ │ │ ├── NormalizableInterface.php │ │ │ │ ├── RecommendationInterface.php │ │ │ │ ├── Recommendations.php │ │ │ │ ├── Resolver.php │ │ │ │ └── UniversalRecommendation.php │ │ │ ├── Safety │ │ │ │ ├── ArrayValidationTrait.php │ │ │ │ └── StructuredArrayValidator.php │ │ │ └── SourceSite │ │ │ │ ├── Drupal7Extension.php │ │ │ │ ├── Drupal7SiteInspector.php │ │ │ │ ├── ExportedDrupal7ExtensionsInspector.php │ │ │ │ ├── ExtensionInterface.php │ │ │ │ ├── SiteInspectorBase.php │ │ │ │ └── SiteInspectorInterface.php │ │ ├── LinkCommand.php │ │ ├── LogTailCommand.php │ │ ├── NewCommand.php │ │ ├── NewFromDrupal7Command.php │ │ ├── TaskWaitCommand.php │ │ └── UnlinkCommand.php │ ├── Archive │ │ └── ArchiveExportCommand.php │ ├── Auth │ │ ├── AuthAcsfLoginCommand.php │ │ ├── AuthAcsfLogoutCommand.php │ │ ├── AuthLoginCommand.php │ │ └── AuthLogoutCommand.php │ ├── CodeStudio │ │ ├── CodeStudioCiCdVariables.php │ │ ├── CodeStudioCommandTrait.php │ │ ├── CodeStudioPhpVersionCommand.php │ │ ├── CodeStudioPipelinesMigrateCommand.php │ │ ├── CodeStudioWizardCommand.php │ │ ├── cs_icon.png │ │ └── drupal_icon.png │ ├── CommandBase.php │ ├── DocsCommand.php │ ├── Env │ │ ├── EnvCertCreateCommand.php │ │ ├── EnvCopyCronCommand.php │ │ ├── EnvCreateCommand.php │ │ ├── EnvDeleteCommand.php │ │ └── EnvMirrorCommand.php │ ├── HelloWorldCommand.php │ ├── Ide │ │ ├── IdeCommandBase.php │ │ ├── IdeCreateCommand.php │ │ ├── IdeDeleteCommand.php │ │ ├── IdeInfoCommand.php │ │ ├── IdeListCommand.php │ │ ├── IdeListMineCommand.php │ │ ├── IdeOpenCommand.php │ │ ├── IdePhpVersionCommand.php │ │ ├── IdeServiceRestartCommand.php │ │ ├── IdeServiceStartCommand.php │ │ ├── IdeServiceStopCommand.php │ │ ├── IdeShareCommand.php │ │ ├── IdeXdebugToggleCommand.php │ │ └── Wizard │ │ │ ├── IdeWizardCommandBase.php │ │ │ ├── IdeWizardCreateSshKeyCommand.php │ │ │ └── IdeWizardDeleteSshKeyCommand.php │ ├── Pull │ │ ├── PullCodeCommand.php │ │ ├── PullCommand.php │ │ ├── PullCommandBase.php │ │ ├── PullDatabaseCommand.php │ │ ├── PullFilesCommand.php │ │ └── PullScriptsCommand.php │ ├── Push │ │ ├── PushArtifactCommand.php │ │ ├── PushCodeCommand.php │ │ ├── PushCommandBase.php │ │ ├── PushDatabaseCommand.php │ │ └── PushFilesCommand.php │ ├── Remote │ │ ├── AliasListCommand.php │ │ ├── AliasesDownloadCommand.php │ │ ├── DrushCommand.php │ │ ├── SshBaseCommand.php │ │ └── SshCommand.php │ ├── Self │ │ ├── ClearCacheCommand.php │ │ ├── ListCommand.php │ │ ├── MakeDocsCommand.php │ │ ├── SelfInfoCommand.php │ │ ├── TelemetryCommand.php │ │ ├── TelemetryDisableCommand.php │ │ └── TelemetryEnableCommand.php │ ├── Ssh │ │ ├── SshKeyCommandBase.php │ │ ├── SshKeyCreateCommand.php │ │ ├── SshKeyCreateUploadCommand.php │ │ ├── SshKeyDeleteCommand.php │ │ ├── SshKeyInfoCommand.php │ │ ├── SshKeyListCommand.php │ │ └── SshKeyUploadCommand.php │ └── WizardCommandBase.php ├── CommandFactoryInterface.php ├── Config │ ├── AcquiaCliConfig.php │ └── CloudDataConfig.php ├── ConnectorFactoryInterface.php ├── DataStore │ ├── AcquiaCliDatastore.php │ ├── CloudDataStore.php │ ├── DataStoreInterface.php │ ├── Datastore.php │ ├── JsonDataStore.php │ └── YamlStore.php ├── EventListener │ ├── ComposerScriptsListener.php │ └── ExceptionListener.php ├── Exception │ └── AcquiaCliException.php ├── Helpers │ ├── AliasCache.php │ ├── DataStoreContract.php │ ├── IdeCommandTrait.php │ ├── LocalMachineHelper.php │ ├── LoopHelper.php │ ├── SshCommandTrait.php │ ├── SshHelper.php │ └── TelemetryHelper.php ├── Kernel.php └── Output │ ├── Checklist.php │ └── Spinner │ └── Spinner.php ├── symfony.lock ├── tests ├── fixtures │ ├── acquia-pipelines.yml │ ├── acsf_db_response.json │ ├── drupal7 │ │ ├── drush_to_extensions_test_file_format.sh │ │ ├── training.acquia.com │ │ │ ├── expected.json │ │ │ └── extensions.json │ │ ├── www.standard-profile.com │ │ │ ├── expected.json │ │ │ └── extensions.json │ │ ├── www.webchick.net │ │ │ ├── expected.json │ │ │ └── extensions.json │ │ └── www.wimleers.com │ │ │ ├── expected.json │ │ │ └── extensions.json │ ├── drush-aliases │ │ ├── .acquia │ │ │ └── cloudapi.conf │ │ ├── .drush │ │ │ └── eemgrasmick.aliases.drushrc.php │ │ ├── README-acquiacloud.txt │ │ └── sites │ │ │ └── devcloud2.site.yml │ ├── git_config │ ├── github-releases.json │ ├── multisite-config.json │ ├── no-multisite-config.json │ ├── test.phar │ └── xdebug.ini ├── integration │ └── testcases │ │ ├── __init__.py │ │ └── test_acli_integration.py └── phpunit │ └── src │ ├── AcsfApi │ ├── AcsfServiceTest.php │ └── EnvVarAcsfAuthenticationTest.php │ ├── Application │ ├── ComposerScriptsListenerTest.php │ ├── ExceptionApplicationTest.php │ ├── HelpApplicationTest.php │ └── KernelTest.php │ ├── ApplicationTestBase.php │ ├── CloudApi │ ├── AccessTokenConnectorTest.php │ ├── AcsfClientServiceTest.php │ ├── ClientServiceTest.php │ └── EnvVarAuthenticationTest.php │ ├── CommandTestBase.php │ ├── Commands │ ├── Acsf │ │ ├── AcsfApiCommandTest.php │ │ ├── AcsfAuthLoginCommandTest.php │ │ ├── AcsfAuthLogoutCommandTest.php │ │ ├── AcsfCommandTestBase.php │ │ └── AcsfListCommandTest.php │ ├── Api │ │ ├── ApiBaseCommandTest.php │ │ ├── ApiCommandTest.php │ │ └── ApiListCommandTest.php │ ├── App │ │ ├── AppOpenCommandTest.php │ │ ├── AppVcsInfoTest.php │ │ ├── From │ │ │ ├── AbandonmentRecommendationTest.php │ │ │ ├── ConfigurationTest.php │ │ │ ├── DefinedRecommendationTest.php │ │ │ ├── ProjectBuilderTest.php │ │ │ ├── RecommendationsTest.php │ │ │ ├── TestRecommendation.php │ │ │ └── TestSiteInspector.php │ │ ├── LinkCommandTest.php │ │ ├── LogTailCommandTest.php │ │ ├── NewCommandTest.php │ │ ├── NewFromDrupal7CommandTest.php │ │ ├── TaskWaitCommandTest.php │ │ └── UnlinkCommandTest.php │ ├── Archive │ │ └── ArchiveExporterCommandTest.php │ ├── Auth │ │ ├── AuthLoginCommandTest.php │ │ └── AuthLogoutCommandTest.php │ ├── CodeStudio │ │ ├── CodeStudioCiCdVariablesTest.php │ │ ├── CodeStudioPhpVersionCommandTest.php │ │ ├── CodeStudioPipelinesMigrateCommandTest.php │ │ └── CodeStudioWizardCommandTest.php │ ├── CommandBaseTest.php │ ├── DocsCommandTest.php │ ├── Env │ │ ├── EnvCertCreateCommandTest.php │ │ ├── EnvCopyCronCommandTest.php │ │ ├── EnvCreateCommandTest.php │ │ ├── EnvDeleteCommandTest.php │ │ └── EnvMirrorCommandTest.php │ ├── Ide │ │ ├── IdeCreateCommandTest.php │ │ ├── IdeDeleteCommandTest.php │ │ ├── IdeHelper.php │ │ ├── IdeInfoCommandTest.php │ │ ├── IdeListCommandMineTest.php │ │ ├── IdeListCommandTest.php │ │ ├── IdeOpenCommandTest.php │ │ ├── IdePhpVersionCommandTest.php │ │ ├── IdeRequiredTestTrait.php │ │ ├── IdeServiceRestartCommandTest.php │ │ ├── IdeServiceStartCommandTest.php │ │ ├── IdeServiceStopCommandTest.php │ │ ├── IdeShareCommandTest.php │ │ ├── IdeXdebugToggleCommandTest.php │ │ └── Wizard │ │ │ ├── IdeWizardCreateSshKeyCommandTest.php │ │ │ ├── IdeWizardDeleteSshKeyCommandTest.php │ │ │ └── IdeWizardTestBase.php │ ├── InferApplicationTest.php │ ├── Pull │ │ ├── PullCodeCommandTest.php │ │ ├── PullCommandTest.php │ │ ├── PullCommandTestBase.php │ │ ├── PullDatabaseCommandTest.php │ │ ├── PullFilesCommandTest.php │ │ └── PullScriptsCommandTest.php │ ├── Push │ │ ├── PushArtifactCommandTest.php │ │ ├── PushCodeCommandTest.php │ │ ├── PushDatabaseCommandTest.php │ │ └── PushFilesCommandTest.php │ ├── Remote │ │ ├── AliasesDownloadCommandTest.php │ │ ├── AliasesListCommandTest.php │ │ ├── DrushCommandTest.php │ │ ├── SshCommandTest.php │ │ └── SshCommandTestBase.php │ ├── Self │ │ ├── ClearCacheCommandTest.php │ │ ├── MakeDocsCommandTest.php │ │ ├── SelfInfoCommandTest.php │ │ ├── TelemetryCommandTest.php │ │ ├── TelemetryDisableCommandTest.php │ │ └── TelemetryEnableCommandTest.php │ ├── Ssh │ │ ├── SshKeyCreateCommandTest.php │ │ ├── SshKeyCreateUploadCommandTest.php │ │ ├── SshKeyDeleteCommandTest.php │ │ ├── SshKeyInfoCommandTest.php │ │ ├── SshKeyListCommandTest.php │ │ └── SshKeyUploadCommandTest.php │ ├── UpdateCommandTest.php │ └── WizardTestBase.php │ ├── Misc │ ├── ApiSpecTest.php │ ├── ChecklistTest.php │ ├── EnvDbCredsTest.php │ ├── ExceptionListenerTest.php │ ├── LocalMachineHelperTest.php │ └── TelemetryHelperTest.php │ └── TestBase.php └── var └── .gitkeep /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.xml.dist] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | BUGSNAG_KEY=abcd1234 2 | AMPLITUDE_KEY=acbd1234 3 | ACLI_VERSION=1.0.0 4 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | export PATH="$PATH:${PWD}/vendor/bin" 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Motivation** 2 | 3 | Fixes #NNN 4 | 5 | **Proposed changes** 6 | 7 | 8 | **Alternatives considered** 9 | 10 | 11 | **Testing steps** 12 | 13 | 14 | 1. Follow the [contribution guide](https://github.com/acquia/cli/blob/HEAD/CONTRIBUTING.md#building-and-testing) to set up your development environment or [download a pre-built acli.phar](https://github.com/acquia/cli/blob/HEAD/CONTRIBUTING.md#automatic-dev-builds) for this PR. 15 | 2. If running from source, clear the kernel cache to pick up new and changed commands: `./bin/acli ckc` 16 | 3. Check for regressions: (add specific steps for this pr) 17 | 4. Check new functionality: (add specific steps for this pr) 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'composer' 4 | directory: '/' 5 | versioning-strategy: lockfile-only 6 | schedule: 7 | interval: 'weekly' 8 | groups: 9 | dependencies: 10 | patterns: 11 | - '*' 12 | - package-ecosystem: 'github-actions' 13 | directory: '/' 14 | schedule: 15 | interval: 'weekly' 16 | ignore: 17 | - dependency-name: "release-drafter/release-drafter" 18 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: '$RESOLVED_VERSION' 2 | tag-template: '$RESOLVED_VERSION' 3 | categories: 4 | - title: '🚨 Major changes' 5 | label: 'breaking change' 6 | - title: '🚀 Enhancements' 7 | label: 'enhancement' 8 | - title: '🐛 Bug Fixes' 9 | label: 'bug' 10 | - title: '🧰 Maintenance' 11 | label: 'chore' 12 | - title: '🧰 Dependency updates' 13 | label: 'dependencies' 14 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 15 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 16 | version-resolver: 17 | major: 18 | labels: 19 | - 'breaking change' 20 | minor: 21 | labels: 22 | - 'enhancement' 23 | patch: 24 | labels: 25 | - 'bug' 26 | default: patch 27 | template: | 28 | ## What's new since $PREVIOUS_TAG 29 | 30 | $CHANGES 31 | prerelease: true 32 | -------------------------------------------------------------------------------- /.github/workflows/autolabel.yml: -------------------------------------------------------------------------------- 1 | name: Auto-label pull requests 2 | on: 3 | pull_request_target: 4 | types: [opened, synchronize, reopened, labeled, unlabeled] 5 | permissions: 6 | pull-requests: write 7 | jobs: 8 | autolabel: 9 | uses: acquia/.github/.github/workflows/autolabel.yml@main 10 | secrets: 11 | github-token: ${{ secrets.GITHUB_TOKEN }} 12 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | name: Auto-merge dependency updates 2 | on: 3 | workflow_run: 4 | types: 5 | - 'completed' 6 | workflows: 7 | - 'CI' 8 | branches: 9 | - 'dependabot/**' 10 | permissions: 11 | contents: write 12 | pull-requests: write 13 | jobs: 14 | automerge: 15 | uses: acquia/.github/.github/workflows/automerge.yml@main 16 | secrets: 17 | github-token: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/create-ccb-ticket.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | render('ccb-ticket.twig', [ 11 | // @see https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables 12 | 'GITHUB_RELEASE_BODY' => $argv[1], 13 | 'GITHUB_RELEASE_NAME' => $argv[2], 14 | 'GITHUB_RELEASE_URL' => $argv[3], 15 | 'GITHUB_ACTIONS_RUN_URL' => $argv[4], 16 | 'JIRA_BASE_URL' => $argv[5], 17 | ]); 18 | $body = htmlspecialchars($body); 19 | $body = preg_replace( 20 | '/[\x{1F600}-\x{1F64F}\x{2700}-\x{27BF}\x{1F680}-\x{1F6FF}\x{24C2}-\x{1F251}\x{1F30D}-\x{1F567}\x{1F900}-\x{1F9FF}\x{1F300}-\x{1F5FF}]/u', 21 | '[emoji-removed]', 22 | $body 23 | ); 24 | $body = str_replace('## ', 'h4. ', $body); 25 | $body = str_replace('&#039;', ''', $body); 26 | 27 | echo $body; -------------------------------------------------------------------------------- /.github/workflows/integration-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests on Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - name: Check out repository code 12 | uses: actions/checkout@v4 13 | 14 | - uses: shivammathur/setup-php@v2 15 | with: 16 | coverage: none 17 | php-version: '8.1' 18 | 19 | - name: Build 20 | run: | 21 | composer install --no-dev --optimize-autoloader 22 | composer box-install 23 | # Warm the symfony cache so it gets bundled with phar. 24 | ./bin/acli 25 | composer box-compile 26 | 27 | - name: Set up Python 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: '3.8' 31 | 32 | - name: Run tests 33 | env: 34 | APPLICATION_UUID: ${{ secrets.APPLICATION_UUID }} 35 | APPLICATION_NAME: ${{ secrets.APPLICATION_NAME }} 36 | ACLI_AUTH_TOKEN: ${{ secrets.ACLI_AUTH_TOKEN }} 37 | ACLI_AUTH_SECRET: ${{ secrets.ACLI_AUTH_SECRET }} 38 | # Add more secret environment variables as needed 39 | run: python3 -m unittest discover -s tests/integration/testcases 40 | -------------------------------------------------------------------------------- /.github/workflows/jira.yml: -------------------------------------------------------------------------------- 1 | name: Sync GitHub issues to Jira 2 | on: 3 | issues: 4 | types: 5 | - opened 6 | - closed 7 | pull_request_target: 8 | types: 9 | - opened 10 | permissions: 11 | issues: write 12 | pull-requests: read 13 | jobs: 14 | jira: 15 | uses: acquia/.github/.github/workflows/jira.yml@main 16 | secrets: 17 | jira-api-token: ${{ secrets.JIRA_API_TOKEN }} 18 | github-token: ${{ secrets.GITHUB_TOKEN }} 19 | jira-user-email: ${{ secrets.JIRA_USER_EMAIL }} 20 | with: 21 | project_key: CLI 22 | -------------------------------------------------------------------------------- /.github/workflows/mutation.yml: -------------------------------------------------------------------------------- 1 | name: Mutation Testing 2 | 3 | on: 4 | push: 5 | # Prevent duplicate jobs on Dependabot PRs that interfere with automerge. 6 | branches-ignore: 7 | - 'dependabot/**' 8 | pull_request: 9 | 10 | jobs: 11 | tests: 12 | runs-on: ubuntu-22.04 13 | 14 | name: Mutation Testing 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup PHP 20 | uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: 8.2 23 | tools: composer:v2 24 | coverage: pcov 25 | 26 | - name: Install dependencies 27 | run: | 28 | composer install --no-progress --no-suggest --no-interaction 29 | 30 | - name: Run Infection for added files only 31 | if: github.event_name == 'pull_request' 32 | run: | 33 | git fetch --depth=1 origin $GITHUB_BASE_REF 34 | # nproc returns 4 threads on GitHub Actions and this seems to provide the best performance. 35 | composer mutation-diff-lines 36 | 37 | - name: Run Infection for all files 38 | if: github.event_name == 'push' 39 | env: 40 | INFECTION_DASHBOARD_API_KEY: ${{ secrets.INFECTION_DASHBOARD_API_KEY }} 41 | run: composer mutation 42 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish release on CCB approval 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | jobs: 7 | publish-release: 8 | runs-on: ubuntu-22.04 9 | name: Publish release on CCB approval 10 | permissions: 11 | contents: write 12 | steps: 13 | - name: Get reviewed release 14 | run: | 15 | ISSUES=$(curl --request GET \ 16 | --url "$JIRA_BASE_URL/rest/api/3/search?jql=project%20%3D%20CLI%20AND%20issuetype%20%3D%20Release%20AND%20status%20%3D%20Reviewed" \ 17 | --user "$JIRA_USER_EMAIL:$JIRA_API_TOKEN" \ 18 | --header 'Accept: application/json') 19 | echo "FIX_VERSION=$(printf '%s' $ISSUES | jq -r '.issues[0].fields.fixVersions[0].name' | sed 's/AcquiaCLI//')" >> $GITHUB_ENV 20 | echo "ISSUE_KEY=$(printf '%s' $ISSUES | jq -r '.issues[0].key')" >> $GITHUB_ENV 21 | env: 22 | JIRA_BASE_URL: ${{ vars.JIRA_BASE_URL }} 23 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 24 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 25 | - name: Login to Jira 26 | if: env.FIX_VERSION != 'null' && env.ISSUE_KEY != 'null' 27 | uses: acquia/gajira-login@master 28 | env: 29 | JIRA_BASE_URL: ${{ vars.JIRA_BASE_URL }} 30 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 31 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 32 | - name: Start release 33 | if: env.FIX_VERSION != 'null' && env.ISSUE_KEY != 'null' 34 | uses: acquia/gajira-transition@master 35 | with: 36 | issue: ${{ env.ISSUE_KEY }} 37 | transition: Start Release 38 | - name: Publish release 39 | if: env.FIX_VERSION != 'null' && env.ISSUE_KEY != 'null' 40 | run: | 41 | gh release edit $FIX_VERSION --prerelease=false --latest --repo acquia/cli 42 | env: 43 | GH_TOKEN: ${{ github.token }} 44 | - name: Close release 45 | if: env.FIX_VERSION != 'null' && env.ISSUE_KEY != 'null' 46 | uses: acquia/gajira-transition@master 47 | with: 48 | issue: ${{ env.ISSUE_KEY }} 49 | transition: Released 50 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_run: 8 | types: 9 | - 'completed' 10 | workflows: 11 | - 'Auto-label pull requests' 12 | - 'Auto-merge dependency updates' 13 | 14 | jobs: 15 | update_release_draft: 16 | runs-on: ubuntu-22.04 17 | permissions: 18 | contents: write 19 | pull-requests: write 20 | steps: 21 | - uses: release-drafter/release-drafter@v6.0.0 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###> symfony/framework-bundle ### 2 | /var/ 3 | /vendor/ 4 | ###< symfony/framework-bundle ### 5 | 6 | drupal 7 | .idea 8 | *.bak 9 | .DS_STORE 10 | 11 | ###> squizlabs/php_codesniffer ### 12 | /phpcs.xml 13 | ###< squizlabs/php_codesniffer ### 14 | 15 | ###> phpunit/phpunit ### 16 | /phpunit.xml 17 | ###< phpunit/phpunit ### 18 | 19 | cx-api-spec 20 | gardener 21 | .env 22 | 23 | // Artifacts from mutation testing 24 | *.cache 25 | -------------------------------------------------------------------------------- /.phplint.yml: -------------------------------------------------------------------------------- 1 | path: ./ 2 | jobs: 10 3 | cache: var/phplint.cache 4 | extensions: 5 | - php 6 | exclude: 7 | - var 8 | - vendor 9 | warning: false 10 | -------------------------------------------------------------------------------- /.phpstorm.meta.php: -------------------------------------------------------------------------------- 1 | AccountResponse::class, 16 | 'getApplications' => ApplicationsResponse::class, 17 | 'getApplicationByUuid' => ApplicationResponse::class, 18 | 'getApplicationEnvironments' => EnvironmentsResponse::class, 19 | 'getCron' => CronResponse::class, 20 | 'getCronJobsByEnvironmentId' => CronsResponse::class, 21 | 'getEnvironment' => EnvironmentResponse::class, 22 | 'getEnvironmentsDatabases' => DatabasesResponse::class, 23 | 'getIde' => IdeResponse::class 24 | ])); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Build status](https://github.com/acquia/cli/actions/workflows/ci.yml/badge.svg?branch=main) [![codecov](https://codecov.io/github/acquia/cli/branch/main/graph/badge.svg?token=93Y86CBRSE)](https://codecov.io/github/acquia/cli) [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Facquia%2Fcli%2Fmain)](https://dashboard.stryker-mutator.io/reports/github.com/acquia/cli/main) 2 | # Acquia CLI 3 | 4 | The official command-line tool for interacting with the Acquia Cloud Platform and services. Acquia CLI (acli) helps you run [Drush](http://www.drush.org/) commands and tail logs from your Acquia-hosted applications, manage [Acquia Cloud IDEs](https://docs.acquia.com/dev-studio/ide/), create and manage teams and applications via the [Cloud Platform API](https://cloudapi-docs.acquia.com/), and much more! 5 | 6 | Acquia CLI is not a local development environment. If you are looking for an integrated development environment, consider [Acquia Cloud IDE](https://docs.acquia.com/dev-studio/ide/) or third-party tools such as [Lando](https://lando.dev/). 7 | 8 | 9 | ## Installation and usage 10 | 11 | Install instructions and official documentation are available at https://docs.acquia.com/acquia-cli/install/ 12 | 13 | ## Contribution 14 | 15 | See [CONTRIBUTING.md](CONTRIBUTING.md) for instructions on building, testing, and contributing to Acquia CLI. 16 | 17 | ## Support 18 | 19 | - To receive support from Acquia, visit the [Acquia Support Portal](https://acquia.my.site.com/s/). 20 | - To receive support from and discuss ideas with other Acquia CLI users, visit the [discussions section](https://github.com/acquia/cli/discussions) on GitHub. 21 | -------------------------------------------------------------------------------- /bin/acli.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | REM Running this file is equivalent to running `php acli` 3 | setlocal DISABLEDELAYEDEXPANSION 4 | SET BIN_TARGET=%~dp0acli 5 | php "%BIN_TARGET%" %* 6 | -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/box-project/box/4.3.7/res/schema.json", 3 | "directories": [ 4 | "config", 5 | "var/cache" 6 | ], 7 | "files": [ 8 | ".env" 9 | ], 10 | "compactors": [ 11 | "KevinGH\\Box\\Compactor\\Php", 12 | "KevinGH\\Box\\Compactor\\Json" 13 | ], 14 | "output": "var/acli.phar", 15 | "exclude-composer-files": false, 16 | "force-autodiscovery": true 17 | } 18 | -------------------------------------------------------------------------------- /box.json.md: -------------------------------------------------------------------------------- 1 | ## Box configuration 2 | 3 | box.json is largely based on the template provided here: https://github.com/humbug/box/blob/master/fixtures/build/dir012/box.json.dist 4 | 5 | This particular configuration is necessary to support Symfony Console. Specifically: 6 | - Must include composer files, since Symfony uses these to determine the root directory 7 | - Must force autodisovery, since Symfony won't be able to find service classes otherwise 8 | - Must force include of config and var directories, since Symfony uses these for cache and config 9 | 10 | See also: https://github.com/humbug/box/blob/master/doc/symfony.md 11 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | # It's not feasible to spin up a working Drupal 7 site as part of ACLI's test suite. 3 | - src/Command/App/From/SourceSite/Drupal7SiteInspector.php 4 | coverage: 5 | status: 6 | project: 7 | default: 8 | only_pulls: true 9 | -------------------------------------------------------------------------------- /config/dev/services.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: '../prod/services.yml' } 3 | 4 | services: 5 | Symfony\Component\Console\Output\BufferedOutput: ~ 6 | Symfony\Component\Console\Output\OutputInterface: 7 | alias: Symfony\Component\Console\Output\BufferedOutput 8 | public: true 9 | Symfony\Component\Console\Input\ArrayInput: ~ 10 | Symfony\Component\Console\Input\InputInterface: 11 | alias: Symfony\Component\Console\Input\ArrayInput 12 | public: true 13 | -------------------------------------------------------------------------------- /config/from_d7_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootPackageDefinition": { 3 | "type": "project", 4 | "description": "Generated by Acquia CLI's app:new:from:drupal7", 5 | "repositories": [ 6 | { 7 | "type": "composer", 8 | "url": "https://packages.drupal.org/8" 9 | } 10 | ], 11 | "require": { 12 | "composer/installers": "^1.9", 13 | "cweagans/composer-patches": "^1.7", 14 | "drupal/core-composer-scaffold": "9.0.1", 15 | "drupal/core-project-message": "9.0.1", 16 | "drupal/core-recommended": "9.0.1", 17 | "drush/drush": "*" 18 | }, 19 | "config": { 20 | "sort-packages": true, 21 | "allow-plugins": { 22 | "composer/installers": true, 23 | "cweagans/composer-patches": true, 24 | "drupal/core-composer-scaffold": true, 25 | "drupal/core-project-message": true 26 | } 27 | }, 28 | "conflict": { 29 | "drupal/drupal": "*" 30 | }, 31 | "minimum-stability": "dev", 32 | "prefer-stable": true, 33 | "extra": { 34 | "enable-patching": true, 35 | "patchLevel": { 36 | "drupal/core": "-p2" 37 | }, 38 | "drupal-scaffold": { 39 | "locations": { 40 | "web-root": "docroot/" 41 | } 42 | }, 43 | "installer-paths": { 44 | "docroot/core": [ 45 | "type:drupal-core" 46 | ], 47 | "docroot/libraries/{$name}": [ 48 | "type:drupal-library" 49 | ], 50 | "docroot/modules/contrib/{$name}": [ 51 | "type:drupal-module" 52 | ], 53 | "docroot/profiles/contrib/{$name}": [ 54 | "type:drupal-profile" 55 | ], 56 | "docroot/themes/contrib/{$name}": [ 57 | "type:drupal-theme" 58 | ], 59 | "drush/Commands/contrib/{$name}": [ 60 | "type:drupal-drush" 61 | ], 62 | "docroot/modules/custom/{$name}": [ 63 | "type:drupal-custom-module" 64 | ], 65 | "docroot/profiles/custom/{$name}": [ 66 | "type:drupal-custom-profile" 67 | ], 68 | "docroot/themes/custom/{$name}": [ 69 | "type:drupal-custom-theme" 70 | ] 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8-alpine 2 | 3 | RUN set -eux ; \ 4 | apk add --no-cache \ 5 | tini 6 | 7 | RUN curl https://github.com/acquia/cli/releases/latest/download/acli.phar -L -o /usr/local/bin/acli \ 8 | && chmod +x /usr/local/bin/acli 9 | 10 | COPY docker-entrypoint.sh /docker-entrypoint.sh 11 | 12 | ENTRYPOINT ["/docker-entrypoint.sh"] 13 | 14 | CMD ["acli"] 15 | -------------------------------------------------------------------------------- /docker/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | isCommand() { 4 | # Retain backwards compatibility with common CI providers, 5 | # see: https://github.com/composer/docker/issues/107 6 | if [ "$1" = "sh" ]; then 7 | return 1 8 | fi 9 | 10 | acli help --no-interaction "$1" > /dev/null 2>&1 11 | } 12 | 13 | # check if the first argument passed in looks like a flag 14 | if [ "${1#-}" != "$1" ]; then 15 | set -- /sbin/tini -- acli "$@" 16 | # check if the first argument passed in is acli 17 | elif [ "$1" = 'acli' ]; then 18 | set -- /sbin/tini -- "$@" 19 | # check if the first argument passed in matches a known command 20 | elif isCommand "$1"; then 21 | set -- /sbin/tini -- acli "$@" 22 | fi 23 | 24 | exec "$@" 25 | -------------------------------------------------------------------------------- /grumphp.yml: -------------------------------------------------------------------------------- 1 | grumphp: 2 | tasks: 3 | phpcs: ~ 4 | fixer: 5 | fix_by_default: true 6 | ascii: 7 | failed: ~ 8 | succeeded: ~ 9 | -------------------------------------------------------------------------------- /infection.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "vendor/infection/infection/resources/schema.json", 3 | "source": { 4 | "directories": [ 5 | "src" 6 | ] 7 | }, 8 | "logs": { 9 | "stryker": { 10 | "report": "main" 11 | }, 12 | "html": "var/infection.html" 13 | }, 14 | "mutators": { 15 | "@default": true, 16 | "global-ignoreSourceCodeByRegex": [ 17 | "\\$this->logger.*" 18 | ] 19 | }, 20 | "timeout": 30, 21 | "testFrameworkOptions": "--exclude-group=serial" 22 | } 23 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | Acquia CLI PHP CodeSniffer configuration. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | src 18 | tests 19 | 20 | 21 | 22 | 23 | tests/fixtures/* 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 1 3 | paths: 4 | - src 5 | - tests 6 | - bin 7 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | tests/phpunit 11 | 12 | 13 | 14 | 15 | 16 | src 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/AcsfApi/AcsfClient.php: -------------------------------------------------------------------------------- 1 | getBody(); 16 | $body = json_decode((string) $bodyJson, false, 512, JSON_THROW_ON_ERROR); 17 | 18 | // ACSF sometimes returns an array rather than an object. 19 | if (is_array($body)) { 20 | return $body; 21 | } 22 | 23 | if (property_exists($body, '_embedded') && property_exists($body->_embedded, 'items')) { 24 | return $body->_embedded->items; 25 | } 26 | 27 | if (property_exists($body, 'error') && property_exists($body, 'message')) { 28 | throw new ApiErrorException($body); 29 | } 30 | // Throw error for 4xx and 5xx responses. 31 | if ( 32 | property_exists($body, 'message') && in_array(substr((string) $response->getStatusCode(), 0, 1), [ 33 | '4', 34 | '5', 35 | ], true) 36 | ) { 37 | $body->error = $response->getStatusCode(); 38 | throw new ApiErrorException($body); 39 | } 40 | 41 | return $body; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/AcsfApi/AcsfClientService.php: -------------------------------------------------------------------------------- 1 | connector); 20 | $this->configureClient($client); 21 | 22 | return $client; 23 | } 24 | 25 | protected function checkAuthentication(): bool 26 | { 27 | return ($this->credentials->getCloudKey() && $this->credentials->getCloudSecret()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/AcsfApi/AcsfConnector.php: -------------------------------------------------------------------------------- 1 | $config 15 | */ 16 | public function __construct(array $config, ?string $baseUri = null, ?string $urlAccessToken = null) 17 | { 18 | parent::__construct($config, $baseUri, $urlAccessToken); 19 | 20 | $this->client = new GuzzleClient([ 21 | 'auth' => [ 22 | $config['key'], 23 | $config['secret'], 24 | ], 25 | 'base_uri' => $this->getBaseUri(), 26 | ]); 27 | } 28 | 29 | /** 30 | * @param array $options 31 | */ 32 | public function sendRequest(string $verb, string $path, array $options): ResponseInterface 33 | { 34 | return $this->client->request($verb, $path, $options); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/AcsfApi/AcsfConnectorFactory.php: -------------------------------------------------------------------------------- 1 | $config 13 | */ 14 | public function __construct(protected array $config, protected ?string $baseUri = null) 15 | { 16 | } 17 | 18 | public function createConnector(): AcsfConnector 19 | { 20 | return new AcsfConnector($this->config, $this->baseUri); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/AcsfApi/AcsfCredentials.php: -------------------------------------------------------------------------------- 1 | getCurrentFactory()) && $activeUser = $this->getFactoryActiveUser($currentFactory)) { 26 | return $activeUser['username']; 27 | } 28 | 29 | return null; 30 | } 31 | 32 | /** 33 | * @param array $factory 34 | */ 35 | public function getFactoryActiveUser(array $factory): mixed 36 | { 37 | if (array_key_exists('active_user', $factory)) { 38 | $activeUser = $factory['active_user']; 39 | if (array_key_exists($activeUser, $factory['users'])) { 40 | return $factory['users'][$activeUser]; 41 | } 42 | } 43 | 44 | return null; 45 | } 46 | 47 | private function getCurrentFactory(): mixed 48 | { 49 | if (($factory = $this->datastoreCloud->get('acsf_active_factory')) && ($acsfFactories = $this->datastoreCloud->get('acsf_factories')) && array_key_exists($factory, $acsfFactories)) { 50 | return $acsfFactories[$factory]; 51 | } 52 | return null; 53 | } 54 | 55 | public function getCloudSecret(): ?string 56 | { 57 | if (getenv('ACSF_KEY')) { 58 | return getenv('ACSF_KEY'); 59 | } 60 | 61 | if (($currentFactory = $this->getCurrentFactory()) && $activeUser = $this->getFactoryActiveUser($currentFactory)) { 62 | return $activeUser['key']; 63 | } 64 | 65 | return null; 66 | } 67 | 68 | public function getBaseUri(): ?string 69 | { 70 | if (getenv('ACSF_FACTORY_URI')) { 71 | return getenv('ACSF_FACTORY_URI'); 72 | } 73 | if ($factory = $this->datastoreCloud->get('acsf_active_factory')) { 74 | return $factory; 75 | } 76 | 77 | return null; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/ApiCredentialsInterface.php: -------------------------------------------------------------------------------- 1 | helpMessages; 26 | } 27 | 28 | public function setHelpMessages(array $helpMessages): void 29 | { 30 | $this->helpMessages = $helpMessages; 31 | } 32 | 33 | public function renderThrowable( 34 | Throwable $e, 35 | OutputInterface $output 36 | ): void { 37 | parent::renderThrowable($e, $output); 38 | 39 | if ($this->getHelpMessages()) { 40 | $io = new SymfonyStyle(new ArrayInput([]), $output); 41 | $outputStyle = new OutputFormatterStyle('white', 'blue'); 42 | $output->getFormatter()->setStyle('help', $outputStyle); 43 | $io->block($this->getHelpMessages(), 'help', 'help', ' ', true, false); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Attribute/RequireAuth.php: -------------------------------------------------------------------------------- 1 | $config 23 | */ 24 | public function __construct(array $config, ?string $baseUri = null, ?string $urlAccessToken = null) 25 | { 26 | $this->accessToken = new AccessToken(['access_token' => $config['access_token']]); 27 | parent::__construct($config, $baseUri, $urlAccessToken); 28 | } 29 | 30 | public function createRequest(string $verb, string $path): RequestInterface 31 | { 32 | if ($file = getenv('ACLI_ACCESS_TOKEN_FILE')) { 33 | if (!file_exists($file)) { 34 | throw new AcquiaCliException('Access token file not found at {file}', ['file' => $file]); 35 | } 36 | $this->accessToken = new AccessToken(['access_token' => trim(file_get_contents($file), "\"\n")]); 37 | } 38 | return $this->provider->getAuthenticatedRequest( 39 | $verb, 40 | $this->getBaseUri() . $path, 41 | $this->accessToken 42 | ); 43 | } 44 | 45 | public function setProvider( 46 | GenericProvider $provider 47 | ): void { 48 | $this->provider = $provider; 49 | } 50 | 51 | public function getAccessToken(): AccessToken 52 | { 53 | return $this->accessToken; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/CloudApi/ConnectorFactory.php: -------------------------------------------------------------------------------- 1 | $config 15 | */ 16 | public function __construct(protected array $config, protected ?string $baseUri = null, protected ?string $accountsUri = null) 17 | { 18 | } 19 | 20 | /** 21 | * @return \Acquia\Cli\CloudApi\AccessTokenConnector|\AcquiaCloudApi\Connector\Connector 22 | */ 23 | public function createConnector(): Connector|AccessTokenConnector 24 | { 25 | // A defined key & secret takes priority. 26 | if ($this->config['key'] && $this->config['secret']) { 27 | return new Connector($this->config, $this->baseUri, $this->accountsUri); 28 | } 29 | 30 | // Fall back to a valid access token. 31 | if ($this->config['accessToken']) { 32 | $accessToken = $this->createAccessToken(); 33 | if (!$accessToken->hasExpired()) { 34 | // @todo Add debug log entry indicating that access token is being used. 35 | return new AccessTokenConnector([ 36 | 'access_token' => $accessToken, 37 | 'key' => null, 38 | 'secret' => null, 39 | ], $this->baseUri, $this->accountsUri); 40 | } 41 | } 42 | 43 | // Fall back to an unauthenticated request. 44 | return new Connector($this->config, $this->baseUri, $this->accountsUri); 45 | } 46 | 47 | private function createAccessToken(): AccessToken 48 | { 49 | return new AccessToken([ 50 | 'access_token' => $this->config['accessToken'], 51 | 'expires' => $this->config['accessTokenExpiry'], 52 | ]); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Command/Acsf/AcsfCommandFactory.php: -------------------------------------------------------------------------------- 1 | localMachineHelper, 40 | $this->datastoreCloud, 41 | $this->datastoreAcli, 42 | $this->cloudCredentials, 43 | $this->telemetryHelper, 44 | $this->projectDir, 45 | $this->cloudApiClientService, 46 | $this->sshHelper, 47 | $this->sshDir, 48 | $this->logger, 49 | $this->selfUpdateManager, 50 | ); 51 | } 52 | 53 | public function createListCommand(): AcsfListCommand 54 | { 55 | return new AcsfListCommand( 56 | $this->localMachineHelper, 57 | $this->datastoreCloud, 58 | $this->datastoreAcli, 59 | $this->cloudCredentials, 60 | $this->telemetryHelper, 61 | $this->projectDir, 62 | $this->cloudApiClientService, 63 | $this->sshHelper, 64 | $this->sshDir, 65 | $this->logger, 66 | $this->selfUpdateManager, 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Command/Acsf/AcsfListCommand.php: -------------------------------------------------------------------------------- 1 | namespace = $namespace; 19 | } 20 | 21 | protected function execute(InputInterface $input, OutputInterface $output): int 22 | { 23 | $commands = $this->getApplication()->all(); 24 | foreach ($commands as $command) { 25 | if ( 26 | $command->getName() !== $this->namespace 27 | // E.g., if the namespace is acsf:api, show all acsf:api:* commands. 28 | && str_contains($command->getName(), $this->namespace . ':') 29 | // This is a lazy way to exclude api:base and acsf:base. 30 | && $command->getDescription() 31 | ) { 32 | $command->setHidden(false); 33 | } else { 34 | $command->setHidden(); 35 | } 36 | } 37 | 38 | $command = $this->getApplication()->find('list'); 39 | $arguments = [ 40 | 'command' => 'list', 41 | 'namespace' => 'acsf', 42 | ]; 43 | $listInput = new ArrayInput($arguments); 44 | 45 | return $command->run($listInput, $output); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Command/Api/ApiCommandFactory.php: -------------------------------------------------------------------------------- 1 | localMachineHelper, 39 | $this->datastoreCloud, 40 | $this->datastoreAcli, 41 | $this->cloudCredentials, 42 | $this->telemetryHelper, 43 | $this->projectDir, 44 | $this->cloudApiClientService, 45 | $this->sshHelper, 46 | $this->sshDir, 47 | $this->logger, 48 | $this->selfUpdateManager, 49 | ); 50 | } 51 | 52 | public function createListCommand(): ApiListCommand 53 | { 54 | return new ApiListCommand( 55 | $this->localMachineHelper, 56 | $this->datastoreCloud, 57 | $this->datastoreAcli, 58 | $this->cloudCredentials, 59 | $this->telemetryHelper, 60 | $this->projectDir, 61 | $this->cloudApiClientService, 62 | $this->sshHelper, 63 | $this->sshDir, 64 | $this->logger, 65 | $this->selfUpdateManager, 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Command/Api/ApiListCommand.php: -------------------------------------------------------------------------------- 1 | namespace = $namespace; 19 | } 20 | 21 | protected function execute(InputInterface $input, OutputInterface $output): int 22 | { 23 | $commands = $this->getApplication()->all(); 24 | foreach ($commands as $command) { 25 | if ( 26 | $command->getName() !== $this->namespace 27 | && str_contains($command->getName(), $this->namespace . ':') 28 | // This is a lazy way to exclude api:base and acsf:base. 29 | && $command->getDescription() 30 | ) { 31 | $command->setHidden(false); 32 | } else { 33 | $command->setHidden(); 34 | } 35 | } 36 | 37 | $command = $this->getApplication()->find('list'); 38 | $arguments = [ 39 | 'command' => 'list', 40 | 'namespace' => 'api', 41 | ]; 42 | $listInput = new ArrayInput($arguments); 43 | 44 | return $command->run($listInput, $output); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Command/App/AppOpenCommand.php: -------------------------------------------------------------------------------- 1 | acceptApplicationUuid(); 26 | } 27 | 28 | protected function execute(InputInterface $input, OutputInterface $output): int 29 | { 30 | if (!$this->localMachineHelper->isBrowserAvailable()) { 31 | throw new AcquiaCliException('No browser is available on this machine'); 32 | } 33 | $applicationUuid = $this->determineCloudApplication(); 34 | $this->localMachineHelper->startBrowser('https://cloud.acquia.com/a/applications/' . $applicationUuid); 35 | 36 | return Command::SUCCESS; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Command/App/From/Configuration.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | protected array $array; 23 | 24 | /** 25 | * Configuration constructor. 26 | * 27 | * @param array $config 28 | * An array of configuration, usually parsed from a configuration file. 29 | */ 30 | protected function __construct(array $config) 31 | { 32 | $this->array = static::schema([ 33 | 'rootPackageDefinition' => 'is_array', 34 | ])($config); 35 | } 36 | 37 | /** 38 | * Creates a configuration object from configuration given as a PHP 39 | * resource. 40 | * 41 | * The given PHP resource is usually obtained by calling fopen($location). 42 | * 43 | * @param resource $configuration_resource 44 | * Configuration to be parse; given as a PHP resource. 45 | * @return \Acquia\Cli\Command\App\From\Configuration 46 | * A new configuration object. 47 | */ 48 | public static function createFromResource($configuration_resource): Configuration 49 | { 50 | return new static(static::parseJsonResource($configuration_resource)); 51 | } 52 | 53 | /** 54 | * Gets an basic root composer package definition for a Drupal 9+ project. 55 | * 56 | * @return array 57 | * An array representing a root composer package definition. From this 58 | * starting point, additional dependencies and metadata can be added until 59 | * an acceptable project is defined for migrating a source site to Drupal 60 | * 9+. 61 | */ 62 | public function getRootPackageDefinition(): array 63 | { 64 | return $this->array['rootPackageDefinition']; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Command/App/From/JsonResourceParserTrait.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public function normalize(): array; 19 | } 20 | -------------------------------------------------------------------------------- /src/Command/App/From/Recommendation/Recommendations.php: -------------------------------------------------------------------------------- 1 | extensions; 44 | } 45 | 46 | public function getPublicFilePath(): string 47 | { 48 | return 'sites/default/files'; 49 | } 50 | 51 | public function getPrivateFilePath(): ?string 52 | { 53 | return null; 54 | } 55 | 56 | /** 57 | * Reads an extensions resource into extensions objects. 58 | * 59 | * @param resource $extensions_resource 60 | * A serialized extensions resource from which to parse extensions. 61 | * @return \Acquia\Cli\Command\App\From\SourceSite\Drupal7Extension[] 62 | * An array of extensions. 63 | */ 64 | protected static function parseExtensionsFromResource($extensions_resource): array 65 | { 66 | return array_map(function (array $extension) { 67 | $extension['status'] = $extension['enabled']; 68 | return Drupal7Extension::createFromStdClass((object) $extension); 69 | }, static::parseJsonResource($extensions_resource)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Command/App/From/SourceSite/ExtensionInterface.php: -------------------------------------------------------------------------------- 1 | readExtensions(), function (ExtensionInterface $extension) use ($state_flags, $type_flags) { 17 | // Generate a flag for the extension's enabled/disabled state. 18 | $has = $extension->isEnabled() ? SiteInspectorInterface::FLAG_EXTENSION_ENABLED : SiteInspectorInterface::FLAG_EXTENSION_DISABLED; 19 | // Incorporate the extension's type. 20 | $has = $has | ($extension->isModule() ? SiteInspectorInterface::FLAG_EXTENSION_MODULE : 0); 21 | $has = $has | ($extension->isTheme() ? SiteInspectorInterface::FLAG_EXTENSION_THEME : 0); 22 | // TRUE if the extension has a flag in $type_flags AND a flag in 23 | // $state_flags, FALSE otherwise. 24 | return ($has & $type_flags) && ($has & $state_flags); 25 | }); 26 | } 27 | 28 | /** 29 | * Returns a list of extensions discovered on the inspected site. 30 | * 31 | * @return \Acquia\Cli\Command\App\From\SourceSite\ExtensionInterface[] 32 | * An array of extensions discovered on the inspected source site. 33 | */ 34 | abstract protected function readExtensions(): array; 35 | } 36 | -------------------------------------------------------------------------------- /src/Command/App/LinkCommand.php: -------------------------------------------------------------------------------- 1 | acceptApplicationUuid(); 21 | } 22 | 23 | protected function execute(InputInterface $input, OutputInterface $output): int 24 | { 25 | $this->validateCwdIsValidDrupalProject(); 26 | if ($cloudApplicationUuid = $this->getCloudUuidFromDatastore()) { 27 | $cloudApplication = $this->getCloudApplication($cloudApplicationUuid); 28 | $output->writeln('This repository is already linked to Cloud application ' . $cloudApplication->name . '. Run acli unlink to unlink it.'); 29 | return 1; 30 | } 31 | $this->determineCloudApplication(true); 32 | 33 | return Command::SUCCESS; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Command/App/TaskWaitCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('notification-uuid', InputArgument::REQUIRED, 'The task notification UUID or Cloud Platform API response containing a linked notification') 23 | ->setHelp('Accepts either a notification UUID or Cloud Platform API response as JSON string. The JSON string must contain the _links->notification->href property.') 24 | ->addUsage('"$(acli api:environments:domain-clear-caches [environmentId] [domain])"'); 25 | } 26 | 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $notificationUuid = $input->getArgument('notification-uuid'); 30 | $success = $this->waitForNotificationToComplete($this->cloudApiClientService->getClient(), $notificationUuid, "Waiting for task $notificationUuid to complete"); 31 | return $success ? Command::SUCCESS : Command::FAILURE; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Command/App/UnlinkCommand.php: -------------------------------------------------------------------------------- 1 | validateCwdIsValidDrupalProject(); 20 | 21 | $projectDir = $this->projectDir; 22 | if (!$this->getCloudUuidFromDatastore()) { 23 | throw new AcquiaCliException('There is no Cloud Platform application linked to {projectDir}', ['projectDir' => $projectDir]); 24 | } 25 | 26 | $application = $this->getCloudApplication($this->datastoreAcli->get('cloud_app_uuid')); 27 | $this->datastoreAcli->set('cloud_app_uuid', null); 28 | $output->writeln("Unlinked $projectDir from Cloud application $application->name"); 29 | 30 | return Command::SUCCESS; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Command/Auth/AuthAcsfLogoutCommand.php: -------------------------------------------------------------------------------- 1 | datastoreCloud->get('acsf_factories'); 19 | if (empty($factories)) { 20 | $this->io->error(['You are not logged into any factories.']); 21 | return Command::FAILURE; 22 | } 23 | foreach ($factories as $url => $factory) { 24 | $factories[$url]['url'] = $url; 25 | } 26 | $factory = $this->promptChooseFromObjectsOrArrays($factories, 'url', 'url', 'Choose a Factory to logout of'); 27 | $factoryUrl = $factory['url']; 28 | 29 | /** @var \Acquia\Cli\AcsfApi\AcsfCredentials $cloudCredentials */ 30 | $cloudCredentials = $this->cloudCredentials; 31 | $activeUser = $cloudCredentials->getFactoryActiveUser($factory); 32 | // @todo Only show factories the user is logged into. 33 | if (!$activeUser) { 34 | $this->io->error("You're already logged out of $factoryUrl"); 35 | return 1; 36 | } 37 | $answer = $this->io->confirm("Are you sure you'd like to logout the user {$activeUser['username']} from $factoryUrl?"); 38 | if (!$answer) { 39 | return Command::SUCCESS; 40 | } 41 | $factories[$factoryUrl]['active_user'] = null; 42 | $this->datastoreCloud->set('acsf_factories', $factories); 43 | $this->datastoreCloud->remove('acsf_active_factory'); 44 | 45 | $output->writeln("Logged {$activeUser['username']} out of $factoryUrl"); 46 | 47 | return Command::SUCCESS; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Command/Auth/AuthLogoutCommand.php: -------------------------------------------------------------------------------- 1 | addOption('delete', null, InputOption::VALUE_NEGATABLE, 'Delete the active Cloud Platform API credentials'); 21 | } 22 | 23 | protected function execute(InputInterface $input, OutputInterface $output): int 24 | { 25 | $keys = $this->datastoreCloud->get('keys'); 26 | $activeKey = $this->datastoreCloud->get('acli_key'); 27 | if (!$activeKey) { 28 | throw new AcquiaCliException('There is no active Cloud Platform API key'); 29 | } 30 | $activeKeyLabel = $keys[$activeKey]['label']; 31 | $output->writeln("The key $activeKeyLabel will be deactivated on this machine. However, the credentials will remain on disk and can be reactivated by running acli auth:login unless you also choose to delete them."); 32 | $delete = $this->determineOption('delete', false, null, null, false); 33 | $this->datastoreCloud->remove('acli_key'); 34 | $action = 'deactivated'; 35 | if ($delete) { 36 | $this->datastoreCloud->remove("keys.$activeKey"); 37 | $action = 'deleted'; 38 | } 39 | $output->writeln("The active Cloud Platform API credentials were $action"); 40 | $output->writeln('No Cloud Platform API key is active. Run acli auth:login to continue using the Cloud Platform API.'); 41 | 42 | return Command::SUCCESS; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Command/CodeStudio/cs_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acquia/cli/29236b4ec1b1e81e16fb19373e9641556dfdf76d/src/Command/CodeStudio/cs_icon.png -------------------------------------------------------------------------------- /src/Command/CodeStudio/drupal_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acquia/cli/29236b4ec1b1e81e16fb19373e9641556dfdf76d/src/Command/CodeStudio/drupal_icon.png -------------------------------------------------------------------------------- /src/Command/Env/EnvDeleteCommand.php: -------------------------------------------------------------------------------- 1 | acceptEnvironmentId(); 24 | } 25 | 26 | protected function execute(InputInterface $input, OutputInterface $output): int 27 | { 28 | $this->output = $output; 29 | $cloudAppUuid = $this->determineCloudApplication(true); 30 | $acquiaCloudClient = $this->cloudApiClientService->getClient(); 31 | $environmentsResource = new Environments($acquiaCloudClient); 32 | $environment = $this->determineEnvironmentCde($environmentsResource, $cloudAppUuid); 33 | $environmentsResource->delete($environment->uuid); 34 | 35 | $this->io->success([ 36 | "The $environment->label environment is being deleted", 37 | ]); 38 | 39 | return Command::SUCCESS; 40 | } 41 | 42 | private function determineEnvironmentCde(Environments $environmentsResource, string $cloudAppUuid): EnvironmentResponse 43 | { 44 | if ($this->input->getArgument('environmentId')) { 45 | // @todo Validate. 46 | $environmentId = $this->input->getArgument('environmentId'); 47 | return $environmentsResource->get($environmentId); 48 | } 49 | $environments = $environmentsResource->getAll($cloudAppUuid); 50 | $cdes = []; 51 | foreach ($environments as $environment) { 52 | if ($environment->flags->cde) { 53 | $cdes[] = $environment; 54 | } 55 | } 56 | if (!$cdes) { 57 | throw new AcquiaCliException('There are no existing CDEs for Application ' . $cloudAppUuid); 58 | } 59 | return $this->promptChooseFromObjectsOrArrays($cdes, 'uuid', 'label', "Which Continuous Delivery Environment (CDE) do you want to delete?"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Command/HelloWorldCommand.php: -------------------------------------------------------------------------------- 1 | io->success('Hello world!'); 18 | 19 | return Command::SUCCESS; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Command/Ide/IdeDeleteCommand.php: -------------------------------------------------------------------------------- 1 | acceptApplicationUuid(); 25 | // @todo make this an argument 26 | $this->addOption('uuid', null, InputOption::VALUE_OPTIONAL, 'UUID of the IDE to delete'); 27 | } 28 | 29 | protected function execute(InputInterface $input, OutputInterface $output): int 30 | { 31 | $acquiaCloudClient = $this->cloudApiClientService->getClient(); 32 | $idesResource = new Ides($acquiaCloudClient); 33 | 34 | $ideUuid = $input->getOption('uuid'); 35 | if ($ideUuid) { 36 | $ide = $idesResource->get($ideUuid); 37 | } else { 38 | $cloudApplicationUuid = $this->determineCloudApplication(); 39 | $ide = $this->promptIdeChoice("Select the IDE you'd like to delete:", $idesResource, $cloudApplicationUuid); 40 | $answer = $this->io->confirm("Are you sure you want to delete $ide->label"); 41 | if (!$answer) { 42 | $this->io->writeln('Ok, never mind.'); 43 | return Command::FAILURE; 44 | } 45 | } 46 | $response = $idesResource->delete($ide->uuid); 47 | $this->io->writeln($response->message); 48 | 49 | // Check to see if an SSH key for this IDE exists on Cloud. 50 | $cloudKey = $this->findIdeSshKeyOnCloud($ide->label, $ide->uuid); 51 | if ($cloudKey) { 52 | $answer = $this->io->confirm('Would you like to delete the SSH key associated with this IDE from your Cloud Platform account?'); 53 | if ($answer) { 54 | $this->deleteSshKeyFromCloud($output, $cloudKey); 55 | } 56 | } 57 | 58 | return Command::SUCCESS; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Command/Ide/IdeInfoCommand.php: -------------------------------------------------------------------------------- 1 | acceptApplicationUuid(); 22 | } 23 | 24 | protected function execute(InputInterface $input, OutputInterface $output): int 25 | { 26 | $applicationUuid = $this->determineCloudApplication(); 27 | 28 | $acquiaCloudClient = $this->cloudApiClientService->getClient(); 29 | $idesResource = new Ides($acquiaCloudClient); 30 | 31 | $ide = $this->promptIdeChoice("Select an IDE to get more information:", $idesResource, $applicationUuid); 32 | $response = $idesResource->get($ide->uuid); 33 | $this->io->definitionList( 34 | ['IDE property' => 'IDE value'], 35 | new TableSeparator(), 36 | ['UUID' => $response->uuid], 37 | ['Label' => $response->label], 38 | ['Owner name' => $response->owner->first_name . ' ' . $response->owner->last_name], 39 | ['Owner username' => $response->owner->username], 40 | ['Owner email' => $response->owner->mail], 41 | ['Cloud application' => $response->links->application->href], 42 | ['IDE URL' => $response->links->ide->href], 43 | ['Web URL' => $response->links->web->href] 44 | ); 45 | 46 | return Command::SUCCESS; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Command/Ide/IdeListCommand.php: -------------------------------------------------------------------------------- 1 | acceptApplicationUuid(); 23 | } 24 | 25 | protected function execute(InputInterface $input, OutputInterface $output): int 26 | { 27 | $applicationUuid = $this->determineCloudApplication(); 28 | 29 | $acquiaCloudClient = $this->cloudApiClientService->getClient(); 30 | $idesResource = new Ides($acquiaCloudClient); 31 | $applicationIdes = $idesResource->getAll($applicationUuid); 32 | 33 | if ($applicationIdes->count()) { 34 | $table = new Table($output); 35 | $table->setStyle('borderless'); 36 | $table->setHeaders(['IDEs']); 37 | foreach ($applicationIdes as $ide) { 38 | $table->addRows([ 39 | ["$ide->label ({$ide->owner->mail})"], 40 | ["IDE URL: links->ide->href}>{$ide->links->ide->href}"], 41 | ["Web URL: links->web->href}>{$ide->links->web->href}"], 42 | new TableSeparator(), 43 | ]); 44 | } 45 | $table->render(); 46 | } else { 47 | $output->writeln('No IDE exists for this application.'); 48 | } 49 | 50 | return Command::SUCCESS; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Command/Ide/IdeListMineCommand.php: -------------------------------------------------------------------------------- 1 | cloudApiClientService->getClient(); 24 | $ides = new Ides($acquiaCloudClient); 25 | $accountIdes = $ides->getMine(); 26 | $applicationResource = new Applications($acquiaCloudClient); 27 | 28 | if (count($accountIdes)) { 29 | $table = new Table($output); 30 | $table->setStyle('borderless'); 31 | $table->setHeaders(['IDEs']); 32 | foreach ($accountIdes as $ide) { 33 | $appUrlParts = explode('/', $ide->links->application->href); 34 | $appUuid = end($appUrlParts); 35 | $application = $applicationResource->get($appUuid); 36 | $applicationUrl = str_replace('/api', '/a', $application->links->self->href); 37 | 38 | $table->addRows([ 39 | ["$ide->label"], 40 | ["UUID: $ide->uuid"], 41 | ["Application: $application->name"], 42 | ["Subscription: {$application->subscription->name}"], 43 | ["IDE URL: links->ide->href}>{$ide->links->ide->href}"], 44 | ["Web URL: links->web->href}>{$ide->links->web->href}"], 45 | new TableSeparator(), 46 | ]); 47 | } 48 | $table->render(); 49 | } else { 50 | $output->writeln('No IDE exists for your account.'); 51 | } 52 | 53 | return Command::SUCCESS; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Command/Ide/IdeOpenCommand.php: -------------------------------------------------------------------------------- 1 | setHidden(AcquiaDrupalEnvironmentDetector::isAhIdeEnv()); 23 | $this->acceptApplicationUuid(); 24 | // @todo Add option to accept an ide UUID. 25 | } 26 | 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $acquiaCloudClient = $this->cloudApiClientService->getClient(); 30 | $cloudApplicationUuid = $this->determineCloudApplication(); 31 | $idesResource = new Ides($acquiaCloudClient); 32 | $ide = $this->promptIdeChoice("Select the IDE you'd like to open:", $idesResource, $cloudApplicationUuid); 33 | 34 | $this->output->writeln(''); 35 | $this->output->writeln("Your IDE URL: links->ide->href}>{$ide->links->ide->href}"); 36 | $this->output->writeln("Your Drupal Site URL: links->web->href}>{$ide->links->web->href}"); 37 | $this->output->writeln('Opening your IDE in browser...'); 38 | 39 | $this->localMachineHelper->startBrowser($ide->links->ide->href); 40 | 41 | return Command::SUCCESS; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Command/Ide/IdePhpVersionCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('version', InputArgument::REQUIRED, 'The PHP version') 24 | ->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv()); 25 | } 26 | 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $this->requireCloudIdeEnvironment(); 30 | $version = $input->getArgument('version'); 31 | $this->validatePhpVersion($version); 32 | $this->localMachineHelper->getFilesystem() 33 | ->dumpFile($this->getIdePhpVersionFilePath(), $version); 34 | $this->restartService('php-fpm'); 35 | 36 | return Command::SUCCESS; 37 | } 38 | 39 | private function getIdePhpFilePathPrefix(): string 40 | { 41 | if (!isset($this->idePhpFilePathPrefix)) { 42 | $this->idePhpFilePathPrefix = '/usr/local/php'; 43 | } 44 | return $this->idePhpFilePathPrefix; 45 | } 46 | 47 | public function setIdePhpFilePathPrefix(string $path): void 48 | { 49 | $this->idePhpFilePathPrefix = $path; 50 | } 51 | 52 | protected function validatePhpVersion(string $version): string 53 | { 54 | parent::validatePhpVersion($version); 55 | $phpFilepath = $this->getIdePhpFilePathPrefix() . $version; 56 | if (!$this->localMachineHelper->getFilesystem()->exists($phpFilepath)) { 57 | throw new AcquiaCliException('The specified PHP version does not exist on this machine.'); 58 | } 59 | 60 | return $version; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Command/Ide/IdeShareCommand.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | private array $shareCodeFilepaths; 23 | 24 | protected function configure(): void 25 | { 26 | $this 27 | ->addOption('regenerate', '', InputOption::VALUE_NONE, 'regenerate the share code') 28 | ->setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv()); 29 | } 30 | 31 | protected function execute(InputInterface $input, OutputInterface $output): int 32 | { 33 | $this->requireCloudIdeEnvironment(); 34 | 35 | if ($input->getOption('regenerate')) { 36 | $this->regenerateShareCode(); 37 | } 38 | 39 | $shareUuid = $this->localMachineHelper->readFile($this->getShareCodeFilepaths()[0]); 40 | $webUrl = self::getThisCloudIdeWebUrl(); 41 | 42 | $this->output->writeln(''); 43 | $this->output->writeln("Your IDE Share URL: https://$webUrl?share=$shareUuid"); 44 | 45 | return Command::SUCCESS; 46 | } 47 | 48 | public function setShareCodeFilepaths(array $filePath): void 49 | { 50 | $this->shareCodeFilepaths = $filePath; 51 | } 52 | 53 | /** 54 | * @return array 55 | */ 56 | private function getShareCodeFilepaths(): array 57 | { 58 | if (!isset($this->shareCodeFilepaths)) { 59 | $this->shareCodeFilepaths = [ 60 | '/usr/local/share/ide/.sharecode', 61 | '/home/ide/.sharecode', 62 | ]; 63 | } 64 | return $this->shareCodeFilepaths; 65 | } 66 | 67 | private function regenerateShareCode(): void 68 | { 69 | $newShareCode = (string) Uuid::uuid4(); 70 | foreach ($this->getShareCodeFilepaths() as $path) { 71 | $this->localMachineHelper->writeFile($path, $newShareCode); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Command/Ide/Wizard/IdeWizardCommandBase.php: -------------------------------------------------------------------------------- 1 | setSshKeyFilepath(self::getSshKeyFilename($this::getThisCloudIdeUuid())); 29 | $this->passphraseFilepath = $this->localMachineHelper->getLocalFilepath('~/.passphrase'); 30 | } 31 | 32 | public static function getSshKeyFilename(mixed $ideUuid): string 33 | { 34 | return 'id_rsa_acquia_ide_' . $ideUuid; 35 | } 36 | 37 | protected function validateEnvironment(): void 38 | { 39 | $this->requireCloudIdeEnvironment(); 40 | } 41 | 42 | protected function getSshKeyLabel(): string 43 | { 44 | return $this::getIdeSshKeyLabel(self::getThisCloudIdeLabel(), self::getThisCloudIdeUuid()); 45 | } 46 | 47 | protected function deleteThisSshKeyFromCloud(mixed $output): void 48 | { 49 | if ($cloudKey = $this->findIdeSshKeyOnCloud($this::getThisCloudIdeLabel(), $this::getThisCloudIdeUuid())) { 50 | $this->deleteSshKeyFromCloud($output, $cloudKey); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Command/Ide/Wizard/IdeWizardDeleteSshKeyCommand.php: -------------------------------------------------------------------------------- 1 | setHidden(!CommandBase::isAcquiaCloudIde()); 26 | } 27 | 28 | protected function execute(InputInterface $input, OutputInterface $output): int 29 | { 30 | $this->requireCloudIdeEnvironment(); 31 | 32 | $cloudKey = $this->findIdeSshKeyOnCloud($this::getThisCloudIdeLabel(), $this::getThisCloudIdeUuid()); 33 | if (!$cloudKey) { 34 | throw new AcquiaCliException('Could not find an SSH key on the Cloud Platform matching any local key in this IDE.'); 35 | } 36 | 37 | $this->deleteSshKeyFromCloud($output, $cloudKey); 38 | $this->deleteLocalSshKey(); 39 | 40 | $this->output->writeln("Deleted local files $this->publicSshKeyFilepath and $this->privateSshKeyFilepath"); 41 | 42 | return Command::SUCCESS; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Command/Pull/PullCodeCommand.php: -------------------------------------------------------------------------------- 1 | acceptEnvironmentId() 25 | ->addOption('dir', null, InputArgument::OPTIONAL, 'The directory containing the Drupal project to be refreshed') 26 | ->addOption( 27 | 'no-scripts', 28 | null, 29 | InputOption::VALUE_NONE, 30 | 'Do not run any additional scripts after code is pulled. E.g., composer install , drush cache-rebuild, etc.' 31 | ); 32 | } 33 | 34 | protected function execute(InputInterface $input, OutputInterface $output): int 35 | { 36 | $this->setDirAndRequireProjectCwd($input); 37 | $clone = $this->determineCloneProject($output); 38 | $sourceEnvironment = $this->determineEnvironment($input, $output, true); 39 | $this->pullCode($input, $output, $clone, $sourceEnvironment); 40 | $this->checkEnvironmentPhpVersions($sourceEnvironment); 41 | $this->matchIdePhpVersion($output, $sourceEnvironment); 42 | if (!$input->getOption('no-scripts')) { 43 | $outputCallback = $this->getOutputCallback($output, $this->checklist); 44 | $this->runComposerScripts($outputCallback, $this->checklist); 45 | $this->runDrushCacheClear($outputCallback, $this->checklist); 46 | } 47 | 48 | return Command::SUCCESS; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Command/Pull/PullFilesCommand.php: -------------------------------------------------------------------------------- 1 | acceptEnvironmentId() 21 | ->acceptSite(); 22 | } 23 | 24 | protected function execute(InputInterface $input, OutputInterface $output): int 25 | { 26 | $this->setDirAndRequireProjectCwd($input); 27 | $sourceEnvironment = $this->determineEnvironment($input, $output, true); 28 | $this->pullFiles($input, $output, $sourceEnvironment); 29 | 30 | return Command::SUCCESS; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Command/Pull/PullScriptsCommand.php: -------------------------------------------------------------------------------- 1 | acceptEnvironmentId() 24 | ->addOption('dir', null, InputArgument::OPTIONAL, 'The directory containing the Drupal project to be refreshed'); 25 | } 26 | 27 | protected function initialize(InputInterface $input, OutputInterface $output): void 28 | { 29 | parent::initialize($input, $output); 30 | $this->checklist = new Checklist($output); 31 | } 32 | 33 | protected function execute(InputInterface $input, OutputInterface $output): int 34 | { 35 | $this->setDirAndRequireProjectCwd($input); 36 | $this->executeAllScripts($this->getOutputCallback($output, $this->checklist), $this->checklist); 37 | 38 | return Command::SUCCESS; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Command/Push/PushCodeCommand.php: -------------------------------------------------------------------------------- 1 | setHidden(!AcquiaDrupalEnvironmentDetector::isAhIdeEnv() && !self::isLandoEnv()); 22 | } 23 | 24 | protected function execute(InputInterface $input, OutputInterface $output): int 25 | { 26 | $output->writeln("Use git to push code changes upstream."); 27 | 28 | return Command::SUCCESS; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Command/Push/PushCommandBase.php: -------------------------------------------------------------------------------- 1 | acceptEnvironmentId() 23 | ->acceptSite(); 24 | } 25 | 26 | protected function execute(InputInterface $input, OutputInterface $output): int 27 | { 28 | $this->setDirAndRequireProjectCwd($input); 29 | $destinationEnvironment = $this->determineEnvironment($input, $output); 30 | $chosenSite = $input->getArgument('site'); 31 | if (!$chosenSite) { 32 | $chosenSite = $this->promptChooseDrupalSite($destinationEnvironment); 33 | } 34 | $answer = $this->io->confirm("Overwrite the public files directory on $destinationEnvironment->name with a copy of the files from the current machine?"); 35 | if (!$answer) { 36 | return Command::SUCCESS; 37 | } 38 | 39 | $this->checklist = new Checklist($output); 40 | $this->checklist->addItem('Pushing public files directory to remote machine'); 41 | $this->rsyncFilesToCloud($destinationEnvironment, $this->getOutputCallback($output, $this->checklist), $chosenSite); 42 | $this->checklist->completePreviousItem(); 43 | 44 | return Command::SUCCESS; 45 | } 46 | 47 | private function rsyncFilesToCloud(EnvironmentResponse $chosenEnvironment, ?callable $outputCallback = null, ?string $site = null): void 48 | { 49 | $sourceDir = $this->getLocalFilesDir($site); 50 | $destinationDir = $chosenEnvironment->sshUrl . ':' . $this->getCloudFilesDir($chosenEnvironment, $site); 51 | 52 | $this->rsyncFiles($sourceDir, $destinationDir, $outputCallback); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Command/Remote/DrushCommand.php: -------------------------------------------------------------------------------- 1 | setHelp('Pay close attention to the argument syntax! Note the usage of -- to separate the drush command arguments and options.') 27 | ->acceptEnvironmentId() 28 | ->addArgument('drush_command', InputArgument::IS_ARRAY, 'Drush command') 29 | ->addUsage('. -- ') 30 | ->addUsage('myapp.dev -- uli 1') 31 | ->addUsage('myapp.dev -- status --fields=db-status'); 32 | } 33 | 34 | /** 35 | * @throws \Acquia\Cli\Exception\AcquiaCliException 36 | */ 37 | protected function execute(InputInterface $input, OutputInterface $output): ?int 38 | { 39 | $environment = $this->determineEnvironment($input, $output, true); 40 | $alias = self::getEnvironmentAlias($environment); 41 | $acliArguments = $input->getArguments(); 42 | $drushArguments = (array) $acliArguments['drush_command']; 43 | // When available, provide the default domain to drush. 44 | if (!empty($environment->default_domain)) { 45 | // Insert at the beginning so a user-supplied --uri arg will override. 46 | array_unshift($drushArguments, "--uri=http://$environment->default_domain"); 47 | } 48 | $drushCommandArguments = [ 49 | "cd /var/www/html/$alias/docroot; ", 50 | 'drush', 51 | implode(' ', $drushArguments), 52 | ]; 53 | 54 | return $this->sshHelper->executeCommand($environment->sshUrl, $drushCommandArguments) 55 | ->getExitCode(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Command/Remote/SshBaseCommand.php: -------------------------------------------------------------------------------- 1 | addArgument('alias', InputArgument::REQUIRED, 'Alias for application & environment in the format `app-name.env`') 25 | ->addArgument('ssh_command', InputArgument::IS_ARRAY, 'Command to run via SSH (if not provided, opens a shell in the site directory)') 26 | ->addUsage("myapp.dev # open a shell in the myapp.dev environment") 27 | ->addUsage("myapp.dev -- ls -al # list files in the myapp.dev environment and return"); 28 | } 29 | 30 | protected function execute(InputInterface $input, OutputInterface $output): ?int 31 | { 32 | $alias = $input->getArgument('alias'); 33 | $alias = $this->normalizeAlias($alias); 34 | $alias = self::validateEnvironmentAlias($alias); 35 | $environment = $this->getEnvironmentFromAliasArg($alias); 36 | if (!isset($environment->sshUrl)) { 37 | throw new AcquiaCliException('Cannot determine environment SSH URL. Check that you have SSH permissions on this environment.'); 38 | } 39 | $sshCommand = [ 40 | 'cd /var/www/html/' . $alias, 41 | ]; 42 | $arguments = $input->getArguments(); 43 | if (empty($arguments['ssh_command'])) { 44 | $sshCommand[] = 'exec $SHELL -l'; 45 | } else { 46 | $sshCommand[] = implode(' ', $arguments['ssh_command']); 47 | } 48 | $sshCommand = (array) implode('; ', $sshCommand); 49 | return $this->sshHelper->executeCommand($environment->sshUrl, $sshCommand) 50 | ->getExitCode(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Command/Self/ClearCacheCommand.php: -------------------------------------------------------------------------------- 1 | writeln('Acquia CLI caches were cleared.'); 25 | 26 | return Command::SUCCESS; 27 | } 28 | 29 | /** 30 | * Clear caches. 31 | */ 32 | public static function clearCaches(): void 33 | { 34 | $cache = self::getAliasCache(); 35 | $cache->clear(); 36 | $systemCacheDir = Path::join(sys_get_temp_dir(), 'symphony-cache'); 37 | $fs = new Filesystem(); 38 | $fs->remove([$systemCacheDir]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Command/Self/SelfInfoCommand.php: -------------------------------------------------------------------------------- 1 | createTable($output, 'Acquia CLI information', ['Property', 'Value']); 21 | $table->addRow(['Version', $this->getApplication()->getVersion()]); 22 | $table->addRow(['Cloud datastore', $this->datastoreCloud->filepath]); 23 | $table->addRow(['ACLI datastore', $this->datastoreAcli->filepath]); 24 | $table->addRow(['Telemetry enabled', var_export($this->telemetryHelper->telemetryEnabled(), true)]); 25 | $table->addRow(['User ID', $this->telemetryHelper->getUserId()]); 26 | foreach ($this->telemetryHelper->getTelemetryUserData() as $key => $value) { 27 | $table->addRow([$key, $value]); 28 | } 29 | $table->render(); 30 | return Command::SUCCESS; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Command/Self/TelemetryCommand.php: -------------------------------------------------------------------------------- 1 | datastoreCloud; 20 | if ($datastore->get(DataStoreContract::SEND_TELEMETRY)) { 21 | $datastore->set(DataStoreContract::SEND_TELEMETRY, false); 22 | $this->io->success('Telemetry has been disabled.'); 23 | } else { 24 | $datastore->set(DataStoreContract::SEND_TELEMETRY, true); 25 | $this->io->success('Telemetry has been enabled.'); 26 | } 27 | $oppositeVerb = $datastore->get(DataStoreContract::SEND_TELEMETRY) ? 'disable' : 'enable'; 28 | $this->io->writeln("Run this command again to $oppositeVerb telemetry"); 29 | 30 | return Command::SUCCESS; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Command/Self/TelemetryDisableCommand.php: -------------------------------------------------------------------------------- 1 | datastoreCloud; 20 | $datastore->set(DataStoreContract::SEND_TELEMETRY, false); 21 | $this->io->success('Telemetry has been disabled.'); 22 | 23 | return Command::SUCCESS; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Command/Self/TelemetryEnableCommand.php: -------------------------------------------------------------------------------- 1 | datastoreCloud; 20 | $datastore->set(DataStoreContract::SEND_TELEMETRY, true); 21 | $this->io->success('Telemetry has been enabled.'); 22 | 23 | return Command::SUCCESS; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Command/Ssh/SshKeyCreateCommand.php: -------------------------------------------------------------------------------- 1 | addOption('filename', null, InputOption::VALUE_REQUIRED, 'The filename of the SSH key') 22 | ->addOption('password', null, InputOption::VALUE_REQUIRED, 'The password for the SSH key'); 23 | } 24 | 25 | protected function execute(InputInterface $input, OutputInterface $output): int 26 | { 27 | $filename = $this->determineFilename(); 28 | $password = $this->determinePassword(); 29 | $this->createSshKey($filename, $password); 30 | $output->writeln('Created new SSH key. ' . $this->publicSshKeyFilepath); 31 | 32 | return Command::SUCCESS; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Command/Ssh/SshKeyCreateUploadCommand.php: -------------------------------------------------------------------------------- 1 | addOption('filename', null, InputOption::VALUE_REQUIRED, 'The filename of the SSH key') 22 | ->addOption('password', null, InputOption::VALUE_REQUIRED, 'The password for the SSH key') 23 | ->addOption('label', null, InputOption::VALUE_REQUIRED, 'The SSH key label to be used with the Cloud Platform') 24 | ->addOption('no-wait', null, InputOption::VALUE_NONE, "Don't wait for the SSH key to be uploaded to the Cloud Platform"); 25 | } 26 | 27 | protected function execute(InputInterface $input, OutputInterface $output): int 28 | { 29 | $filename = $this->determineFilename(); 30 | $password = $this->determinePassword(); 31 | $this->createSshKey($filename, $password); 32 | $publicKey = $this->localMachineHelper->readFile($this->publicSshKeyFilepath); 33 | $chosenLocalKey = basename($this->privateSshKeyFilepath); 34 | $label = $this->determineSshKeyLabel(); 35 | $this->uploadSshKey($label, $publicKey); 36 | $this->io->success("Uploaded $chosenLocalKey to the Cloud Platform with label $label"); 37 | 38 | return Command::SUCCESS; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Command/Ssh/SshKeyDeleteCommand.php: -------------------------------------------------------------------------------- 1 | addOption('cloud-key-uuid', 'uuid', InputOption::VALUE_REQUIRED); 24 | } 25 | 26 | protected function execute(InputInterface $input, OutputInterface $output): int 27 | { 28 | return $this->deleteSshKeyFromCloud($output, $input->getOption('cloud-key-uuid')); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Command/Ssh/SshKeyUploadCommand.php: -------------------------------------------------------------------------------- 1 | addOption('filepath', null, InputOption::VALUE_REQUIRED, 'The filepath of the public SSH key to upload') 22 | ->addOption('label', null, InputOption::VALUE_REQUIRED, 'The SSH key label to be used with the Cloud Platform') 23 | ->addOption('no-wait', null, InputOption::VALUE_NONE, "Don't wait for the SSH key to be uploaded to the Cloud Platform"); 24 | } 25 | 26 | protected function execute(InputInterface $input, OutputInterface $output): int 27 | { 28 | [$chosenLocalKey, $publicKey] = $this->determinePublicSshKey(); 29 | $label = $this->determineSshKeyLabel(); 30 | $this->uploadSshKey($label, $publicKey); 31 | $this->io->success("Uploaded $chosenLocalKey to the Cloud Platform with label $label"); 32 | 33 | return Command::SUCCESS; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/CommandFactoryInterface.php: -------------------------------------------------------------------------------- 1 | getRootNode() 22 | ->children() 23 | ->scalarNode('cloud_app_uuid')->end() 24 | ->arrayNode('push') 25 | ->children() 26 | ->arrayNode('artifact') 27 | ->children() 28 | ->arrayNode('destination_git_urls') 29 | ->scalarPrototype()->end() 30 | ->end() 31 | ->end() 32 | ->end() 33 | ->end() 34 | ->end() 35 | ->end(); 36 | return $treeBuilder; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ConnectorFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected array $config; 16 | 17 | public function __construct( 18 | protected LocalMachineHelper $localMachineHelper, 19 | AcquiaCliConfig $configDefinition, 20 | string $acliConfigFilepath 21 | ) { 22 | $filePath = $localMachineHelper->getLocalFilepath($acliConfigFilepath); 23 | parent::__construct($filePath, $configDefinition); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/DataStore/CloudDataStore.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected array $config; 16 | 17 | public function __construct( 18 | protected LocalMachineHelper $localMachineHelper, 19 | CloudDataConfig $cloudDataConfig, 20 | string $cloudConfigFilepath 21 | ) { 22 | parent::__construct($cloudConfigFilepath, $cloudDataConfig); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/DataStore/DataStoreInterface.php: -------------------------------------------------------------------------------- 1 | fileSystem = new Filesystem(); 30 | $this->filepath = $path; 31 | $this->expander = new Expander(); 32 | $this->expander->setStringifier(new Stringifier()); 33 | $this->data = new Data(); 34 | } 35 | 36 | public function set(string $key, mixed $value): void 37 | { 38 | $this->data->set($key, $value); 39 | $this->dump(); 40 | } 41 | 42 | public function get(string $key): mixed 43 | { 44 | try { 45 | return $this->data->get($key); 46 | } catch (MissingPathException) { 47 | return null; 48 | } 49 | } 50 | 51 | public function remove(string $key): void 52 | { 53 | $this->data->remove($key); 54 | $this->dump(); 55 | } 56 | 57 | public function exists(string $key): bool 58 | { 59 | return $this->data->has($key); 60 | } 61 | 62 | /** 63 | * @param string $path Path to the datastore on disk. 64 | * @return array 65 | */ 66 | protected function processConfig(array $config, ConfigurationInterface $definition, string $path): array 67 | { 68 | try { 69 | return (new Processor())->processConfiguration( 70 | $definition, 71 | [$definition->getName() => $config], 72 | ); 73 | } catch (InvalidConfigurationException $e) { 74 | throw new AcquiaCliException( 75 | 'Configuration file at the following path contains invalid keys: {path} {error}', 76 | ['path' => $path, 'error' => $e->getMessage()] 77 | ); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/DataStore/JsonDataStore.php: -------------------------------------------------------------------------------- 1 | fileSystem->exists($path)) { 18 | $array = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR); 19 | $array = $this->expander->expandArrayProperties($array); 20 | $cleaned = $this->cleanLegacyConfig($array); 21 | 22 | if ($configDefinition) { 23 | $array = $this->processConfig($array, $configDefinition, $path); 24 | } 25 | $this->data->import($array); 26 | 27 | // Dump the new values to disk. 28 | if ($cleaned) { 29 | $this->dump(); 30 | } 31 | } 32 | } 33 | 34 | public function dump(): void 35 | { 36 | $this->fileSystem->dumpFile($this->filepath, json_encode($this->data->export(), JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT)); 37 | } 38 | 39 | protected function cleanLegacyConfig(array &$array): bool 40 | { 41 | // Legacy format of credential storage. 42 | $dump = false; 43 | if (array_key_exists('key', $array) || array_key_exists('secret', $array)) { 44 | unset($array['key'], $array['secret']); 45 | $dump = true; 46 | } 47 | return $dump; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/DataStore/YamlStore.php: -------------------------------------------------------------------------------- 1 | fileSystem->exists($path)) { 19 | $array = Yaml::parseFile($path); 20 | $array = $this->expander->expandArrayProperties($array); 21 | if ($configDefinition) { 22 | $array = $this->processConfig($array, $configDefinition, $path); 23 | } 24 | $this->data->import($array); 25 | } 26 | } 27 | 28 | public function dump(): void 29 | { 30 | $this->fileSystem->dumpFile($this->filepath, Yaml::dump($this->data->export())); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Exception/AcquiaCliException.php: -------------------------------------------------------------------------------- 1 | $code, 25 | 'message' => $rawMessage, 26 | ]; 27 | Amplitude::getInstance() 28 | ->queueEvent('Threw exception', $eventProperties); 29 | 30 | parent::__construct($this->interpolateString($rawMessage, $replacements), $code); 31 | } 32 | 33 | /** 34 | * Returns the replacements context array. 35 | * 36 | * @return string $this->replacements 37 | */ 38 | public function getRawMessage(): string 39 | { 40 | return $this->rawMessage; 41 | } 42 | 43 | /** 44 | * Replace the variables into the message string. 45 | * 46 | * @param string $message The raw, uninterpolated message string. 47 | * @param array $replacements The values to replace into the message. 48 | */ 49 | protected function interpolateString(string $message, array $replacements): string 50 | { 51 | $tr = []; 52 | foreach ($replacements as $key => $val) { 53 | $tr['{' . $key . '}'] = $val; 54 | } 55 | 56 | return strtr($message, $tr); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Helpers/AliasCache.php: -------------------------------------------------------------------------------- 1 | localMachineHelper->readFile($this->getIdePhpVersionFilePath())); 17 | } catch (FilesystemException) { 18 | return null; 19 | } 20 | } 21 | 22 | public function setPhpVersionFilePath(string $path): void 23 | { 24 | $this->phpVersionFilePath = $path; 25 | } 26 | 27 | protected function getIdePhpVersionFilePath(): string 28 | { 29 | if (!isset($this->phpVersionFilePath)) { 30 | $this->phpVersionFilePath = '/home/ide/configs/php/.version'; 31 | } 32 | return $this->phpVersionFilePath; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Helpers/LoopHelper.php: -------------------------------------------------------------------------------- 1 | setMessage($spinnerMessage); 23 | $spinner->start(); 24 | 25 | $cancelTimers = static function () use (&$timers, $spinner): void { 26 | // @infection-ignore-all 27 | array_map('\React\EventLoop\Loop::cancelTimer', $timers); 28 | $timers = []; 29 | $spinner->finish(); 30 | }; 31 | $periodicCallback = static function () use ($statusCallback, $doneCallback, $cancelTimers): void { 32 | // @infection-ignore-all 33 | if ($statusCallback()) { 34 | $cancelTimers(); 35 | $doneCallback(); 36 | } 37 | }; 38 | 39 | // Spinner timer. 40 | $timers[] = Loop::addPeriodicTimer( 41 | $spinner->interval(), 42 | static function () use ($spinner): void { 43 | $spinner->advance(); 44 | } 45 | ); 46 | 47 | // Primary timer checking for result status. 48 | $timers[] = Loop::addPeriodicTimer(5, $periodicCallback); 49 | // Initial timer to speed up tests. 50 | $timers[] = Loop::addTimer(0.1, $periodicCallback); 51 | 52 | // Watchdog timer. 53 | $timers[] = Loop::addTimer(45 * 60, static function () use ($io, $doneCallback, $cancelTimers): void { 54 | $cancelTimers(); 55 | $io->error("Timed out after 45 minutes!"); 56 | $doneCallback(); 57 | }); 58 | 59 | // Manually run the loop. React EventLoop advises against this and suggests 60 | // using autorun instead, but I'm not sure how to pass the correct exit code 61 | // to Symfony if this isn't blocking. 62 | Loop::run(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/fixtures/drupal7/drush_to_extensions_test_file_format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e 4 | 5 | jq_filter='to_entries | [.[] | { 6 | name: .key, 7 | humanName: (.value.name | capture("(?.+)(? \\(.*\\))") | .humanName), 8 | type: (if .value.type == "Module" then "module" else "theme" end), 9 | enabled: (.value.status == "Enabled"), 10 | version: .value.version, 11 | }] | sort_by(.name)' 12 | 13 | main () { 14 | which jq >/dev/null || jq_not_installed=true 15 | if test $jq_not_installed; then 16 | echo "You must have jq installed and available in your \$PATH. See https://stedolan.github.io/jq/." 17 | fi 18 | jq "$jq_filter" < /dev/stdin 19 | } 20 | 21 | main $@ 22 | -------------------------------------------------------------------------------- /tests/fixtures/drush-aliases/.acquia/cloudapi.conf: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /tests/fixtures/drush-aliases/README-acquiacloud.txt: -------------------------------------------------------------------------------- 1 | This folder contains two hidden folders named .acquia and .drush. Both of these folders should be placed in the $HOME directory on your system. The .acquia folder contains your Cloud Platform API credentials and the .drush folder contains your Drush aliases for the sites you currently have access to on the Cloud Platform. 2 | 3 | Refer to "About Drush on the Cloud Platform" (https://docs.acquia.com/acquia-cloud/manage/ssh/drush/) for more information." 4 | -------------------------------------------------------------------------------- /tests/fixtures/drush-aliases/sites/devcloud2.site.yml: -------------------------------------------------------------------------------- 1 | # Application 'eemgrasmick', environment 'prod'. 2 | prod: 3 | root: /var/www/html/eemgrasmick.prod/docroot 4 | ac-site: eemgrasmick 5 | ac-env: prod 6 | ac-realm: prod 7 | uri: eemgrasmick.prod.acquia-sites.com 8 | prod.livedev: 9 | parent: '@eemgrasmick.prod' 10 | root: /mnt/gfs/eemgrasmick.prod/livedev/docroot 11 | host: eemgrasmick.ssh.prod.acquia-sites.com 12 | user: eemgrasmick.prod 13 | paths: 14 | drush-script: drush9 15 | -------------------------------------------------------------------------------- /tests/fixtures/git_config: -------------------------------------------------------------------------------- 1 | [core] 2 | repositoryformatversion = 0 3 | filemode = true 4 | bare = false 5 | logallrefupdates = true 6 | ignorecase = true 7 | precomposeunicode = true 8 | [branch "master"] 9 | [remote "acquia"] 10 | url = site@svn-3.hosted.acquia-sites.com:site.git 11 | fetch = +refs/heads/*:refs/remotes/acquia/* 12 | [branch "develop"] 13 | remote = acquia 14 | merge = refs/heads/develop 15 | -------------------------------------------------------------------------------- /tests/fixtures/multisite-config.json: -------------------------------------------------------------------------------- 1 | {"cloud":{"site":"profserv2","env":"01dev"},"memcache_inc":"profiles\/gardens\/modules\/acquia\/memcache\/memcache.inc", 2 | "sites": { 3 | "oracletest1.dev-profserv2.acsitefactory.com": {"name":"jxr5000596dev","flags":{"preferred_domain":true},"conf":{"gardens_site_id":5000596,"gardens_db_name":"jxr5000596dev","acsf_site_id":5000596,"acsf_db_name":"jxr5000596dev"}}, 4 | "oracletest3.dev-profserv2.acsitefactory.com": {"name":"jxr5000606dev","flags":{"preferred_domain":true},"conf":{"gardens_site_id":5000606,"gardens_db_name":"jxr5000606dev","acsf_site_id":5000606,"acsf_db_name":"jxr5000606dev"}}, 5 | "oracletest4.dev-profserv2.acsitefactory.com": {"name":"jxr5000611dev","flags":{"preferred_domain":true},"conf":{"gardens_site_id":5000611,"gardens_db_name":"jxr5000611dev","acsf_site_id":5000611,"acsf_db_name":"jxr5000611dev"}}, 6 | "jeff1.dev-profserv2.acsitefactory.com": {"name":"jxr5000616dev","flags":{"preferred_domain":true},"conf":{"gardens_site_id":5000616,"gardens_db_name":"jxr5000616dev","acsf_site_id":5000616,"acsf_db_name":"jxr5000616dev"}}, 7 | "stephen.dev-profserv2.acsitefactory.com": {"name":"jxr5000621dev","flags":{"preferred_domain":true},"conf":{"gardens_site_id":5000621,"gardens_db_name":"jxr5000621dev","acsf_site_id":5000621,"acsf_db_name":"jxr5000621dev"}}, 8 | "oracletest5.dev-profserv2.acsitefactory.com": {"name":"jxr5000626dev","flags":{"preferred_domain":true},"conf":{"gardens_site_id":5000626,"gardens_db_name":"jxr5000626dev","acsf_site_id":5000626,"acsf_db_name":"jxr5000626dev"}}, 9 | "oracletest2.dev-profserv2.acsitefactory.com": {"name":"jxr5000631dev","flags":{"preferred_domain":true},"conf":{"gardens_site_id":5000631,"gardens_db_name":"jxr5000631dev","acsf_site_id":5000631,"acsf_db_name":"jxr5000631dev"}} 10 | }} 11 | -------------------------------------------------------------------------------- /tests/fixtures/no-multisite-config.json: -------------------------------------------------------------------------------- 1 | {"cloud":{"site":"profserv2","env":"01dev"},"memcache_inc":"profiles\/gardens\/modules\/acquia\/memcache\/memcache.inc", 2 | "sites": { 3 | }} 4 | -------------------------------------------------------------------------------- /tests/fixtures/test.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acquia/cli/29236b4ec1b1e81e16fb19373e9641556dfdf76d/tests/fixtures/test.phar -------------------------------------------------------------------------------- /tests/fixtures/xdebug.ini: -------------------------------------------------------------------------------- 1 | ; Acquia Hosting XDebug defaults 2 | ; This file configures the default settings for xdebug. 3 | [xdebug] 4 | ;zend_extension=xdebug.so 5 | xdebug.remote_enable=1 6 | xdebug.remote_autostart=1 7 | xdebug.remote_port=9001 8 | xdebug.auto_trace=Off 9 | xdebug.default_enable=Off 10 | xdebug.max_nesting_level=2000 11 | xdebug.profiler_enable=Off 12 | -------------------------------------------------------------------------------- /tests/integration/testcases/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acquia/cli/29236b4ec1b1e81e16fb19373e9641556dfdf76d/tests/integration/testcases/__init__.py -------------------------------------------------------------------------------- /tests/phpunit/src/AcsfApi/AcsfServiceTest.php: -------------------------------------------------------------------------------- 1 | cloudCredentials = new AcsfCredentials($this->datastoreCloud); 19 | } 20 | 21 | /** 22 | * @return array 23 | */ 24 | public static function providerTestIsMachineAuthenticated(): array 25 | { 26 | return [ 27 | [ 28 | ['ACSF_USERNAME' => 'key', 'ACSF_KEY' => 'secret'], 29 | true, 30 | ], 31 | [ 32 | ['ACSF_USERNAME' => 'key', 'ACSF_KEY' => 'secret'], 33 | true, 34 | ], 35 | [ 36 | ['ACSF_USERNAME' => null, 'ACSF_KEY' => null], 37 | false, 38 | ], 39 | [ 40 | ['ACSF_USERNAME' => 'key', 'ACSF_KEY' => null], 41 | false, 42 | ], 43 | ]; 44 | } 45 | 46 | /** 47 | * @dataProvider providerTestIsMachineAuthenticated 48 | */ 49 | public function testIsMachineAuthenticated(array $envVars, bool $isAuthenticated): void 50 | { 51 | self::setEnvVars($envVars); 52 | $clientService = new AcsfClientService(new AcsfConnectorFactory([ 53 | 'key' => null, 54 | 'secret' => null, 55 | ]), $this->prophet->prophesize(Application::class) 56 | ->reveal(), $this->cloudCredentials); 57 | $this->assertEquals($isAuthenticated, $clientService->isMachineAuthenticated()); 58 | self::unsetEnvVars($envVars); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/phpunit/src/AcsfApi/EnvVarAcsfAuthenticationTest.php: -------------------------------------------------------------------------------- 1 | cloudCredentials = new AcsfCredentials($this->datastoreCloud); 18 | putenv('ACSF_USERNAME=' . self::$key); 19 | putenv('ACSF_KEY=' . self::$secret); 20 | putenv('ACSF_FACTORY_URI=' . self::$acsfCurrentFactoryUrl); 21 | } 22 | 23 | protected function tearDown(): void 24 | { 25 | parent::tearDown(); 26 | putenv('ACSF_USERNAME'); 27 | putenv('ACSF_KEY'); 28 | } 29 | 30 | public function testKeyAndSecret(): void 31 | { 32 | $this->removeMockCloudConfigFile(); 33 | self::assertEquals(self::$key, $this->cloudCredentials->getCloudKey()); 34 | self::assertEquals(self::$secret, $this->cloudCredentials->getCloudSecret()); 35 | self::assertEquals(self::$acsfCurrentFactoryUrl, $this->cloudCredentials->getBaseUri()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/phpunit/src/Application/ExceptionApplicationTest.php: -------------------------------------------------------------------------------- 1 | setInput([ 26 | 'applicationUuid' => '2ed281d4-9dec-4cc3-ac63-691c3ba002c2', 27 | 'command' => 'aliases', 28 | ]); 29 | $this->mockUnauthorizedRequest(); 30 | $buffer = $this->runApp(); 31 | // This is sensitive to the display width of the test environment, so that's fun. 32 | self::assertStringContainsString('Your Cloud Platform API credentials are invalid.', $buffer); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/phpunit/src/Application/HelpApplicationTest.php: -------------------------------------------------------------------------------- 1 | setInput([ 24 | 'command' => 'help', 25 | 'command_name' => 'app:link', 26 | ]); 27 | $buffer = $this->runApp(); 28 | $this->assertStringContainsString('The Cloud Platform application UUID or alias (i.e. an application name optionally prefixed with the realm)', $buffer); 29 | $this->assertStringContainsString('Usage: 30 | app:link [] 31 | link 32 | app:link [] 33 | app:link myapp 34 | app:link prod:myapp 35 | app:link abcd1234-1111-2222-3333-0e02b2c3d470', $buffer); 36 | } 37 | 38 | /** 39 | * @group serial 40 | */ 41 | public function testEnvironmentAliasHelp(): void 42 | { 43 | $this->setInput([ 44 | 'command' => 'help', 45 | 'command_name' => 'log:tail', 46 | ]); 47 | $buffer = $this->runApp(); 48 | $this->assertStringContainsString('The Cloud Platform environment ID or alias (i.e. an application and environment name optionally prefixed with the realm)', $buffer); 49 | $this->assertStringContainsString('Usage: 50 | app:log:tail [] 51 | tail 52 | log:tail 53 | app:log:tail [] 54 | app:log:tail myapp.dev 55 | app:log:tail prod:myapp.dev 56 | app:log:tail 12345-abcd1234-1111-2222-3333-0e02b2c3d470', $buffer); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/phpunit/src/ApplicationTestBase.php: -------------------------------------------------------------------------------- 1 | kernel = new Kernel('dev', false); 27 | $this->kernel->boot(); 28 | $this->kernel->getContainer() 29 | ->set(CloudDataStore::class, $this->datastoreCloud); 30 | $this->kernel->getContainer() 31 | ->set(ClientService::class, $this->clientServiceProphecy->reveal()); 32 | $output = new BufferedOutput(); 33 | $this->kernel->getContainer()->set(OutputInterface::class, $output); 34 | } 35 | 36 | protected function runApp(): string 37 | { 38 | putenv("ACLI_REPO_ROOT=" . $this->projectDir); 39 | putenv("ACLI_VERSION=" . 'dev-unknown'); 40 | $input = $this->kernel->getContainer()->get(InputInterface::class); 41 | $output = $this->kernel->getContainer()->get(OutputInterface::class); 42 | /** @var Application $application */ 43 | $application = $this->kernel->getContainer()->get(Application::class); 44 | $application->setAutoExit(false); 45 | $application->run($input, $output); 46 | return $output->fetch(); 47 | } 48 | 49 | protected function setInput(array $args): void 50 | { 51 | // ArrayInput requires command to be the first parameter. 52 | if (array_key_exists('command', $args)) { 53 | $newArgs = []; 54 | $newArgs['command'] = $args['command']; 55 | unset($args['command']); 56 | $args = array_merge($newArgs, $args); 57 | } 58 | $input = new ArrayInput($args); 59 | $input->setInteractive(false); 60 | $this->kernel->getContainer()->set(InputInterface::class, $input); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/phpunit/src/CloudApi/ClientServiceTest.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public static function providerTestIsMachineAuthenticated(): array 19 | { 20 | return [ 21 | [ 22 | [ 23 | 'ACLI_ACCESS_TOKEN' => 'token', 24 | 'ACLI_KEY' => 'key', 25 | 'ACLI_SECRET' => 'secret', 26 | ], 27 | true, 28 | ], 29 | [ 30 | [ 31 | 'ACLI_ACCESS_TOKEN' => null, 32 | 'ACLI_KEY' => 'key', 33 | 'ACLI_SECRET' => 'secret', 34 | ], 35 | true, 36 | ], 37 | [ 38 | [ 39 | 'ACLI_ACCESS_TOKEN' => null, 40 | 'ACLI_KEY' => null, 41 | 'ACLI_SECRET' => null, 42 | ], 43 | false, 44 | ], 45 | [ 46 | [ 47 | 'ACLI_ACCESS_TOKEN' => null, 48 | 'ACLI_KEY' => 'key', 49 | 'ACLI_SECRET' => null, 50 | ], 51 | false, 52 | ], 53 | ]; 54 | } 55 | 56 | /** 57 | * @dataProvider providerTestIsMachineAuthenticated 58 | */ 59 | public function testIsMachineAuthenticated(array $envVars, bool $isAuthenticated): void 60 | { 61 | self::setEnvVars($envVars); 62 | $cloudDatastore = $this->prophet->prophesize(CloudDataStore::class); 63 | $clientService = new ClientService(new ConnectorFactory([ 64 | 'accessToken' => null, 65 | 'key' => null, 66 | 'secret' => null, 67 | ]), $this->application, new CloudCredentials($cloudDatastore->reveal())); 68 | $this->assertEquals($isAuthenticated, $clientService->isMachineAuthenticated()); 69 | self::unsetEnvVars($envVars); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/phpunit/src/CloudApi/EnvVarAuthenticationTest.php: -------------------------------------------------------------------------------- 1 | cloudApiBaseUri); 19 | } 20 | 21 | protected function tearDown(): void 22 | { 23 | parent::tearDown(); 24 | putenv('ACLI_KEY'); 25 | putenv('ACLI_SECRET'); 26 | } 27 | 28 | public function testKeyAndSecret(): void 29 | { 30 | $this->removeMockCloudConfigFile(); 31 | self::assertEquals(self::$key, $this->cloudCredentials->getCloudKey()); 32 | self::assertEquals(self::$secret, $this->cloudCredentials->getCloudSecret()); 33 | self::assertEquals($this->cloudApiBaseUri, $this->cloudCredentials->getBaseUri()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Acsf/AcsfCommandTestBase.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | protected function getApiCommands(): array 35 | { 36 | $apiCommandHelper = new ApiCommandHelper($this->logger); 37 | $commandFactory = $this->getCommandFactory(); 38 | return $apiCommandHelper->getApiCommands(self::$apiSpecFixtureFilePath, $this->apiCommandPrefix, $commandFactory); 39 | } 40 | 41 | /** 42 | * @return array 43 | */ 44 | protected static function getAcsfCredentialsFileContents(): array 45 | { 46 | return [ 47 | 'acsf_active_factory' => self::$acsfCurrentFactoryUrl, 48 | 'acsf_factories' => [ 49 | self::$acsfCurrentFactoryUrl => [ 50 | 'active_user' => self::$acsfActiveUser, 51 | 'url' => self::$acsfCurrentFactoryUrl, 52 | 'users' => [ 53 | self::$acsfUsername => [ 54 | 'key' => self::$acsfKey, 55 | 'username' => self::$acsfUsername, 56 | ], 57 | ], 58 | ], 59 | ], 60 | DataStoreContract::SEND_TELEMETRY => false, 61 | ]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Acsf/AcsfListCommandTest.php: -------------------------------------------------------------------------------- 1 | application->addCommands($this->getApiCommands()); 21 | } 22 | 23 | protected function createCommand(): CommandBase 24 | { 25 | return $this->injectCommand(AcsfListCommand::class); 26 | } 27 | 28 | /** 29 | * @throws \Exception 30 | */ 31 | public function testAcsfListCommand(): void 32 | { 33 | $this->executeCommand(); 34 | $output = $this->getDisplay(); 35 | $this->assertStringContainsString('acsf:api', $output); 36 | $this->assertStringContainsString('acsf:api:ping', $output); 37 | $this->assertStringContainsString('acsf:info:audit-events-find', $output); 38 | } 39 | 40 | /** 41 | * @throws \Exception 42 | */ 43 | public function testApiNamespaceListCommand(): void 44 | { 45 | $this->command = $this->injectCommand(AcsfListCommandBase::class); 46 | $name = 'acsf:api'; 47 | $this->command->setName($name); 48 | $this->command->setNamespace($name); 49 | $this->executeCommand(); 50 | $output = $this->getDisplay(); 51 | $this->assertStringContainsString('acsf:api:ping', $output); 52 | $this->assertStringNotContainsString('acsf:groups', $output); 53 | } 54 | 55 | /** 56 | * @throws \Exception 57 | */ 58 | public function testListCommand(): void 59 | { 60 | $this->command = $this->injectCommand(ListCommand::class); 61 | $this->executeCommand(); 62 | $output = $this->getDisplay(); 63 | $this->assertStringContainsString('acsf:api', $output); 64 | $this->assertStringNotContainsString('acsf:api:ping', $output); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Api/ApiBaseCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(ApiBaseCommand::class); 17 | } 18 | 19 | public function testApiBaseCommand(): void 20 | { 21 | $this->expectException(AcquiaCliException::class); 22 | $this->expectExceptionMessage('api:base is not a valid command'); 23 | $this->executeCommand(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Api/ApiListCommandTest.php: -------------------------------------------------------------------------------- 1 | application->addCommands($this->getApiCommands()); 22 | } 23 | 24 | protected function createCommand(): CommandBase 25 | { 26 | return $this->injectCommand(ApiListCommand::class); 27 | } 28 | 29 | public function testApiListCommand(): void 30 | { 31 | $this->executeCommand(); 32 | $output = $this->getDisplay(); 33 | $this->assertStringContainsString(' api:accounts:ssh-keys-list', $output); 34 | } 35 | 36 | public function testApiNamespaceListCommand(): void 37 | { 38 | $this->command = $this->injectCommand(ApiListCommandBase::class); 39 | $name = 'api:accounts'; 40 | $this->command->setName($name); 41 | $this->command->setNamespace($name); 42 | $this->executeCommand(); 43 | $output = $this->getDisplay(); 44 | $this->assertStringContainsString('api:accounts:', $output); 45 | $this->assertStringContainsString('api:accounts:ssh-keys-list', $output); 46 | $this->assertStringNotContainsString('api:subscriptions', $output); 47 | } 48 | 49 | public function testListCommand(): void 50 | { 51 | $this->command = $this->injectCommand(ListCommand::class); 52 | $this->executeCommand(); 53 | $output = $this->getDisplay(); 54 | $this->assertStringContainsString(' api:accounts', $output); 55 | $this->assertStringNotContainsString(' api:accounts:ssh-keys-list', $output); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/App/AppOpenCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(AppOpenCommand::class); 20 | } 21 | 22 | public function testAppOpenCommand(): void 23 | { 24 | $applicationUuid = 'a47ac10b-58cc-4372-a567-0e02b2c3d470'; 25 | $localMachineHelper = $this->mockLocalMachineHelper(); 26 | $localMachineHelper->startBrowser('https://cloud.acquia.com/a/applications/' . $applicationUuid) 27 | ->shouldBeCalled(); 28 | $localMachineHelper->isBrowserAvailable()->willReturn(true); 29 | $this->mockRequest('getApplicationByUuid', $applicationUuid); 30 | $this->executeCommand(['applicationUuid' => $applicationUuid]); 31 | } 32 | 33 | public function testAppOpenNoBrowser(): void 34 | { 35 | $localMachineHelper = $this->mockLocalMachineHelper(); 36 | $localMachineHelper->isBrowserAvailable()->willReturn(false); 37 | 38 | $this->expectException(AcquiaCliException::class); 39 | $this->expectExceptionMessage('No browser is available on this machine'); 40 | $this->executeCommand(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/App/From/ConfigurationTest.php: -------------------------------------------------------------------------------- 1 | expectExceptionObject($expected_exception); 31 | Configuration::createFromResource($test_stream); 32 | } 33 | 34 | /** 35 | * @return array 36 | */ 37 | public static function getTestConfigurations(): array 38 | { 39 | return [ 40 | 'bad JSON in configuration file' => [ 41 | '{,}', 42 | new JsonException('Syntax error', JSON_ERROR_SYNTAX), 43 | ], 44 | 'empty configuration file' => [ 45 | json_encode((object) []), 46 | new DomainException('Missing required key: rootPackageDefinition'), 47 | ], 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/App/From/TestRecommendation.php: -------------------------------------------------------------------------------- 1 | packageName = $package_name ?: self::ABANDON; 29 | } 30 | 31 | public function applies(ExtensionInterface $extension): bool 32 | { 33 | return $this->shouldApply; 34 | } 35 | 36 | public function getPackageName(): string 37 | { 38 | return $this->packageName; 39 | } 40 | 41 | public function getVersionConstraint(): string 42 | { 43 | return $this->versionConstraint; 44 | } 45 | 46 | public function hasModulesToInstall(): bool 47 | { 48 | return !empty($this->install); 49 | } 50 | 51 | /** 52 | * {@inheritDoc} 53 | */ 54 | public function getModulesToInstall(): array 55 | { 56 | return $this->install; 57 | } 58 | 59 | public function isVetted(): bool 60 | { 61 | return $this->vetted; 62 | } 63 | 64 | public function hasPatches(): bool 65 | { 66 | return !empty($this->patches); 67 | } 68 | 69 | /** 70 | * {@inheritDoc} 71 | */ 72 | public function getPatches(): array 73 | { 74 | return $this->patches; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/App/From/TestSiteInspector.php: -------------------------------------------------------------------------------- 1 | extensions; 36 | } 37 | 38 | public function getPublicFilePath(): string 39 | { 40 | return $this->filePublicPath; 41 | } 42 | 43 | public function getPrivateFilePath(): ?string 44 | { 45 | return $this->filePrivatePath; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/App/UnlinkCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(UnlinkCommand::class); 20 | } 21 | 22 | public function testUnlinkCommand(): void 23 | { 24 | $applicationsResponse = self::getMockResponseFromSpec( 25 | '/applications', 26 | 'get', 27 | '200' 28 | ); 29 | $cloudApplication = $applicationsResponse->{'_embedded'}->items[0]; 30 | $cloudApplicationUuid = $cloudApplication->uuid; 31 | $this->createMockAcliConfigFile($cloudApplicationUuid); 32 | $this->createDataStores(); 33 | $this->command = $this->injectCommand(UnlinkCommand::class); 34 | $this->mockApplicationRequest(); 35 | 36 | // Assert we set it correctly. 37 | $this->assertEquals($applicationsResponse->{'_embedded'}->items[0]->uuid, $this->datastoreAcli->get('cloud_app_uuid')); 38 | 39 | $this->executeCommand(); 40 | $output = $this->getDisplay(); 41 | 42 | // Assert it's been unset. 43 | $this->assertNull($this->datastoreAcli->get('cloud_app_uuid')); 44 | $this->assertStringContainsString("Unlinked $this->projectDir from Cloud application " . $cloudApplication->name, $output); 45 | } 46 | 47 | public function testUnlinkCommandInvalidDir(): void 48 | { 49 | $this->expectException(AcquiaCliException::class); 50 | $this->expectExceptionMessage('There is no Cloud Platform application linked to ' . $this->projectDir); 51 | $this->executeCommand(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Auth/AuthLogoutCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(AuthLogoutCommand::class); 20 | } 21 | 22 | public function testAuthLogoutCommand(): void 23 | { 24 | $this->executeCommand(); 25 | $output = $this->getDisplay(); 26 | $this->assertFileExists($this->cloudConfigFilepath); 27 | $this->assertStringContainsString('The key Test Key will be deactivated on this machine.', $output); 28 | $this->assertStringContainsString('Do you want to delete the active Cloud Platform API credentials (option --delete)? (yes/no) [no]:', $output); 29 | $this->assertStringContainsString('The active Cloud Platform API credentials were deactivated', $output); 30 | } 31 | 32 | public function testAuthLogoutInvalidDatastore(): void 33 | { 34 | $this->clientServiceProphecy->isMachineAuthenticated() 35 | ->willReturn(false); 36 | $this->removeMockCloudConfigFile(); 37 | $data = [ 38 | 'acli_key' => 'key2', 39 | 'keys' => [ 40 | 'key1' => [ 41 | 'label' => 'foo', 42 | 'secret' => 'foo', 43 | 'uuid' => 'foo', 44 | ], 45 | ], 46 | ]; 47 | $this->fs->dumpFile($this->cloudConfigFilepath, json_encode($data)); 48 | $this->expectException(AcquiaCliException::class); 49 | $this->expectExceptionMessage("Configuration file at the following path contains invalid keys: $this->cloudConfigFilepath Invalid configuration for path \"cloud_api\": acli_key must exist in keys"); 50 | $this->createDataStores(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/CodeStudio/CodeStudioCiCdVariablesTest.php: -------------------------------------------------------------------------------- 1 | getDefaultsForNode(); 16 | $this->testBooleanValues($variables); 17 | $variables = $codeStudioCiCdVariablesObj->getDefaultsForPhp(); 18 | $this->testBooleanValues($variables); 19 | } 20 | 21 | protected function testBooleanValues(array $variables): void 22 | { 23 | foreach ($variables as $variable) { 24 | if ($variable['key'] !== "MYSQL_VERSION" && $variable['key'] !== "PHP_VERSION" && $variable['key'] !== "NODE_VERSION" && $variable['key'] !== "NODE_HOSTING_TYPE") { 25 | $maskedValue = $variable['masked']; 26 | $this->assertEquals(true, $maskedValue); 27 | } else { 28 | $maskedValue = $variable['masked']; 29 | $this->assertEquals(false, $maskedValue); 30 | } 31 | $protectedValue = $variable['protected']; 32 | $this->assertEquals(false, $protectedValue); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Ide/IdeHelper.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | public static function getEnvVars(): array 29 | { 30 | return [ 31 | 'ACQUIA_USER_UUID' => '4acf8956-45df-3cf4-5106-065b62cf1ac8', 32 | 'AH_SITE_ENVIRONMENT' => 'IDE', 33 | 'REMOTEIDE_LABEL' => self::$remoteIdeLabel, 34 | 'REMOTEIDE_UUID' => self::$remoteIdeUuid, 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Ide/IdeInfoCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(IdeInfoCommand::class); 19 | } 20 | 21 | /** 22 | * @group brokenProphecy 23 | */ 24 | public function testIdeInfoCommand(): void 25 | { 26 | $applications = $this->mockRequest('getApplications'); 27 | $this->mockRequest('getApplicationByUuid', $applications[0]->uuid); 28 | $ides = $this->mockRequest('getApplicationIdes', $applications[0]->uuid); 29 | $this->mockRequest('getIde', $ides[0]->uuid); 30 | $inputs = [ 31 | // Would you like Acquia CLI to search for a Cloud application that matches your local git config? 32 | 'n', 33 | // Select the application. 34 | 0, 35 | // Would you like to link the project at ... ? 36 | 'y', 37 | // Select an IDE ... 38 | 0, 39 | ]; 40 | $this->executeCommand([], $inputs); 41 | 42 | // Assert. 43 | $output = $this->getDisplay(); 44 | $this->assertStringContainsString('Select a Cloud Platform application:', $output); 45 | $this->assertStringContainsString('[0] Sample application 1', $output); 46 | $this->assertStringContainsString('[1] Sample application 2', $output); 47 | $this->assertStringContainsString('IDE property IDE value', $output); 48 | $this->assertStringContainsString('UUID 215824ff-272a-4a8c-9027-df32ed1d68a9', $output); 49 | $this->assertStringContainsString('Label Example IDE', $output); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Ide/IdeOpenCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(IdeOpenCommand::class); 19 | } 20 | 21 | /** 22 | * @group brokenProphecy 23 | */ 24 | public function testIdeOpenCommand(): void 25 | { 26 | $applications = $this->mockRequest('getApplications'); 27 | $this->mockRequest('getApplicationByUuid', $applications[0]->uuid); 28 | $this->mockRequest('getApplicationIdes', $applications[0]->uuid); 29 | $localMachineHelper = $this->mockLocalMachineHelper(); 30 | $localMachineHelper->isBrowserAvailable()->willReturn(true); 31 | $localMachineHelper->startBrowser('https://9a83c081-ef78-4dbd-8852-11cc3eb248f7.ides.acquia.com') 32 | ->willReturn(true); 33 | 34 | $inputs = [ 35 | // Would you like Acquia CLI to search for a Cloud application that matches your local git config? 36 | 'n', 37 | // Select a Cloud Platform application: 38 | 0, 39 | // Would you like to link the project at ... ? 40 | 'y', 41 | // Select the IDE you'd like to open: 42 | 0, 43 | ]; 44 | $this->executeCommand([], $inputs); 45 | 46 | // Assert. 47 | $output = $this->getDisplay(); 48 | $this->assertStringContainsString('Select a Cloud Platform application:', $output); 49 | $this->assertStringContainsString('[0] Sample application 1', $output); 50 | $this->assertStringContainsString('Select the IDE you\'d like to open:', $output); 51 | $this->assertStringContainsString('[0] IDE Label 1', $output); 52 | $this->assertStringContainsString('Your IDE URL: https://9a83c081-ef78-4dbd-8852-11cc3eb248f7.ides.acquia.com', $output); 53 | $this->assertStringContainsString('Your Drupal Site URL: https://9a83c081-ef78-4dbd-8852-11cc3eb248f7.web.ahdev.cloud', $output); 54 | $this->assertStringContainsString('Opening your IDE in browser...', $output); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Ide/IdeRequiredTestTrait.php: -------------------------------------------------------------------------------- 1 | injectCommand(IdeServiceRestartCommand::class); 22 | } 23 | 24 | public function testIdeServiceRestartCommand(): void 25 | { 26 | $localMachineHelper = $this->mockLocalMachineHelper(); 27 | $this->mockRestartPhp($localMachineHelper); 28 | 29 | $this->executeCommand(['service' => 'php'], []); 30 | 31 | // Assert. 32 | $output = $this->getDisplay(); 33 | $this->assertStringContainsString('Restarted php', $output); 34 | } 35 | 36 | /** 37 | * @group brokenProphecy 38 | */ 39 | public function testIdeServiceRestartCommandInvalid(): void 40 | { 41 | $localMachineHelper = $this->mockLocalMachineHelper(); 42 | $this->mockRestartPhp($localMachineHelper); 43 | 44 | $this->expectException(ValidatorException::class); 45 | $this->expectExceptionMessage('Specify a valid service name'); 46 | $this->executeCommand(['service' => 'rambulator'], []); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Ide/IdeServiceStartCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(IdeServiceStartCommand::class); 22 | } 23 | 24 | public function testIdeServiceStartCommand(): void 25 | { 26 | $localMachineHelper = $this->mockLocalMachineHelper(); 27 | $this->mockStartPhp($localMachineHelper); 28 | 29 | $this->executeCommand(['service' => 'php'], []); 30 | 31 | // Assert. 32 | $output = $this->getDisplay(); 33 | $this->assertStringContainsString('Starting php', $output); 34 | } 35 | 36 | /** 37 | * @group brokenProphecy 38 | */ 39 | public function testIdeServiceStartCommandInvalid(): void 40 | { 41 | $localMachineHelper = $this->mockLocalMachineHelper(); 42 | $this->mockStartPhp($localMachineHelper); 43 | 44 | $this->expectException(ValidatorException::class); 45 | $this->expectExceptionMessage('Specify a valid service name'); 46 | $this->executeCommand(['service' => 'rambulator'], []); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Ide/IdeServiceStopCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(IdeServiceStopCommand::class); 22 | } 23 | 24 | public function testIdeServiceStopCommand(): void 25 | { 26 | $localMachineHelper = $this->mockLocalMachineHelper(); 27 | $this->mockStopPhp($localMachineHelper); 28 | 29 | $this->executeCommand(['service' => 'php'], []); 30 | 31 | // Assert. 32 | $output = $this->getDisplay(); 33 | $this->assertStringContainsString('Stopping php', $output); 34 | } 35 | 36 | /** 37 | * @group brokenProphecy 38 | */ 39 | public function testIdeServiceStopCommandInvalid(): void 40 | { 41 | $localMachineHelper = $this->mockLocalMachineHelper(); 42 | $this->mockStopPhp($localMachineHelper); 43 | 44 | $this->expectException(ValidatorException::class); 45 | $this->expectExceptionMessage('Specify a valid service name'); 46 | $this->executeCommand(['service' => 'rambulator'], []); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Ide/IdeShareCommandTest.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | private array $shareCodeFilepaths; 23 | 24 | private string $shareCode; 25 | 26 | /** 27 | * This method is called before each test. 28 | */ 29 | public function setUp(?OutputInterface $output = null): void 30 | { 31 | parent::setUp(); 32 | $this->shareCode = 'a47ac10b-58cc-4372-a567-0e02b2c3d470'; 33 | $shareCodeFilepath = $this->fs->tempnam(sys_get_temp_dir(), 'acli_share_uuid_'); 34 | $this->fs->dumpFile($shareCodeFilepath, $this->shareCode); 35 | $this->command->setShareCodeFilepaths([$shareCodeFilepath]); 36 | IdeHelper::setCloudIdeEnvVars(); 37 | } 38 | 39 | protected function createCommand(): CommandBase 40 | { 41 | return $this->injectCommand(IdeShareCommand::class); 42 | } 43 | 44 | public function testIdeShareCommand(): void 45 | { 46 | $this->executeCommand(); 47 | $output = $this->getDisplay(); 48 | $this->assertStringContainsString('Your IDE Share URL: ', $output); 49 | $this->assertStringContainsString($this->shareCode, $output); 50 | } 51 | 52 | public function testIdeShareRegenerateCommand(): void 53 | { 54 | $this->executeCommand(['--regenerate' => true]); 55 | $output = $this->getDisplay(); 56 | $this->assertStringContainsString('Your IDE Share URL: ', $output); 57 | $this->assertStringNotContainsString($this->shareCode, $output); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Ide/Wizard/IdeWizardCreateSshKeyCommandTest.php: -------------------------------------------------------------------------------- 1 | mockApplicationRequest(); 22 | $this->mockListSshKeysRequest(); 23 | $this->mockRequest('getAccount'); 24 | $this->mockPermissionsRequest($applicationResponse); 25 | $this->sshKeyFileName = IdeWizardCreateSshKeyCommand::getSshKeyFilename(IdeHelper::$remoteIdeUuid); 26 | } 27 | 28 | /** 29 | * @return \Acquia\Cli\Command\Ide\Wizard\IdeWizardCreateSshKeyCommand 30 | */ 31 | protected function createCommand(): CommandBase 32 | { 33 | return $this->injectCommand(IdeWizardCreateSshKeyCommand::class); 34 | } 35 | 36 | public function testCreate(): void 37 | { 38 | $this->runTestCreate(); 39 | } 40 | 41 | /** 42 | * @group brokenProphecy 43 | */ 44 | public function testSshKeyAlreadyUploaded(): void 45 | { 46 | $this->runTestSshKeyAlreadyUploaded(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Ide/Wizard/IdeWizardDeleteSshKeyCommandTest.php: -------------------------------------------------------------------------------- 1 | mockListSshKeysRequestWithIdeKey(IdeHelper::$remoteIdeLabel, IdeHelper::$remoteIdeUuid); 20 | 21 | $this->mockDeleteSshKeyRequest($mockBody->{'_embedded'}->items[0]->uuid); 22 | 23 | // Create the file so it can be deleted. 24 | $sshKeyFilename = $this->command::getSshKeyFilename(IdeHelper::$remoteIdeUuid); 25 | $this->fs->touch($this->sshDir . '/' . $sshKeyFilename); 26 | $this->fs->dumpFile($this->sshDir . '/' . $sshKeyFilename . '.pub', $mockBody->{'_embedded'}->items[0]->public_key); 27 | 28 | // Run it! 29 | $this->executeCommand(); 30 | 31 | $this->assertFileDoesNotExist($this->sshDir . '/' . $sshKeyFilename); 32 | } 33 | 34 | /** 35 | * @return \Acquia\Cli\Command\Ide\Wizard\IdeWizardCreateSshKeyCommand 36 | */ 37 | protected function createCommand(): CommandBase 38 | { 39 | return $this->injectCommand(IdeWizardDeleteSshKeyCommand::class); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Ide/Wizard/IdeWizardTestBase.php: -------------------------------------------------------------------------------- 1 | injectCommand(PullScriptsCommand::class); 19 | } 20 | 21 | public function testRefreshScripts(): void 22 | { 23 | touch(Path::join($this->projectDir, 'composer.json')); 24 | $localMachineHelper = $this->mockLocalMachineHelper(); 25 | $process = $this->mockProcess(); 26 | 27 | // Composer. 28 | $this->mockExecuteComposerExists($localMachineHelper); 29 | $this->mockExecuteComposerInstall($localMachineHelper, $process); 30 | 31 | // Drush. 32 | $this->mockExecuteDrushExists($localMachineHelper); 33 | $this->mockExecuteDrushStatus($localMachineHelper, $this->projectDir); 34 | $this->mockExecuteDrushCacheRebuild($localMachineHelper, $process); 35 | $this->mockExecuteDrushSqlSanitize($localMachineHelper, $process); 36 | 37 | $inputs = [ 38 | // Would you like Acquia CLI to search for a Cloud application that matches your local git config? 39 | 'n', 40 | // Select a Cloud Platform application: 41 | 0, 42 | // Would you like to link the project at ... ? 43 | 'n', 44 | // Choose an Acquia environment: 45 | 0, 46 | ]; 47 | 48 | $this->executeCommand([ 49 | '--dir' => $this->projectDir, 50 | ], $inputs); 51 | 52 | $this->getDisplay(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Push/PushCodeCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(PushCodeCommand::class); 19 | } 20 | 21 | public function testPushCode(): void 22 | { 23 | $this->executeCommand(); 24 | 25 | $output = $this->getDisplay(); 26 | 27 | $this->assertStringContainsString('Use git to push code changes upstream.', $output); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Remote/AliasesListCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(AliasListCommand::class); 19 | } 20 | 21 | /** 22 | * @throws \Exception 23 | */ 24 | public function testRemoteAliasesListCommand(): void 25 | { 26 | $applicationsResponse = $this->mockApplicationsRequest(); 27 | $this->mockApplicationRequest(); 28 | $this->mockEnvironmentsRequest($applicationsResponse); 29 | 30 | $inputs = [ 31 | // Would you like Acquia CLI to search for a Cloud application that matches your local git config? 32 | 'n', 33 | // Select a Cloud Platform application: 34 | '0', 35 | // Would you like to link the project at ... 36 | 'n', 37 | ]; 38 | $this->executeCommand([], $inputs); 39 | 40 | // Assert. 41 | $output = $this->getDisplay(); 42 | 43 | $this->assertStringContainsString('Environments for Sample application 1', $output); 44 | $this->assertStringContainsString('| Alias | UUID | SSH URL |', $output); 45 | $this->assertStringContainsString('| devcloud2.dev | 24-a47ac10b-58cc-4372-a567-0e02b2c3d470 | site.dev@sitedev.ssh.hosted.acquia-sites.com', $output); 46 | $this->assertStringContainsString('Run acli api:environments:find to get more information about a specific environment', $output); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Remote/DrushCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(DrushCommand::class); 20 | } 21 | 22 | /** 23 | * @return array>> 24 | */ 25 | public static function providerTestRemoteDrushCommand(): array 26 | { 27 | return [ 28 | [ 29 | [ 30 | 'drush_command' => 'status --fields=db-status', 31 | ], 32 | ], 33 | [ 34 | [ 35 | 'drush_command' => 'status --fields=db-status', 36 | ], 37 | ], 38 | ]; 39 | } 40 | 41 | /** 42 | * @dataProvider providerTestRemoteDrushCommand 43 | */ 44 | public function testRemoteDrushCommand(array $args): void 45 | { 46 | $this->mockGetEnvironment(); 47 | [$process, $localMachineHelper] = $this->mockForExecuteCommand(); 48 | $localMachineHelper->checkRequiredBinariesExist(['ssh']) 49 | ->shouldBeCalled(); 50 | $sshCommand = [ 51 | 'ssh', 52 | 'site.dev@sitedev.ssh.hosted.acquia-sites.com', 53 | '-t', 54 | '-o StrictHostKeyChecking=no', 55 | '-o AddressFamily inet', 56 | '-o LogLevel=ERROR', 57 | 'cd /var/www/html/site.dev/docroot; ', 58 | 'drush', 59 | '--uri=http://sitedev.hosted.acquia-sites.com status --fields=db-status', 60 | ]; 61 | $localMachineHelper 62 | ->execute($sshCommand, Argument::type('callable'), null, true, null) 63 | ->willReturn($process->reveal()) 64 | ->shouldBeCalled(); 65 | 66 | $this->command->sshHelper = new SshHelper($this->output, $localMachineHelper->reveal(), $this->logger); 67 | $this->executeCommand($args, self::inputChooseEnvironment()); 68 | 69 | // Assert. 70 | $this->getDisplay(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Remote/SshCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(SshCommand::class); 21 | } 22 | 23 | /** 24 | * @group serial 25 | */ 26 | public function testRemoteAliasesDownloadCommand(): void 27 | { 28 | ClearCacheCommand::clearCaches(); 29 | $this->mockForGetEnvironmentFromAliasArg(); 30 | [$process, $localMachineHelper] = $this->mockForExecuteCommand(); 31 | $localMachineHelper->checkRequiredBinariesExist(['ssh']) 32 | ->shouldBeCalled(); 33 | $sshCommand = [ 34 | 'ssh', 35 | 'site.dev@sitedev.ssh.hosted.acquia-sites.com', 36 | '-t', 37 | '-o StrictHostKeyChecking=no', 38 | '-o AddressFamily inet', 39 | '-o LogLevel=ERROR', 40 | 'cd /var/www/html/devcloud2.dev; exec $SHELL -l', 41 | ]; 42 | $localMachineHelper 43 | ->execute($sshCommand, Argument::type('callable'), null, true, null) 44 | ->willReturn($process->reveal()) 45 | ->shouldBeCalled(); 46 | 47 | $this->command->sshHelper = new SshHelper($this->output, $localMachineHelper->reveal(), $this->logger); 48 | 49 | $args = [ 50 | 'alias' => 'devcloud2.dev', 51 | ]; 52 | $this->executeCommand($args); 53 | 54 | // Assert. 55 | $output = $this->getDisplay(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Remote/SshCommandTestBase.php: -------------------------------------------------------------------------------- 1 | mockApplicationsRequest(1); 16 | $this->mockEnvironmentsRequest($applicationsResponse); 17 | $this->clientProphecy->addQuery('filter', 'hosting=@*:devcloud2') 18 | ->shouldBeCalled(); 19 | $this->mockRequest('getAccount'); 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | protected function mockForExecuteCommand(): array 26 | { 27 | $process = $this->prophet->prophesize(Process::class); 28 | $process->isSuccessful()->willReturn(true); 29 | $process->getExitCode()->willReturn(0); 30 | $localMachineHelper = $this->prophet->prophesize(LocalMachineHelper::class); 31 | $localMachineHelper->useTty()->willReturn(false)->shouldBeCalled(); 32 | return [$process, $localMachineHelper]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Self/MakeDocsCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(MakeDocsCommand::class); 20 | } 21 | 22 | public function testMakeDocsCommand(): void 23 | { 24 | $this->executeCommand(); 25 | $output = $this->getDisplay(); 26 | $this->assertStringContainsString('Console Tool', $output); 27 | $this->assertStringContainsString('============', $output); 28 | $this->assertStringContainsString('- `help`_', $output); 29 | } 30 | 31 | public function testMakeDocsCommandDump(): void 32 | { 33 | $vfs = vfsStream::setup('root'); 34 | $this->executeCommand(['--dump' => $vfs->url()]); 35 | $this->assertStringContainsString('The completion command dumps', $vfs->getChild('completion.json')->getContent()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Self/SelfInfoCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(SelfInfoCommand::class); 19 | } 20 | 21 | /** 22 | * @throws \Exception 23 | */ 24 | public function testSelfInfoCommand(): void 25 | { 26 | $this->mockRequest('getAccount'); 27 | $this->executeCommand(); 28 | $output = $this->getDisplay(); 29 | $this->assertStringContainsString('Property', $output); 30 | $this->assertStringContainsString('--------', $output); 31 | $this->assertStringContainsString('Version', $output); 32 | $this->assertStringContainsString('Cloud datastore', $output); 33 | $this->assertStringContainsString('ACLI datastore', $output); 34 | $this->assertStringContainsString('Telemetry enabled', $output); 35 | $this->assertStringContainsString('User ID', $output); 36 | $this->assertStringContainsString('is_acquian', $output); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Self/TelemetryDisableCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(TelemetryDisableCommand::class); 19 | } 20 | 21 | public function testTelemetryDisableCommand(): void 22 | { 23 | $this->executeCommand(); 24 | $output = $this->getDisplay(); 25 | $this->assertStringContainsString('Telemetry has been disabled.', $output); 26 | 27 | $settings = json_decode(file_get_contents($this->cloudConfigFilepath), true); 28 | $this->assertFalse($settings['send_telemetry']); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Self/TelemetryEnableCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(TelemetryEnableCommand::class); 21 | } 22 | 23 | public function testTelemetryEnableCommand(): void 24 | { 25 | $this->executeCommand(); 26 | $output = $this->getDisplay(); 27 | $this->assertStringContainsString('Telemetry has been enabled.', $output); 28 | 29 | $settings = json_decode(file_get_contents($this->cloudConfigFilepath), true); 30 | $this->assertTrue($settings['send_telemetry']); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Ssh/SshKeyInfoCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(SshKeyInfoCommand::class); 16 | } 17 | 18 | public function setUp(): void 19 | { 20 | parent::setUp(); 21 | $this->setupFsFixture(); 22 | $this->command = $this->createCommand(); 23 | } 24 | 25 | public function testInfo(): void 26 | { 27 | $this->mockListSshKeysRequest(); 28 | 29 | $inputs = [ 30 | // Choose key. 31 | '0', 32 | ]; 33 | $this->executeCommand([], $inputs); 34 | 35 | // Assert. 36 | $output = $this->getDisplay(); 37 | $this->assertStringContainsString('Choose an SSH key to view', $output); 38 | $this->assertStringContainsString('SSH key property SSH key value', $output); 39 | $this->assertStringContainsString('UUID 02905393-65d7-4bef-873b-24593f73d273', $output); 40 | $this->assertStringContainsString('Label PC Home', $output); 41 | $this->assertStringContainsString('Fingerprint (md5) 5d:23:fb:45:70:df:ef:ad:ca:bf:81:93:cd:50:26:28', $output); 42 | $this->assertStringContainsString('Created at 2017-05-09T20:30:35.000Z', $output); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/Ssh/SshKeyListCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(SshKeyListCommand::class); 19 | } 20 | 21 | public function setUp(): void 22 | { 23 | parent::setUp(); 24 | $this->setupFsFixture(); 25 | $this->command = $this->createCommand(); 26 | } 27 | 28 | public function testList(): void 29 | { 30 | 31 | $mockBody = self::getMockResponseFromSpec('/account/ssh-keys', 'get', '200'); 32 | $this->clientProphecy->request('get', '/account/ssh-keys') 33 | ->willReturn($mockBody->{'_embedded'}->items) 34 | ->shouldBeCalled(); 35 | $mockRequestArgs = self::getMockRequestBodyFromSpec('/account/ssh-keys'); 36 | $tempFileName = $this->createLocalSshKey($mockRequestArgs['public_key']); 37 | $baseFilename = basename($tempFileName); 38 | $this->executeCommand(); 39 | 40 | // Assert. 41 | $output = $this->getDisplay(); 42 | $this->assertStringContainsString('Local filename', $output); 43 | $this->assertStringContainsString('Cloud Platform label', $output); 44 | $this->assertStringContainsString('Fingerprint', $output); 45 | $this->assertStringContainsString($baseFilename, $output); 46 | $this->assertStringContainsString($mockBody->_embedded->items[0]->label, $output); 47 | $this->assertStringContainsString($mockBody->_embedded->items[1]->label, $output); 48 | $this->assertStringContainsString($mockBody->_embedded->items[2]->label, $output); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/phpunit/src/Commands/UpdateCommandTest.php: -------------------------------------------------------------------------------- 1 | injectCommand(HelloWorldCommand::class); 20 | } 21 | 22 | public function testSelfUpdate(): void 23 | { 24 | $this->application->setVersion($this->startVersion); 25 | $this->mockSelfUpdateCommand(); 26 | $this->executeCommand(); 27 | self::assertEquals(0, $this->getStatusCode()); 28 | self::assertStringContainsString("Acquia CLI $this->endVersion is available", $this->getDisplay()); 29 | } 30 | 31 | public function testBadResponseFailsSilently(): void 32 | { 33 | $this->application->setVersion($this->startVersion); 34 | $this->mockSelfUpdateCommand(true); 35 | $this->executeCommand(); 36 | self::assertEquals(0, $this->getStatusCode()); 37 | self::assertStringNotContainsString("Acquia CLI $this->endVersion is available", $this->getDisplay()); 38 | } 39 | 40 | /** 41 | * @throws \GuzzleHttp\Exception\GuzzleException 42 | */ 43 | private function mockSelfUpdateCommand(bool $exception = false): void 44 | { 45 | $selfUpdateManagerProphecy = $this->prophet->prophesize(SelfUpdateManager::class); 46 | if ($exception) { 47 | $selfUpdateManagerProphecy->isUpToDate()->willThrow(new Exception())->shouldBeCalled(); 48 | } else { 49 | $selfUpdateManagerProphecy->isUpToDate() 50 | ->willReturn(false) 51 | ->shouldBeCalled(); 52 | $selfUpdateManagerProphecy->getLatestReleaseFromGithub() 53 | ->willReturn(['tag_name' => $this->endVersion]) 54 | ->shouldBeCalled(); 55 | } 56 | $this->command->selfUpdateManager = $selfUpdateManagerProphecy->reveal(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/phpunit/src/Misc/ApiSpecTest.php: -------------------------------------------------------------------------------- 1 | assertFileExists($apiSpecFile); 16 | $apiSpec = file_get_contents($apiSpecFile); 17 | $this->assertStringNotContainsString('x-internal', $apiSpec); 18 | $this->assertStringNotContainsString('cloud.acquia.dev', $apiSpec); 19 | $this->assertStringNotContainsString('network.acquia-sites.com', $apiSpec); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/phpunit/src/Misc/ChecklistTest.php: -------------------------------------------------------------------------------- 1 | section() 20 | // method which is only available for ConsoleOutput. Could make a custom testing 21 | // output class with the method. 22 | parent::setUp(); 23 | $this->output = new ConsoleOutput(); 24 | } 25 | 26 | /** 27 | * @group serial 28 | */ 29 | public function testSpinner(): void 30 | { 31 | putenv('PHPUNIT_RUNNING=1'); 32 | $checklist = new Checklist($this->output); 33 | $checklist->addItem('Testing!'); 34 | $items = $checklist->getItems(); 35 | $progressBar = $items[0]['spinner']->getProgressBar(); 36 | $this->assertEquals(' ', $progressBar->getMessage('detail')); 37 | 38 | // Make the spinner spin with some output. 39 | $outputCallback = static function (string $type, string $buffer) use ($checklist): void { 40 | $checklist->updateProgressBar($buffer); 41 | }; 42 | $this->localMachineHelper->execute([ 43 | 'echo', 44 | 'hello world', 45 | ], $outputCallback, null, false); 46 | 47 | // Complete the item. 48 | $checklist->completePreviousItem(); 49 | $items = $checklist->getItems(); 50 | /** @var \Symfony\Component\Console\Helper\ProgressBar $progressBar */ 51 | $progressBar = $items[0]['spinner']->getProgressBar(); 52 | $this->assertEquals('Testing!', $progressBar->getMessage()); 53 | $this->assertEquals('', $progressBar->getBarCharacter()); 54 | $this->assertEquals('⢸', $progressBar->getProgressCharacter()); 55 | $this->assertEquals('⌛', $progressBar->getEmptyBarCharacter()); 56 | $this->assertEquals(1, $progressBar->getBarWidth()); 57 | $this->assertEquals(' ', $progressBar->getMessage('detail')); 58 | 59 | putenv('PHPUNIT_RUNNING'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/phpunit/src/Misc/EnvDbCredsTest.php: -------------------------------------------------------------------------------- 1 | dbUser = 'myuserisgood'; 25 | $this->dbPassword = 'mypasswordisgreat'; 26 | $this->dbName = 'mynameisgrand'; 27 | $this->dbHost = 'myhostismeh'; 28 | self::setEnvVars($this->getEnvVars()); 29 | parent::setUp(); 30 | } 31 | 32 | public function tearDown(): void 33 | { 34 | parent::tearDown(); 35 | self::unsetEnvVars($this->getEnvVars()); 36 | } 37 | 38 | /** 39 | * @return array 40 | */ 41 | protected function getEnvVars(): array 42 | { 43 | return [ 44 | 'ACLI_DB_HOST' => $this->dbHost, 45 | 'ACLI_DB_NAME' => $this->dbName, 46 | 'ACLI_DB_PASSWORD' => $this->dbPassword, 47 | 'ACLI_DB_USER' => $this->dbUser, 48 | ]; 49 | } 50 | 51 | protected function createCommand(): CommandBase 52 | { 53 | return $this->injectCommand(ClearCacheCommand::class); 54 | } 55 | 56 | public function testEnvDbCreds(): void 57 | { 58 | $this->assertEquals($this->dbUser, $this->command->getLocalDbUser()); 59 | $this->assertEquals($this->dbPassword, $this->command->getLocalDbPassword()); 60 | $this->assertEquals($this->dbName, $this->command->getLocalDbName()); 61 | $this->assertEquals($this->dbHost, $this->command->getLocalDbHost()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /var/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acquia/cli/29236b4ec1b1e81e16fb19373e9641556dfdf76d/var/.gitkeep --------------------------------------------------------------------------------