diff --git a/.actrc b/.actrc deleted file mode 100644 index 99e6b7ecc..000000000 --- a/.actrc +++ /dev/null @@ -1,3 +0,0 @@ -# Configuration file for nektos/act. -# See https://github.com/nektos/act#configuration --P ubuntu-latest=shivammathur/node:latest diff --git a/.distignore b/.distignore index 44abaac57..b964b40c7 100644 --- a/.distignore +++ b/.distignore @@ -1,15 +1,11 @@ .DS_Store .git -.github .gitignore .gitlab-ci.yml .editorconfig .travis.yml behat.yml -.circleci/config.yml -bitbucket-pipelines.yml -phpcs.xml.dist -phpunit.xml.dist +circle.yml bin/ features/ utils/ diff --git a/.editorconfig b/.editorconfig index 84f918ed5..fa483b1bb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,8 +4,6 @@ # WordPress Coding Standards # https://make.wordpress.org/core/handbook/coding-standards/ -# From https://github.com/WordPress/wordpress-develop/blob/trunk/.editorconfig with a couple of additions. - root = true [*] @@ -15,12 +13,13 @@ insert_final_newline = true trim_trailing_whitespace = true indent_style = tab -[{*.yml,*.feature,.jshintrc,*.json}] +[{.jshintrc,*.json,*.yml,*.feature}] indent_style = space indent_size = 2 -[*.md] -trim_trailing_whitespace = false - [{*.txt,wp-config-sample.php}] end_of_line = crlf + +[composer.json] +indent_style = space +indent_size = 4 diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index d84f4ade2..000000000 --- a/.gitattributes +++ /dev/null @@ -1,14 +0,0 @@ -/.actrc export-ignore -/.distignore export-ignore -/.editorconfig export-ignore -/.github export-ignore -/.gitignore export-ignore -/.typos.toml export-ignore -/AGENTS.md export-ignore -/behat.yml export-ignore -/features export-ignore -/phpcs.xml.dist export-ignore -/phpstan.neon.dist export-ignore -/phpunit.xml.dist export-ignore -/tests export-ignore -/wp-cli.yml export-ignore diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index f69375fb2..000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @wp-cli/committers diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE deleted file mode 100644 index 6c06a141b..000000000 --- a/.github/ISSUE_TEMPLATE +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE deleted file mode 100644 index 66079d9da..000000000 --- a/.github/PULL_REQUEST_TEMPLATE +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index d6c7b8b04..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: 2 -updates: - - package-ecosystem: composer - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 - labels: - - scope:distribution - - package-ecosystem: github-actions - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 - labels: - - scope:distribution - diff --git a/.github/workflows/check-branch-alias.yml b/.github/workflows/check-branch-alias.yml deleted file mode 100644 index 78da63710..000000000 --- a/.github/workflows/check-branch-alias.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Check Branch Alias - -on: - release: - types: [released] - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -jobs: - check-branch-alias: - uses: wp-cli/.github/.github/workflows/reusable-check-branch-alias.yml@main diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml deleted file mode 100644 index e9fe57761..000000000 --- a/.github/workflows/code-quality.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Code Quality Checks - -on: - pull_request: - push: - branches: - - main - - master - schedule: - - cron: '17 2 * * *' # Run every day on a seemly random time. - -jobs: - code-quality: - uses: wp-cli/.github/.github/workflows/reusable-code-quality.yml@main diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml deleted file mode 100644 index d5e319e5e..000000000 --- a/.github/workflows/copilot-setup-steps.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: "Copilot Setup Steps" - -on: - workflow_dispatch: - push: - paths: - - .github/workflows/copilot-setup-steps.yml - pull_request: - paths: - - .github/workflows/copilot-setup-steps.yml - -permissions: - contents: read - -jobs: - copilot-setup-steps: - name: Setup environment - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - persist-credentials: false - - - name: Check existence of composer.json file - id: check_composer_file - run: echo "files_exists=$(test -f composer.json && echo true || echo false)" >> "$GITHUB_OUTPUT" - - - name: Set up PHP environment - if: steps.check_composer_file.outputs.files_exists == 'true' - uses: shivammathur/setup-php@7c071dfe9dc99bdf297fa79cb49ea005b9fcadbc # v2 - with: - php-version: 'latest' - ini-values: zend.assertions=1, error_reporting=-1, display_errors=On - coverage: 'none' - tools: composer,cs2pr - env: - COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Install Composer dependencies & cache dependencies - if: steps.check_composer_file.outputs.files_exists == 'true' - uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # 4.0.0 - env: - COMPOSER_ROOT_VERSION: dev-${{ github.event.repository.default_branch }} diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml deleted file mode 100644 index 68334703a..000000000 --- a/.github/workflows/issue-triage.yml +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: Issue and PR Triage - -'on': - issues: - types: [opened] - pull_request_target: - types: [opened] - workflow_dispatch: - inputs: - issue_number: - description: 'Issue/PR number to triage (leave empty to process all)' - required: false - type: string - -permissions: - issues: write - pull-requests: write - actions: write - contents: read - models: read - -jobs: - issue-triage: - uses: wp-cli/.github/.github/workflows/reusable-issue-triage.yml@main - with: - issue_number: >- - ${{ - (github.event_name == 'workflow_dispatch' && inputs.issue_number) || - (github.event_name == 'pull_request_target' && github.event.pull_request.number) || - (github.event_name == 'issues' && github.event.issue.number) || - '' - }} diff --git a/.github/workflows/manage-labels.yml b/.github/workflows/manage-labels.yml deleted file mode 100644 index 45711bded..000000000 --- a/.github/workflows/manage-labels.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Manage Labels - -'on': - workflow_dispatch: - push: - branches: - - main - - master - paths: - - 'composer.json' - -permissions: - issues: write - contents: read - -jobs: - manage-labels: - uses: wp-cli/.github/.github/workflows/reusable-manage-labels.yml@main diff --git a/.github/workflows/regenerate-readme.yml b/.github/workflows/regenerate-readme.yml deleted file mode 100644 index 6198d6308..000000000 --- a/.github/workflows/regenerate-readme.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Regenerate README file - -on: - workflow_dispatch: - push: - branches: - - main - - master - paths-ignore: - - "features/**" - - "README.md" - -permissions: - contents: write - pull-requests: write - -jobs: - regenerate-readme: - uses: wp-cli/.github/.github/workflows/reusable-regenerate-readme.yml@main diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml deleted file mode 100644 index bf67592d8..000000000 --- a/.github/workflows/testing.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Testing - -on: - workflow_dispatch: - pull_request: - push: - branches: - - main - - master - schedule: - - cron: '17 1 * * *' # Run every day on a seemly random time. - -jobs: - test: - uses: wp-cli/.github/.github/workflows/reusable-testing.yml@main diff --git a/.github/workflows/welcome-new-contributors.yml b/.github/workflows/welcome-new-contributors.yml deleted file mode 100644 index bc01490b3..000000000 --- a/.github/workflows/welcome-new-contributors.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Welcome New Contributors - -on: - pull_request_target: - types: [opened] - branches: - - main - - master - -permissions: - pull-requests: write - -jobs: - welcome: - uses: wp-cli/.github/.github/workflows/reusable-welcome-new-contributors.yml@main diff --git a/.gitignore b/.gitignore index bcf211b32..f3b908269 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,3 @@ vendor/ *.zip *.tar.gz composer.lock -*.log -phpunit.xml -phpcs.xml -.phpcs.xml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..bfbbd2d6f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,48 @@ +sudo: false + +language: php + +notifications: + email: + on_success: never + on_failure: change + +branches: + only: + - master + +cache: + - composer + - $HOME/.composer/cache + +env: + global: + - PATH="$TRAVIS_BUILD_DIR/vendor/bin:$PATH" + - WP_CLI_BIN_DIR="$TRAVIS_BUILD_DIR/vendor/bin" + +matrix: + include: + - php: 7.1 + env: WP_VERSION=latest + - php: 7.0 + env: WP_VERSION=latest + - php: 5.6 + env: WP_VERSION=latest + - php: 5.6 + env: WP_VERSION=trunk + - php: 5.3 + env: WP_VERSION=latest + +before_install: + - phpenv config-rm xdebug.ini + +install: + - composer require wp-cli/wp-cli:dev-3728-scaffold-command + - composer install + - bash bin/install-package-tests.sh + +before_script: + - composer validate + +script: + - behat --format progress --strict diff --git a/.typos.toml b/.typos.toml deleted file mode 100644 index 965f891fc..000000000 --- a/.typos.toml +++ /dev/null @@ -1,6 +0,0 @@ -[default] -extend-ignore-re = [ - "(?Rm)^.*(#|//)\\s*spellchecker:disable-line$", - "(?s)(#|//)\\s*spellchecker:off.*?\\n\\s*(#|//)\\s*spellchecker:on", - "(#|//)\\s*spellchecker:ignore-next-line\\n.*" -] diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 1ff84f6d1..000000000 --- a/AGENTS.md +++ /dev/null @@ -1,121 +0,0 @@ -# Instructions - -This package is part of WP-CLI, the official command line interface for WordPress. For a detailed explanation of the project structure and development workflow, please refer to the main @README.md file. - -## Best Practices for Code Contributions - -When contributing to this package, please adhere to the following guidelines: - -* **Follow Existing Conventions:** Before writing any code, analyze the existing codebase in this package to understand the coding style, naming conventions, and architectural patterns. -* **Focus on the Package's Scope:** All changes should be relevant to the functionality of the package. -* **Write Tests:** All new features and bug fixes must be accompanied by acceptance tests using Behat. You can find the existing tests in the `features/` directory. There may be PHPUnit unit tests as well in the `tests/` directory. -* **Update Documentation:** If your changes affect the user-facing functionality, please update the relevant inline code documentation. - -### Building and running - -Before submitting any changes, it is crucial to validate them by running the full suite of static code analysis and tests. To run the full suite of checks, execute the following command: `composer test`. - -This single command ensures that your changes meet all the quality gates of the project. While you can run the individual steps separately, it is highly recommended to use this single command to ensure a comprehensive validation. - -### Useful Composer Commands - -The project uses Composer to manage dependencies and run scripts. The following commands are available: - -* `composer install`: Install dependencies. -* `composer test`: Run the full test suite, including linting, code style checks, static analysis, and unit/behavior tests. -* `composer lint`: Check for syntax errors. -* `composer phpcs`: Check for code style violations. -* `composer phpcbf`: Automatically fix code style violations. -* `composer phpstan`: Run static analysis. -* `composer phpunit`: Run unit tests. -* `composer behat`: Run behavior-driven tests. - -### Coding Style - -The project follows the `WP_CLI_CS` coding standard, which is enforced by PHP_CodeSniffer. The configuration can be found in `phpcs.xml.dist`. Before submitting any code, please run `composer phpcs` to check for violations and `composer phpcbf` to automatically fix them. - -## Documentation - -The `README.md` file might be generated dynamically from the project's codebase using `wp scaffold package-readme` ([doc](https://github.com/wp-cli/scaffold-package-command#wp-scaffold-package-readme)). In that case, changes need to be made against the corresponding part of the codebase. - -### Inline Documentation - -Only write high-value comments if at all. Avoid talking to the user through comments. - -## Testing - -The project has a comprehensive test suite that includes unit tests, behavior-driven tests, and static analysis. - -* **Unit tests** are written with PHPUnit and can be found in the `tests/` directory. The configuration is in `phpunit.xml.dist`. -* **Behavior-driven tests** are written with Behat and can be found in the `features/` directory. The configuration is in `behat.yml`. -* **Static analysis** is performed with PHPStan. - -All tests are run on GitHub Actions for every pull request. - -When writing tests, aim to follow existing patterns. Key conventions include: - -* When adding tests, first examine existing tests to understand and conform to established conventions. -* For unit tests, extend the base `WP_CLI\Tests\TestCase` test class. -* For Behat tests, only WP-CLI commands installed in `composer.json` can be run. - -### Behat Steps - -WP-CLI makes use of a Behat-based testing framework and provides a set of custom step definitions to write feature tests. - -> **Note:** If you are expecting an error output in a test, you need to use `When I try ...` instead of `When I run ...` . - -#### Given - -* `Given an empty directory` - Creates an empty directory. -* `Given /^an? (empty|non-existent) ([^\s]+) directory$/` - Creates or deletes a specific directory. -* `Given an empty cache` - Clears the WP-CLI cache directory. -* `Given /^an? ([^\s]+) (file|cache file):$/` - Creates a file with the given contents. -* `Given /^"([^"]+)" replaced with "([^"]+)" in the ([^\s]+) file$/` - Search and replace a string in a file using regex. -* `Given /^that HTTP requests to (.*?) will respond with:$/` - Mock HTTP requests to a given URL. -* `Given WP files` - Download WordPress files without installing. -* `Given wp-config.php` - Create a wp-config.php file using `wp config create`. -* `Given a database` - Creates an empty database. -* `Given a WP install(ation)` - Installs WordPress. -* `Given a WP install(ation) in :subdir` - Installs WordPress in a given directory. -* `Given a WP install(ation) with Composer` - Installs WordPress with Composer. -* `Given a WP install(ation) with Composer and a custom vendor directory :vendor_directory` - Installs WordPress with Composer and a custom vendor directory. -* `Given /^a WP multisite (subdirectory|subdomain)?\s?(install|installation)$/` - Installs WordPress Multisite. -* `Given these installed and active plugins:` - Installs and activates one or more plugins. -* `Given a custom wp-content directory` - Configure a custom `wp-content` directory. -* `Given download:` - Download multiple files into the given destinations. -* `Given /^save (STDOUT|STDERR) ([\'].+[^\'])?\s?as \{(\w+)\}$/` - Store STDOUT or STDERR contents in a variable. -* `Given /^a new Phar with (?:the same version|version "([^"]+)")$/` - Build a new WP-CLI Phar file with a given version. -* `Given /^a downloaded Phar with (?:the same version|version "([^"]+)")$/` - Download a specific WP-CLI Phar version from GitHub. -* `Given /^save the (.+) file ([\'].+[^\'])? as \{(\w+)\}$/` - Stores the contents of the given file in a variable. -* `Given a misconfigured WP_CONTENT_DIR constant directory` - Modify wp-config.php to set `WP_CONTENT_DIR` to an empty string. -* `Given a dependency on current wp-cli` - Add `wp-cli/wp-cli` as a Composer dependency. -* `Given a PHP built-in web server` - Start a PHP built-in web server in the current directory. -* `Given a PHP built-in web server to serve :subdir` - Start a PHP built-in web server in the given subdirectory. - -#### When - -* ``When /^I launch in the background `([^`]+)`$/`` - Launch a given command in the background. -* ``When /^I (run|try) `([^`]+)`$/`` - Run or try a given command. -* ``When /^I (run|try) `([^`]+)` from '([^\s]+)'$/`` - Run or try a given command in a subdirectory. -* `When /^I (run|try) the previous command again$/` - Run or try the previous command again. - -#### Then - -* `Then /^the return code should( not)? be (\d+)$/` - Expect a specific exit code of the previous command. -* `Then /^(STDOUT|STDERR) should( strictly)? (be|contain|not contain):$/` - Check the contents of STDOUT or STDERR. -* `Then /^(STDOUT|STDERR) should be a number$/` - Expect STDOUT or STDERR to be a numeric value. -* `Then /^(STDOUT|STDERR) should not be a number$/` - Expect STDOUT or STDERR to not be a numeric value. -* `Then /^STDOUT should be a table containing rows:$/` - Expect STDOUT to be a table containing the given rows. -* `Then /^STDOUT should end with a table containing rows:$/` - Expect STDOUT to end with a table containing the given rows. -* `Then /^STDOUT should be JSON containing:$/` - Expect valid JSON output in STDOUT. -* `Then /^STDOUT should be a JSON array containing:$/` - Expect valid JSON array output in STDOUT. -* `Then /^STDOUT should be CSV containing:$/` - Expect STDOUT to be CSV containing certain values. -* `Then /^STDOUT should be YAML containing:$/` - Expect STDOUT to be YAML containing certain content. -* `Then /^(STDOUT|STDERR) should be empty$/` - Expect STDOUT or STDERR to be empty. -* `Then /^(STDOUT|STDERR) should not be empty$/` - Expect STDOUT or STDERR not to be empty. -* `Then /^(STDOUT|STDERR) should be a version string (<|<=|>|>=|==|=|<>) ([+\w.{}-]+)$/` - Expect STDOUT or STDERR to be a version string comparing to the given version. -* `Then /^the (.+) (file|directory) should( strictly)? (exist|not exist|be:|contain:|not contain):$/` - Expect a certain file or directory to (not) exist or (not) contain certain contents. -* `Then /^the contents of the (.+) file should( not)? match (((\/.*\/)|(#.#))([a-z]+)?)$/` - Match file contents against a regex. -* `Then /^(STDOUT|STDERR) should( not)? match (((\/.*\/)|(#.#))([a-z]+)?)$/` - Match STDOUT or STDERR against a regex. -* `Then /^an email should (be sent|not be sent)$/` - Expect an email to be sent (or not). -* `Then the HTTP status code should be :code` - Expect the HTTP status code for visiting `http://localhost:8080`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index fa86aa52d..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,8 +0,0 @@ -Contributing -============ - -We appreciate you taking the initiative to contribute to this project. - -Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. - -For a more thorough introduction, [check out WP-CLI's guide to contributing](https://make.wordpress.org/cli/handbook/contributing/). This package follows those policy and guidelines. diff --git a/LICENSE b/LICENSE index 1d356479f..5d08052f4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (C) 2011-2018 WP-CLI Development Group (https://github.com/wp-cli/scaffold-command/contributors) +Copyright (C) 2011-2017 WP-CLI Development Group (https://github.com/wp-cli/scaffold-command/contributors) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 195afac25..c16f3ee25 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ wp-cli/scaffold-command ======================= -Generates code for post types, taxonomies, blocks, plugins, child themes, etc. +Generate code for post types, taxonomies, plugins, child themes, etc. -[![Testing](https://github.com/wp-cli/scaffold-command/actions/workflows/testing.yml/badge.svg)](https://github.com/wp-cli/scaffold-command/actions/workflows/testing.yml) [![Code Coverage](https://codecov.io/gh/wp-cli/scaffold-command/branch/main/graph/badge.svg)](https://codecov.io/gh/wp-cli/scaffold-command/tree/main) +[![Build Status](https://travis-ci.org/wp-cli/scaffold-command.svg?branch=master)](https://travis-ci.org/wp-cli/scaffold-command) -Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contributing) | [Support](#support) +Quick links: [Using](#using) | [Installing](#installing) | [Contributing](#contributing) ## Using @@ -13,7 +13,7 @@ This package implements the following commands: ### wp scaffold -Generates code for post types, taxonomies, plugins, child themes, etc. +Generate code for post types, taxonomies, plugins, child themes, etc. ~~~ wp scaffold @@ -21,32 +21,30 @@ wp scaffold **EXAMPLES** - # Generate a new plugin with unit tests. + # Generate a new plugin with unit tests $ wp scaffold plugin sample-plugin Success: Created plugin files. Success: Created test files. - # Generate theme based on _s. + # Generate theme based on _s $ wp scaffold _s sample-theme --theme_name="Sample Theme" --author="John Doe" Success: Created theme 'Sample Theme'. - # Generate code for post type registration in given theme. + # Generate code for post type registration in given theme $ wp scaffold post-type movie --label=Movie --theme=simple-life - Success: Created '/var/www/example.com/public_html/wp-content/themes/simple-life/post-types/movie.php'. + Success: Created /var/www/example.com/public_html/wp-content/themes/simple-life/post-types/movie.php -### wp scaffold underscores +### wp scaffold _s -Generates starter code for a theme based on _s. +Generate starter code for a theme based on _s. ~~~ -wp scaffold underscores [--activate] [--enable-network] [--theme_name=] [--author=<full-name>] [--author_uri=<uri>] [--sassify] [--woocommerce] [--force] +wp scaffold _s <slug> [--activate] [--enable-network] [--theme_name=<title>] [--author=<full-name>] [--author_uri=<uri>] [--sassify] [--force] ~~~ -**Alias:** `_s` - -See the [Underscores website](https://underscores.me/) for more details. +See the [Underscores website](http://underscores.me/) for more details. **OPTIONS** @@ -71,9 +69,6 @@ See the [Underscores website](https://underscores.me/) for more details. [--sassify] Include stylesheets as SASS. - [--woocommerce] - Include WooCommerce boilerplate files. - [--force] Overwrite files that already exist. @@ -85,57 +80,9 @@ See the [Underscores website](https://underscores.me/) for more details. -### wp scaffold block - -Generates PHP, JS and CSS code for registering a Gutenberg block for a plugin or theme. - -~~~ -wp scaffold block <slug> [--title=<title>] [--dashicon=<dashicon>] [--category=<category>] [--theme] [--plugin=<plugin>] [--force] -~~~ - -**Warning: `wp scaffold block` is deprecated.** - -The official script to generate a block is the [@wordpress/create-block](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) package. - -See the [Create a Block tutorial](https://developer.wordpress.org/block-editor/getting-started/tutorial/) for a complete walk-through. - -**OPTIONS** - - <slug> - The internal name of the block. - - [--title=<title>] - The display title for your block. - - [--dashicon=<dashicon>] - The dashicon to make it easier to identify your block. - - [--category=<category>] - The category name to help users browse and discover your block. - --- - default: widgets - options: - - common - - embed - - formatting - - layout - - widgets - --- - - [--theme] - Create files in the active theme directory. Specify a theme with `--theme=<theme>` to have the file placed in that theme. - - [--plugin=<plugin>] - Create files in the given plugin's directory. - - [--force] - Overwrite files that already exist. - - - ### wp scaffold child-theme -Generates child theme based on an existing theme. +Generate child theme based on an existing theme. ~~~ wp scaffold child-theme <slug> --parent_theme=<slug> [--theme_name=<title>] [--author=<full-name>] [--author_uri=<uri>] [--theme_uri=<uri>] [--activate] [--enable-network] [--force] @@ -182,7 +129,7 @@ Creates a child theme folder with `functions.php` and `style.css` files. ### wp scaffold plugin -Generates starter code for a plugin. +Generate starter code for a plugin. ~~~ wp scaffold plugin <slug> [--dir=<dirname>] [--plugin_name=<title>] [--plugin_description=<description>] [--plugin_author=<author>] [--plugin_author_uri=<url>] [--plugin_uri=<url>] [--skip-tests] [--ci=<provider>] [--activate] [--activate-network] [--force] @@ -192,7 +139,7 @@ The following files are always generated: * `plugin-slug.php` is the main PHP plugin file. * `readme.txt` is the readme file for the plugin. -* `package.json` needed by NPM holds various metadata relevant to the project. Packages: `grunt`, `grunt-wp-i18n` and `grunt-wp-readme-to-markdown`. Scripts: `start`, `readme`, `i18n`. +* `package.json` needed by NPM holds various metadata relevant to the project. Packages: `grunt`, `grunt-wp-i18n` and `grunt-wp-readme-to-markdown`. * `Gruntfile.js` is the JS file containing Grunt tasks. Tasks: `i18n` containing `addtextdomain` and `makepot`, `readme` containing `wp_readme_to_markdown`. * `.editorconfig` is the configuration file for Editor. * `.gitignore` tells which files (or patterns) git should ignore. @@ -201,11 +148,11 @@ The following files are always generated: The following files are also included unless the `--skip-tests` is used: * `phpunit.xml.dist` is the configuration file for PHPUnit. -* `.circleci/config.yml` is the configuration file for CircleCI. Use `--ci=<provider>` to select a different service. +* `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service. * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database. * `tests/bootstrap.php` is the file that makes the current plugin active when running the test suite. * `tests/test-sample.php` is a sample file containing test cases. -* `.phpcs.xml.dist` is a collection of PHP_CodeSniffer rules. +* `phpcs.ruleset.xml` is a collenction of PHP_CodeSniffer rules. **OPTIONS** @@ -236,12 +183,11 @@ The following files are also included unless the `--skip-tests` is used: [--ci=<provider>] Choose a configuration file for a continuous integration provider. --- - default: circle + default: travis options: + - travis - circle - gitlab - - bitbucket - - github --- [--activate] @@ -263,7 +209,7 @@ The following files are also included unless the `--skip-tests` is used: ### wp scaffold plugin-tests -Generates files needed for running PHPUnit tests in a plugin. +Generate files needed for running PHPUnit tests in a plugin. ~~~ wp scaffold plugin-tests [<plugin>] [--dir=<dirname>] [--ci=<provider>] [--force] @@ -272,13 +218,13 @@ wp scaffold plugin-tests [<plugin>] [--dir=<dirname>] [--ci=<provider>] [--force The following files are generated by default: * `phpunit.xml.dist` is the configuration file for PHPUnit. -* `.circleci/config.yml` is the configuration file for CircleCI. Use `--ci=<provider>` to select a different service. +* `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service. * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database. * `tests/bootstrap.php` is the file that makes the current plugin active when running the test suite. * `tests/test-sample.php` is a sample file containing the actual tests. -* `.phpcs.xml.dist` is a collection of PHP_CodeSniffer rules. +* `phpcs.ruleset.xml` is a collenction of PHP_CodeSniffer rules. -Learn more from the [plugin unit tests documentation](https://make.wordpress.org/cli/handbook/misc/plugin-unit-tests/). +Learn more from the [plugin unit tests documentation](http://wp-cli.org/docs/plugin-unit-tests/). **ENVIRONMENT** @@ -296,12 +242,11 @@ variable. [--ci=<provider>] Choose a configuration file for a continuous integration provider. --- - default: circle + default: travis options: + - travis - circle - - gitlab - - bitbucket - - github + - gitlab --- [--force] @@ -317,14 +262,12 @@ variable. ### wp scaffold post-type -Generates PHP code for registering a custom post type. +Generate PHP code for registering a custom post type. ~~~ wp scaffold post-type <slug> [--label=<label>] [--textdomain=<textdomain>] [--dashicon=<dashicon>] [--theme] [--plugin=<plugin>] [--raw] [--force] ~~~ -**Alias:** `cpt` - **OPTIONS** <slug> @@ -362,14 +305,12 @@ wp scaffold post-type <slug> [--label=<label>] [--textdomain=<textdomain>] [--da ### wp scaffold taxonomy -Generates PHP code for registering a custom taxonomy. +Generate PHP code for registering a custom taxonomy. ~~~ wp scaffold taxonomy <slug> [--post_types=<post-types>] [--label=<label>] [--textdomain=<textdomain>] [--theme] [--plugin=<plugin>] [--raw] [--force] ~~~ -**Alias:** `tax` - **OPTIONS** <slug> @@ -406,7 +347,7 @@ wp scaffold taxonomy <slug> [--post_types=<post-types>] [--label=<label>] [--tex ### wp scaffold theme-tests -Generates files needed for running PHPUnit tests in a theme. +Generate files needed for running PHPUnit tests in a theme. ~~~ wp scaffold theme-tests [<theme>] [--dir=<dirname>] [--ci=<provider>] [--force] @@ -415,13 +356,13 @@ wp scaffold theme-tests [<theme>] [--dir=<dirname>] [--ci=<provider>] [--force] The following files are generated by default: * `phpunit.xml.dist` is the configuration file for PHPUnit. -* `.circleci/config.yml` is the configuration file for CircleCI. Use `--ci=<provider>` to select a different service. +* `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service. * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database. * `tests/bootstrap.php` is the file that makes the current theme active when running the test suite. * `tests/test-sample.php` is a sample file containing the actual tests. -* `.phpcs.xml.dist` is a collection of PHP_CodeSniffer rules. +* `phpcs.ruleset.xml` is a collenction of PHP_CodeSniffer rules. -Learn more from the [plugin unit tests documentation](https://make.wordpress.org/cli/handbook/misc/plugin-unit-tests/). +Learn more from the [plugin unit tests documentation](http://wp-cli.org/docs/plugin-unit-tests/). **ENVIRONMENT** @@ -439,12 +380,11 @@ variable. [--ci=<provider>] Choose a configuration file for a continuous integration provider. --- - default: circle + default: travis options: + - travis - circle - - gitlab - - bitbucket - - github + - gitlab --- [--force] @@ -458,11 +398,9 @@ variable. ## Installing -This package is included with WP-CLI itself, no additional installation necessary. +Installing this package requires WP-CLI v0.23.0 or greater. Update to the latest stable release with `wp cli update`. -To install the latest version of this package over what's included in WP-CLI, run: - - wp package install git@github.com:wp-cli/scaffold-command.git +Once you've done so, you can install this package with `wp package install wp-cli/scaffold-command`. ## Contributing @@ -470,29 +408,30 @@ We appreciate you taking the initiative to contribute to this project. Contributing isn’t limited to just code. We encourage you to contribute in the way that best fits your abilities, by writing tutorials, giving a demo at your local meetup, helping other users with their support questions, or revising our documentation. -For a more thorough introduction, [check out WP-CLI's guide to contributing](https://make.wordpress.org/cli/handbook/contributing/). This package follows those policy and guidelines. - ### Reporting a bug Think you’ve found a bug? We’d love for you to help us get it fixed. Before you create a new issue, you should [search existing issues](https://github.com/wp-cli/scaffold-command/issues?q=label%3Abug%20) to see if there’s an existing resolution to it, or if it’s already been fixed in a newer version. -Once you’ve done a bit of searching and discovered there isn’t an open or fixed issue for your bug, please [create a new issue](https://github.com/wp-cli/scaffold-command/issues/new). Include as much detail as you can, and clear steps to reproduce if possible. For more guidance, [review our bug report documentation](https://make.wordpress.org/cli/handbook/bug-reports/). +Once you’ve done a bit of searching and discovered there isn’t an open or fixed issue for your bug, please [create a new issue](https://github.com/wp-cli/scaffold-command/issues/new) with the following: -### Creating a pull request - -Want to contribute a new feature? Please first [open a new issue](https://github.com/wp-cli/scaffold-command/issues/new) to discuss whether the feature is a good fit for the project. +1. What you were doing (e.g. "When I run `wp post list`"). +2. What you saw (e.g. "I see a fatal about a class being undefined."). +3. What you expected to see (e.g. "I expected to see the list of posts.") -Once you've decided to commit the time to seeing your pull request through, [please follow our guidelines for creating a pull request](https://make.wordpress.org/cli/handbook/pull-requests/) to make sure it's a pleasant experience. See "[Setting up](https://make.wordpress.org/cli/handbook/pull-requests/#setting-up)" for details specific to working on this package locally. +Include as much detail as you can, and clear steps to reproduce if possible. -### License +### Creating a pull request -This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. +Want to contribute a new feature? Please first [open a new issue](https://github.com/wp-cli/scaffold-command/issues/new) to discuss whether the feature is a good fit for the project. -## Support +Once you've decided to commit the time to seeing your pull request through, please follow our guidelines for creating a pull request to make sure it's a pleasant experience: -GitHub issues aren't for general support questions, but there are other venues you can try: https://wp-cli.org/#support +1. Create a feature branch for each contribution. +2. Submit your pull request early for feedback. +3. Include functional tests with your changes. [Read the WP-CLI documentation](https://wp-cli.org/docs/pull-requests/#functional-tests) for an introduction. +4. Follow the [WordPress Coding Standards](http://make.wordpress.org/core/handbook/coding-standards/). *This README.md is generated dynamically from the project's codebase using `wp scaffold package-readme` ([doc](https://github.com/wp-cli/scaffold-package-command#wp-scaffold-package-readme)). To suggest changes, please submit a pull request against the corresponding part of the codebase.* diff --git a/behat.yml b/behat.yml deleted file mode 100644 index d6ee86224..000000000 --- a/behat.yml +++ /dev/null @@ -1,7 +0,0 @@ -default: - suites: - default: - contexts: - - WP_CLI\Tests\Context\FeatureContext - paths: - - features diff --git a/bin/install-package-tests.sh b/bin/install-package-tests.sh new file mode 100755 index 000000000..2ff49dd8d --- /dev/null +++ b/bin/install-package-tests.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -ex + +install_db() { + mysql -e 'CREATE DATABASE IF NOT EXISTS wp_cli_test;' -uroot + mysql -e 'GRANT ALL PRIVILEGES ON wp_cli_test.* TO "wp_cli_test"@"localhost" IDENTIFIED BY "password1"' -uroot +} + +install_db diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index e69de29bb..000000000 diff --git a/composer.json b/composer.json index ca135714a..c8c0bb522 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,11 @@ { "name": "wp-cli/scaffold-command", + "description": "Generate code for post types, taxonomies, plugins, child themes, etc.", "type": "wp-cli-package", - "description": "Generates code for post types, taxonomies, blocks, plugins, child themes, etc.", "homepage": "https://github.com/wp-cli/scaffold-command", + "support": { + "issues": "https://github.com/wp-cli/scaffold-command/issues" + }, "license": "MIT", "authors": [ { @@ -11,32 +14,26 @@ "homepage": "https://runcommand.io" } ], - "require": { - "wp-cli/wp-cli": "^2.13" + "minimum-stability": "dev", + "prefer-stable": true, + "autoload": { + "psr-4": { + "": "src/" + }, + "files": [ "scaffold-command.php" ] }, + "require": {}, "require-dev": { - "wp-cli/extension-command": "^1.2 || ^2", - "wp-cli/wp-cli-tests": "^5" - }, - "config": { - "process-timeout": 7200, - "sort-packages": true, - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true, - "johnpbloch/wordpress-core-installer": true, - "phpstan/extension-installer": true - }, - "lock": false + "behat/behat": "~2.5", + "wp-cli/wp-cli": "*" }, "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-master": "1.x-dev" }, - "bundled": true, "commands": [ "scaffold", - "scaffold underscores", - "scaffold block", + "scaffold _s", "scaffold child-theme", "scaffold plugin", "scaffold plugin-tests", @@ -44,35 +41,5 @@ "scaffold taxonomy", "scaffold theme-tests" ] - }, - "autoload": { - "classmap": [ - "src/" - ], - "files": [ - "scaffold-command.php" - ] - }, - "minimum-stability": "dev", - "prefer-stable": true, - "scripts": { - "behat": "run-behat-tests", - "behat-rerun": "rerun-behat-tests", - "lint": "run-linter-tests", - "phpcs": "run-phpcs-tests", - "phpstan": "run-phpstan-tests", - "phpcbf": "run-phpcbf-cleanup", - "phpunit": "run-php-unit-tests", - "prepare-tests": "install-package-tests", - "test": [ - "@lint", - "@phpcs", - "@phpstan", - "@phpunit", - "@behat" - ] - }, - "support": { - "issues": "https://github.com/wp-cli/scaffold-command/issues" } } diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php new file mode 100644 index 000000000..13bb008bf --- /dev/null +++ b/features/bootstrap/FeatureContext.php @@ -0,0 +1,359 @@ +<?php + +use Behat\Behat\Context\ClosuredContextInterface, + Behat\Behat\Context\TranslatedContextInterface, + Behat\Behat\Context\BehatContext, + Behat\Behat\Event\SuiteEvent; + +use \WP_CLI\Process; +use \WP_CLI\Utils; + +// Inside a community package +if ( file_exists( __DIR__ . '/utils.php' ) ) { + require_once __DIR__ . '/utils.php'; + require_once __DIR__ . '/Process.php'; + require_once __DIR__ . '/ProcessRun.php'; + $project_composer = dirname( dirname( dirname( __FILE__ ) ) ) . '/composer.json'; + if ( file_exists( $project_composer ) ) { + $composer = json_decode( file_get_contents( $project_composer ) ); + if ( ! empty( $composer->autoload->files ) ) { + $contents = 'require:' . PHP_EOL; + foreach( $composer->autoload->files as $file ) { + $contents .= ' - ' . dirname( dirname( dirname( __FILE__ ) ) ) . '/' . $file . PHP_EOL; + } + @mkdir( sys_get_temp_dir() . '/wp-cli-package-test/' ); + $project_config = sys_get_temp_dir() . '/wp-cli-package-test/config.yml'; + file_put_contents( $project_config, $contents ); + putenv( 'WP_CLI_CONFIG_PATH=' . $project_config ); + } + } +// Inside WP-CLI +} else { + require_once __DIR__ . '/../../php/utils.php'; + require_once __DIR__ . '/../../php/WP_CLI/Process.php'; + require_once __DIR__ . '/../../php/WP_CLI/ProcessRun.php'; + require_once __DIR__ . '/../../vendor/autoload.php'; +} + +/** + * Features context. + */ +class FeatureContext extends BehatContext implements ClosuredContextInterface { + + private static $cache_dir, $suite_cache_dir; + + private static $db_settings = array( + 'dbname' => 'wp_cli_test', + 'dbuser' => 'wp_cli_test', + 'dbpass' => 'password1', + 'dbhost' => '127.0.0.1', + ); + + private $running_procs = array(); + + public $variables = array(); + + /** + * Get the environment variables required for launched `wp` processes + * @beforeSuite + */ + private static function get_process_env_variables() { + // Ensure we're using the expected `wp` binary + $bin_dir = getenv( 'WP_CLI_BIN_DIR' ) ?: realpath( __DIR__ . '/../../bin' ); + $vendor_dir = realpath( __DIR__ . '/../../vendor/bin' ); + $env = array( + 'PATH' => $bin_dir . ':' . $vendor_dir . ':' . getenv( 'PATH' ), + 'BEHAT_RUN' => 1, + 'HOME' => '/tmp/wp-cli-home', + ); + if ( $config_path = getenv( 'WP_CLI_CONFIG_PATH' ) ) { + $env['WP_CLI_CONFIG_PATH'] = $config_path; + } + return $env; + } + + // We cache the results of `wp core download` to improve test performance + // Ideally, we'd cache at the HTTP layer for more reliable tests + private static function cache_wp_files() { + self::$cache_dir = sys_get_temp_dir() . '/wp-cli-test core-download-cache'; + + if ( is_readable( self::$cache_dir . '/wp-config-sample.php' ) ) + return; + + $cmd = Utils\esc_cmd( 'wp core download --force --path=%s', self::$cache_dir ); + if ( getenv( 'WP_VERSION' ) ) { + $cmd .= Utils\esc_cmd( ' --version=%s', getenv( 'WP_VERSION' ) ); + } + Process::create( $cmd, null, self::get_process_env_variables() )->run_check(); + } + + /** + * @BeforeSuite + */ + public static function prepare( SuiteEvent $event ) { + $result = Process::create( 'wp cli info', null, self::get_process_env_variables() )->run_check(); + echo PHP_EOL; + echo $result->stdout; + echo PHP_EOL; + self::cache_wp_files(); + $result = Process::create( Utils\esc_cmd( 'wp core version --path=%s', self::$cache_dir ) , null, self::get_process_env_variables() )->run_check(); + echo 'WordPress ' . $result->stdout; + echo PHP_EOL; + } + + /** + * @AfterSuite + */ + public static function afterSuite( SuiteEvent $event ) { + if ( self::$suite_cache_dir ) { + Process::create( Utils\esc_cmd( 'rm -r %s', self::$suite_cache_dir ), null, self::get_process_env_variables() )->run(); + } + } + + /** + * @BeforeScenario + */ + public function beforeScenario( $event ) { + $this->variables['SRC_DIR'] = realpath( __DIR__ . '/../..' ); + } + + /** + * @AfterScenario + */ + public function afterScenario( $event ) { + if ( isset( $this->variables['RUN_DIR'] ) ) { + // remove altered WP install, unless there's an error + if ( $event->getResult() < 4 ) { + $this->proc( Utils\esc_cmd( 'rm -r %s', $this->variables['RUN_DIR'] ) )->run(); + } + } + + // Remove WP-CLI package directory + if ( isset( $this->variables['PACKAGE_PATH'] ) ) { + $this->proc( Utils\esc_cmd( 'rm -rf %s', $this->variables['PACKAGE_PATH'] ) )->run(); + } + + foreach ( $this->running_procs as $proc ) { + self::terminate_proc( $proc ); + } + } + + /** + * Terminate a process and any of its children. + */ + private static function terminate_proc( $proc ) { + $status = proc_get_status( $proc ); + + $master_pid = $status['pid']; + + $output = `ps -o ppid,pid,command | grep $master_pid`; + + foreach ( explode( PHP_EOL, $output ) as $line ) { + if ( preg_match( '/^\s*(\d+)\s+(\d+)/', $line, $matches ) ) { + $parent = $matches[1]; + $child = $matches[2]; + + if ( $parent == $master_pid ) { + if ( ! posix_kill( (int) $child, 9 ) ) { + throw new RuntimeException( posix_strerror( posix_get_last_error() ) ); + } + } + } + } + + if ( ! posix_kill( (int) $master_pid, 9 ) ) { + throw new RuntimeException( posix_strerror( posix_get_last_error() ) ); + } + } + + public static function create_cache_dir() { + self::$suite_cache_dir = sys_get_temp_dir() . '/' . uniqid( "wp-cli-test-suite-cache-", TRUE ); + mkdir( self::$suite_cache_dir ); + return self::$suite_cache_dir; + } + + /** + * Initializes context. + * Every scenario gets it's own context object. + * + * @param array $parameters context parameters (set them up through behat.yml) + */ + public function __construct( array $parameters ) { + if ( getenv( 'WP_CLI_TEST_DBHOST' ) ) { + self::$db_settings['dbhost'] = getenv( 'WP_CLI_TEST_DBHOST' ); + } + $this->drop_db(); + $this->set_cache_dir(); + $this->variables['CORE_CONFIG_SETTINGS'] = Utils\assoc_args_to_str( self::$db_settings ); + } + + public function getStepDefinitionResources() { + return glob( __DIR__ . '/../steps/*.php' ); + } + + public function getHookDefinitionResources() { + return array(); + } + + public function replace_variables( $str ) { + return preg_replace_callback( '/\{([A-Z_]+)\}/', array( $this, '_replace_var' ), $str ); + } + + private function _replace_var( $matches ) { + $cmd = $matches[0]; + + foreach ( array_slice( $matches, 1 ) as $key ) { + $cmd = str_replace( '{' . $key . '}', $this->variables[ $key ], $cmd ); + } + + return $cmd; + } + + public function create_run_dir() { + if ( !isset( $this->variables['RUN_DIR'] ) ) { + $this->variables['RUN_DIR'] = sys_get_temp_dir() . '/' . uniqid( "wp-cli-test-run-", TRUE ); + mkdir( $this->variables['RUN_DIR'] ); + } + } + + public function build_phar( $version = 'same' ) { + $this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . '/' . uniqid( "wp-cli-build-", TRUE ) . '.phar'; + + // Test running against WP-CLI proper + $make_phar_path = __DIR__ . '/../../utils/make-phar.php'; + if ( ! file_exists( $make_phar_path ) ) { + // Test running against a package installed as a WP-CLI dependency + // WP-CLI installed as a project dependency + $make_phar_path = __DIR__ . '/../../../../../utils/make-phar.php'; + if ( ! file_exists( $make_phar_path ) ) { + // WP-CLI as a dependency of this project + $make_phar_path = __DIR__ . '/../../vendor/wp-cli/wp-cli/utils/make-phar.php'; + } + } + + $this->proc( Utils\esc_cmd( + 'php -dphar.readonly=0 %1$s %2$s --version=%3$s && chmod +x %2$s', + $make_phar_path, + $this->variables['PHAR_PATH'], + $version + ) )->run_check(); + } + + private function set_cache_dir() { + $path = sys_get_temp_dir() . '/wp-cli-test-cache'; + $this->proc( Utils\esc_cmd( 'mkdir -p %s', $path ) )->run_check(); + $this->variables['CACHE_DIR'] = $path; + } + + private static function run_sql( $sql ) { + Utils\run_mysql_command( 'mysql --no-defaults', array( + 'execute' => $sql, + 'host' => self::$db_settings['dbhost'], + 'user' => self::$db_settings['dbuser'], + 'pass' => self::$db_settings['dbpass'], + ) ); + } + + public function create_db() { + $dbname = self::$db_settings['dbname']; + self::run_sql( "CREATE DATABASE IF NOT EXISTS $dbname" ); + } + + public function drop_db() { + $dbname = self::$db_settings['dbname']; + self::run_sql( "DROP DATABASE IF EXISTS $dbname" ); + } + + public function proc( $command, $assoc_args = array(), $path = '' ) { + if ( !empty( $assoc_args ) ) + $command .= Utils\assoc_args_to_str( $assoc_args ); + + $env = self::get_process_env_variables(); + if ( isset( $this->variables['SUITE_CACHE_DIR'] ) ) { + $env['WP_CLI_CACHE_DIR'] = $this->variables['SUITE_CACHE_DIR']; + } + + if ( isset( $this->variables['RUN_DIR'] ) ) { + $cwd = "{$this->variables['RUN_DIR']}/{$path}"; + } else { + $cwd = null; + } + + return Process::create( $command, $cwd, $env ); + } + + /** + * Start a background process. Will automatically be closed when the tests finish. + */ + public function background_proc( $cmd ) { + $descriptors = array( + 0 => STDIN, + 1 => array( 'pipe', 'w' ), + 2 => array( 'pipe', 'w' ), + ); + + $proc = proc_open( $cmd, $descriptors, $pipes, $this->variables['RUN_DIR'], self::get_process_env_variables() ); + + sleep(1); + + $status = proc_get_status( $proc ); + + if ( !$status['running'] ) { + throw new RuntimeException( stream_get_contents( $pipes[2] ) ); + } else { + $this->running_procs[] = $proc; + } + } + + public function move_files( $src, $dest ) { + rename( $this->variables['RUN_DIR'] . "/$src", $this->variables['RUN_DIR'] . "/$dest" ); + } + + public function add_line_to_wp_config( &$wp_config_code, $line ) { + $token = "/* That's all, stop editing!"; + + $wp_config_code = str_replace( $token, "$line\n\n$token", $wp_config_code ); + } + + public function download_wp( $subdir = '' ) { + $dest_dir = $this->variables['RUN_DIR'] . "/$subdir"; + + if ( $subdir ) { + mkdir( $dest_dir ); + } + + $this->proc( Utils\esc_cmd( "cp -r %s/* %s", self::$cache_dir, $dest_dir ) )->run_check(); + + // disable emailing + mkdir( $dest_dir . '/wp-content/mu-plugins' ); + copy( __DIR__ . '/../extra/no-mail.php', $dest_dir . '/wp-content/mu-plugins/no-mail.php' ); + } + + public function create_config( $subdir = '' ) { + $params = self::$db_settings; + // Replaces all characters that are not alphanumeric or an underscore into an underscore. + $params['dbprefix'] = $subdir ? preg_replace( '#[^a-zA-Z\_0-9]#', '_', $subdir ) : 'wp_'; + + $params['skip-salts'] = true; + $this->proc( 'wp core config', $params, $subdir )->run_check(); + } + + public function install_wp( $subdir = '' ) { + $this->create_db(); + $this->create_run_dir(); + $this->download_wp( $subdir ); + + $this->create_config( $subdir ); + + $install_args = array( + 'url' => 'http://example.com', + 'title' => 'WP CLI Site', + 'admin_user' => 'admin', + 'admin_email' => 'admin@example.com', + 'admin_password' => 'password1' + ); + + $this->proc( 'wp core install', $install_args, $subdir )->run_check(); + } +} + diff --git a/features/bootstrap/Process.php b/features/bootstrap/Process.php new file mode 100644 index 000000000..74939c5d0 --- /dev/null +++ b/features/bootstrap/Process.php @@ -0,0 +1,75 @@ +<?php + +namespace WP_CLI; + +/** + * Run a system process, and learn what happened. + */ +class Process { + + /** + * @param string $command Command to execute. + * @param string $cwd Directory to execute the command in. + * @param array $env Environment variables to set when running the command. + */ + public static function create( $command, $cwd = null, $env = array() ) { + $proc = new self; + + $proc->command = $command; + $proc->cwd = $cwd; + $proc->env = $env; + + return $proc; + } + + private $command, $cwd, $env; + + private function __construct() {} + + /** + * Run the command. + * + * @return ProcessRun + */ + public function run() { + $cwd = $this->cwd; + + $descriptors = array( + 0 => STDIN, + 1 => array( 'pipe', 'w' ), + 2 => array( 'pipe', 'w' ), + ); + + $proc = proc_open( $this->command, $descriptors, $pipes, $cwd, $this->env ); + + $stdout = stream_get_contents( $pipes[1] ); + fclose( $pipes[1] ); + + $stderr = stream_get_contents( $pipes[2] ); + fclose( $pipes[2] ); + + return new ProcessRun( array( + 'stdout' => $stdout, + 'stderr' => $stderr, + 'return_code' => proc_close( $proc ), + 'command' => $this->command, + 'cwd' => $cwd, + 'env' => $this->env + ) ); + } + + /** + * Run the command, but throw an Exception on error. + * + * @return ProcessRun + */ + public function run_check() { + $r = $this->run(); + + if ( $r->return_code || !empty( $r->STDERR ) ) { + throw new \RuntimeException( $r ); + } + + return $r; + } +} diff --git a/features/bootstrap/ProcessRun.php b/features/bootstrap/ProcessRun.php new file mode 100644 index 000000000..4611cfbb6 --- /dev/null +++ b/features/bootstrap/ProcessRun.php @@ -0,0 +1,33 @@ +<?php + +namespace WP_CLI; + +/** + * Results of an executed command. + */ +class ProcessRun { + + /** + * @var array $props Properties of executed command. + */ + public function __construct( $props ) { + foreach ( $props as $key => $value ) { + $this->$key = $value; + } + } + + /** + * Return properties of executed command as a string. + * + * @return string + */ + public function __toString() { + $out = "$ $this->command\n"; + $out .= "$this->stdout\n$this->stderr"; + $out .= "cwd: $this->cwd\n"; + $out .= "exit status: $this->return_code"; + + return $out; + } + +} diff --git a/features/bootstrap/support.php b/features/bootstrap/support.php new file mode 100644 index 000000000..75ee5fbd3 --- /dev/null +++ b/features/bootstrap/support.php @@ -0,0 +1,194 @@ +<?php + +// Utility functions used by Behat steps + +function assertEquals( $expected, $actual ) { + if ( $expected != $actual ) { + throw new Exception( "Actual value: " . var_export( $actual, true ) ); + } +} + +function assertNotEquals( $expected, $actual ) { + if ( $expected == $actual ) { + throw new Exception( "Actual value: " . var_export( $actual, true ) ); + } +} + +function assertNumeric( $actual ) { + if ( !is_numeric( $actual ) ) { + throw new Exception( "Actual value: " . var_export( $actual, true ) ); + } +} + +function assertNotNumeric( $actual ) { + if ( is_numeric( $actual ) ) { + throw new Exception( "Actual value: " . var_export( $actual, true ) ); + } +} + +function checkString( $output, $expected, $action, $message = false ) { + switch ( $action ) { + + case 'be': + $r = $expected === rtrim( $output, "\n" ); + break; + + case 'contain': + $r = false !== strpos( $output, $expected ); + break; + + case 'not contain': + $r = false === strpos( $output, $expected ); + break; + + default: + throw new Behat\Behat\Exception\PendingException(); + } + + if ( !$r ) { + if ( false === $message ) + $message = $output; + throw new Exception( $message ); + } +} + +function compareTables( $expected_rows, $actual_rows, $output ) { + // the first row is the header and must be present + if ( $expected_rows[0] != $actual_rows[0] ) { + throw new \Exception( $output ); + } + + unset( $actual_rows[0] ); + unset( $expected_rows[0] ); + + $missing_rows = array_diff( $expected_rows, $actual_rows ); + if ( !empty( $missing_rows ) ) { + throw new \Exception( $output ); + } +} + +function compareContents( $expected, $actual ) { + if ( gettype( $expected ) != gettype( $actual ) ) { + return false; + } + + if ( is_object( $expected ) ) { + foreach ( get_object_vars( $expected ) as $name => $value ) { + if ( ! compareContents( $value, $actual->$name ) ) + return false; + } + } else if ( is_array( $expected ) ) { + foreach ( $expected as $key => $value ) { + if ( ! compareContents( $value, $actual[$key] ) ) + return false; + } + } else { + return $expected === $actual; + } + + return true; +} + +/** + * Compare two strings containing JSON to ensure that @a $actualJson contains at + * least what the JSON string @a $expectedJson contains. + * + * @return whether or not @a $actualJson contains @a $expectedJson + * @retval true @a $actualJson contains @a $expectedJson + * @retval false @a $actualJson does not contain @a $expectedJson + * + * @param[in] $actualJson the JSON string to be tested + * @param[in] $expectedJson the expected JSON string + * + * Examples: + * expected: {'a':1,'array':[1,3,5]} + * + * 1 ) + * actual: {'a':1,'b':2,'c':3,'array':[1,2,3,4,5]} + * return: true + * + * 2 ) + * actual: {'b':2,'c':3,'array':[1,2,3,4,5]} + * return: false + * element 'a' is missing from the root object + * + * 3 ) + * actual: {'a':0,'b':2,'c':3,'array':[1,2,3,4,5]} + * return: false + * the value of element 'a' is not 1 + * + * 4 ) + * actual: {'a':1,'b':2,'c':3,'array':[1,2,4,5]} + * return: false + * the contents of 'array' does not include 3 + */ +function checkThatJsonStringContainsJsonString( $actualJson, $expectedJson ) { + $actualValue = json_decode( $actualJson ); + $expectedValue = json_decode( $expectedJson ); + + if ( !$actualValue ) { + return false; + } + + return compareContents( $expectedValue, $actualValue ); +} + +/** + * Compare two strings to confirm $actualCSV contains $expectedCSV + * Both strings are expected to have headers for their CSVs. + * $actualCSV must match all data rows in $expectedCSV + * + * @param string A CSV string + * @param array A nested array of values + * @return bool Whether $actualCSV contains $expectedCSV + */ +function checkThatCsvStringContainsValues( $actualCSV, $expectedCSV ) { + $actualCSV = array_map( 'str_getcsv', explode( PHP_EOL, $actualCSV ) ); + + if ( empty( $actualCSV ) ) + return false; + + // Each sample must have headers + $actualHeaders = array_values( array_shift( $actualCSV ) ); + $expectedHeaders = array_values( array_shift( $expectedCSV ) ); + + // Each expectedCSV must exist somewhere in actualCSV in the proper column + $expectedResult = 0; + foreach ( $expectedCSV as $expected_row ) { + $expected_row = array_combine( $expectedHeaders, $expected_row ); + foreach ( $actualCSV as $actual_row ) { + + if ( count( $actualHeaders ) != count( $actual_row ) ) + continue; + + $actual_row = array_intersect_key( array_combine( $actualHeaders, $actual_row ), $expected_row ); + if ( $actual_row == $expected_row ) + $expectedResult++; + } + } + + return $expectedResult >= count( $expectedCSV ); +} + +/** + * Compare two strings containing YAML to ensure that @a $actualYaml contains at + * least what the YAML string @a $expectedYaml contains. + * + * @return whether or not @a $actualYaml contains @a $expectedJson + * @retval true @a $actualYaml contains @a $expectedJson + * @retval false @a $actualYaml does not contain @a $expectedJson + * + * @param[in] $actualYaml the YAML string to be tested + * @param[in] $expectedYaml the expected YAML string + */ +function checkThatYamlStringContainsYamlString( $actualYaml, $expectedYaml ) { + $actualValue = spyc_load( $actualYaml ); + $expectedValue = spyc_load( $expectedYaml ); + + if ( !$actualValue ) { + return false; + } + + return compareContents( $expectedValue, $actualValue ); +} + diff --git a/features/bootstrap/utils.php b/features/bootstrap/utils.php new file mode 100644 index 000000000..4143e277e --- /dev/null +++ b/features/bootstrap/utils.php @@ -0,0 +1,860 @@ +<?php + +// Utilities that do NOT depend on WordPress code. + +namespace WP_CLI\Utils; + +use \Composer\Semver\Comparator; +use \Composer\Semver\Semver; +use \WP_CLI; +use \WP_CLI\Dispatcher; +use \WP_CLI\Iterators\Transform; + +function inside_phar() { + return 0 === strpos( WP_CLI_ROOT, 'phar://' ); +} + +// Files that need to be read by external programs have to be extracted from the Phar archive. +function extract_from_phar( $path ) { + if ( ! inside_phar() ) { + return $path; + } + + $fname = basename( $path ); + + $tmp_path = get_temp_dir() . "wp-cli-$fname"; + + copy( $path, $tmp_path ); + + register_shutdown_function( function() use ( $tmp_path ) { + @unlink( $tmp_path ); + } ); + + return $tmp_path; +} + +function load_dependencies() { + if ( inside_phar() ) { + if ( file_exists( WP_CLI_ROOT . '/vendor/autoload.php' ) ) { + require WP_CLI_ROOT . '/vendor/autoload.php'; + } elseif ( file_exists( dirname( dirname( WP_CLI_ROOT ) ) . '/autoload.php' ) ) { + require dirname( dirname( WP_CLI_ROOT ) ) . '/autoload.php'; + } + return; + } + + $has_autoload = false; + + foreach ( get_vendor_paths() as $vendor_path ) { + if ( file_exists( $vendor_path . '/autoload.php' ) ) { + require $vendor_path . '/autoload.php'; + $has_autoload = true; + break; + } + } + + if ( !$has_autoload ) { + fputs( STDERR, "Internal error: Can't find Composer autoloader.\nTry running: composer install\n" ); + exit(3); + } +} + +function get_vendor_paths() { + $vendor_paths = array( + WP_CLI_ROOT . '/../../../vendor', // part of a larger project / installed via Composer (preferred) + WP_CLI_ROOT . '/vendor', // top-level project / installed as Git clone + ); + $maybe_composer_json = WP_CLI_ROOT . '/../../../composer.json'; + if ( file_exists( $maybe_composer_json ) && is_readable( $maybe_composer_json ) ) { + $composer = json_decode( file_get_contents( $maybe_composer_json ) ); + if ( ! empty( $composer->config ) && ! empty( $composer->config->{'vendor-dir'} ) ) { + array_unshift( $vendor_paths, WP_CLI_ROOT . '/../../../' . $composer->config->{'vendor-dir'} ); + } + } + return $vendor_paths; +} + +// Using require() directly inside a class grants access to private methods to the loaded code +function load_file( $path ) { + require_once $path; +} + +function load_command( $name ) { + $path = WP_CLI_ROOT . "/php/commands/$name.php"; + + if ( is_readable( $path ) ) { + include_once $path; + } +} + +function load_all_commands() { + $cmd_dir = WP_CLI_ROOT . '/php/commands'; + + $iterator = new \DirectoryIterator( $cmd_dir ); + + foreach ( $iterator as $filename ) { + if ( '.php' != substr( $filename, -4 ) ) + continue; + + include_once "$cmd_dir/$filename"; + } +} + +/** + * Like array_map(), except it returns a new iterator, instead of a modified array. + * + * Example: + * + * $arr = array('Football', 'Socker'); + * + * $it = iterator_map($arr, 'strtolower', function($val) { + * return str_replace('foo', 'bar', $val); + * }); + * + * foreach ( $it as $val ) { + * var_dump($val); + * } + * + * @param array|object Either a plain array or another iterator + * @param callback The function to apply to an element + * @return object An iterator that applies the given callback(s) + */ +function iterator_map( $it, $fn ) { + if ( is_array( $it ) ) { + $it = new \ArrayIterator( $it ); + } + + if ( !method_exists( $it, 'add_transform' ) ) { + $it = new Transform( $it ); + } + + foreach ( array_slice( func_get_args(), 1 ) as $fn ) { + $it->add_transform( $fn ); + } + + return $it; +} + +/** + * Search for file by walking up the directory tree until the first file is found or until $stop_check($dir) returns true + * @param string|array The files (or file) to search for + * @param string|null The directory to start searching from; defaults to CWD + * @param callable Function which is passed the current dir each time a directory level is traversed + * @return null|string Null if the file was not found + */ +function find_file_upward( $files, $dir = null, $stop_check = null ) { + $files = (array) $files; + if ( is_null( $dir ) ) { + $dir = getcwd(); + } + while ( @is_readable( $dir ) ) { + // Stop walking up when the supplied callable returns true being passed the $dir + if ( is_callable( $stop_check ) && call_user_func( $stop_check, $dir ) ) { + return null; + } + + foreach ( $files as $file ) { + $path = $dir . DIRECTORY_SEPARATOR . $file; + if ( file_exists( $path ) ) { + return $path; + } + } + + $parent_dir = dirname( $dir ); + if ( empty($parent_dir) || $parent_dir === $dir ) { + break; + } + $dir = $parent_dir; + } + return null; +} + +function is_path_absolute( $path ) { + // Windows + if ( isset($path[1]) && ':' === $path[1] ) + return true; + + return $path[0] === '/'; +} + +/** + * Composes positional arguments into a command string. + * + * @param array + * @return string + */ +function args_to_str( $args ) { + return ' ' . implode( ' ', array_map( 'escapeshellarg', $args ) ); +} + +/** + * Composes associative arguments into a command string. + * + * @param array + * @return string + */ +function assoc_args_to_str( $assoc_args ) { + $str = ''; + + foreach ( $assoc_args as $key => $value ) { + if ( true === $value ) { + $str .= " --$key"; + } elseif( is_array( $value ) ) { + foreach( $value as $_ => $v ) { + $str .= assoc_args_to_str( array( $key => $v ) ); + } + } else { + $str .= " --$key=" . escapeshellarg( $value ); + } + } + + return $str; +} + +/** + * Given a template string and an arbitrary number of arguments, + * returns the final command, with the parameters escaped. + */ +function esc_cmd( $cmd ) { + if ( func_num_args() < 2 ) + trigger_error( 'esc_cmd() requires at least two arguments.', E_USER_WARNING ); + + $args = func_get_args(); + + $cmd = array_shift( $args ); + + return vsprintf( $cmd, array_map( 'escapeshellarg', $args ) ); +} + +function locate_wp_config() { + static $path; + + if ( null === $path ) { + if ( file_exists( ABSPATH . 'wp-config.php' ) ) + $path = ABSPATH . 'wp-config.php'; + elseif ( file_exists( ABSPATH . '../wp-config.php' ) && ! file_exists( ABSPATH . '/../wp-settings.php' ) ) + $path = ABSPATH . '../wp-config.php'; + else + $path = false; + + if ( $path ) + $path = realpath( $path ); + } + + return $path; +} + +function wp_version_compare( $since, $operator ) { + return version_compare( str_replace( array( '-src' ), '', $GLOBALS['wp_version'] ), $since, $operator ); +} + +/** + * Render a collection of items as an ASCII table, JSON, CSV, YAML, list of ids, or count. + * + * Given a collection of items with a consistent data structure: + * + * ``` + * $items = array( + * array( + * 'key' => 'foo', + * 'value' => 'bar', + * ) + * ); + * ``` + * + * Render `$items` as an ASCII table: + * + * ``` + * WP_CLI\Utils\format_items( 'table', $items, array( 'key', 'value' ) ); + * + * # +-----+-------+ + * # | key | value | + * # +-----+-------+ + * # | foo | bar | + * # +-----+-------+ + * ``` + * + * Or render `$items` as YAML: + * + * ``` + * WP_CLI\Utils\format_items( 'yaml', $items, array( 'key', 'value' ) ); + * + * # --- + * # - + * # key: foo + * # value: bar + * ``` + * + * @access public + * @category Output + * + * @param string $format Format to use: 'table', 'json', 'csv', 'yaml', 'ids', 'count' + * @param array $items An array of items to output. + * @param array|string $fields Named fields for each item of data. Can be array or comma-separated list. + * @return null + */ +function format_items( $format, $items, $fields ) { + $assoc_args = compact( 'format', 'fields' ); + $formatter = new \WP_CLI\Formatter( $assoc_args ); + $formatter->display_items( $items ); +} + +/** + * Write data as CSV to a given file. + * + * @access public + * + * @param resource $fd File descriptor + * @param array $rows Array of rows to output + * @param array $headers List of CSV columns (optional) + */ +function write_csv( $fd, $rows, $headers = array() ) { + if ( ! empty( $headers ) ) { + fputcsv( $fd, $headers ); + } + + foreach ( $rows as $row ) { + if ( ! empty( $headers ) ) { + $row = pick_fields( $row, $headers ); + } + + fputcsv( $fd, array_values( $row ) ); + } +} + +/** + * Pick fields from an associative array or object. + * + * @param array|object Associative array or object to pick fields from + * @param array List of fields to pick + * @return array + */ +function pick_fields( $item, $fields ) { + $item = (object) $item; + + $values = array(); + + foreach ( $fields as $field ) { + $values[ $field ] = isset( $item->$field ) ? $item->$field : null; + } + + return $values; +} + +/** + * Launch system's $EDITOR for the user to edit some text. + * + * @access public + * @category Input + * + * @param string $content Some form of text to edit (e.g. post content) + * @return string|bool Edited text, if file is saved from editor; false, if no change to file. + */ +function launch_editor_for_input( $input, $filename = 'WP-CLI' ) { + + $tmpdir = get_temp_dir(); + + do { + $tmpfile = basename( $filename ); + $tmpfile = preg_replace( '|\.[^.]*$|', '', $tmpfile ); + $tmpfile .= '-' . substr( md5( rand() ), 0, 6 ); + $tmpfile = $tmpdir . $tmpfile . '.tmp'; + $fp = @fopen( $tmpfile, 'x' ); + if ( ! $fp && is_writable( $tmpdir ) && file_exists( $tmpfile ) ) { + $tmpfile = ''; + continue; + } + if ( $fp ) { + fclose( $fp ); + } + } while( ! $tmpfile ); + + if ( ! $tmpfile ) { + \WP_CLI::error( 'Error creating temporary file.' ); + } + + $output = ''; + file_put_contents( $tmpfile, $input ); + + $editor = getenv( 'EDITOR' ); + if ( !$editor ) { + if ( isset( $_SERVER['OS'] ) && false !== strpos( $_SERVER['OS'], 'indows' ) ) + $editor = 'notepad'; + else + $editor = 'vi'; + } + + $descriptorspec = array( STDIN, STDOUT, STDERR ); + $process = proc_open( "$editor " . escapeshellarg( $tmpfile ), $descriptorspec, $pipes ); + $r = proc_close( $process ); + if ( $r ) { + exit( $r ); + } + + $output = file_get_contents( $tmpfile ); + + unlink( $tmpfile ); + + if ( $output === $input ) + return false; + + return $output; +} + +/** + * @param string MySQL host string, as defined in wp-config.php + * @return array + */ +function mysql_host_to_cli_args( $raw_host ) { + $assoc_args = array(); + + $host_parts = explode( ':', $raw_host ); + if ( count( $host_parts ) == 2 ) { + list( $assoc_args['host'], $extra ) = $host_parts; + $extra = trim( $extra ); + if ( is_numeric( $extra ) ) { + $assoc_args['port'] = intval( $extra ); + $assoc_args['protocol'] = 'tcp'; + } else if ( $extra !== '' ) { + $assoc_args['socket'] = $extra; + } + } else { + $assoc_args['host'] = $raw_host; + } + + return $assoc_args; +} + +function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) { + if ( !$descriptors ) + $descriptors = array( STDIN, STDOUT, STDERR ); + + if ( isset( $assoc_args['host'] ) ) { + $assoc_args = array_merge( $assoc_args, mysql_host_to_cli_args( $assoc_args['host'] ) ); + } + + $pass = $assoc_args['pass']; + unset( $assoc_args['pass'] ); + + $old_pass = getenv( 'MYSQL_PWD' ); + putenv( 'MYSQL_PWD=' . $pass ); + + $final_cmd = $cmd . assoc_args_to_str( $assoc_args ); + + $proc = proc_open( $final_cmd, $descriptors, $pipes ); + if ( !$proc ) + exit(1); + + $r = proc_close( $proc ); + + putenv( 'MYSQL_PWD=' . $old_pass ); + + if ( $r ) exit( $r ); +} + +/** + * Render PHP or other types of files using Mustache templates. + * + * IMPORTANT: Automatic HTML escaping is disabled! + */ +function mustache_render( $template_name, $data = array() ) { + // Transform absolute path to relative path inside of Phar + if ( inside_phar() && 0 === stripos( $template_name, 'phar://' ) ) { + $search = ''; + $replace = ''; + if ( file_exists( WP_CLI_ROOT . '/vendor/autoload.php' ) ) { + $search = dirname( __DIR__ ); + $replace = WP_CLI_ROOT; + } elseif ( file_exists( dirname( dirname( WP_CLI_ROOT ) ) . '/autoload.php' ) ) { + $search = dirname( dirname( dirname( __DIR__ ) ) ); + $replace = dirname( dirname( dirname( WP_CLI_ROOT ) ) ); + } + $template_name = str_replace( $search, $replace, $template_name ); + } + if ( ! file_exists( $template_name ) ) + $template_name = WP_CLI_ROOT . "/templates/$template_name"; + + $template = file_get_contents( $template_name ); + + $m = new \Mustache_Engine( array( + 'escape' => function ( $val ) { return $val; } + ) ); + + return $m->render( $template, $data ); +} + +/** + * Create a progress bar to display percent completion of a given operation. + * + * Progress bar is written to STDOUT, and disabled when command is piped. Progress + * advances with `$progress->tick()`, and completes with `$progress->finish()`. + * Process bar also indicates elapsed time and expected total time. + * + * ``` + * # `wp user generate` ticks progress bar each time a new user is created. + * # + * # $ wp user generate --count=500 + * # Generating users 22 % [=======> ] 0:05 / 0:23 + * + * $progress = \WP_CLI\Utils\make_progress_bar( 'Generating users', $count ); + * for ( $i = 0; $i < $count; $i++ ) { + * // uses wp_insert_user() to insert the user + * $progress->tick(); + * } + * $progress->finish(); + * ``` + * + * @access public + * @category Output + * + * @param string $message Text to display before the progress bar. + * @param integer $count Total number of ticks to be performed. + * @return cli\progress\Bar|WP_CLI\NoOp + */ +function make_progress_bar( $message, $count ) { + if ( \cli\Shell::isPiped() ) + return new \WP_CLI\NoOp; + + return new \cli\progress\Bar( $message, $count ); +} + +function parse_url( $url ) { + $url_parts = \parse_url( $url ); + + if ( !isset( $url_parts['scheme'] ) ) { + $url_parts = parse_url( 'http://' . $url ); + } + + return $url_parts; +} + +/** + * Check if we're running in a Windows environment (cmd.exe). + */ +function is_windows() { + return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; +} + +/** + * Replace magic constants in some PHP source code. + * + * @param string $source The PHP code to manipulate. + * @param string $path The path to use instead of the magic constants + */ +function replace_path_consts( $source, $path ) { + $replacements = array( + '__FILE__' => "'$path'", + '__DIR__' => "'" . dirname( $path ) . "'" + ); + + $old = array_keys( $replacements ); + $new = array_values( $replacements ); + + return str_replace( $old, $new, $source ); +} + +/** + * Make a HTTP request to a remote URL. + * + * Wraps the Requests HTTP library to ensure every request includes a cert. + * + * ``` + * # `wp core download` verifies the hash for a downloaded WordPress archive + * + * $md5_response = Utils\http_request( 'GET', $download_url . '.md5' ); + * if ( 20 != substr( $md5_response->status_code, 0, 2 ) ) { + * WP_CLI::error( "Couldn't access md5 hash for release (HTTP code {$response->status_code})" ); + * } + * ``` + * + * @access public + * + * @param string $method HTTP method (GET, POST, DELETE, etc.) + * @param string $url URL to make the HTTP request to. + * @param array $headers Add specific headers to the request. + * @param array $options + * @return object + */ +function http_request( $method, $url, $data = null, $headers = array(), $options = array() ) { + + $cert_path = '/rmccue/requests/library/Requests/Transport/cacert.pem'; + if ( inside_phar() ) { + // cURL can't read Phar archives + $options['verify'] = extract_from_phar( + WP_CLI_ROOT . '/vendor' . $cert_path ); + } else { + foreach( get_vendor_paths() as $vendor_path ) { + if ( file_exists( $vendor_path . $cert_path ) ) { + $options['verify'] = $vendor_path . $cert_path; + break; + } + } + if ( empty( $options['verify'] ) ){ + WP_CLI::error_log( "Cannot find SSL certificate." ); + } + } + + try { + $request = \Requests::request( $url, $headers, $data, $method, $options ); + return $request; + } catch( \Requests_Exception $ex ) { + // Handle SSL certificate issues gracefully + \WP_CLI::warning( $ex->getMessage() ); + $options['verify'] = false; + try { + return \Requests::request( $url, $headers, $data, $method, $options ); + } catch( \Requests_Exception $ex ) { + \WP_CLI::error( $ex->getMessage() ); + } + } +} + +/** + * Increments a version string using the "x.y.z-pre" format + * + * Can increment the major, minor or patch number by one + * If $new_version == "same" the version string is not changed + * If $new_version is not a known keyword, it will be used as the new version string directly + * + * @param string $current_version + * @param string $new_version + * @return string + */ +function increment_version( $current_version, $new_version ) { + // split version assuming the format is x.y.z-pre + $current_version = explode( '-', $current_version, 2 ); + $current_version[0] = explode( '.', $current_version[0] ); + + switch ( $new_version ) { + case 'same': + // do nothing + break; + + case 'patch': + $current_version[0][2]++; + + $current_version = array( $current_version[0] ); // drop possible pre-release info + break; + + case 'minor': + $current_version[0][1]++; + $current_version[0][2] = 0; + + $current_version = array( $current_version[0] ); // drop possible pre-release info + break; + + case 'major': + $current_version[0][0]++; + $current_version[0][1] = 0; + $current_version[0][2] = 0; + + $current_version = array( $current_version[0] ); // drop possible pre-release info + break; + + default: // not a keyword + $current_version = array( array( $new_version ) ); + break; + } + + // reconstruct version string + $current_version[0] = implode( '.', $current_version[0] ); + $current_version = implode( '-', $current_version ); + + return $current_version; +} + +/** + * Compare two version strings to get the named semantic version. + * + * @access public + * + * @param string $new_version + * @param string $original_version + * @return string $name 'major', 'minor', 'patch' + */ +function get_named_sem_ver( $new_version, $original_version ) { + + if ( ! Comparator::greaterThan( $new_version, $original_version ) ) { + return ''; + } + + $parts = explode( '-', $original_version ); + $bits = explode( '.', $parts[0] ); + $major = $bits[0]; + if ( isset( $bits[1] ) ) { + $minor = $bits[1]; + } + if ( isset( $bits[2] ) ) { + $patch = $bits[2]; + } + + if ( ! is_null( $minor ) && Semver::satisfies( $new_version, "{$major}.{$minor}.x" ) ) { + return 'patch'; + } else if ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) { + return 'minor'; + } else { + return 'major'; + } +} + +/** + * Return the flag value or, if it's not set, the $default value. + * + * Because flags can be negated (e.g. --no-quiet to negate --quiet), this + * function provides a safer alternative to using + * `isset( $assoc_args['quiet'] )` or similar. + * + * @access public + * @category Input + * + * @param array $assoc_args Arguments array. + * @param string $flag Flag to get the value. + * @param mixed $default Default value for the flag. Default: NULL + * @return mixed + */ +function get_flag_value( $assoc_args, $flag, $default = null ) { + return isset( $assoc_args[ $flag ] ) ? $assoc_args[ $flag ] : $default; +} + +/** + * Get the system's temp directory. Warns user if it isn't writable. + * + * @access public + * @category System + * + * @return string + */ +function get_temp_dir() { + static $temp = ''; + + $trailingslashit = function( $path ) { + return rtrim( $path ) . '/'; + }; + + if ( $temp ) + return $trailingslashit( $temp ); + + if ( function_exists( 'sys_get_temp_dir' ) ) { + $temp = sys_get_temp_dir(); + } else if ( ini_get( 'upload_tmp_dir' ) ) { + $temp = ini_get( 'upload_tmp_dir' ); + } else { + $temp = '/tmp/'; + } + + if ( ! @is_writable( $temp ) ) { + \WP_CLI::warning( "Temp directory isn't writable: {$temp}" ); + } + + return $trailingslashit( $temp ); +} + +/** + * Parse a SSH url for its host, port, and path. + * + * Similar to parse_url(), but adds support for defined SSH aliases. + * + * ``` + * host OR host/path/to/wordpress OR host:port/path/to/wordpress + * ``` + * + * @access public + * + * @return mixed + */ +function parse_ssh_url( $url, $component = -1 ) { + preg_match( '#^([^:/~]+)(:([\d]+))?((/|~)(.+))?$#', $url, $matches ); + $bits = array(); + foreach( array( + 1 => 'host', + 3 => 'port', + 4 => 'path', + ) as $i => $key ) { + if ( ! empty( $matches[ $i ] ) ) { + $bits[ $key ] = $matches[ $i ]; + } + } + switch ( $component ) { + case PHP_URL_HOST: + return isset( $bits['host'] ) ? $bits['host'] : null; + case PHP_URL_PATH: + return isset( $bits['path'] ) ? $bits['path'] : null; + case PHP_URL_PORT: + return isset( $bits['port'] ) ? $bits['port'] : null; + default: + return $bits; + } +} + +/** + * Report the results of the same operation against multiple resources. + * + * @access public + * @category Input + * + * @param string $noun Resource being affected (e.g. plugin) + * @param string $verb Type of action happening to the noun (e.g. activate) + * @param integer $total Total number of resource being affected. + * @param integer $successes Number of successful operations. + * @param integer $failures Number of failures. + */ +function report_batch_operation_results( $noun, $verb, $total, $successes, $failures ) { + $plural_noun = $noun . 's'; + if ( in_array( $verb, array( 'reset' ), true ) ) { + $past_tense_verb = $verb; + } else { + $past_tense_verb = 'e' === substr( $verb, -1 ) ? $verb . 'd' : $verb . 'ed'; + } + $past_tense_verb_upper = ucfirst( $past_tense_verb ); + if ( $failures ) { + if ( $successes ) { + WP_CLI::error( "Only {$past_tense_verb} {$successes} of {$total} {$plural_noun}." ); + } else { + WP_CLI::error( "No {$plural_noun} {$past_tense_verb}." ); + } + } else { + if ( $successes ) { + WP_CLI::success( "{$past_tense_verb_upper} {$successes} of {$total} {$plural_noun}." ); + } else { + $message = $total > 1 ? ucfirst( $plural_noun ) : ucfirst( $noun ); + WP_CLI::success( "{$message} already {$past_tense_verb}." ); + } + } +} + +/** + * Parse a string of command line arguments into an $argv-esqe variable. + * + * @access public + * @category Input + * + * @param string $arguments + * @return array + */ +function parse_str_to_argv( $arguments ) { + preg_match_all ('/(?<=^|\s)([\'"]?)(.+?)(?<!\\\\)\1(?=$|\s)/', $arguments, $matches ); + $argv = isset( $matches[0] ) ? $matches[0] : array(); + $argv = array_map( function( $arg ){ + foreach( array( '"', "'" ) as $char ) { + if ( $char === substr( $arg, 0, 1 ) && $char === substr( $arg, -1 ) ) { + $arg = substr( $arg, 1, -1 ); + break; + } + } + return $arg; + }, $argv ); + return $argv; +} + +/** + * Locale-independent version of basename() + * + * @access public + * + * @param string $path + * @param string $suffix + * @return string + */ +function basename( $path, $suffix = '' ) { + return urldecode( \basename( str_replace( array( '%2F', '%5C' ), '/', urlencode( $path ) ), $suffix ) ); +} diff --git a/features/extra/no-mail.php b/features/extra/no-mail.php new file mode 100644 index 000000000..de7a42272 --- /dev/null +++ b/features/extra/no-mail.php @@ -0,0 +1,7 @@ +<?php + +function wp_mail( $to ) { + // Log for testing purposes + WP_CLI::log( "WP-CLI test suite: Sent email to {$to}." ); +} + diff --git a/features/install-wp-tests.feature b/features/install-wp-tests.feature deleted file mode 100644 index b233e7d50..000000000 --- a/features/install-wp-tests.feature +++ /dev/null @@ -1,545 +0,0 @@ -# Note: You need to execute the mysql command `GRANT ALL PRIVILEGES ON wp_cli_test_scaffold.* TO "wp_cli_test"@"localhost" IDENTIFIED BY "{DB_PASSWORD}";` for these tests to work locally. -Feature: Scaffold install-wp-tests.sh tests - - # TODO: Fix this on Windows. Fails because /usr/bin/env bash is not available. - @skip-windows - Scenario: Help should be displayed - Given a WP install - And I run `wp plugin path` - And save STDOUT as {PLUGIN_DIR} - And I run `wp scaffold plugin hello-world` - - When I try `/usr/bin/env bash {PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh` - Then STDOUT should contain: - """ - Usage: - """ - And the return code should be 1 - - @less-than-php-8.0 @require-php-7.0 @require-mysql - Scenario: Install latest version of WordPress - Given a WP install - And a affirmative-response file: - """ - Y - """ - And a negative-response file: - """ - No - """ - And a get-phpunit-phar-url.php file: - """ - <?php - $version = 4; - if(PHP_VERSION_ID >= 50600) { - $version = 5; - } - if(PHP_VERSION_ID >= 70000) { - $version = 6; - } - if(PHP_VERSION_ID >= 70100) { - $version = 7; - } - if(PHP_VERSION_ID >= 80000) { - $version = 9; - } - echo "https://phar.phpunit.de/phpunit-{$version}.phar"; - """ - And I run `wp eval-file get-phpunit-phar-url.php --skip-wordpress` - And save STDOUT as {PHPUNIT_PHAR_URL} - And I run `curl -sS -L --fail -o phpunit {PHPUNIT_PHAR_URL}` - And I run `wp plugin path` - And save STDOUT as {PLUGIN_DIR} - And I run `wp scaffold plugin hello-world` - # This throws a warning for the password provided via command line. - And I try `mysql -u{DB_USER} -p{DB_PASSWORD} -h{MYSQL_HOST} -P{MYSQL_PORT} --protocol=tcp -e "DROP DATABASE IF EXISTS wp_cli_test_scaffold"` - - When I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_CORE_DIR={RUN_DIR}/wordpress /usr/bin/env bash {PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh wp_cli_test_scaffold {DB_USER} {DB_PASSWORD} {DB_HOST} latest` - Then the return code should be 0 - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - data - """ - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - includes - """ - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - wp-tests-config.php - """ - And the {RUN_DIR}/wordpress directory should contain: - """ - index.php - license.txt - readme.html - wp-activate.php - wp-admin - wp-blog-header.php - wp-comments-post.php - wp-config-sample.php - wp-content - wp-cron.php - wp-includes - wp-links-opml.php - wp-load.php - wp-login.php - wp-mail.php - wp-settings.php - wp-signup.php - wp-trackback.php - xmlrpc.php - """ - And the {PLUGIN_DIR}/hello-world/phpunit.xml.dist file should exist - And STDERR should contain: - """ - install_test_suite - """ - - # This throws a warning for the password provided via command line. - When I try `mysql -u{DB_USER} -p{DB_PASSWORD} -h{MYSQL_HOST} -P{MYSQL_PORT} --protocol=tcp -e "SHOW DATABASES"` - Then STDOUT should contain: - """ - wp_cli_test_scaffold - """ - - When I run `mkdir polyfills && composer init --name=test/package --require="yoast/phpunit-polyfills:^1" --no-interaction --quiet --working-dir=polyfills` - Then the return code should be 0 - - When I run `composer install --no-interaction --working-dir=polyfills --quiet` - Then the return code should be 0 - - When I run `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_TESTS_PHPUNIT_POLYFILLS_PATH={RUN_DIR}/polyfills/vendor/yoast/phpunit-polyfills php phpunit -c {PLUGIN_DIR}/hello-world/phpunit.xml.dist` - Then the return code should be 0 - - When I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_CORE_DIR={RUN_DIR}/wordpress /usr/bin/env bash {PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh wp_cli_test_scaffold {DB_USER} {DB_PASSWORD} {DB_HOST} latest < affirmative-response` - Then the return code should be 0 - And STDERR should contain: - """ - Reinstalling - """ - And STDOUT should contain: - """ - Database (wp_cli_test_scaffold) recreated. - """ - - When I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_CORE_DIR={RUN_DIR}/wordpress /usr/bin/env bash {PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh wp_cli_test_scaffold {DB_USER} {DB_PASSWORD} {DB_HOST} latest < negative-response` - Then the return code should be 0 - And STDERR should contain: - """ - Reinstalling - """ - And STDOUT should contain: - """ - Leaving the existing database (wp_cli_test_scaffold) in place - """ - - @require-php-8.0 @less-than-wp-5.8 - Scenario: Install latest version of WordPress on PHP 8.0+ and WordPress less then 5.8 - Given a WP install - And a affirmative-response file: - """ - Y - """ - And a negative-response file: - """ - No - """ - And a get-phpunit-phar-url.php file: - """ - <?php - $version = 4; - if(PHP_VERSION_ID >= 50600) { - $version = 5; - } - if(PHP_VERSION_ID >= 70000) { - $version = 6; - } - if(PHP_VERSION_ID >= 70100) { - $version = 7; - } - if(PHP_VERSION_ID >= 80000) { - $version = 9; - } - echo "https://phar.phpunit.de/phpunit-{$version}.phar"; - """ - And I run `wp eval-file get-phpunit-phar-url.php --skip-wordpress` - And save STDOUT as {PHPUNIT_PHAR_URL} - And I run `curl -sS --fail -L -o phpunit {PHPUNIT_PHAR_URL}` - And I run `wp plugin path` - And save STDOUT as {PLUGIN_DIR} - And I run `wp scaffold plugin hello-world` - # This throws a warning for the password provided via command line. - And I try `mysql -u{DB_USER} -p{DB_PASSWORD} -h{MYSQL_HOST} -P{MYSQL_PORT} --protocol=tcp -e "DROP DATABASE IF EXISTS wp_cli_test_scaffold"` - - When I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_CORE_DIR={RUN_DIR}/wordpress /usr/bin/env bash {PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh wp_cli_test_scaffold {DB_USER} {DB_PASSWORD} {DB_HOST} latest` - Then the return code should be 0 - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - data - """ - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - includes - """ - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - wp-tests-config.php - """ - And the {RUN_DIR}/wordpress directory should contain: - """ - index.php - license.txt - readme.html - wp-activate.php - wp-admin - wp-blog-header.php - wp-comments-post.php - wp-config-sample.php - wp-content - wp-cron.php - wp-includes - wp-links-opml.php - wp-load.php - wp-login.php - wp-mail.php - wp-settings.php - wp-signup.php - wp-trackback.php - xmlrpc.php - """ - And the {PLUGIN_DIR}/hello-world/phpunit.xml.dist file should exist - And STDERR should contain: - """ - install_test_suite - """ - - # This throws a warning for the password provided via command line. - When I try `mysql -u{DB_USER} -p{DB_PASSWORD} -h{MYSQL_HOST} -P{MYSQL_PORT} --protocol=tcp -e "SHOW DATABASES"` - Then STDOUT should contain: - """ - wp_cli_test_scaffold - """ - - When I run `mkdir polyfills && composer init --name=test/package --require="yoast/phpunit-polyfills:^1" --no-interaction --quiet --working-dir=polyfills` - Then the return code should be 0 - - When I run `composer install --no-interaction --working-dir=polyfills --quiet` - Then the return code should be 0 - - When I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_TESTS_PHPUNIT_POLYFILLS_PATH={RUN_DIR}/polyfills/vendor/yoast/phpunit-polyfills php phpunit -c {PLUGIN_DIR}/hello-world/phpunit.xml.dist` - Then the return code should be 1 - And STDOUT should contain: - """ - Looks like you're using PHPUnit 9.5. - """ - And STDOUT should contain: - """ - WordPress requires at least PHPUnit 5. - """ - And STDOUT should contain: - """ - and is currently only compatible with PHPUnit up to 7.x. - """ - - When I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_CORE_DIR={RUN_DIR}/wordpress /usr/bin/env bash {PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh wp_cli_test_scaffold {DB_USER} {DB_PASSWORD} {DB_HOST} latest < affirmative-response` - Then the return code should be 0 - And STDERR should contain: - """ - Reinstalling - """ - And STDOUT should contain: - """ - Database (wp_cli_test_scaffold) recreated. - """ - - When I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_CORE_DIR={RUN_DIR}/wordpress /usr/bin/env bash {PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh wp_cli_test_scaffold {DB_USER} {DB_PASSWORD} {DB_HOST} latest < negative-response` - Then the return code should be 0 - And STDERR should contain: - """ - Reinstalling - """ - And STDOUT should contain: - """ - Leaving the existing database (wp_cli_test_scaffold) in place - """ - - @require-php-8.0 @require-wp-5.8 @require-mysql - Scenario: Install latest version of WordPress on PHP 8.0+ and WordPress above 5.8 - Given a WP install - And a affirmative-response file: - """ - Y - """ - And a negative-response file: - """ - No - """ - And a get-phpunit-phar-url.php file: - """ - <?php - $version = 4; - if(PHP_VERSION_ID >= 50600) { - $version = 5; - } - if(PHP_VERSION_ID >= 70000) { - $version = 6; - } - if(PHP_VERSION_ID >= 70100) { - $version = 7; - } - if(PHP_VERSION_ID >= 80000) { - $version = 9; - } - echo "https://phar.phpunit.de/phpunit-{$version}.phar"; - """ - And I run `wp eval-file get-phpunit-phar-url.php --skip-wordpress` - And save STDOUT as {PHPUNIT_PHAR_URL} - And I run `curl -sS -L --fail -o phpunit {PHPUNIT_PHAR_URL}` - And I run `wp plugin path` - And save STDOUT as {PLUGIN_DIR} - And I run `wp scaffold plugin hello-world` - # This throws a warning for the password provided via command line. - And I try `mysql -u{DB_USER} -p{DB_PASSWORD} -h{MYSQL_HOST} -P{MYSQL_PORT} --protocol=tcp -e "DROP DATABASE IF EXISTS wp_cli_test_scaffold"` - - When I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_CORE_DIR={RUN_DIR}/wordpress /usr/bin/env bash {PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh wp_cli_test_scaffold {DB_USER} {DB_PASSWORD} {DB_HOST} latest` - Then the return code should be 0 - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - data - """ - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - includes - """ - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - wp-tests-config.php - """ - And the {RUN_DIR}/wordpress directory should contain: - """ - index.php - license.txt - readme.html - wp-activate.php - wp-admin - wp-blog-header.php - wp-comments-post.php - wp-config-sample.php - wp-content - wp-cron.php - wp-includes - wp-links-opml.php - wp-load.php - wp-login.php - wp-mail.php - wp-settings.php - wp-signup.php - wp-trackback.php - xmlrpc.php - """ - And the {PLUGIN_DIR}/hello-world/phpunit.xml.dist file should exist - And STDERR should contain: - """ - install_test_suite - """ - - # This throws a warning for the password provided via command line. - When I try `mysql -u{DB_USER} -p{DB_PASSWORD} -h{MYSQL_HOST} -P{MYSQL_PORT} --protocol=tcp -e "SHOW DATABASES"` - Then STDOUT should contain: - """ - wp_cli_test_scaffold - """ - - When I run `mkdir polyfills && composer init --name=test/package --require="yoast/phpunit-polyfills:^1" --no-interaction --quiet --working-dir=polyfills` - Then the return code should be 0 - - When I run `composer install --no-interaction --working-dir=polyfills --quiet` - Then the return code should be 0 - - When I run `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_TESTS_PHPUNIT_POLYFILLS_PATH={RUN_DIR}/polyfills/vendor/yoast/phpunit-polyfills php phpunit -c {PLUGIN_DIR}/hello-world/phpunit.xml.dist` - Then the return code should be 0 - - When I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_CORE_DIR={RUN_DIR}/wordpress /usr/bin/env bash {PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh wp_cli_test_scaffold {DB_USER} {DB_PASSWORD} {DB_HOST} latest < affirmative-response` - Then the return code should be 0 - And STDERR should contain: - """ - Reinstalling - """ - And STDOUT should contain: - """ - Database (wp_cli_test_scaffold) recreated. - """ - - When I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_CORE_DIR={RUN_DIR}/wordpress /usr/bin/env bash {PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh wp_cli_test_scaffold {DB_USER} {DB_PASSWORD} {DB_HOST} latest < negative-response` - Then the return code should be 0 - And STDERR should contain: - """ - Reinstalling - """ - And STDOUT should contain: - """ - Leaving the existing database (wp_cli_test_scaffold) in place - """ - - @require-php-7.4 @require-mysql - Scenario: Install WordPress from trunk - Given a WP install - And a get-phpunit-phar-url.php file: - """ - <?php - $version = 4; - if(PHP_VERSION_ID >= 50600) { - $version = 5; - } - if(PHP_VERSION_ID >= 70000) { - $version = 6; - } - if(PHP_VERSION_ID >= 70100) { - $version = 7; - } - if(PHP_VERSION_ID >= 80000) { - $version = 9; - } - echo "https://phar.phpunit.de/phpunit-{$version}.phar"; - """ - And I run `wp eval-file get-phpunit-phar-url.php --skip-wordpress` - And save STDOUT as {PHPUNIT_PHAR_URL} - And I run `curl -sS -f -L -o phpunit {PHPUNIT_PHAR_URL}` - And I run `wp plugin path` - And save STDOUT as {PLUGIN_DIR} - And I run `wp scaffold plugin hello-world` - # This throws a warning for the password provided via command line. - And I try `mysql -u{DB_USER} -p{DB_PASSWORD} -h{MYSQL_HOST} -P{MYSQL_PORT} --protocol=tcp -e "DROP DATABASE IF EXISTS wp_cli_test_scaffold"` - - When I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_CORE_DIR={RUN_DIR}/wordpress /usr/bin/env bash {PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh wp_cli_test_scaffold {DB_USER} {DB_PASSWORD} {DB_HOST} trunk` - Then the return code should be 0 - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - data - """ - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - includes - """ - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - wp-tests-config.php - """ - And the {RUN_DIR}/wordpress directory should contain: - """ - index.php - """ - - # WP 5.0+: js - - And the {RUN_DIR}/wordpress directory should contain: - """ - license.txt - readme.html - """ - - # WP 5.0+: styles - - And the {RUN_DIR}/wordpress directory should contain: - """ - wp-activate.php - wp-admin - wp-blog-header.php - wp-comments-post.php - wp-config-sample.php - wp-content - wp-cron.php - wp-includes - wp-links-opml.php - wp-load.php - wp-login.php - wp-mail.php - wp-settings.php - wp-signup.php - wp-trackback.php - xmlrpc.php - """ - And the contents of the {RUN_DIR}/wordpress/wp-includes/version.php file should match /\-(alpha|beta[0-9]+|RC[0-9]+)\-/ - And the {PLUGIN_DIR}/hello-world/phpunit.xml.dist file should exist - And STDERR should contain: - """ - install_test_suite - """ - - # This throws a warning for the password provided via command line. - When I try `mysql -u{DB_USER} -p{DB_PASSWORD} -h{MYSQL_HOST} -P{MYSQL_PORT} --protocol=tcp -e "SHOW DATABASES"` - Then STDOUT should contain: - """ - wp_cli_test_scaffold - """ - - When I run `composer init --no-interaction --quiet --name=wp-cli/test-scenario --require="yoast/phpunit-polyfills=^1.0.1" --working-dir={RUN_DIR}/wordpress-tests-lib` - Then the return code should be 0 - - When I run `composer install --no-interaction --quiet --working-dir={RUN_DIR}/wordpress-tests-lib` - Then the return code should be 0 - - When I run `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_TESTS_PHPUNIT_POLYFILLS_PATH={RUN_DIR}/wordpress-tests-lib/vendor/yoast/phpunit-polyfills php phpunit -c {PLUGIN_DIR}/hello-world/phpunit.xml.dist` - Then the return code should be 0 - - @require-mysql - Scenario: Install WordPress 3.7 and phpunit will not run - Given a WP install - And I run `wp plugin path` - And save STDOUT as {PLUGIN_DIR} - And I run `wp scaffold plugin hello-world` - # This throws a warning for the password provided via command line. - And I try `mysql -u{DB_USER} -p{DB_PASSWORD} -h{MYSQL_HOST} -P{MYSQL_PORT} --protocol=tcp -e "DROP DATABASE IF EXISTS wp_cli_test_scaffold"` - - When I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_CORE_DIR={RUN_DIR}/wordpress /usr/bin/env bash {PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh wp_cli_test_scaffold {DB_USER} {DB_PASSWORD} {DB_HOST} 3.7` - Then the return code should be 0 - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - data - """ - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - includes - """ - And the {RUN_DIR}/wordpress-tests-lib directory should contain: - """ - wp-tests-config.php - """ - And the {RUN_DIR}/wordpress directory should contain: - """ - index.php - license.txt - readme.html - wp-activate.php - wp-admin - wp-blog-header.php - wp-comments-post.php - wp-config-sample.php - wp-content - wp-cron.php - wp-includes - wp-links-opml.php - wp-load.php - wp-login.php - wp-mail.php - wp-settings.php - wp-signup.php - wp-trackback.php - xmlrpc.php - """ - And the {RUN_DIR}/wordpress/wp-includes/version.php file should contain: - """ - 3.7 - """ - And STDERR should contain: - """ - install_test_suite - """ - - # This throws a warning for the password provided via command line. - When I try `mysql -u{DB_USER} -p{DB_PASSWORD} -h{MYSQL_HOST} -P{MYSQL_PORT} --protocol=tcp -e "SHOW DATABASES"` - And STDOUT should contain: - """ - wp_cli_test_scaffold - """ diff --git a/features/scaffold-block.feature b/features/scaffold-block.feature deleted file mode 100644 index 3481741ea..000000000 --- a/features/scaffold-block.feature +++ /dev/null @@ -1,188 +0,0 @@ -Feature: WordPress block code scaffolding - - Background: - Given a WP install - And I run `wp scaffold plugin movies` - And I run `wp plugin path movies --dir` - And save STDOUT as {PLUGIN_DIR} - And I run `wp theme install twentytwelve --activate --force` - And I run `wp theme path twentytwelve --dir` - And save STDOUT as {THEME_DIR} - - Scenario: Scaffold a block with an invalid slug - When I try `wp scaffold block The_Godfather` - Then STDERR should be: - """ - Error: Invalid block slug specified. Block slugs can contain only lowercase alphanumeric characters or dashes, and start with a letter. - """ - - Scenario: Scaffold a block with a missing plugin and theme - When I try `wp scaffold block the-godfather` - Then STDERR should be: - """ - Error: No plugin or theme selected. - """ - - Scenario: Scaffold a block for an invalid plugin - When I try `wp scaffold block the-godfather --plugin=unknown` - Then STDERR should be: - """ - Error: Can't find 'unknown' plugin. - """ - - Scenario: Scaffold a block for an invalid plugin slug - When I try `wp scaffold block some-block --plugin=plugin.name.with.dots` - Then STDERR should contain: - """ - Error: Invalid plugin name specified. - """ - - Scenario: Scaffold a block for a specific plugin - When I run `wp scaffold block the-green-mile --plugin=movies` - Then the {PLUGIN_DIR}/blocks/the-green-mile.php file should exist - And the {PLUGIN_DIR}/blocks/the-green-mile.php file should contain: - """ - function the_green_mile_block_init() { - """ - And the {PLUGIN_DIR}/blocks/the-green-mile.php file should contain: - """ - index_js = 'the-green-mile/index.js'; - """ - And the {PLUGIN_DIR}/blocks/the-green-mile.php file should contain: - """ - $editor_css = 'the-green-mile/editor.css'; - """ - And the {PLUGIN_DIR}/blocks/the-green-mile.php file should contain: - """ - $style_css = 'the-green-mile/style.css'; - """ - And the {PLUGIN_DIR}/blocks/the-green-mile.php file should contain: - """ - register_block_type( - """ - And the {PLUGIN_DIR}/blocks/the-green-mile.php file should contain: - """ - 'movies/the-green-mile', - """ - And the {PLUGIN_DIR}/blocks/the-green-mile.php file should contain: - """ - add_action( 'init', 'the_green_mile_block_init' ); - """ - And the {PLUGIN_DIR}/blocks/the-green-mile/index.js file should exist - And the {PLUGIN_DIR}/blocks/the-green-mile/index.js file should contain: - """ - registerBlockType( 'movies/the-green-mile', { - """ - And the {PLUGIN_DIR}/blocks/the-green-mile/index.js file should contain: - """ - title: __( 'The green mile', 'movies' ), - """ - And the {PLUGIN_DIR}/blocks/the-green-mile/index.js file should contain: - """ - category: 'widgets', - """ - And the {PLUGIN_DIR}/blocks/the-green-mile/index.js file should contain: - """ - __( 'Hello from the editor!', 'movies' ) - """ - And the {PLUGIN_DIR}/blocks/the-green-mile/index.js file should contain: - """ - __( 'Hello from the saved content!', 'movies' ) - """ - And the {PLUGIN_DIR}/blocks/the-green-mile/editor.css file should exist - And the {PLUGIN_DIR}/blocks/the-green-mile/editor.css file should contain: - """ - .wp-block-movies-the-green-mile { - """ - And the {PLUGIN_DIR}/blocks/the-green-mile/style.css file should exist - And the {PLUGIN_DIR}/blocks/the-green-mile/style.css file should contain: - """ - .wp-block-movies-the-green-mile { - """ - And STDOUT should be: - """ - Success: Created block 'The green mile'. - """ - - Scenario: Scaffold a block with a specific title provided - When I run `wp scaffold block shawshank-redemption --plugin=movies --title="The Shawshank Redemption"` - Then the {PLUGIN_DIR}/blocks/shawshank-redemption/index.js file should contain: - """ - title: __( 'The Shawshank Redemption', 'movies' ), - """ - And STDOUT should be: - """ - Success: Created block 'The Shawshank Redemption'. - """ - - Scenario: Scaffold a block with a specific dashicon provided - When I run `wp scaffold block forrest-gump --plugin=movies --dashicon=movie` - Then the {PLUGIN_DIR}/blocks/forrest-gump/index.js file should contain: - """ - icon: 'movie', - """ - And STDOUT should be: - """ - Success: Created block 'Forrest gump'. - """ - - Scenario: Scaffold a block with a specific category provided - When I run `wp scaffold block pulp-fiction --plugin=movies --category=embed` - Then the {PLUGIN_DIR}/blocks/pulp-fiction/index.js file should contain: - """ - category: 'embed', - """ - And STDOUT should be: - """ - Success: Created block 'Pulp fiction'. - """ - - Scenario: Scaffold a block for an active theme - When I run `wp scaffold block fight-club --theme` - Then the {THEME_DIR}/blocks/fight-club.php file should exist - And the {THEME_DIR}/blocks/fight-club/index.js file should exist - And the {THEME_DIR}/blocks/fight-club/editor.css file should exist - And the {THEME_DIR}/blocks/fight-club/style.css file should exist - And STDOUT should be: - """ - Success: Created block 'Fight club'. - """ - - Scenario: Scaffold a block for an invalid theme - When I try `wp scaffold block intouchables --theme=unknown` - Then STDERR should be: - """ - Error: Can't find 'unknown' theme. - """ - - Scenario: Scaffold a block for a specific theme - When I run `wp scaffold block intouchables --theme=twentytwelve` - Then the {THEME_DIR}/blocks/intouchables.php file should exist - And the {THEME_DIR}/blocks/intouchables/index.js file should exist - And the {THEME_DIR}/blocks/intouchables/editor.css file should exist - And the {THEME_DIR}/blocks/intouchables/style.css file should exist - And STDOUT should be: - """ - Success: Created block 'Intouchables'. - """ - - Scenario: Plugin- or theme-specific functions are only used in the correct context - When I run `wp scaffold block plugin-block --plugin=movies` - And I run `wp scaffold block theme-block --theme=twentytwelve` - Then the {PLUGIN_DIR}/blocks/plugin-block.php file should contain: - """ - plugins_url - """ - And the {PLUGIN_DIR}/blocks/plugin-block.php file should not contain: - """ - get_stylesheet_directory - """ - And the {THEME_DIR}/blocks/theme-block.php file should contain: - """ - get_stylesheet_directory - """ - And the {THEME_DIR}/blocks/theme-block.php file should not contain: - """ - plugins_url - """ - diff --git a/features/scaffold-lint.feature b/features/scaffold-lint.feature deleted file mode 100644 index 2bc7c1b85..000000000 --- a/features/scaffold-lint.feature +++ /dev/null @@ -1,85 +0,0 @@ -Feature: Lint scaffolded code - - Background: - Given a WP install - And I run `wp plugin path` - And save STDOUT as {PLUGIN_DIR} - - # Create a helper plugin to install phpcs once for all scenarios - When I run `wp scaffold plugin phpcs-helper --skip-tests` - Then the return code should be 0 - - # Install coding standards - When I run `composer config --working-dir={PLUGIN_DIR}/phpcs-helper allow-plugins.dealerdirect/phpcodesniffer-composer-installer true` - Then the return code should be 0 - - When I run `composer require --dev --working-dir={PLUGIN_DIR}/phpcs-helper dealerdirect/phpcodesniffer-composer-installer wp-coding-standards/wpcs --no-interaction --quiet` - Then the return code should be 0 - - Scenario: Scaffold plugin and lint it - When I run `wp scaffold plugin test-plugin` - Then STDOUT should not be empty - And the {PLUGIN_DIR}/test-plugin/test-plugin.php file should exist - And the {PLUGIN_DIR}/test-plugin/.phpcs.xml.dist file should exist - - When I run `{PLUGIN_DIR}/phpcs-helper/vendor/bin/phpcs --standard=WordPress {PLUGIN_DIR}/test-plugin/test-plugin.php` - Then the return code should be 0 - - Scenario: Scaffold post-type and lint it - When I run `wp theme install twentytwentyone --force --activate` - And I run `wp eval 'echo STYLESHEETPATH;'` - And save STDOUT as {STYLESHEETPATH} - - And I run `wp scaffold post-type movie --theme` - Then STDOUT should not be empty - And the {STYLESHEETPATH}/post-types/movie.php file should exist - - When I run `{PLUGIN_DIR}/phpcs-helper/vendor/bin/phpcs --standard=WordPress {STYLESHEETPATH}/post-types/movie.php` - Then the return code should be 0 - - Scenario: Scaffold taxonomy and lint it - When I run `wp theme install twentytwentyone --force --activate` - And I run `wp eval 'echo STYLESHEETPATH;'` - And save STDOUT as {STYLESHEETPATH} - - And I run `wp scaffold taxonomy genre --theme` - Then STDOUT should not be empty - And the {STYLESHEETPATH}/taxonomies/genre.php file should exist - - When I run `{PLUGIN_DIR}/phpcs-helper/vendor/bin/phpcs --standard=WordPress {STYLESHEETPATH}/taxonomies/genre.php` - Then the return code should be 0 - - Scenario: Scaffold plugin tests and lint them - When I run `wp scaffold plugin test-plugin` - Then STDOUT should not be empty - And the {PLUGIN_DIR}/test-plugin/tests directory should exist - And the {PLUGIN_DIR}/test-plugin/tests/bootstrap.php file should exist - And the {PLUGIN_DIR}/test-plugin/tests/test-sample.php file should exist - - # Run phpcs on the test files - When I run `{PLUGIN_DIR}/phpcs-helper/vendor/bin/phpcs --standard=WordPress {PLUGIN_DIR}/test-plugin/tests/bootstrap.php {PLUGIN_DIR}/test-plugin/tests/test-sample.php` - Then the return code should be 0 - - Scenario: Scaffold child theme and lint it - When I run `wp theme install twentytwentyone --force --activate` - And I run `wp theme path` - And save STDOUT as {THEME_DIR} - - And I run `wp scaffold child-theme test-child --parent_theme=twentytwentyone` - Then STDOUT should not be empty - And the {THEME_DIR}/test-child/functions.php file should exist - - When I run `{PLUGIN_DIR}/phpcs-helper/vendor/bin/phpcs --standard=WordPress {THEME_DIR}/test-child/functions.php` - Then the return code should be 0 - - Scenario: Scaffold block and lint it - When I run `wp scaffold plugin movies` - And I run `wp plugin path movies --dir` - And save STDOUT as {MOVIES_DIR} - - And I run `wp scaffold block the-green-mile --plugin=movies` - Then STDOUT should not be empty - And the {MOVIES_DIR}/blocks/the-green-mile.php file should exist - - When I run `{PLUGIN_DIR}/phpcs-helper/vendor/bin/phpcs --standard=WordPress {MOVIES_DIR}/blocks/the-green-mile.php` - Then the return code should be 0 diff --git a/features/scaffold-plugin-tests.feature b/features/scaffold-plugin-tests.feature index b224dbd30..c4c45ba90 100644 --- a/features/scaffold-plugin-tests.feature +++ b/features/scaffold-plugin-tests.feature @@ -1,7 +1,5 @@ Feature: Scaffold plugin unit tests - # TODO: Fix this on Windows. Fails because is_executable() fails for .sh files. - @skip-windows Scenario: Scaffold plugin tests Given a WP install When I run `wp plugin path` @@ -23,7 +21,7 @@ Feature: Scaffold plugin unit tests """ And the {PLUGIN_DIR}/hello-world/tests/bootstrap.php file should contain: """ - require dirname( __DIR__ ) . '/hello-world.php'; + require dirname( dirname( __FILE__ ) ) . '/hello-world.php'; """ And the {PLUGIN_DIR}/hello-world/tests/bootstrap.php file should contain: """ @@ -37,35 +35,41 @@ Feature: Scaffold plugin unit tests """ install-wp-tests.sh """ - And the {PLUGIN_DIR}/hello-world/phpunit.xml.dist file should contain: - """ - <exclude>./tests/test-sample.php</exclude> - """ - And the {PLUGIN_DIR}/hello-world/.phpcs.xml.dist file should exist + And the {PLUGIN_DIR}/hello-world/phpunit.xml.dist file should exist + And the {PLUGIN_DIR}/hello-world/phpcs.ruleset.xml file should exist And the {PLUGIN_DIR}/hello-world/circle.yml file should not exist - And the {PLUGIN_DIR}/hello-world/bitbucket-pipelines.yml file should not exist And the {PLUGIN_DIR}/hello-world/.gitlab-ci.yml file should not exist - And the {PLUGIN_DIR}/hello-world/.circleci/config.yml file should contain: - """ - jobs: - php56-build: - <<: *php_job - docker: - - image: circleci/php:5.6 - - image: *mysql_image - """ - And the {PLUGIN_DIR}/hello-world/.circleci/config.yml file should contain: - """ - workflows: - version: 2 - main: - jobs: - - php56-build - - php70-build - - php71-build - - php72-build - - php73-build - - php74-build + And the {PLUGIN_DIR}/hello-world/.travis.yml file should contain: + """ + script: + - | + if [[ ! -z "$WP_VERSION" ]] ; then + phpunit + WP_MULTISITE=1 phpunit + fi + - | + if [[ "$WP_TRAVISCI" == "phpcs" ]] ; then + phpcs --standard=phpcs.ruleset.xml $(find . -name '*.php') + fi + """ + And the {PLUGIN_DIR}/hello-world/.travis.yml file should contain: + """ + matrix: + include: + - php: 7.1 + env: WP_VERSION=latest + - php: 7.0 + env: WP_VERSION=latest + - php: 5.6 + env: WP_VERSION=4.4 + - php: 5.6 + env: WP_VERSION=latest + - php: 5.6 + env: WP_VERSION=trunk + - php: 5.6 + env: WP_TRAVISCI=phpcs + - php: 5.3 + env: WP_VERSION=latest """ When I run `wp eval "if ( is_executable( '{PLUGIN_DIR}/hello-world/bin/install-wp-tests.sh' ) ) { echo 'executable'; } else { exit( 1 ); }"` @@ -80,34 +84,10 @@ Feature: Scaffold plugin unit tests When I run `wp plugin path hello-world --dir` Then save STDOUT as {PLUGIN_DIR} - And the {PLUGIN_DIR}/circle.yml file should not exist - And the {PLUGIN_DIR}/.circleci/config.yml file should contain: - """ - version: 2 - """ - And the {PLUGIN_DIR}/.circleci/config.yml file should contain: - """ - php56-build - """ - And the {PLUGIN_DIR}/.circleci/config.yml file should contain: - """ - php70-build - """ - And the {PLUGIN_DIR}/.circleci/config.yml file should contain: + And the {PLUGIN_DIR}/.travis.yml file should not exist + And the {PLUGIN_DIR}/circle.yml file should contain: """ - php71-build - """ - And the {PLUGIN_DIR}/.circleci/config.yml file should contain: - """ - php72-build - """ - And the {PLUGIN_DIR}/.circleci/config.yml file should contain: - """ - php73-build - """ - And the {PLUGIN_DIR}/.circleci/config.yml file should contain: - """ - php74-build + version: 5.6.22 """ Scenario: Scaffold plugin tests with Circle as the provider, part two @@ -119,28 +99,28 @@ Feature: Scaffold plugin unit tests When I run `wp scaffold plugin-tests hello-world --ci=circle` Then STDOUT should not be empty - And the {PLUGIN_DIR}/circle.yml file should not exist - And the {PLUGIN_DIR}/.circleci/config.yml file should contain: - """ - version: 2 - """ - And the {PLUGIN_DIR}/.circleci/config.yml file should contain: - """ - rm -rf $WP_TESTS_DIR $WP_CORE_DIR - bash bin/install-wp-tests.sh wordpress_test root '' 127.0.0.1 4.5 $SKIP_DB_CREATE - phpunit - WP_MULTISITE=1 phpunit - SKIP_DB_CREATE=true - rm -rf $WP_TESTS_DIR $WP_CORE_DIR - bash bin/install-wp-tests.sh wordpress_test root '' 127.0.0.1 latest $SKIP_DB_CREATE - phpunit - WP_MULTISITE=1 phpunit - SKIP_DB_CREATE=true - rm -rf $WP_TESTS_DIR $WP_CORE_DIR - bash bin/install-wp-tests.sh wordpress_test root '' 127.0.0.1 trunk $SKIP_DB_CREATE - phpunit - WP_MULTISITE=1 phpunit - SKIP_DB_CREATE=true + And the {PLUGIN_DIR}/.travis.yml file should not exist + And the {PLUGIN_DIR}/circle.yml file should contain: + """ + version: 5.6.22 + """ + And the {PLUGIN_DIR}/circle.yml file should contain: + """ + - | + rm -rf $WP_TESTS_DIR $WP_CORE_DIR + bash bin/install-wp-tests.sh wordpress_test ubuntu '' 127.0.0.1 4.4 + phpunit + WP_MULTISITE=1 phpunit + - | + rm -rf $WP_TESTS_DIR $WP_CORE_DIR + bash bin/install-wp-tests.sh wordpress_test ubuntu '' 127.0.0.1 latest + phpunit + WP_MULTISITE=1 phpunit + - | + rm -rf $WP_TESTS_DIR $WP_CORE_DIR + bash bin/install-wp-tests.sh wordpress_test ubuntu '' 127.0.0.1 trunk + phpunit + WP_MULTISITE=1 phpunit """ Scenario: Scaffold plugin tests with Gitlab as the provider @@ -152,178 +132,23 @@ Feature: Scaffold plugin unit tests When I run `wp scaffold plugin-tests hello-world --ci=gitlab` Then STDOUT should not be empty + And the {PLUGIN_DIR}/.travis.yml file should not exist And the {PLUGIN_DIR}/.gitlab-ci.yml file should contain: """ MYSQL_DATABASE """ - Scenario: Scaffold plugin tests with Bitbucket Pipelines as the provider - Given a WP install - And I run `wp scaffold plugin hello-world --skip-tests` - - When I run `wp plugin path hello-world --dir` - Then save STDOUT as {PLUGIN_DIR} - - When I run `wp scaffold plugin-tests hello-world --ci=bitbucket` - Then STDOUT should not be empty - And the {PLUGIN_DIR}/bitbucket-pipelines.yml file should contain: - """ - pipelines: - default: - """ - And the {PLUGIN_DIR}/bitbucket-pipelines.yml file should contain: - """ - - step: - image: php:7.4 - name: "PHP 7.4" - script: - # Install Dependencies - - apt-get update && apt-get install -y subversion git zip libzip-dev --no-install-recommends - """ - And the {PLUGIN_DIR}/bitbucket-pipelines.yml file should contain: - """ - - step: - image: php:8.0 - name: "PHP 8.0" - script: - # Install Dependencies - - apt-get update && apt-get install -y subversion git zip libzip-dev --no-install-recommends - """ - And the {PLUGIN_DIR}/bitbucket-pipelines.yml file should contain: - """ - - step: - image: php:8.2 - name: "PHP 8.2" - script: - # Install Dependencies - - apt-get update && apt-get install -y subversion git zip libzip-dev --no-install-recommends - """ - And the {PLUGIN_DIR}/bitbucket-pipelines.yml file should contain: - """ - definitions: - services: - database: - image: mysql:latest - environment: - MYSQL_DATABASE: 'wordpress_tests' - MYSQL_ROOT_PASSWORD: 'root' - """ - Scenario: Scaffold plugin tests with invalid slug Given a WP install - And a wp-content/plugins/foo.php file: - """ - <?php - /** - * Plugin Name: Foo - * Description: Foo plugin - */ - """ - Then the {RUN_DIR}/wp-content/plugins/foo.php file should exist - - When I try `wp scaffold plugin-tests foo` - Then STDERR should match #Error: Invalid plugin slug specified\. No such target directory '.*wp-content/plugins/foo'\.# - And the return code should be 1 When I try `wp scaffold plugin-tests .` - Then STDERR should be: + Then STDERR should contain: """ - Error: Invalid plugin slug specified. The slug cannot be '.' or '..'. + Error: Invalid plugin slug specified. """ - And the return code should be 1 When I try `wp scaffold plugin-tests ../` - Then STDERR should be: - """ - Error: Invalid plugin slug specified. The slug can only contain alphanumeric characters, underscores, and dashes. - """ - And the return code should be 1 - - When I try `wp scaffold plugin-tests my-plugin/` - Then STDERR should be: - """ - Error: Invalid plugin slug specified. The slug can only contain alphanumeric characters, underscores, and dashes. - """ - And the return code should be 1 - - When I try `wp scaffold plugin-tests my-plugin\\` - Then STDERR should be: - """ - Error: Invalid plugin slug specified. The slug can only contain alphanumeric characters, underscores, and dashes. - """ - And the return code should be 1 - - Scenario: Scaffold plugin tests with invalid directory - Given a WP install - And I run `wp scaffold plugin hello-world --skip-tests` - - When I run `wp plugin path hello-world --dir` - Then save STDOUT as {PLUGIN_DIR} - - When I try `wp scaffold plugin-tests hello-world --dir=non-existent-dir` - Then STDERR should be: + Then STDERR should contain: """ - Error: Invalid plugin directory specified. No such directory 'non-existent-dir'. + Error: Invalid plugin slug specified. """ - And the return code should be 1 - - When I run `rm -rf {PLUGIN_DIR} && touch {PLUGIN_DIR}` - Then the return code should be 0 - When I try `wp scaffold plugin-tests hello-world` - Then STDERR should match #Error: Invalid plugin slug specified\. No such target directory '.*hello-world'\.# - And the return code should be 1 - - Scenario: Scaffold plugin tests with a symbolic link - Given a WP install - And I run `wp scaffold plugin hello-world --skip-tests` - - When I run `wp plugin path hello-world --dir` - Then save STDOUT as {PLUGIN_DIR} - - When I run `mv {PLUGIN_DIR} {RUN_DIR} && ln -s {RUN_DIR}/hello-world {PLUGIN_DIR}` - Then the return code should be 0 - - When I run `wp scaffold plugin-tests hello-world` - Then STDOUT should not be empty - And the {PLUGIN_DIR}/tests directory should contain: - """ - bootstrap.php - """ - - Scenario: Scaffold plugin tests with custom main file - Given a WP install - And a wp-content/plugins/foo/bar.php file: - """ - <?php - /** - * Plugin Name: Foo - * Plugin URI: https://example.com - * Description: Foo description - * Author: John Doe - * Author URI: https://example.com - * Text Domain: foo - * Domain Path: /languages - * Version: 0.1.0 - * - * @package Foo - */ - """ - - When I run `wp scaffold plugin-tests foo` - Then the wp-content/plugins/foo/tests/bootstrap.php file should contain: - """ - require dirname( __DIR__ ) . '/bar.php'; - """ - - Scenario: Accept bitbucket as valid CI in plugin scaffold - Given a WP install - When I run `wp plugin path` - Then save STDOUT as {PLUGIN_DIR} - - When I run `wp scaffold plugin hello-world --ci=bitbucket` - Then STDOUT should not be empty - And the {PLUGIN_DIR}/hello-world/.editorconfig file should exist - And the {PLUGIN_DIR}/hello-world/hello-world.php file should exist - And the {PLUGIN_DIR}/hello-world/readme.txt file should exist - And the {PLUGIN_DIR}/hello-world/bitbucket-pipelines.yml file should exist - And the {PLUGIN_DIR}/hello-world/tests directory should exist diff --git a/features/scaffold-taxonomy.feature b/features/scaffold-taxonomy.feature deleted file mode 100644 index f6b367a83..000000000 --- a/features/scaffold-taxonomy.feature +++ /dev/null @@ -1,31 +0,0 @@ -Feature: Scaffold a custom taxonomy - - Scenario: Scaffold a taxonomy that uses Doctrine pluralization - Given a WP install - - When I run `wp scaffold taxonomy fungus --raw` - Then STDOUT should contain: - """ - __( 'Popular Fungi' - """ - - Scenario: Extended scaffolded taxonomy includes term_updated_messages - Given a WP install - - When I run `wp scaffold taxonomy fungus` - Then STDOUT should contain: - """ - add_filter( 'term_updated_messages', 'fungus_updated_messages' ); - """ - And STDOUT should contain: - """ - $messages['fungus'] = array( - """ - And STDOUT should contain: - """ - 1 => __( 'Fungus added.', 'YOUR-TEXTDOMAIN' ), - """ - And STDOUT should contain: - """ - 6 => __( 'Fungi deleted.', 'YOUR-TEXTDOMAIN' ), - """ diff --git a/features/scaffold-theme-tests.feature b/features/scaffold-theme-tests.feature index afecb9ade..a5ccff3f1 100644 --- a/features/scaffold-theme-tests.feature +++ b/features/scaffold-theme-tests.feature @@ -2,274 +2,113 @@ Feature: Scaffold theme unit tests Background: Given a WP install - And I try `wp theme install twentytwelve --force` - And I run `wp scaffold child-theme t12child --parent_theme=twentytwelve` + And I run `wp theme install p2` + And I run `wp scaffold child-theme p2child --parent_theme=p2` When I run `wp theme path` Then save STDOUT as {THEME_DIR} - @require-php-7.0 @require-mysql Scenario: Scaffold theme tests - When I run `wp scaffold theme-tests t12child` + When I run `wp scaffold theme-tests p2child` Then STDOUT should not be empty - And the {THEME_DIR}/t12child/tests directory should contain: + And the {THEME_DIR}/p2child/tests directory should contain: """ bootstrap.php test-sample.php """ - And the {THEME_DIR}/t12child/tests/bootstrap.php file should contain: + And the {THEME_DIR}/p2child/tests/bootstrap.php file should contain: """ - register_theme_directory( $theme_root ); + register_theme_directory( dirname( $theme_dir ) ); """ - And the {THEME_DIR}/t12child/tests/bootstrap.php file should contain: + And the {THEME_DIR}/p2child/tests/bootstrap.php file should contain: """ - * @package T12child + * @package P2child """ - And the {THEME_DIR}/t12child/tests/test-sample.php file should contain: + And the {THEME_DIR}/p2child/tests/test-sample.php file should contain: """ - * @package T12child + * @package P2child """ - And the {THEME_DIR}/t12child/bin directory should contain: + And the {THEME_DIR}/p2child/bin directory should contain: """ install-wp-tests.sh """ - And the {THEME_DIR}/t12child/phpunit.xml.dist file should contain: - """ - <exclude>./tests/test-sample.php</exclude> - """ - And the {THEME_DIR}/t12child/.phpcs.xml.dist file should exist - And the {THEME_DIR}/t12child/bitbucket-pipelines.yml file should not exist - And the {THEME_DIR}/t12child/.gitlab-ci.yml file should not exist - And the {THEME_DIR}/t12child/.circleci/config.yml file should contain: - """ - jobs: - php56-build: - <<: *php_job - docker: - - image: circleci/php:5.6 - - image: *mysql_image - """ - And the {THEME_DIR}/t12child/.circleci/config.yml file should contain: - """ - workflows: - version: 2 - main: - jobs: - - php56-build - - php70-build - - php71-build - - php72-build - - php73-build - - php74-build - """ - - When I run `wp eval "if ( is_executable( '{THEME_DIR}/t12child/bin/install-wp-tests.sh' ) ) { echo 'executable'; } else { exit( 1 ); }"` + And the {THEME_DIR}/p2child/phpunit.xml.dist file should exist + And the {THEME_DIR}/p2child/phpcs.ruleset.xml file should exist + And the {THEME_DIR}/p2child/circle.yml file should not exist + And the {THEME_DIR}/p2child/.gitlab-ci.yml file should not exist + And the {THEME_DIR}/p2child/.travis.yml file should contain: + """ + script: + - | + if [[ ! -z "$WP_VERSION" ]] ; then + phpunit + WP_MULTISITE=1 phpunit + fi + - | + if [[ "$WP_TRAVISCI" == "phpcs" ]] ; then + phpcs --standard=phpcs.ruleset.xml $(find . -name '*.php') + fi + """ + And the {THEME_DIR}/p2child/.travis.yml file should contain: + """ + matrix: + include: + - php: 7.1 + env: WP_VERSION=latest + - php: 7.0 + env: WP_VERSION=latest + - php: 5.6 + env: WP_VERSION=latest + - php: 5.6 + env: WP_VERSION=trunk + - php: 5.6 + env: WP_TRAVISCI=phpcs + - php: 5.3 + env: WP_VERSION=latest + """ + + When I run `wp eval "if ( is_executable( '{THEME_DIR}/p2child/bin/install-wp-tests.sh' ) ) { echo 'executable'; } else { exit( 1 ); }"` Then STDOUT should be: """ executable """ - # Warning: overwriting generated functions.php file, so functions.php file loaded only tests beyond here... - Given a wp-content/themes/t12child/functions.php file: - """ - <?php echo __FILE__ . " loaded.\n"; - """ - # This throws a warning for the password provided via command line. - And I try `mysql -u{DB_USER} -p{DB_PASSWORD} -h{MYSQL_HOST} -P{MYSQL_PORT} --protocol=tcp -e "DROP DATABASE IF EXISTS wp_cli_test_scaffold"` - - And I try `WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib WP_CORE_DIR={RUN_DIR}/wordpress {THEME_DIR}/t12child/bin/install-wp-tests.sh wp_cli_test_scaffold {DB_USER} {DB_PASSWORD} {DB_HOST} latest` - Then the return code should be 0 - - When I run `cd {THEME_DIR}/t12child; WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib phpunit` - Then STDOUT should contain: - """ - t12child/functions.php loaded. - """ - And STDOUT should contain: - """ - Running as single site - """ - And STDOUT should contain: - """ - No tests executed! - """ - - When I run `cd {THEME_DIR}/t12child; WP_MULTISITE=1 WP_TESTS_DIR={RUN_DIR}/wordpress-tests-lib phpunit` - Then STDOUT should contain: - """ - t12child/functions.php loaded. - """ - And STDOUT should contain: - """ - Running as multisite - """ - And STDOUT should contain: - """ - No tests executed! - """ - Scenario: Scaffold theme tests invalid theme When I try `wp scaffold theme-tests p3child` Then STDERR should be: """ - Error: Invalid theme slug specified. The theme 'p3child' does not exist. + Error: Invalid theme slug specified. """ - And the return code should be 1 Scenario: Scaffold theme tests with Circle as the provider - When I run `wp scaffold theme-tests t12child --ci=circle` + When I run `wp scaffold theme-tests p2child --ci=circle` Then STDOUT should not be empty - And the {THEME_DIR}/t12child/circle.yml file should not exist - And the {THEME_DIR}/t12child/.circleci/config.yml file should contain: - """ - version: 2 - """ - And the {THEME_DIR}/t12child/.circleci/config.yml file should contain: - """ - php56-build - """ - And the {THEME_DIR}/t12child/.circleci/config.yml file should contain: + And the {THEME_DIR}/p2child/.travis.yml file should not exist + And the {THEME_DIR}/p2child/circle.yml file should contain: """ - php70-build - """ - And the {THEME_DIR}/t12child/.circleci/config.yml file should contain: - """ - php71-build - """ - And the {THEME_DIR}/t12child/.circleci/config.yml file should contain: - """ - php72-build - """ - And the {THEME_DIR}/t12child/.circleci/config.yml file should contain: - """ - php73-build - """ - And the {THEME_DIR}/t12child/.circleci/config.yml file should contain: - """ - php74-build + version: 5.6.22 """ Scenario: Scaffold theme tests with Gitlab as the provider - When I run `wp scaffold theme-tests t12child --ci=gitlab` + When I run `wp scaffold theme-tests p2child --ci=gitlab` Then STDOUT should not be empty - And the {THEME_DIR}/t12child/.gitlab-ci.yml file should contain: + And the {THEME_DIR}/p2child/.travis.yml file should not exist + And the {THEME_DIR}/p2child/.gitlab-ci.yml file should contain: """ MYSQL_DATABASE """ - Scenario: Scaffold theme tests with Bitbucket Pipelines as the provider - When I run `wp scaffold theme-tests t12child --ci=bitbucket` - Then STDOUT should not be empty - And the {THEME_DIR}/t12child/bitbucket-pipelines.yml file should contain: - """ - pipelines: - default: - """ - And the {THEME_DIR}/t12child/bitbucket-pipelines.yml file should contain: - """ - - step: - image: php:7.4 - name: "PHP 7.4" - script: - # Install Dependencies - - apt-get update && apt-get install -y subversion git zip libzip-dev --no-install-recommends - """ - And the {THEME_DIR}/t12child/bitbucket-pipelines.yml file should contain: - """ - - step: - image: php:8.0 - name: "PHP 8.0" - script: - # Install Dependencies - - apt-get update && apt-get install -y subversion git zip libzip-dev --no-install-recommends - """ - And the {THEME_DIR}/t12child/bitbucket-pipelines.yml file should contain: - """ - - step: - image: php:8.2 - name: "PHP 8.2" - script: - # Install Dependencies - - apt-get update && apt-get install -y subversion git zip libzip-dev --no-install-recommends - """ - And the {THEME_DIR}/t12child/bitbucket-pipelines.yml file should contain: - """ - definitions: - services: - database: - image: mysql:latest - environment: - MYSQL_DATABASE: 'wordpress_tests' - MYSQL_ROOT_PASSWORD: 'root' - """ - Scenario: Scaffold theme tests with invalid slug When I try `wp scaffold theme-tests .` - Then STDERR should be: + Then STDERR should contain: """ - Error: Invalid theme slug specified. The slug cannot be '.' or '..'. + Error: Invalid theme slug specified. """ - And the return code should be 1 When I try `wp scaffold theme-tests ../` - Then STDERR should be: + Then STDERR should contain: """ - Error: Invalid theme slug specified. The slug can only contain alphanumeric characters, underscores, and dashes. - """ - And the return code should be 1 - - When I try `wp scaffold theme-tests t12child/` - Then STDERR should be: - """ - Error: Invalid theme slug specified. The slug can only contain alphanumeric characters, underscores, and dashes. - """ - And the return code should be 1 - - When I try `wp scaffold theme-tests t12child\\` - Then STDERR should be: - """ - Error: Invalid theme slug specified. The slug can only contain alphanumeric characters, underscores, and dashes. - """ - And the return code should be 1 - - Scenario: Scaffold theme tests with invalid directory - When I try `wp scaffold theme-tests twentytwelve --dir=non-existent-dir` - Then STDERR should be: - """ - Error: Invalid theme directory specified. No such directory 'non-existent-dir'. - """ - And the return code should be 1 - - # Temporarily move. - When I run `mv -f {THEME_DIR}/twentytwelve {THEME_DIR}/hide-twentytwelve && touch {THEME_DIR}/twentytwelve` - Then the return code should be 0 - - When I try `wp scaffold theme-tests twentytwelve` - Then STDERR should be: - """ - Error: Invalid theme slug specified. No such target directory '{THEME_DIR}/twentytwelve'. - """ - And the return code should be 1 - - # Restore. - When I run `rm -f {THEME_DIR}/twentytwelve && mv -f {THEME_DIR}/hide-twentytwelve {THEME_DIR}/twentytwelve` - Then the return code should be 0 - - # TODO: Fix this on Windows. Fails because unlink fails on directories. - @skip-windows - Scenario: Scaffold theme tests with a symbolic link - # Temporarily move the whole theme dir and create a symbolic link to it. - When I run `mv -f {THEME_DIR} {RUN_DIR}/alt-themes && ln -s {RUN_DIR}/alt-themes {THEME_DIR}` - Then the return code should be 0 - - When I run `wp scaffold theme-tests twentytwelve` - Then STDOUT should not be empty - And the {THEME_DIR}/twentytwelve/tests directory should contain: - """ - bootstrap.php + Error: Invalid theme slug specified. """ - # Restore. - When I run `unlink {THEME_DIR} && mv -f {RUN_DIR}/alt-themes {THEME_DIR}` - Then the return code should be 0 diff --git a/features/scaffold.feature b/features/scaffold.feature index 2cf2b0a5d..72f1ca197 100644 --- a/features/scaffold.feature +++ b/features/scaffold.feature @@ -3,16 +3,12 @@ Feature: WordPress code scaffolding @theme Scenario: Scaffold a child theme Given a WP install - And I run `wp theme path` + Given I run `wp theme path` And save STDOUT as {THEME_DIR} - When I run `wp scaffold child-theme zombieland --parent_theme=umbrella --theme_name=Zombieland --author=Tallahassee --author_uri=https://wp-cli.org --theme_uri=http://www.zombieland.com` + When I run `wp scaffold child-theme zombieland --parent_theme=umbrella --theme_name=Zombieland --author=Tallahassee --author_uri=http://www.wp-cli.org --theme_uri=http://www.zombieland.com` Then the {THEME_DIR}/zombieland/style.css file should exist And the {THEME_DIR}/zombieland/functions.php file should exist - And the {THEME_DIR}/zombieland/functions.php file should contain: - """ - wp_style_add_data( 'zombieland-style', 'rtl', 'add' ); - """ And STDOUT should be: """ Success: Created '{THEME_DIR}/zombieland'. @@ -21,7 +17,7 @@ Feature: WordPress code scaffolding Scenario: Scaffold a child theme with only --parent_theme parameter Given a WP install - And I run `wp theme path` + Given I run `wp theme path` And save STDOUT as {THEME_DIR} When I run `wp scaffold child-theme hello-world --parent_theme=simple-life` @@ -40,7 +36,6 @@ Feature: WordPress code scaffolding """ Error: The parent theme is missing. Please install the "just-test" parent theme. """ - And the return code should be 1 Scenario: Scaffold a child theme with non existing parent theme and also network activate parameter Given a WP install @@ -48,58 +43,18 @@ Feature: WordPress code scaffolding When I try `wp scaffold child-theme hello-world --parent_theme=just-test --enable-network --quiet` Then STDERR should contain: """ - Error: This is not a multisite install + Error: This is not a multisite install. """ - And the return code should be 1 - @require-wp-4.6 Scenario: Scaffold a child theme and network enable it Given a WP multisite install - When I run `wp scaffold child-theme zombieland --parent_theme=umbrella --theme_name=Zombieland --author=Tallahassee --author_uri=https://wp-cli.org --theme_uri=http://www.zombieland.com --enable-network` + When I run `wp scaffold child-theme zombieland --parent_theme=umbrella --theme_name=Zombieland --author=Tallahassee --author_uri=http://www.wp-cli.org --theme_uri=http://www.zombieland.com --enable-network` Then STDOUT should contain: """ Success: Network enabled the 'Zombieland' theme. """ - Scenario: Scaffold a child theme and activate it with different slug and name - Given a WP install - - When I run `wp theme install twentytwentyone --force` - Then STDOUT should not be empty - - And I run `wp theme path` - And save STDOUT as {THEME_DIR} - - When I run `wp scaffold child-theme first-run --parent_theme=twentytwentyone --theme_name="First Run Name" --activate` - Then STDOUT should contain: - """ - Success: Created '{THEME_DIR}/first-run'. - """ - And STDOUT should contain: - """ - Success: Switched to 'First Run Name' theme. - """ - - When I run `wp theme list --fields=name,status --format=csv` - Then STDOUT should contain: - """ - first-run,active - """ - - # Now delete the theme and create it again to test the fix for the caching issue - When I run `rm -rf {THEME_DIR}/first-run` - And I run `wp theme activate twentytwentyone` - And I run `wp scaffold child-theme first-run --parent_theme=twentytwentyone --theme_name="First Run Name" --activate` - Then STDOUT should contain: - """ - Success: Created '{THEME_DIR}/first-run'. - """ - And STDOUT should contain: - """ - Success: Switched to 'First Run Name' theme. - """ - Scenario: Scaffold a child theme with invalid slug Given a WP install When I try `wp scaffold child-theme . --parent_theme=simple-life` @@ -107,46 +62,16 @@ Feature: WordPress code scaffolding """ Error: Invalid theme slug specified. """ - And the return code should be 1 - When I try `wp scaffold child-theme ../ --parent_theme=simple-life` Then STDERR should contain: """ Error: Invalid theme slug specified. """ - And the return code should be 1 - - @theme - Scenario: Scaffold a child theme with a relative --path argument containing '..' - Given a WP install in 'subdir' - And I run `wp --path=.. theme path` from 'subdir/wp-content' - And save STDOUT as {THEME_DIR} - - When I run `wp scaffold child-theme zombieland --parent_theme=umbrella --path=..` from 'subdir/wp-content' - Then the {THEME_DIR}/zombieland/style.css file should exist - And the {THEME_DIR}/zombieland/functions.php file should exist - - @theme - Scenario: Scaffold a child theme with dots in the slug - Given a WP install - And I run `wp theme path` - And save STDOUT as {THEME_DIR} - - When I run `wp scaffold child-theme my-theme-2.0.1 --parent_theme=umbrella` - Then the {THEME_DIR}/my-theme-2.0.1/functions.php file should exist - And the {THEME_DIR}/my-theme-2.0.1/functions.php file should contain: - """ - function my_theme_2_0_1_parent_theme_enqueue_styles() - """ - And the {THEME_DIR}/my-theme-2.0.1/functions.php file should contain: - """ - add_action( 'wp_enqueue_scripts', 'my_theme_2_0_1_parent_theme_enqueue_styles' ); - """ @tax @cpt Scenario: Scaffold a Custom Taxonomy and Custom Post Type and write it to active theme Given a WP install - And I run `wp eval 'echo STYLESHEETPATH;'` + Given I run `wp eval 'echo STYLESHEETPATH;'` And save STDOUT as {STYLESHEETPATH} When I run `wp scaffold taxonomy zombie-speed --theme` @@ -162,7 +87,7 @@ Feature: WordPress code scaffolding When I run `wp scaffold post-type zombie` Then STDOUT should contain: """ - 'rest_base' => 'zombie' + register_post_type( 'zombie' """ And STDOUT should contain: """ @@ -197,13 +122,13 @@ Feature: WordPress code scaffolding Given a WP install When I run `wp scaffold taxonomy zombie-speed --label="Speed"` Then STDOUT should contain: - """ - __( 'Speeds' - """ + """ + __( 'Speeds' + """ And STDOUT should contain: - """ - _x( 'Speed', 'taxonomy general name', - """ + """ + _x( 'Speed', 'taxonomy general name', + """ # Test for all flags but --label, --theme, --plugin and --raw @cpt @@ -220,7 +145,7 @@ Feature: WordPress code scaffolding """ And STDOUT should contain: """ - 'menu_icon' => 'dashicons-admin-post', + 'menu_icon' => 'dashicons-admin-post', """ Scenario: CPT slug is too long @@ -230,7 +155,6 @@ Feature: WordPress code scaffolding """ Error: Post type slugs cannot exceed 20 characters in length. """ - And the return code should be 1 @cpt Scenario: Scaffold a Custom Post Type with label @@ -246,30 +170,14 @@ Feature: WordPress code scaffolding When I run `wp scaffold post-type zombie --dashicon="art"` Then STDOUT should contain: """ - 'menu_icon' => 'dashicons-art', - """ - - Scenario: Scaffold a Custom Post Type with dashicon in the case of passing "dashicon-info" - Given a WP install - When I run `wp scaffold post-type zombie --dashicon="dashicon-info"` - Then STDOUT should contain: - """ - 'menu_icon' => 'dashicons-info', - """ - - Scenario: Scaffold a Custom Post Type with dashicon in the case of passing "dashicons-info" - Given a WP install - When I run `wp scaffold post-type zombie --dashicon="dashicons-info"` - Then STDOUT should contain: - """ - 'menu_icon' => 'dashicons-info', + 'menu_icon' => 'dashicons-art', """ Scenario: Scaffold a plugin Given a WP install - And I run `wp plugin path` + Given I run `wp plugin path` And save STDOUT as {PLUGIN_DIR} - And I run `wp core version` + Given I run `wp core version` And save STDOUT as {WP_VERSION} When I run `wp scaffold plugin hello-world --plugin_author="Hello World Author"` @@ -278,30 +186,20 @@ Feature: WordPress code scaffolding And the {PLUGIN_DIR}/hello-world/.editorconfig file should exist And the {PLUGIN_DIR}/hello-world/hello-world.php file should exist And the {PLUGIN_DIR}/hello-world/readme.txt file should exist - And the {PLUGIN_DIR}/hello-world/composer.json file should exist + And the {PLUGIN_DIR}/hello-world/package.json file should exist + And the {PLUGIN_DIR}/hello-world/Gruntfile.js file should exist And the {PLUGIN_DIR}/hello-world/.gitignore file should contain: """ .DS_Store - phpcs.xml - phpunit.xml Thumbs.db wp-cli.local.yml node_modules/ - vendor/ """ And the {PLUGIN_DIR}/hello-world/.distignore file should contain: """ .git .gitignore """ - And the {PLUGIN_DIR}/hello-world/.phpcs.xml.dist file should contain: - """ - <rule ref="PHPCompatibilityWP"/> - """ - And the {PLUGIN_DIR}/hello-world/.phpcs.xml.dist file should contain: - """ - <config name="testVersion" value="7.2-"/> - """ And the {PLUGIN_DIR}/hello-world/hello-world.php file should contain: """ * Plugin Name: Hello World @@ -323,10 +221,14 @@ Feature: WordPress code scaffolding Tested up to: {WP_VERSION} """ - When I run `cat {PLUGIN_DIR}/hello-world/composer.json` - Then STDOUT should contain: + When I run `cat {PLUGIN_DIR}/hello-world/package.json` + Then STDOUT should be JSON containing: """ - wp-cli/i18n-command + {"author":"Hello World Author"} + """ + And STDOUT should be JSON containing: + """ + {"version":"0.1.0"} """ Scenario: Scaffold a plugin by prompting @@ -338,10 +240,10 @@ Feature: WordPress code scaffolding Hello World An awesome introductory plugin for WordPress WP-CLI - https://wp-cli.org - https://wp-cli.org + http://wp-cli.org + http://wp-cli.org n - circle + travis Y n n @@ -375,7 +277,6 @@ Feature: WordPress code scaffolding Plugin 'hello-world' activated. """ - @require-wp-4.6 Scenario: Scaffold a plugin and network activate it Given a WP multisite install When I run `wp scaffold plugin hello-world --activate-network` @@ -391,23 +292,18 @@ Feature: WordPress code scaffolding """ Error: Invalid plugin slug specified. """ - And the return code should be 1 - When I try `wp scaffold plugin ../` Then STDERR should contain: """ Error: Invalid plugin slug specified. """ - And the return code should be 1 - @require-php-5.6 @require-wp-4.6 Scenario: Scaffold starter code for a theme Given a WP install - And I run `wp theme path` + Given I run `wp theme path` And save STDOUT as {THEME_DIR} - # Allow for warnings to be generated due to https://github.com/wp-cli/scaffold-command/issues/181 - When I try `wp scaffold _s starter-theme` + When I run `wp scaffold _s starter-theme` Then STDOUT should contain: """ Success: Created theme 'Starter-theme'. @@ -415,40 +311,21 @@ Feature: WordPress code scaffolding And the {THEME_DIR}/starter-theme/style.css file should exist And the {THEME_DIR}/starter-theme/.editorconfig file should exist - @require-php-5.6 @require-wp-4.6 Scenario: Scaffold starter code for a theme with sass Given a WP install - And I run `wp theme path` + Given I run `wp theme path` And save STDOUT as {THEME_DIR} - # Allow for warnings to be generated due to https://github.com/wp-cli/scaffold-command/issues/181 - When I try `wp scaffold _s starter-theme --sassify` + When I run `wp scaffold _s starter-theme --sassify` Then STDOUT should contain: """ Success: Created theme 'Starter-theme'. """ And the {THEME_DIR}/starter-theme/sass directory should exist - @require-php-5.6 @require-wp-4.6 - Scenario: Scaffold starter code for a WooCommerce theme - Given a WP install - And I run `wp theme path` - And save STDOUT as {THEME_DIR} - - # Allow for warnings to be generated due to https://github.com/wp-cli/scaffold-command/issues/181 - When I try `wp scaffold _s starter-theme --woocommerce` - Then STDOUT should contain: - """ - Success: Created theme 'Starter-theme'. - """ - And the {THEME_DIR}/starter-theme/woocommerce.css file should exist - And the {THEME_DIR}/starter-theme/inc/woocommerce.php file should exist - - @require-php-5.6 @require-wp-4.6 @require-mysql Scenario: Scaffold starter code for a theme and activate it Given a WP install - # Allow for warnings to be generated due to https://github.com/wp-cli/scaffold-command/issues/181 - When I try `wp scaffold _s starter-theme --activate` + When I run `wp scaffold _s starter-theme --activate` Then STDOUT should contain: """ Success: Switched to 'Starter-theme' theme. @@ -461,21 +338,16 @@ Feature: WordPress code scaffolding """ Error: Invalid theme slug specified. """ - And the return code should be 1 - When I try `wp scaffold _s ../` Then STDERR should contain: """ Error: Invalid theme slug specified. """ - And the return code should be 1 - When I try `wp scaffold _s 1themestartingwithnumber` Then STDERR should contain: """ Error: Invalid theme slug specified. Theme slugs can only contain letters, numbers, underscores and hyphens, and can only start with a letter or underscore. """ - And the return code should be 1 Scenario: Scaffold plugin and tests for non-standard plugin directory Given a WP install @@ -490,7 +362,6 @@ Feature: WordPress code scaffolding """ Error: Invalid plugin directory specified. """ - And the return code should be 1 When I run `wp scaffold plugin-tests --dir=wp-content/mu-plugins/custom-plugin` Then STDOUT should contain: @@ -500,9 +371,9 @@ Feature: WordPress code scaffolding And the wp-content/mu-plugins/custom-plugin/tests directory should exist And the wp-content/mu-plugins/custom-plugin/tests/bootstrap.php file should exist And the wp-content/mu-plugins/custom-plugin/tests/bootstrap.php file should contain: - """ - require dirname( __DIR__ ) . '/custom-plugin.php'; - """ + """ + require dirname( dirname( __FILE__ ) ) . '/custom-plugin.php'; + """ Scenario: Scaffold tests for a plugin with a different slug than plugin directory Given a WP install @@ -524,9 +395,9 @@ Feature: WordPress code scaffolding And the wp-content/mu-plugins/custom-plugin2/tests directory should exist And the wp-content/mu-plugins/custom-plugin2/tests/bootstrap.php file should exist And the wp-content/mu-plugins/custom-plugin2/tests/bootstrap.php file should contain: - """ - require dirname( __DIR__ ) . '/custom-plugin-slug.php'; - """ + """ + require dirname( dirname( __FILE__ ) ) . '/custom-plugin-slug.php'; + """ Scenario: Scaffold tests parses plugin readme.txt Given a WP install @@ -538,56 +409,56 @@ Feature: WordPress code scaffolding When I run `wp scaffold plugin hello-world` Then STDOUT should not be empty And the {PLUGIN_DIR}/hello-world/readme.txt file should exist - And the {PLUGIN_DIR}/hello-world/.circleci/config.yml file should exist - And the {PLUGIN_DIR}/hello-world/.circleci/config.yml file should contain: - """ - workflows: - version: 2 - main: - jobs: - - php56-build - - php70-build - - php71-build - - php72-build - - php73-build - - php74-build - """ - - @require-php-5.6 @require-wp-4.6 + And the {PLUGIN_DIR}/hello-world/.travis.yml file should exist + And the {PLUGIN_DIR}/hello-world/.travis.yml file should contain: + """ + matrix: + include: + - php: 7.1 + env: WP_VERSION=latest + - php: 7.0 + env: WP_VERSION=latest + - php: 5.6 + env: WP_VERSION=4.4 + - php: 5.6 + env: WP_VERSION=latest + - php: 5.6 + env: WP_VERSION=trunk + - php: 5.6 + env: WP_TRAVISCI=phpcs + - php: 5.3 + env: WP_VERSION=latest + """ + Scenario: Scaffold starter code for a theme and network enable it Given a WP multisite install - # Allow for warnings to be generated due to https://github.com/wp-cli/scaffold-command/issues/181 - When I try `wp scaffold _s starter-theme --enable-network` + When I run `wp scaffold _s starter-theme --enable-network` Then STDOUT should contain: """ Success: Network enabled the 'Starter-theme' theme. """ - @require-php-5.6 @require-wp-4.6 @require-mysql Scenario: Scaffold starter code for a theme, but can't unzip theme files Given a WP install And a misconfigured WP_CONTENT_DIR constant directory When I try `wp scaffold _s starter-theme` Then STDERR should contain: - """ - Error: Could not decompress your theme files - """ - And the return code should be 1 + """ + Error: Could not decompress your theme files + """ Scenario: Overwrite existing files Given a WP install When I run `wp scaffold plugin test` - And I try `wp scaffold plugin test --force` + And I run `wp scaffold plugin test --force` Then STDERR should contain: - """ - already exists - """ + """ + already exists + """ And STDOUT should contain: - """ - Replacing - """ - And the return code should be 0 - + """ + Replacing + """ Scenario: Scaffold tests for invalid plugin directory Given a WP install @@ -596,4 +467,3 @@ Feature: WordPress code scaffolding """ Error: Invalid plugin slug specified. """ - And the return code should be 1 diff --git a/features/steps/given.php b/features/steps/given.php new file mode 100644 index 000000000..a1b6a1bb9 --- /dev/null +++ b/features/steps/given.php @@ -0,0 +1,164 @@ +<?php + +use Behat\Gherkin\Node\PyStringNode, + Behat\Gherkin\Node\TableNode, + WP_CLI\Process; + +$steps->Given( '/^an empty directory$/', + function ( $world ) { + $world->create_run_dir(); + } +); + +$steps->Given( '/^an empty cache/', + function ( $world ) { + $world->variables['SUITE_CACHE_DIR'] = FeatureContext::create_cache_dir(); + } +); + +$steps->Given( '/^an? ([^\s]+) file:$/', + function ( $world, $path, PyStringNode $content ) { + $content = (string) $content . "\n"; + $full_path = $world->variables['RUN_DIR'] . "/$path"; + Process::create( \WP_CLI\utils\esc_cmd( 'mkdir -p %s', dirname( $full_path ) ) )->run_check(); + file_put_contents( $full_path, $content ); + } +); + +$steps->Given( '/^"([^"]+)" replaced with "([^"]+)" in the ([^\s]+) file$/', function( $world, $search, $replace, $path ) { + $full_path = $world->variables['RUN_DIR'] . "/$path"; + $contents = file_get_contents( $full_path ); + $contents = str_replace( $search, $replace, $contents ); + file_put_contents( $full_path, $contents ); +}); + +$steps->Given( '/^WP files$/', + function ( $world ) { + $world->download_wp(); + } +); + +$steps->Given( '/^wp-config\.php$/', + function ( $world ) { + $world->create_config(); + } +); + +$steps->Given( '/^a database$/', + function ( $world ) { + $world->create_db(); + } +); + +$steps->Given( '/^a WP install$/', + function ( $world ) { + $world->install_wp(); + } +); + +$steps->Given( "/^a WP install in '([^\s]+)'$/", + function ( $world, $subdir ) { + $world->install_wp( $subdir ); + } +); + +$steps->Given( '/^a WP multisite (subdirectory|subdomain)?\s?install$/', + function ( $world, $type = 'subdirectory' ) { + $world->install_wp(); + $subdomains = ! empty( $type ) && 'subdomain' === $type ? 1 : 0; + $world->proc( 'wp core install-network', array( 'title' => 'WP CLI Network', 'subdomains' => $subdomains ) )->run_check(); + } +); + +$steps->Given( '/^these installed and active plugins:$/', + function( $world, $stream ) { + $plugins = implode( ' ', array_map( 'trim', explode( PHP_EOL, (string)$stream ) ) ); + $world->proc( "wp plugin install $plugins --activate" )->run_check(); + } +); + +$steps->Given( '/^a custom wp-content directory$/', + function ( $world ) { + $wp_config_path = $world->variables['RUN_DIR'] . "/wp-config.php"; + + $wp_config_code = file_get_contents( $wp_config_path ); + + $world->move_files( 'wp-content', 'my-content' ); + $world->add_line_to_wp_config( $wp_config_code, + "define( 'WP_CONTENT_DIR', dirname(__FILE__) . '/my-content' );" ); + + $world->move_files( 'my-content/plugins', 'my-plugins' ); + $world->add_line_to_wp_config( $wp_config_code, + "define( 'WP_PLUGIN_DIR', __DIR__ . '/my-plugins' );" ); + + file_put_contents( $wp_config_path, $wp_config_code ); + } +); + +$steps->Given( '/^download:$/', + function ( $world, TableNode $table ) { + foreach ( $table->getHash() as $row ) { + $path = $world->replace_variables( $row['path'] ); + if ( file_exists( $path ) ) { + // assume it's the same file and skip re-download + continue; + } + + Process::create( \WP_CLI\Utils\esc_cmd( 'curl -sSL %s > %s', $row['url'], $path ) )->run_check(); + } + } +); + +$steps->Given( '/^save (STDOUT|STDERR) ([\'].+[^\'])?\s?as \{(\w+)\}$/', + function ( $world, $stream, $output_filter, $key ) { + + $stream = strtolower( $stream ); + + if ( $output_filter ) { + $output_filter = '/' . trim( str_replace( '%s', '(.+[^\b])', $output_filter ), "' " ) . '/'; + if ( false !== preg_match( $output_filter, $world->result->$stream, $matches ) ) + $output = array_pop( $matches ); + else + $output = ''; + } else { + $output = $world->result->$stream; + } + $world->variables[ $key ] = trim( $output, "\n" ); + } +); + +$steps->Given( '/^a new Phar(?: with version "([^"]+)")$/', + function ( $world, $version ) { + $world->build_phar( $version ); + } +); + +$steps->Given( '/^save the (.+) file ([\'].+[^\'])?as \{(\w+)\}$/', + function ( $world, $filepath, $output_filter, $key ) { + $full_file = file_get_contents( $world->replace_variables( $filepath ) ); + + if ( $output_filter ) { + $output_filter = '/' . trim( str_replace( '%s', '(.+[^\b])', $output_filter ), "' " ) . '/'; + if ( false !== preg_match( $output_filter, $full_file, $matches ) ) + $output = array_pop( $matches ); + else + $output = ''; + } else { + $output = $full_file; + } + $world->variables[ $key ] = trim( $output, "\n" ); + } +); + +$steps->Given('/^a misconfigured WP_CONTENT_DIR constant directory$/', + function($world) { + $wp_config_path = $world->variables['RUN_DIR'] . "/wp-config.php"; + + $wp_config_code = file_get_contents( $wp_config_path ); + + $world->add_line_to_wp_config( $wp_config_code, + "define( 'WP_CONTENT_DIR', '' );" ); + + file_put_contents( $wp_config_path, $wp_config_code ); + } +); diff --git a/features/steps/then.php b/features/steps/then.php new file mode 100644 index 000000000..a5974f16c --- /dev/null +++ b/features/steps/then.php @@ -0,0 +1,210 @@ +<?php + +use Behat\Gherkin\Node\PyStringNode, + Behat\Gherkin\Node\TableNode; + +$steps->Then( '/^the return code should be (\d+)$/', + function ( $world, $return_code ) { + if ( $return_code != $world->result->return_code ) { + throw new RuntimeException( $world->result ); + } + } +); + +$steps->Then( '/^(STDOUT|STDERR) should (be|contain|not contain):$/', + function ( $world, $stream, $action, PyStringNode $expected ) { + + $stream = strtolower( $stream ); + + $expected = $world->replace_variables( (string) $expected ); + + checkString( $world->result->$stream, $expected, $action, $world->result ); + } +); + +$steps->Then( '/^(STDOUT|STDERR) should be a number$/', + function ( $world, $stream ) { + + $stream = strtolower( $stream ); + + assertNumeric( trim( $world->result->$stream, "\n" ) ); + } +); + +$steps->Then( '/^(STDOUT|STDERR) should not be a number$/', + function ( $world, $stream ) { + + $stream = strtolower( $stream ); + + assertNotNumeric( trim( $world->result->$stream, "\n" ) ); + } +); + +$steps->Then( '/^STDOUT should be a table containing rows:$/', + function ( $world, TableNode $expected ) { + $output = $world->result->stdout; + $actual_rows = explode( "\n", rtrim( $output, "\n" ) ); + + $expected_rows = array(); + foreach ( $expected->getRows() as $row ) { + $expected_rows[] = $world->replace_variables( implode( "\t", $row ) ); + } + + compareTables( $expected_rows, $actual_rows, $output ); + } +); + +$steps->Then( '/^STDOUT should end with a table containing rows:$/', + function ( $world, TableNode $expected ) { + $output = $world->result->stdout; + $actual_rows = explode( "\n", rtrim( $output, "\n" ) ); + + $expected_rows = array(); + foreach ( $expected->getRows() as $row ) { + $expected_rows[] = $world->replace_variables( implode( "\t", $row ) ); + } + + $start = array_search( $expected_rows[0], $actual_rows ); + + if ( false === $start ) + throw new \Exception( $world->result ); + + compareTables( $expected_rows, array_slice( $actual_rows, $start ), $output ); + } +); + +$steps->Then( '/^STDOUT should be JSON containing:$/', + function ( $world, PyStringNode $expected ) { + $output = $world->result->stdout; + $expected = $world->replace_variables( (string) $expected ); + + if ( !checkThatJsonStringContainsJsonString( $output, $expected ) ) { + throw new \Exception( $world->result ); + } +}); + +$steps->Then( '/^STDOUT should be a JSON array containing:$/', + function ( $world, PyStringNode $expected ) { + $output = $world->result->stdout; + $expected = $world->replace_variables( (string) $expected ); + + $actualValues = json_decode( $output ); + $expectedValues = json_decode( $expected ); + + $missing = array_diff( $expectedValues, $actualValues ); + if ( !empty( $missing ) ) { + throw new \Exception( $world->result ); + } +}); + +$steps->Then( '/^STDOUT should be CSV containing:$/', + function ( $world, TableNode $expected ) { + $output = $world->result->stdout; + + $expected_rows = $expected->getRows(); + foreach ( $expected as &$row ) { + foreach ( $row as &$value ) { + $value = $world->replace_variables( $value ); + } + } + + if ( ! checkThatCsvStringContainsValues( $output, $expected_rows ) ) + throw new \Exception( $world->result ); + } +); + +$steps->Then( '/^STDOUT should be YAML containing:$/', + function ( $world, PyStringNode $expected ) { + $output = $world->result->stdout; + $expected = $world->replace_variables( (string) $expected ); + + if ( !checkThatYamlStringContainsYamlString( $output, $expected ) ) { + throw new \Exception( $world->result ); + } +}); + +$steps->Then( '/^(STDOUT|STDERR) should be empty$/', + function ( $world, $stream ) { + + $stream = strtolower( $stream ); + + if ( !empty( $world->result->$stream ) ) { + throw new \Exception( $world->result ); + } + } +); + +$steps->Then( '/^(STDOUT|STDERR) should not be empty$/', + function ( $world, $stream ) { + + $stream = strtolower( $stream ); + + if ( '' === rtrim( $world->result->$stream, "\n" ) ) { + throw new Exception( $world->result ); + } + } +); + +$steps->Then( '/^(STDOUT|STDERR) should be a version string (<|<=|>|>=|==|=|!=|<>) ([+\w\.-]+)$/', + function ( $world, $stream, $operator, $goal_ver ) { + $stream = strtolower( $stream ); + if ( false === version_compare( trim( $world->result->$stream, "\n" ), $goal_ver, $operator ) ) { + throw new Exception( $world->result ); + } + } +); + +$steps->Then( '/^the (.+) (file|directory) should (exist|not exist|be:|contain:|not contain:)$/', + function ( $world, $path, $type, $action, $expected = null ) { + $path = $world->replace_variables( $path ); + + // If it's a relative path, make it relative to the current test dir + if ( '/' !== $path[0] ) + $path = $world->variables['RUN_DIR'] . "/$path"; + + if ( 'file' == $type ) { + $test = 'file_exists'; + } else if ( 'directory' == $type ) { + $test = 'is_dir'; + } + + switch ( $action ) { + case 'exist': + if ( ! $test( $path ) ) { + throw new Exception( $world->result ); + } + break; + case 'not exist': + if ( $test( $path ) ) { + throw new Exception( $world->result ); + } + break; + default: + if ( ! $test( $path ) ) { + throw new Exception( "$path doesn't exist." ); + } + $action = substr( $action, 0, -1 ); + $expected = $world->replace_variables( (string) $expected ); + if ( 'file' == $type ) { + $contents = file_get_contents( $path ); + } else if ( 'directory' == $type ) { + $files = glob( rtrim( $path, '/' ) . '/*' ); + foreach( $files as &$file ) { + $file = str_replace( $path . '/', '', $file ); + } + $contents = implode( PHP_EOL, $files ); + } + checkString( $contents, $expected, $action ); + } + } +); + +$steps->Then( '/^an email should (be sent|not be sent)$/', function( $world, $expected ) { + if ( 'be sent' === $expected ) { + assertNotEquals( 0, $world->email_sends ); + } else if ( 'not be sent' === $expected ) { + assertEquals( 0, $world->email_sends ); + } else { + throw new Exception( 'Invalid expectation' ); + } +}); diff --git a/features/steps/when.php b/features/steps/when.php new file mode 100644 index 000000000..afe3f7a0d --- /dev/null +++ b/features/steps/when.php @@ -0,0 +1,54 @@ +<?php + +use Behat\Gherkin\Node\PyStringNode, + Behat\Gherkin\Node\TableNode, + WP_CLI\Process; + +function invoke_proc( $proc, $mode ) { + $map = array( + 'run' => 'run_check', + 'try' => 'run' + ); + $method = $map[ $mode ]; + + return $proc->$method(); +} + +function capture_email_sends( $stdout ) { + $stdout = preg_replace( '#WP-CLI test suite: Sent email to.+\n?#', '', $stdout, -1, $email_sends ); + return array( $stdout, $email_sends ); +} + +$steps->When( '/^I launch in the background `([^`]+)`$/', + function ( $world, $cmd ) { + $world->background_proc( $cmd ); + } +); + +$steps->When( '/^I (run|try) `([^`]+)`$/', + function ( $world, $mode, $cmd ) { + $cmd = $world->replace_variables( $cmd ); + $world->result = invoke_proc( $world->proc( $cmd ), $mode ); + list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout ); + } +); + +$steps->When( "/^I (run|try) `([^`]+)` from '([^\s]+)'$/", + function ( $world, $mode, $cmd, $subdir ) { + $cmd = $world->replace_variables( $cmd ); + $world->result = invoke_proc( $world->proc( $cmd, array(), $subdir ), $mode ); + list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout ); + } +); + +$steps->When( '/^I (run|try) the previous command again$/', + function ( $world, $mode ) { + if ( !isset( $world->result ) ) + throw new \Exception( 'No previous command.' ); + + $proc = Process::create( $world->result->command, $world->result->cwd, $world->result->env ); + $world->result = invoke_proc( $proc, $mode ); + list( $world->result->stdout, $world->email_sends ) = capture_email_sends( $world->result->stdout ); + } +); + diff --git a/phpcs.xml.dist b/phpcs.xml.dist deleted file mode 100644 index 5e9e44b3b..000000000 --- a/phpcs.xml.dist +++ /dev/null @@ -1,59 +0,0 @@ -<?xml version="1.0"?> -<ruleset name="WP-CLI-scaffold"> - <description>Custom ruleset for WP-CLI scaffold-command</description> - - <!-- - ############################################################################# - COMMAND LINE ARGUMENTS - For help understanding this file: https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Annotated-ruleset.xml - For help using PHPCS: https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Usage - ############################################################################# - --> - - <!-- What to scan. --> - <file>.</file> - - <!-- Show progress. --> - <arg value="p"/> - - <!-- Strip the filepaths down to the relevant bit. --> - <arg name="basepath" value="./"/> - - <!-- Check up to 8 files simultaneously. --> - <arg name="parallel" value="8"/> - - <!-- - ############################################################################# - USE THE WP_CLI_CS RULESET - ############################################################################# - --> - - <rule ref="WP_CLI_CS"/> - - <!-- - ############################################################################# - PROJECT SPECIFIC CONFIGURATION FOR SNIFFS - ############################################################################# - --> - - <!-- For help understanding the `testVersion` configuration setting: - https://github.com/PHPCompatibility/PHPCompatibility#sniffing-your-code-for-compatibility-with-specific-php-versions --> - <config name="testVersion" value="7.2-"/> - - <!-- Verify that everything in the global namespace is either namespaced or prefixed. - See: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#naming-conventions-prefix-everything-in-the-global-namespace --> - <rule ref="WordPress.NamingConventions.PrefixAllGlobals"> - <properties> - <property name="prefixes" type="array"> - <element value="WP_CLI\Scaffold"/><!-- Namespaces. --> - <element value="wpcli_scaffold"/><!-- Global variables and such. --> - </property> - </properties> - </rule> - - <!-- Exclude existing classes from the prefix rule as it would break BC to prefix them now. --> - <rule ref="WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound"> - <exclude-pattern>*/src/Scaffold_Command\.php$</exclude-pattern> - </rule> - -</ruleset> diff --git a/phpstan.neon.dist b/phpstan.neon.dist deleted file mode 100644 index 20dfba73b..000000000 --- a/phpstan.neon.dist +++ /dev/null @@ -1,13 +0,0 @@ -parameters: - level: 9 - paths: - - src - - scaffold-command.php - scanDirectories: - - vendor/wp-cli/wp-cli/php - scanFiles: - - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php - treatPhpDocTypesAsCertain: false - ignoreErrors: - - identifier: missingType.parameter - - identifier: missingType.return diff --git a/scaffold-command.php b/scaffold-command.php index a2b0f5010..9930bc76d 100644 --- a/scaffold-command.php +++ b/scaffold-command.php @@ -4,9 +4,9 @@ return; } -$wpcli_scaffold_autoloader = __DIR__ . '/vendor/autoload.php'; -if ( file_exists( $wpcli_scaffold_autoloader ) ) { - require_once $wpcli_scaffold_autoloader; +$autoload = dirname( __FILE__ ) . '/vendor/autoload.php'; +if ( file_exists( $autoload ) ) { + require_once $autoload; } WP_CLI::add_command( 'scaffold', 'Scaffold_Command' ); diff --git a/src/Scaffold_Command.php b/src/Scaffold_Command.php index f1a3e6beb..dae04d370 100644 --- a/src/Scaffold_Command.php +++ b/src/Scaffold_Command.php @@ -1,33 +1,32 @@ <?php use WP_CLI\Utils; -use WP_CLI\Path; -use WP_CLI\Inflector; +use WP_CLI\Process; /** - * Generates code for post types, taxonomies, plugins, child themes, etc. + * Generate code for post types, taxonomies, plugins, child themes, etc. * * ## EXAMPLES * - * # Generate a new plugin with unit tests. + * # Generate a new plugin with unit tests * $ wp scaffold plugin sample-plugin * Success: Created plugin files. * Success: Created test files. * - * # Generate theme based on _s. + * # Generate theme based on _s * $ wp scaffold _s sample-theme --theme_name="Sample Theme" --author="John Doe" * Success: Created theme 'Sample Theme'. * - * # Generate code for post type registration in given theme. + * # Generate code for post type registration in given theme * $ wp scaffold post-type movie --label=Movie --theme=simple-life - * Success: Created '/var/www/example.com/public_html/wp-content/themes/simple-life/post-types/movie.php'. + * Success: Created /var/www/example.com/public_html/wp-content/themes/simple-life/post-types/movie.php * * @package wp-cli */ class Scaffold_Command extends WP_CLI_Command { /** - * Generates PHP code for registering a custom post type. + * Generate PHP code for registering a custom post type. * * ## OPTIONS * @@ -69,24 +68,22 @@ class Scaffold_Command extends WP_CLI_Command { public function post_type( $args, $assoc_args ) { if ( strlen( $args[0] ) > 20 ) { - WP_CLI::error( 'Post type slugs cannot exceed 20 characters in length.' ); + WP_CLI::error( "Post type slugs cannot exceed 20 characters in length." ); } - $defaults = [ + $defaults = array( 'textdomain' => '', 'dashicon' => 'admin-post', - ]; + ); - $templates = [ + $this->_scaffold( $args[0], $assoc_args, $defaults, '/post-types/', array( 'post_type.mustache', - 'post_type_extended.mustache', - ]; - - $this->scaffold( $args[0], $assoc_args, $defaults, '/post-types/', $templates ); + 'post_type_extended.mustache' + ) ); } /** - * Generates PHP code for registering a custom taxonomy. + * Generate PHP code for registering a custom taxonomy. * * ## OPTIONS * @@ -125,41 +122,33 @@ public function post_type( $args, $assoc_args ) { * @alias tax */ public function taxonomy( $args, $assoc_args ) { - $defaults = [ + $defaults = array( 'textdomain' => '', - 'post_types' => "'post'", - ]; + 'post_types' => "'post'" + ); if ( isset( $assoc_args['post_types'] ) ) { $assoc_args['post_types'] = $this->quote_comma_list_elements( $assoc_args['post_types'] ); } - $templates = [ + $this->_scaffold( $args[0], $assoc_args, $defaults, '/taxonomies/', array( 'taxonomy.mustache', - 'taxonomy_extended.mustache', - ]; - - $this->scaffold( $args[0], $assoc_args, $defaults, '/taxonomies/', $templates ); + 'taxonomy_extended.mustache' + ) ); } - private function scaffold( $slug, $assoc_args, $defaults, $subdir, $templates ) { + private function _scaffold( $slug, $assoc_args, $defaults, $subdir, $templates ) { $wp_filesystem = $this->init_wp_filesystem(); - $control_defaults = [ + $control_args = $this->extract_args( $assoc_args, array( 'label' => preg_replace( '/_|-/', ' ', strtolower( $slug ) ), 'theme' => false, 'plugin' => false, 'raw' => false, - ]; - $control_args = $this->extract_args( $assoc_args, $control_defaults ); + ) ); $vars = $this->extract_args( $assoc_args, $defaults ); - $dashicon = $this->extract_dashicon( $assoc_args ); - if ( $dashicon ) { - $vars['dashicon'] = $dashicon; - } - $vars['slug'] = $slug; $vars['textdomain'] = $this->get_textdomain( $vars['textdomain'], $control_args ); @@ -170,7 +159,8 @@ private function scaffold( $slug, $assoc_args, $defaults, $subdir, $templates ) $vars['label_plural'] = $this->pluralize( $vars['label'] ); $vars['label_plural_ucfirst'] = ucfirst( $vars['label_plural'] ); - $machine_name = $this->generate_machine_name( $slug ); + // We use the machine name for function declarations + $machine_name = preg_replace( '/-/', '_', $slug ); $machine_name_plural = $this->pluralize( $slug ); list( $raw_template, $extended_template ) = $templates; @@ -178,41 +168,26 @@ private function scaffold( $slug, $assoc_args, $defaults, $subdir, $templates ) $raw_output = self::mustache_render( $raw_template, $vars ); if ( ! $control_args['raw'] ) { - $vars['machine_name'] = $machine_name; - $vars['output'] = rtrim( $raw_output ); - - $target_slug = ''; - - if ( true === $control_args['theme'] ) { - $target_slug = get_stylesheet(); - } elseif ( false !== $control_args['theme'] ) { - $target_slug = $control_args['theme']; - } elseif ( false !== $control_args['plugin'] ) { - $target_slug = $control_args['plugin']; - } - - $target_name = ( $target_slug ) ? $this->generate_machine_name( $target_slug ) : ''; - - if ( empty( $target_name ) ) { - $target_name = $machine_name; - } - - $vars['prefix'] = $target_name; + $vars = array_merge( $vars, array( + 'machine_name' => $machine_name, + 'output' => $raw_output + ) ); $final_output = self::mustache_render( $extended_template, $vars ); } else { $final_output = $raw_output; } - $path = $this->get_output_path( $control_args, $subdir ); - if ( is_string( $path ) && ! empty( $path ) ) { - $filename = "{$path}{$slug}.php"; + if ( $path = $this->get_output_path( $control_args, $subdir ) ) { + $filename = $path . $slug . '.php'; - $force = Utils\get_flag_value( $assoc_args, 'force' ); - $files_written = $this->create_files( [ $filename => $final_output ], $force ); - $skip_message = "Skipped creating '{$filename}'."; - $success_message = "Created '{$filename}'."; - $this->log_whether_files_written( $files_written, $skip_message, $success_message ); + $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); + $files_written = $this->create_files( array( $filename => $final_output ), $force ); + $this->log_whether_files_written( + $files_written, + $skip_message = "Skipped creating '$filename'.", + $success_message = "Created '$filename'." + ); } else { // STDOUT @@ -221,109 +196,9 @@ private function scaffold( $slug, $assoc_args, $defaults, $subdir, $templates ) } /** - * Generates PHP, JS and CSS code for registering a Gutenberg block for a plugin or theme. - * - * **Warning: `wp scaffold block` is deprecated.** + * Generate starter code for a theme based on _s. * - * The official script to generate a block is the [@wordpress/create-block](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) package. - * - * See the [Create a Block tutorial](https://developer.wordpress.org/block-editor/getting-started/tutorial/) for a complete walk-through. - * - * ## OPTIONS - * - * <slug> - * : The internal name of the block. - * - * [--title=<title>] - * : The display title for your block. - * - * [--dashicon=<dashicon>] - * : The dashicon to make it easier to identify your block. - * - * [--category=<category>] - * : The category name to help users browse and discover your block. - * --- - * default: widgets - * options: - * - common - * - embed - * - formatting - * - layout - * - widgets - * --- - * - * [--theme] - * : Create files in the active theme directory. Specify a theme with `--theme=<theme>` to have the file placed in that theme. - * - * [--plugin=<plugin>] - * : Create files in the given plugin's directory. - * - * [--force] - * : Overwrite files that already exist. - * - * @subcommand block - */ - public function block( $args, $assoc_args ) { - - $slug = $args[0]; - if ( ! preg_match( '/^[a-z][a-z0-9\-]*$/', $slug ) ) { - WP_CLI::error( 'Invalid block slug specified. Block slugs can contain only lowercase alphanumeric characters or dashes, and start with a letter.' ); - } - - $defaults = [ - 'title' => str_replace( '-', ' ', $slug ), - 'category' => 'widgets', - ]; - $data = $this->extract_args( $assoc_args, $defaults ); - - $data['slug'] = $slug; - $data['title_ucfirst'] = ucfirst( $data['title'] ); - $data['title_ucfirst_js'] = esc_js( $data['title_ucfirst'] ); - - $dashicon = $this->extract_dashicon( $assoc_args ); - if ( $dashicon ) { - $data['dashicon'] = $dashicon; - } - - $control_defaults = [ - 'force' => false, - 'plugin' => false, - 'theme' => false, - ]; - $control_args = $this->extract_args( $assoc_args, $control_defaults ); - - if ( isset( $control_args['plugin'] ) ) { - if ( ! preg_match( '/^[A-Za-z0-9\-]*$/', $control_args['plugin'] ) ) { - WP_CLI::error( 'Invalid plugin name specified. The block editor can only register blocks for plugins that have nothing but lowercase alphanumeric characters or dashes in their slug.' ); - } - } - - $data['namespace'] = $control_args['plugin'] ? $control_args['plugin'] : $this->get_theme_name( $control_args['theme'] ); - $data['machine_name'] = $this->generate_machine_name( $slug ); - $data['plugin'] = $control_args['plugin'] ? true : false; - $data['theme'] = ! $data['plugin']; - - $block_dir = $this->get_output_path( $control_args, '/blocks' ); - if ( ! $block_dir ) { - WP_CLI::error( 'No plugin or theme selected.' ); - } - - $files_to_create = [ - "{$block_dir}/{$slug}.php" => self::mustache_render( 'block-php.mustache', $data ), - "{$block_dir}/{$slug}/index.js" => self::mustache_render( 'block-index-js.mustache', $data ), - "{$block_dir}/{$slug}/editor.css" => self::mustache_render( 'block-editor-css.mustache', $data ), - "{$block_dir}/{$slug}/style.css" => self::mustache_render( 'block-style-css.mustache', $data ), - ]; - $files_written = $this->create_files( $files_to_create, $control_args['force'] ); - $skip_message = 'All block files were skipped.'; - $success_message = "Created block '{$data['title_ucfirst']}'."; - $this->log_whether_files_written( $files_written, $skip_message, $success_message ); - } - - /** - * Generates starter code for a theme based on _s. - * - * See the [Underscores website](https://underscores.me/) for more details. + * See the [Underscores website](http://underscores.me/) for more details. * * ## OPTIONS * @@ -348,9 +223,6 @@ public function block( $args, $assoc_args ) { * [--sassify] * : Include stylesheets as SASS. * - * [--woocommerce] - * : Include WooCommerce boilerplate files. - * * [--force] * : Overwrite files that already exist. * @@ -359,76 +231,70 @@ public function block( $args, $assoc_args ) { * # Generate a theme with name "Sample Theme" and author "John Doe" * $ wp scaffold _s sample-theme --theme_name="Sample Theme" --author="John Doe" * Success: Created theme 'Sample Theme'. - * - * @alias _s */ - public function underscores( $args, $assoc_args ) { + public function _s( $args, $assoc_args ) { $theme_slug = $args[0]; - $theme_path = WP_CONTENT_DIR . '/themes'; - $url = 'https://underscores.me'; + $theme_path = WP_CONTENT_DIR . "/themes"; + $url = "http://underscores.me"; $timeout = 30; + if ( in_array( $theme_slug, array( '.', '..' ) ) ) { + WP_CLI::error( "Invalid theme slug specified." ); + } + if ( ! preg_match( '/^[a-z_]\w+$/i', str_replace( '-', '_', $theme_slug ) ) ) { - WP_CLI::error( 'Invalid theme slug specified. Theme slugs can only contain letters, numbers, underscores and hyphens, and can only start with a letter or underscore.' ); + WP_CLI::error( "Invalid theme slug specified. Theme slugs can only contain letters, numbers, underscores and hyphens, and can only start with a letter or underscore." ); } - $defaults = [ + $data = wp_parse_args( $assoc_args, array( 'theme_name' => ucfirst( $theme_slug ), - 'author' => 'Me', - 'author_uri' => '', - ]; - $data = wp_parse_args( $assoc_args, $defaults ); + 'author' => "Me", + 'author_uri' => "", + ) ); $_s_theme_path = "$theme_path/$data[theme_name]"; - $error_msg = $this->check_target_directory( 'theme', $_s_theme_path ); - if ( ! empty( $error_msg ) ) { - WP_CLI::error( "Invalid theme slug specified. {$error_msg}" ); + if ( ! $this->check_target_directory( "theme", $_s_theme_path ) ) { + WP_CLI::error( "Invalid theme slug specified." ); } - $force = Utils\get_flag_value( $assoc_args, 'force' ); + $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); $should_write_file = $this->prompt_if_files_will_be_overwritten( $_s_theme_path, $force ); if ( ! $should_write_file ) { WP_CLI::log( 'No files created' ); die; } - $theme_description = "Custom theme: {$data['theme_name']}, developed by {$data['author']}"; + $theme_description = "Custom theme: " . $data['theme_name'] . ", developed by " . $data['author']; - $body = []; + $body = array(); $body['underscoresme_name'] = $data['theme_name']; $body['underscoresme_slug'] = $theme_slug; $body['underscoresme_author'] = $data['author']; $body['underscoresme_author_uri'] = $data['author_uri']; $body['underscoresme_description'] = $theme_description; - $body['underscoresme_generate_submit'] = 'Generate'; - $body['underscoresme_generate'] = '1'; - if ( Utils\get_flag_value( $assoc_args, 'sassify' ) ) { + $body['underscoresme_generate_submit'] = "Generate"; + $body['underscoresme_generate'] = "1"; + if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'sassify' ) ) { $body['underscoresme_sass'] = 1; } - if ( Utils\get_flag_value( $assoc_args, 'woocommerce' ) ) { - $body['underscoresme_woocommerce'] = 1; - } - - $tmpfname = wp_tempnam( $url ); - $post_args = [ + $tmpfname = wp_tempnam( $url ); + $response = wp_remote_post( $url, array( 'timeout' => $timeout, 'body' => $body, 'stream' => true, - 'filename' => $tmpfname, - ]; - - $response = wp_remote_post( $url, $post_args ); + 'filename' => $tmpfname + ) ); if ( is_wp_error( $response ) ) { WP_CLI::error( $response ); } $response_code = wp_remote_retrieve_response_code( $response ); - if ( 200 !== (int) $response_code ) { - WP_CLI::error( "Couldn't create theme (received {$response_code} response)." ); + if ( 200 != $response_code ) { + WP_CLI::error( "Couldn't create theme (received $response_code response)." ); } $this->maybe_create_themes_dir(); @@ -439,24 +305,23 @@ public function underscores( $args, $assoc_args ) { unlink( $tmpfname ); if ( true === $unzip_result ) { - $files_to_create = [ - "{$theme_path}/{$theme_slug}/.editorconfig" => file_get_contents( self::get_template_path( '.editorconfig' ) ), - ]; - $this->create_files( $files_to_create, false ); + $this->create_files( array( + "$theme_path/{$theme_slug}/.editorconfig" => file_get_contents( self::get_template_path( '.editorconfig' ) ), + ), false ); WP_CLI::success( "Created theme '{$data['theme_name']}'." ); } else { WP_CLI::error( "Could not decompress your theme files ('{$tmpfname}') at '{$theme_path}': {$unzip_result->get_error_message()}" ); } - if ( Utils\get_flag_value( $assoc_args, 'activate' ) ) { - WP_CLI::run_command( [ 'theme', 'activate', $theme_slug ] ); - } elseif ( Utils\get_flag_value( $assoc_args, 'enable-network' ) ) { - WP_CLI::run_command( [ 'theme', 'enable', $theme_slug ], [ 'network' => true ] ); + if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) { + WP_CLI::run_command( array( 'theme', 'activate', $theme_slug ) ); + } else if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'enable-network' ) ) { + WP_CLI::run_command( array( 'theme', 'enable', $theme_slug ), array( 'network' => true ) ); } } /** - * Generates child theme based on an existing theme. + * Generate child theme based on an existing theme. * * Creates a child theme folder with `functions.php` and `style.css` files. * @@ -497,54 +362,51 @@ public function underscores( $args, $assoc_args ) { * * @subcommand child-theme */ - public function child_theme( $args, $assoc_args ) { + function child_theme( $args, $assoc_args ) { $theme_slug = $args[0]; - if ( in_array( $theme_slug, [ '.', '..' ], true ) ) { - WP_CLI::error( "Invalid theme slug specified. The slug cannot be '.' or '..'." ); + if ( in_array( $theme_slug, array( '.', '..' ) ) ) { + WP_CLI::error( "Invalid theme slug specified." ); } - $defaults = [ + $data = wp_parse_args( $assoc_args, array( 'theme_name' => ucfirst( $theme_slug ), - 'author' => 'Me', - 'author_uri' => '', - 'theme_uri' => '', - ]; + 'author' => "Me", + 'author_uri' => "", + 'theme_uri' => "" + ) ); + $data['slug'] = $theme_slug; + $data['parent_theme_function_safe'] = str_replace( '-', '_', $data['parent_theme'] ); - $data = wp_parse_args( $assoc_args, $defaults ); - $data['slug'] = $theme_slug; - $data['prefix_safe'] = preg_replace( '/[^a-zA-Z0-9_]/', '_', $theme_slug ); - $data['description'] = ucfirst( $data['parent_theme'] ) . ' child theme.'; + $data['description'] = ucfirst( $data['parent_theme'] ) . " child theme."; - $theme_dir = WP_CONTENT_DIR . "/themes/{$theme_slug}"; + $theme_dir = WP_CONTENT_DIR . "/themes" . "/$theme_slug"; - $error_msg = $this->check_target_directory( 'theme', $theme_dir ); - if ( ! empty( $error_msg ) ) { - WP_CLI::error( "Invalid theme slug specified. {$error_msg}" ); + if ( ! $this->check_target_directory( "theme", $theme_dir ) ) { + WP_CLI::error( "Invalid theme slug specified." ); } - $theme_style_path = "{$theme_dir}/style.css"; - $theme_functions_path = "{$theme_dir}/functions.php"; + $theme_style_path = "$theme_dir/style.css"; + $theme_functions_path = "$theme_dir/functions.php"; $this->maybe_create_themes_dir(); - $files_to_create = [ - $theme_style_path => self::mustache_render( 'child_theme.mustache', $data ), - $theme_functions_path => self::mustache_render( 'child_theme_functions.mustache', $data ), - "{$theme_dir}/.editorconfig" => file_get_contents( self::get_template_path( '.editorconfig' ) ), - ]; - $force = Utils\get_flag_value( $assoc_args, 'force' ); - $files_written = $this->create_files( $files_to_create, $force ); - $skip_message = 'All theme files were skipped.'; - $success_message = "Created '{$theme_dir}'."; - $this->log_whether_files_written( $files_written, $skip_message, $success_message ); - - if ( Utils\get_flag_value( $assoc_args, 'activate' ) ) { - wp_get_theme( $theme_slug )->cache_delete(); - WP_CLI::run_command( [ 'theme', 'activate', $theme_slug ] ); - } elseif ( Utils\get_flag_value( $assoc_args, 'enable-network' ) ) { - wp_get_theme( $theme_slug )->cache_delete(); - WP_CLI::run_command( [ 'theme', 'enable', $theme_slug ], [ 'network' => true ] ); + $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); + $files_written = $this->create_files( array( + $theme_style_path => self::mustache_render( 'child_theme.mustache', $data ), + $theme_functions_path => self::mustache_render( 'child_theme_functions.mustache', $data ), + "$theme_dir/.editorconfig" => file_get_contents( self::get_template_path( '.editorconfig' ) ), + ), $force ); + $this->log_whether_files_written( + $files_written, + $skip_message = 'All theme files were skipped.', + $success_message = "Created '$theme_dir'." + ); + + if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) { + WP_CLI::run_command( array( 'theme', 'activate', $theme_slug ) ); + } else if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'enable-network' ) ) { + WP_CLI::run_command( array( 'theme', 'enable', $theme_slug ), array( 'network' => true ) ); } } @@ -552,18 +414,15 @@ private function get_output_path( $assoc_args, $subdir ) { if ( $assoc_args['theme'] ) { $theme = $assoc_args['theme']; if ( is_string( $theme ) ) { - $path = get_theme_root( $theme ) . "/{$theme}"; + $path = get_theme_root( $theme ) . '/' . $theme; } else { $path = get_stylesheet_directory(); } - if ( ! is_dir( $path ) ) { - WP_CLI::error( "Can't find '{$theme}' theme." ); - } } elseif ( $assoc_args['plugin'] ) { $plugin = $assoc_args['plugin']; - $path = WP_PLUGIN_DIR . "/{$plugin}"; + $path = WP_PLUGIN_DIR . '/' . $plugin; if ( ! is_dir( $path ) ) { - WP_CLI::error( "Can't find '{$plugin}' plugin." ); + WP_CLI::error( "Can't find '$plugin' plugin." ); } } else { return false; @@ -575,13 +434,13 @@ private function get_output_path( $assoc_args, $subdir ) { } /** - * Generates starter code for a plugin. + * Generate starter code for a plugin. * * The following files are always generated: * * * `plugin-slug.php` is the main PHP plugin file. * * `readme.txt` is the readme file for the plugin. - * * `package.json` needed by NPM holds various metadata relevant to the project. Packages: `grunt`, `grunt-wp-i18n` and `grunt-wp-readme-to-markdown`. Scripts: `start`, `readme`, `i18n`. + * * `package.json` needed by NPM holds various metadata relevant to the project. Packages: `grunt`, `grunt-wp-i18n` and `grunt-wp-readme-to-markdown`. * * `Gruntfile.js` is the JS file containing Grunt tasks. Tasks: `i18n` containing `addtextdomain` and `makepot`, `readme` containing `wp_readme_to_markdown`. * * `.editorconfig` is the configuration file for Editor. * * `.gitignore` tells which files (or patterns) git should ignore. @@ -590,11 +449,11 @@ private function get_output_path( $assoc_args, $subdir ) { * The following files are also included unless the `--skip-tests` is used: * * * `phpunit.xml.dist` is the configuration file for PHPUnit. - * * `.circleci/config.yml` is the configuration file for CircleCI. Use `--ci=<provider>` to select a different service. + * * `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service. * * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database. * * `tests/bootstrap.php` is the file that makes the current plugin active when running the test suite. * * `tests/test-sample.php` is a sample file containing test cases. - * * `.phpcs.xml.dist` is a collection of PHP_CodeSniffer rules. + * * `phpcs.ruleset.xml` is a collenction of PHP_CodeSniffer rules. * * ## OPTIONS * @@ -625,12 +484,11 @@ private function get_output_path( $assoc_args, $subdir ) { * [--ci=<provider>] * : Choose a configuration file for a continuous integration provider. * --- - * default: circle + * default: travis * options: + * - travis * - circle * - gitlab - * - bitbucket - * - github * --- * * [--activate] @@ -648,16 +506,16 @@ private function get_output_path( $assoc_args, $subdir ) { * Success: Created plugin files. * Success: Created test files. */ - public function plugin( $args, $assoc_args ) { + function plugin( $args, $assoc_args ) { $plugin_slug = $args[0]; $plugin_name = ucwords( str_replace( '-', ' ', $plugin_slug ) ); $plugin_package = str_replace( ' ', '_', $plugin_name ); - if ( in_array( $plugin_slug, [ '.', '..' ], true ) ) { - WP_CLI::error( "Invalid plugin slug specified. The slug cannot be '.' or '..'." ); + if ( in_array( $plugin_slug, array( '.', '..' ) ) ) { + WP_CLI::error( "Invalid plugin slug specified." ); } - $defaults = [ + $data = wp_parse_args( $assoc_args, array( 'plugin_slug' => $plugin_slug, 'plugin_name' => $plugin_name, 'plugin_package' => $plugin_package, @@ -665,9 +523,8 @@ public function plugin( $args, $assoc_args ) { 'plugin_author' => 'YOUR NAME HERE', 'plugin_author_uri' => 'YOUR SITE HERE', 'plugin_uri' => 'PLUGIN SITE HERE', - 'plugin_tested_up_to' => get_bloginfo( 'version' ), - ]; - $data = wp_parse_args( $assoc_args, $defaults ); + 'plugin_tested_up_to' => get_bloginfo('version'), + ) ); $data['textdomain'] = $plugin_slug; @@ -675,64 +532,60 @@ public function plugin( $args, $assoc_args ) { if ( ! is_dir( $assoc_args['dir'] ) ) { WP_CLI::error( "Cannot create plugin in directory that doesn't exist." ); } - $plugin_dir = "{$assoc_args['dir']}/{$plugin_slug}"; + $plugin_dir = $assoc_args['dir'] . "/$plugin_slug"; } else { - $plugin_dir = WP_PLUGIN_DIR . "/{$plugin_slug}"; + $plugin_dir = WP_PLUGIN_DIR . "/$plugin_slug"; $this->maybe_create_plugins_dir(); - $error_msg = $this->check_target_directory( 'plugin', $plugin_dir ); - if ( ! empty( $error_msg ) ) { - WP_CLI::error( "Invalid plugin slug specified. {$error_msg}" ); + if ( ! $this->check_target_directory( "plugin", $plugin_dir ) ) { + WP_CLI::error( "Invalid plugin slug specified." ); } } - $plugin_path = "{$plugin_dir}/{$plugin_slug}.php"; - $plugin_readme_path = "{$plugin_dir}/readme.txt"; - - $files_to_create = [ - $plugin_path => self::mustache_render( 'plugin.mustache', $data ), - $plugin_readme_path => self::mustache_render( 'plugin-readme.mustache', $data ), - "{$plugin_dir}/composer.json" => self::mustache_render( 'plugin-composer.mustache', $data ), - "{$plugin_dir}/.gitignore" => self::mustache_render( 'plugin-gitignore.mustache', $data ), - "{$plugin_dir}/.distignore" => self::mustache_render( 'plugin-distignore.mustache', $data ), - "{$plugin_dir}/.editorconfig" => file_get_contents( self::get_template_path( '.editorconfig' ) ), - ]; - $force = Utils\get_flag_value( $assoc_args, 'force' ); - $files_written = $this->create_files( $files_to_create, $force ); - - $skip_message = 'All plugin files were skipped.'; - $success_message = 'Created plugin files.'; - $this->log_whether_files_written( $files_written, $skip_message, $success_message ); - - if ( ! Utils\get_flag_value( $assoc_args, 'skip-tests' ) ) { - $command_args = [ - 'dir' => $plugin_dir, - 'ci' => empty( $assoc_args['ci'] ) ? '' : $assoc_args['ci'], - 'force' => $force, - ]; - WP_CLI::run_command( [ 'scaffold', 'plugin-tests', $plugin_slug ], $command_args ); + $plugin_path = "$plugin_dir/$plugin_slug.php"; + $plugin_readme_path = "$plugin_dir/readme.txt"; + + $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); + $files_written = $this->create_files( array( + $plugin_path => self::mustache_render( 'plugin.mustache', $data ), + $plugin_readme_path => self::mustache_render( 'plugin-readme.mustache', $data ), + "$plugin_dir/package.json" => self::mustache_render( 'plugin-packages.mustache', $data ), + "$plugin_dir/Gruntfile.js" => self::mustache_render( 'plugin-gruntfile.mustache', $data ), + "$plugin_dir/.gitignore" => self::mustache_render( 'plugin-gitignore.mustache', $data ), + "$plugin_dir/.distignore" => self::mustache_render( 'plugin-distignore.mustache', $data ), + "$plugin_dir/.editorconfig" => file_get_contents( self::get_template_path( '.editorconfig' ) ), + ), $force ); + + $this->log_whether_files_written( + $files_written, + $skip_message = 'All plugin files were skipped.', + $success_message = 'Created plugin files.' + ); + + if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-tests' ) ) { + WP_CLI::run_command( array( 'scaffold', 'plugin-tests', $plugin_slug ), array( 'dir' => $plugin_dir, 'ci' => $assoc_args['ci'], 'force' => $force ) ); } - if ( Utils\get_flag_value( $assoc_args, 'activate' ) ) { - WP_CLI::run_command( [ 'plugin', 'activate', $plugin_slug ] ); - } elseif ( Utils\get_flag_value( $assoc_args, 'activate-network' ) ) { - WP_CLI::run_command( [ 'plugin', 'activate', $plugin_slug ], [ 'network' => true ] ); + if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) { + WP_CLI::run_command( array( 'plugin', 'activate', $plugin_slug ) ); + } else if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate-network' ) ) { + WP_CLI::run_command( array( 'plugin', 'activate', $plugin_slug), array( 'network' => true ) ); } } /** - * Generates files needed for running PHPUnit tests in a plugin. + * Generate files needed for running PHPUnit tests in a plugin. * * The following files are generated by default: * * * `phpunit.xml.dist` is the configuration file for PHPUnit. - * * `.circleci/config.yml` is the configuration file for CircleCI. Use `--ci=<provider>` to select a different service. + * * `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service. * * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database. * * `tests/bootstrap.php` is the file that makes the current plugin active when running the test suite. * * `tests/test-sample.php` is a sample file containing the actual tests. - * * `.phpcs.xml.dist` is a collection of PHP_CodeSniffer rules. + * * `phpcs.ruleset.xml` is a collenction of PHP_CodeSniffer rules. * - * Learn more from the [plugin unit tests documentation](https://make.wordpress.org/cli/handbook/misc/plugin-unit-tests/). + * Learn more from the [plugin unit tests documentation](http://wp-cli.org/docs/plugin-unit-tests/). * * ## ENVIRONMENT * @@ -750,12 +603,11 @@ public function plugin( $args, $assoc_args ) { * [--ci=<provider>] * : Choose a configuration file for a continuous integration provider. * --- - * default: circle + * default: travis * options: + * - travis * - circle - * - gitlab - * - bitbucket - * - github + * - gitlab * --- * * [--force] @@ -774,18 +626,18 @@ public function plugin_tests( $args, $assoc_args ) { } /** - * Generates files needed for running PHPUnit tests in a theme. + * Generate files needed for running PHPUnit tests in a theme. * * The following files are generated by default: * * * `phpunit.xml.dist` is the configuration file for PHPUnit. - * * `.circleci/config.yml` is the configuration file for CircleCI. Use `--ci=<provider>` to select a different service. + * * `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service. * * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database. * * `tests/bootstrap.php` is the file that makes the current theme active when running the test suite. * * `tests/test-sample.php` is a sample file containing the actual tests. - * * `.phpcs.xml.dist` is a collection of PHP_CodeSniffer rules. + * * `phpcs.ruleset.xml` is a collenction of PHP_CodeSniffer rules. * - * Learn more from the [plugin unit tests documentation](https://make.wordpress.org/cli/handbook/misc/plugin-unit-tests/). + * Learn more from the [plugin unit tests documentation](http://wp-cli.org/docs/plugin-unit-tests/). * * ## ENVIRONMENT * @@ -803,12 +655,11 @@ public function plugin_tests( $args, $assoc_args ) { * [--ci=<provider>] * : Choose a configuration file for a continuous integration provider. * --- - * default: circle + * default: travis * options: + * - travis * - circle - * - gitlab - * - bitbucket - * - github + * - gitlab * --- * * [--force] @@ -831,47 +682,34 @@ private function scaffold_plugin_theme_tests( $args, $assoc_args, $type ) { if ( ! empty( $args[0] ) ) { $slug = $args[0]; - // Validate slug contains only alphanumeric characters, underscores, and dashes. - if ( in_array( $slug, [ '.', '..' ], true ) ) { - WP_CLI::error( "Invalid {$type} slug specified. The slug cannot be '.' or '..'." ); - } - if ( ! preg_match( '/^[a-zA-Z0-9_-]+$/', $slug ) ) { - WP_CLI::error( "Invalid {$type} slug specified. The slug can only contain alphanumeric characters, underscores, and dashes." ); + if ( in_array( $slug, array( '.', '..' ) ) ) { + WP_CLI::error( "Invalid {$type} slug specified." ); } if ( 'theme' === $type ) { $theme = wp_get_theme( $slug ); if ( $theme->exists() ) { $target_dir = $theme->get_stylesheet_directory(); } else { - WP_CLI::error( "Invalid {$type} slug specified. The theme '{$slug}' does not exist." ); + WP_CLI::error( "Invalid {$type} slug specified." ); } } else { - $target_dir = WP_PLUGIN_DIR . "/{$slug}"; + $target_dir = WP_PLUGIN_DIR . "/$slug"; } if ( empty( $assoc_args['dir'] ) && ! is_dir( $target_dir ) ) { - WP_CLI::error( "Invalid {$type} slug specified. No such target directory '{$target_dir}'." ); + WP_CLI::error( "Invalid {$type} slug specified." ); } - - $error_msg = $this->check_target_directory( $type, $target_dir ); - if ( ! empty( $error_msg ) ) { - WP_CLI::error( "Invalid {$type} slug specified. {$error_msg}" ); + if ( ! $this->check_target_directory( $type, $target_dir ) ) { + WP_CLI::error( "Invalid {$type} slug specified." ); } } if ( ! empty( $assoc_args['dir'] ) ) { $target_dir = $assoc_args['dir']; if ( ! is_dir( $target_dir ) ) { - WP_CLI::error( "Invalid {$type} directory specified. No such directory '{$target_dir}'." ); + WP_CLI::error( "Invalid {$type} directory specified." ); } if ( empty( $slug ) ) { - $slug = Path::basename( $target_dir ); - // Validate derived slug as well. - if ( in_array( $slug, [ '.', '..' ], true ) ) { - WP_CLI::error( "Invalid {$type} slug specified. The slug cannot be '.' or '..'." ); - } - if ( ! preg_match( '/^[a-zA-Z0-9_-]+$/', $slug ) ) { - WP_CLI::error( "Invalid {$type} slug specified. The slug can only contain alphanumeric characters, underscores, and dashes." ); - } + $slug = Utils\basename( $target_dir ); } } @@ -883,15 +721,15 @@ private function scaffold_plugin_theme_tests( $args, $assoc_args, $type ) { $package = str_replace( ' ', '_', $name ); $tests_dir = "{$target_dir}/tests"; - $bin_dir = "{$target_dir}/bin"; + $bin_dir = "{$target_dir}/bin"; $wp_filesystem->mkdir( $tests_dir ); $wp_filesystem->mkdir( $bin_dir ); - $wp_versions_to_test = []; + $wp_versions_to_test = array(); // Parse plugin readme.txt - if ( file_exists( "{$target_dir}/readme.txt" ) ) { - $readme_content = (string) file_get_contents( "{$target_dir}/readme.txt" ); + if ( file_exists( $target_dir . '/readme.txt' ) ) { + $readme_content = file_get_contents( $target_dir . '/readme.txt' ); preg_match( '/Requires at least\:(.*)\n/m', $readme_content, $matches ); if ( isset( $matches[1] ) && $matches[1] ) { @@ -901,61 +739,34 @@ private function scaffold_plugin_theme_tests( $args, $assoc_args, $type ) { $wp_versions_to_test[] = 'latest'; $wp_versions_to_test[] = 'trunk'; - $main_file = "{$slug}.php"; - - if ( 'plugin' === $type ) { - if ( ! function_exists( 'get_plugins' ) ) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - - $all_plugins = get_plugins(); - - if ( ! empty( $all_plugins ) ) { - $filtered = array_filter( - array_keys( $all_plugins ), - static function ( $item ) use ( $slug ) { - return ( false !== strpos( $item, "{$slug}/" ) ); - } - ); - - if ( ! empty( $filtered ) ) { - $main_file = basename( reset( $filtered ) ); - } - } - } - - $template_data = [ - "{$type}_slug" => $slug, - "{$type}_package" => $package, - "{$type}_main_file" => $main_file, - ]; - - $force = Utils\get_flag_value( $assoc_args, 'force' ); - $files_to_create = [ - "{$tests_dir}/bootstrap.php" => self::mustache_render( "{$type}-bootstrap.mustache", $template_data ), - "{$tests_dir}/test-sample.php" => self::mustache_render( "{$type}-test-sample.mustache", $template_data ), - ]; - if ( 'circle' === $assoc_args['ci'] ) { - $files_to_create[ "{$target_dir}/.circleci/config.yml" ] = self::mustache_render( 'plugin-circle.mustache', compact( 'wp_versions_to_test' ) ); - } elseif ( 'gitlab' === $assoc_args['ci'] ) { - $files_to_create[ "{$target_dir}/.gitlab-ci.yml" ] = self::mustache_render( 'plugin-gitlab.mustache' ); - } elseif ( 'bitbucket' === $assoc_args['ci'] ) { - $files_to_create[ "{$target_dir}/bitbucket-pipelines.yml" ] = self::mustache_render( 'plugin-bitbucket.mustache' ); - } elseif ( 'github' === $assoc_args['ci'] ) { - $files_to_create[ "{$target_dir}/.github/workflows/testing.yml" ] = self::mustache_render( 'plugin-github.mustache' ); + $template_data = array( + "{$type}_slug" => $slug, + "{$type}_package" => $package, + ); + + $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); + $files_to_create = array( + "$tests_dir/bootstrap.php" => self::mustache_render( "{$type}-bootstrap.mustache", $template_data ), + "$tests_dir/test-sample.php" => self::mustache_render( "{$type}-test-sample.mustache", $template_data ), + ); + if ( 'travis' === $assoc_args['ci'] ) { + $files_to_create["{$target_dir}/.travis.yml"] = self::mustache_render( 'plugin-travis.mustache', compact( 'wp_versions_to_test' ) ); + } else if ( 'circle' === $assoc_args['ci'] ) { + $files_to_create["{$target_dir}/circle.yml"] = self::mustache_render( 'plugin-circle.mustache', compact( 'wp_versions_to_test' ) ); + } else if ( 'gitlab' === $assoc_args['ci'] ) { + $files_to_create["{$target_dir}/.gitlab-ci.yml"] = self::mustache_render( 'plugin-gitlab.mustache' ); } - $files_written = $this->create_files( $files_to_create, $force ); - $to_copy = [ + $to_copy = array( 'install-wp-tests.sh' => $bin_dir, 'phpunit.xml.dist' => $target_dir, - '.phpcs.xml.dist' => $target_dir, - ]; + 'phpcs.ruleset.xml' => $target_dir, + ); foreach ( $to_copy as $file => $dir ) { - $file_name = "{$dir}/{$file}"; - $force = Utils\get_flag_value( $assoc_args, 'force' ); + $file_name = "$dir/$file"; + $force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ); $should_write_file = $this->prompt_if_files_will_be_overwritten( $file_name, $force ); if ( ! $should_write_file ) { continue; @@ -964,45 +775,41 @@ static function ( $item ) use ( $slug ) { $wp_filesystem->copy( self::get_template_path( $file ), $file_name, true ); if ( 'install-wp-tests.sh' === $file ) { - if ( ! $wp_filesystem->chmod( "{$dir}/{$file}", 0755 ) ) { + if ( ! $wp_filesystem->chmod( "$dir/$file", 0755 ) ) { WP_CLI::warning( "Couldn't mark 'install-wp-tests.sh' as executable." ); } } } - - $skip_message = 'All test files were skipped.'; - $success_message = 'Created test files.'; - $this->log_whether_files_written( $files_written, $skip_message, $success_message ); + $this->log_whether_files_written( + $files_written, + $skip_message = 'All test files were skipped.', + $success_message = 'Created test files.' + ); } - /** - * Checks that the `$target_dir` is a child directory of the WP themes or plugins directory, depending on `$type`. - * - * @param string $type "theme" or "plugin" - * @param string $target_dir The theme/plugin directory to check. - * - * @return null|string Returns null on success, error message on error. - */ private function check_target_directory( $type, $target_dir ) { - $parent_dir = dirname( self::canonicalize_path( str_replace( '\\', '/', $target_dir ) ) ); - - $themes_dir = self::canonicalize_path( str_replace( '\\', '/', WP_CONTENT_DIR . '/themes' ) ); - if ( 'theme' === $type && $themes_dir !== $parent_dir ) { - return sprintf( 'The target directory \'%1$s\' is not in \'%2$s\'.', $target_dir, $themes_dir ); + if ( realpath( $target_dir ) ) { + $target_dir = realpath( $target_dir ); } - $plugins_dir = self::canonicalize_path( str_replace( '\\', '/', WP_PLUGIN_DIR ) ); - if ( 'plugin' === $type && $plugins_dir !== $parent_dir ) { - return sprintf( 'The target directory \'%1$s\' is not in \'%2$s\'.', $target_dir, $plugins_dir ); + $parent_dir = dirname( $target_dir ); + + if ( "theme" === $type ) { + if ( WP_CONTENT_DIR . '/themes' === $parent_dir ) { + return true; + } + } elseif ( "plugin" === $type ) { + if ( WP_PLUGIN_DIR === $parent_dir ) { + return true; + } } - // Success. - return null; + return false; } protected function create_files( $files_and_contents, $force ) { $wp_filesystem = $this->init_wp_filesystem(); - $wrote_files = []; + $wrote_files = array(); foreach ( $files_and_contents as $filename => $contents ) { $should_write_file = $this->prompt_if_files_will_be_overwritten( $filename, $force ); @@ -1010,18 +817,10 @@ protected function create_files( $files_and_contents, $force ) { continue; } - $contents = str_replace( "\r\n", "\n", $contents ); - $wp_filesystem->mkdir( dirname( $filename ) ); - // Create multi-level folders. - if ( false === $wp_filesystem->exists( dirname( $filename ) ) ) { - $wp_filesystem->mkdir( dirname( dirname( $filename ) ) ); - $wp_filesystem->mkdir( dirname( $filename ) ); - } - if ( ! $wp_filesystem->put_contents( $filename, $contents ) ) { - WP_CLI::error( "Error creating file: {$filename}" ); + WP_CLI::error( "Error creating file: $filename" ); } elseif ( $should_write_file ) { $wrote_files[] = $filename; } @@ -1039,12 +838,12 @@ protected function prompt_if_files_will_be_overwritten( $filename, $force ) { WP_CLI::log( $filename ); if ( ! $force ) { do { - $answer = cli\prompt( + $answer = cli\prompt( 'Skip this file, or replace it with scaffolding?', $default = false, - $marker = '[s/r]: ' + $marker = '[s/r]: ' ); - } while ( ! in_array( $answer, [ 's', 'r' ], true ) ); + } while ( ! in_array( $answer, array( 's', 'r' ) ) ); $should_write_file = 'r' === $answer; } @@ -1062,20 +861,6 @@ protected function log_whether_files_written( $files_written, $skip_message, $su } } - /** - * Extracts dashicon name when provided or return null otherwise. - * - * @param array{dashicon?: string} $assoc_args - * @return string|null - */ - private function extract_dashicon( $assoc_args ) { - $dashicon = Utils\get_flag_value( $assoc_args, 'dashicon' ); - if ( ! $dashicon ) { - return null; - } - return preg_replace( '/dashicon(-|s-)/', '', $dashicon ); - } - /** * If you're writing your files to your theme directory your textdomain also needs to be the same as your theme. * Same goes for when plugin is being used. @@ -1086,7 +871,7 @@ private function get_textdomain( $textdomain, $args ) { } if ( $args['theme'] ) { - return $this->get_theme_name( $args['theme'] ); + return strtolower( wp_get_theme()->template ); } if ( $args['plugin'] && true !== $args['plugin'] ) { @@ -1096,32 +881,67 @@ private function get_textdomain( $textdomain, $args ) { return 'YOUR-TEXTDOMAIN'; } - /** - * Generates the machine name for function declarations. - * - * @param string $slug Slug name to convert. - * @return string - */ - private function generate_machine_name( $slug ) { - return str_replace( '-', '_', $slug ); - } - - /** - * Pluralizes a noun. - * - * @see Inflector::pluralize() - * @param string $word Word to be pluralized. - * @return string - */ private function pluralize( $word ) { - return Inflector::pluralize( $word ); + $plural = array( + '/(quiz)$/i' => '\1zes', + '/^(ox)$/i' => '\1en', + '/([m|l])ouse$/i' => '\1ice', + '/(matr|vert|ind)ix|ex$/i' => '\1ices', + '/(x|ch|ss|sh)$/i' => '\1es', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(hive)$/i' => '\1s', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/sis$/i' => 'ses', + '/([ti])um$/i' => '\1a', + '/(buffal|tomat)o$/i' => '\1oes', + '/(bu)s$/i' => '1ses', + '/(alias|status)/i' => '\1es', + '/(octop|vir)us$/i' => '1i', + '/(ax|test)is$/i' => '\1es', + '/s$/i' => 's', + '/$/' => 's' + ); + + $uncountable = array( 'equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep' ); + + $irregular = array( + 'person' => 'people', + 'man' => 'men', + 'woman' => 'women', + 'child' => 'children', + 'sex' => 'sexes', + 'move' => 'moves' + ); + + $lowercased_word = strtolower( $word ); + + foreach ( $uncountable as $_uncountable ) { + if ( substr( $lowercased_word, ( - 1 * strlen( $_uncountable ) ) ) == $_uncountable ) { + return $word; + } + } + + foreach ( $irregular as $_plural => $_singular ) { + if ( preg_match( '/(' . $_plural . ')$/i', $word, $arr ) ) { + return preg_replace( '/(' . $_plural . ')$/i', substr( $arr[0], 0, 1 ) . substr( $_singular, 1 ), $word ); + } + } + + foreach ( $plural as $rule => $replacement ) { + if ( preg_match( $rule, $word ) ) { + return preg_replace( $rule, $replacement, $word ); + } + } + + return false; } protected function extract_args( $assoc_args, $defaults ) { - $out = []; + $out = array(); foreach ( $defaults as $key => $value ) { - $out[ $key ] = Utils\get_flag_value( $assoc_args, $key, $value ); + $out[ $key ] = \WP_CLI\Utils\get_flag_value( $assoc_args, $key, $value ); } return $out; @@ -1132,7 +952,7 @@ protected function quote_comma_list_elements( $comma_list ) { } /** - * Creates the themes directory if it doesn't already exist. + * Create the themes directory if it doesn't already exist */ protected function maybe_create_themes_dir() { @@ -1140,20 +960,22 @@ protected function maybe_create_themes_dir() { if ( ! is_dir( $themes_dir ) ) { wp_mkdir_p( $themes_dir ); } + } /** - * Creates the plugins directory if it doesn't already exist. + * Create the plugins directory if it doesn't already exist */ protected function maybe_create_plugins_dir() { if ( ! is_dir( WP_PLUGIN_DIR ) ) { wp_mkdir_p( WP_PLUGIN_DIR ); } + } /** - * Initializes WP_Filesystem. + * Initialize WP Filesystem */ protected function init_wp_filesystem() { global $wp_filesystem; @@ -1163,68 +985,25 @@ protected function init_wp_filesystem() { } /** - * Localizes the template path. + * Localize path to template */ - private static function mustache_render( $template, $data = [] ) { - return Utils\mustache_render( dirname( __DIR__ ) . "/templates/{$template}", $data ); + private static function mustache_render( $template, $data = array() ) { + return Utils\mustache_render( dirname( dirname( __FILE__ ) ) . '/templates/' . $template, $data ); } /** - * Gets the template path based on installation type. + * Get template path based on installation type */ private static function get_template_path( $template ) { - $command_root = Path::phar_safe( dirname( __DIR__ ) ); - $template_path = "{$command_root}/templates/{$template}"; - + $template_path = WP_CLI_ROOT . '/vendor/wp-cli/scaffold-command/templates/' . $template; if ( ! file_exists( $template_path ) ) { - WP_CLI::error( "Couldn't find {$template}" ); - } - - return $template_path; - } - - /* - * Returns the canonicalized path, with dot and double dot segments resolved. - * - * Copied from Symfony\Component\DomCrawler\AbstractUriElement::canonicalizePath(). - * Implements RFC 3986, section 5.2.4. - * - * @param string $path The path to make canonical. - * - * @return string The canonicalized path. - */ - private static function canonicalize_path( $path ) { - if ( '' === $path || '/' === $path ) { - return $path; - } - - if ( '.' === substr( $path, -1 ) ) { - $path .= '/'; - } - - $output = []; - - foreach ( explode( '/', $path ) as $segment ) { - if ( '..' === $segment ) { - array_pop( $output ); - } elseif ( '.' !== $segment ) { - $output[] = $segment; + // scaffold command must've been built with vendor/wp-cli/wp-cli + $template_path = WP_CLI_ROOT . '/../../../templates/' . $template; + if ( ! file_exists( $template_path ) ) { + WP_CLI::error( "Couldn't find {$template}" ); } } - - return implode( '/', $output ); + return $template_path; } - /** - * Gets an active theme's name when true provided or the same name otherwise. - * - * @param string|true $theme Theme name or true. - * @return string - */ - private function get_theme_name( $theme ) { - if ( true === $theme ) { - $theme = wp_get_theme()->template; - } - return strtolower( $theme ); - } } diff --git a/templates/.phpcs.xml.dist b/templates/.phpcs.xml.dist deleted file mode 100644 index 1c6774448..000000000 --- a/templates/.phpcs.xml.dist +++ /dev/null @@ -1,47 +0,0 @@ -<?xml version="1.0"?> -<ruleset name="WordPress Coding Standards based custom ruleset for your plugin"> - <description>Generally-applicable sniffs for WordPress plugins.</description> - - <!-- What to scan --> - <file>.</file> - <exclude-pattern>/vendor/</exclude-pattern> - <exclude-pattern>/node_modules/</exclude-pattern> - - <!-- How to scan --> - <!-- Usage instructions: https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Usage --> - <!-- Annotated ruleset: https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Annotated-ruleset.xml --> - <arg value="sp"/> <!-- Show sniff and progress --> - <arg name="basepath" value="./"/><!-- Strip the file paths down to the relevant bit --> - <arg name="colors"/> - <arg name="extensions" value="php"/> - <arg name="parallel" value="8"/><!-- Enables parallel processing when available for faster results. --> - - <!-- Rules: Check PHP version compatibility --> - <!-- https://github.com/PHPCompatibility/PHPCompatibility#sniffing-your-code-for-compatibility-with-specific-php-versions --> - <config name="testVersion" value="7.2-"/> - <!-- https://github.com/PHPCompatibility/PHPCompatibilityWP --> - <rule ref="PHPCompatibilityWP"/> - - <!-- Rules: WordPress Coding Standards --> - <!-- https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards --> - <!-- https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties --> - <config name="minimum_supported_wp_version" value="4.6"/> - <rule ref="WordPress"/> - <rule ref="WordPress.NamingConventions.PrefixAllGlobals"> - <properties> - <!-- Value: replace the function, class, and variable prefixes used. Separate multiple prefixes with a comma. --> - <property name="prefixes" type="array" value="my-plugin"/> - </properties> - </rule> - <rule ref="WordPress.WP.I18n"> - <properties> - <!-- Value: replace the text domain used. --> - <property name="text_domain" type="array" value="my-plugin"/> - </properties> - </rule> - <rule ref="WordPress.WhiteSpace.ControlStructureSpacing"> - <properties> - <property name="blank_line_check" value="true"/> - </properties> - </rule> -</ruleset> diff --git a/templates/block-editor-css.mustache b/templates/block-editor-css.mustache deleted file mode 100644 index fc68223e0..000000000 --- a/templates/block-editor-css.mustache +++ /dev/null @@ -1,9 +0,0 @@ -/** - * The following styles get applied inside the editor only. - * - * Replace them with your own styles or remove the file completely. - */ - -.wp-block-{{namespace}}-{{slug}} { - border: 1px dotted #f00; -} diff --git a/templates/block-index-js.mustache b/templates/block-index-js.mustache deleted file mode 100644 index 60c4f5efb..000000000 --- a/templates/block-index-js.mustache +++ /dev/null @@ -1,84 +0,0 @@ -( function( wp ) { - /** - * Registers a new block provided a unique name and an object defining its behavior. - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/#registering-a-block - */ - var registerBlockType = wp.blocks.registerBlockType; - /** - * Returns a new element of given type. Element is an abstraction layer atop React. - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/packages/packages-element/ - */ - var el = wp.element.createElement; - /** - * Retrieves the translation of text. - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/packages/packages-i18n/ - */ - var __ = wp.i18n.__; - - /** - * Every block starts by registering a new block type definition. - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/#registering-a-block - */ - registerBlockType( '{{namespace}}/{{slug}}', { - /** - * This is the display title for your block, which can be translated with `i18n` functions. - * The block inserter will show this name. - */ - title: __( '{{title_ucfirst_js}}', '{{namespace}}' ), - - {{#dashicon}} - /** - * An icon property should be specified to make it easier to identify a block. - * These can be any of WordPress’ Dashicons, or a custom svg element. - */ - icon: '{{dashicon}}', - - {{/dashicon}} - /** - * Blocks are grouped into categories to help users browse and discover them. - * The categories provided by core are `common`, `embed`, `formatting`, `layout` and `widgets`. - */ - category: '{{category}}', - - /** - * Optional block extended support features. - */ - supports: { - // Removes support for an HTML mode. - html: false, - }, - - /** - * The edit function describes the structure of your block in the context of the editor. - * This represents what the editor will render when the block is used. - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#edit - * - * @param {Object} [props] Properties passed from the editor. - * @return {Element} Element to render. - */ - edit: function( props ) { - return el( - 'p', - { className: props.className }, - __( 'Hello from the editor!', '{{namespace}}' ) - ); - }, - - /** - * The save function defines the way in which the different attributes should be combined - * into the final markup, which is then serialized by Gutenberg into `post_content`. - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#save - * - * @return {Element} Element to render. - */ - save: function() { - return el( - 'p', - {}, - __( 'Hello from the saved content!', '{{namespace}}' ) - ); - } - } ); -} )( - window.wp -); diff --git a/templates/block-php.mustache b/templates/block-php.mustache deleted file mode 100644 index f85813807..000000000 --- a/templates/block-php.mustache +++ /dev/null @@ -1,81 +0,0 @@ -<?php -/** - * Functions to register client-side assets (scripts and stylesheets) for the - * Gutenberg block. - * - * @package {{namespace}} - */ - -/** - * Registers all block assets so that they can be enqueued through Gutenberg in - * the corresponding context. - * - * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets/ - */ -function {{machine_name}}_block_init() { - // Skip block registration if Gutenberg is not enabled/merged. - if ( ! function_exists( 'register_block_type' ) ) { - return; - } - {{#plugin}} - $dir = __DIR__; - {{/plugin}} - {{#theme}} - $dir = get_stylesheet_directory() . '/blocks'; - {{/theme}} - - $index_js = '{{slug}}/index.js'; - wp_register_script( - '{{slug}}-block-editor', - {{#plugin}} - plugins_url( $index_js, __FILE__ ), - {{/plugin}} - {{#theme}} - get_stylesheet_directory_uri() . "/blocks/{$index_js}", - {{/theme}} - array( - 'wp-blocks', - 'wp-i18n', - 'wp-element', - ), - filemtime( "{$dir}/{$index_js}" ), - false - ); - - $editor_css = '{{slug}}/editor.css'; - wp_register_style( - '{{slug}}-block-editor', - {{#plugin}} - plugins_url( $editor_css, __FILE__ ), - {{/plugin}} - {{#theme}} - get_stylesheet_directory_uri() . "/blocks/{$editor_css}", - {{/theme}} - array(), - filemtime( "{$dir}/{$editor_css}" ) - ); - - $style_css = '{{slug}}/style.css'; - wp_register_style( - '{{slug}}-block', - {{#plugin}} - plugins_url( $style_css, __FILE__ ), - {{/plugin}} - {{#theme}} - get_stylesheet_directory_uri() . "/blocks/{$style_css}", - {{/theme}} - array(), - filemtime( "{$dir}/{$style_css}" ) - ); - - register_block_type( - '{{namespace}}/{{slug}}', - array( - 'editor_script' => '{{slug}}-block-editor', - 'editor_style' => '{{slug}}-block-editor', - 'style' => '{{slug}}-block', - ) - ); -} - -add_action( 'init', '{{machine_name}}_block_init' ); diff --git a/templates/block-style-css.mustache b/templates/block-style-css.mustache deleted file mode 100644 index 9a6f5fc7f..000000000 --- a/templates/block-style-css.mustache +++ /dev/null @@ -1,11 +0,0 @@ -/** - * The following styles get applied both on the front of your site and in the editor. - * - * Replace them with your own styles or remove the file completely. - */ - -.wp-block-{{namespace}}-{{slug}} { - background-color: #000; - color: #fff; - padding: 2px; -} diff --git a/templates/child_theme_functions.mustache b/templates/child_theme_functions.mustache index 34ba77a67..d654c23db 100644 --- a/templates/child_theme_functions.mustache +++ b/templates/child_theme_functions.mustache @@ -1,24 +1,12 @@ <?php -/** - * {{theme_name}} Theme functions and definitions. - * - * @link https://developer.wordpress.org/themes/basics/theme-functions/ - * - * @package {{slug}} - */ -add_action( 'wp_enqueue_scripts', '{{prefix_safe}}_parent_theme_enqueue_styles' ); +add_action( 'wp_enqueue_scripts', '{{parent_theme_function_safe}}_parent_theme_enqueue_styles' ); + +function {{parent_theme_function_safe}}_parent_theme_enqueue_styles() { + wp_enqueue_style( '{{parent_theme}}-style', get_template_directory_uri() . '/style.css' ); + wp_enqueue_style( '{{slug}}-style', + get_stylesheet_directory_uri() . '/style.css', + array( '{{parent_theme}}-style' ) + ); -/** - * Enqueue scripts and styles. - */ -function {{prefix_safe}}_parent_theme_enqueue_styles() { - wp_enqueue_style( '{{parent_theme}}-style', get_template_directory_uri() . '/style.css', array(), '0.1.0' ); - wp_enqueue_style( - '{{slug}}-style', - get_stylesheet_directory_uri() . '/style.css', - array( '{{parent_theme}}-style' ), - '0.1.0' - ); - wp_style_add_data( '{{slug}}-style', 'rtl', 'add' ); } diff --git a/templates/install-wp-tests.sh b/templates/install-wp-tests.sh index 509919caa..73bb4c787 100644 --- a/templates/install-wp-tests.sh +++ b/templates/install-wp-tests.sh @@ -1,16 +1,7 @@ #!/usr/bin/env bash -# See https://raw.githubusercontent.com/wp-cli/scaffold-command/master/templates/install-wp-tests.sh - -# Set up colors for output -RED="\033[0;31m" -GREEN="\033[0;32m" -YELLOW="\033[0;33m" -CYAN="\033[0;36m" -RESET="\033[0m" - if [ $# -lt 3 ]; then - echo -e "${YELLOW}Usage:${RESET} $0 <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]" + echo "usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]" exit 1 fi @@ -21,93 +12,30 @@ DB_HOST=${4-localhost} WP_VERSION=${5-latest} SKIP_DB_CREATE=${6-false} -TMPDIR=${TMPDIR-/tmp} -TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") -WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} -WP_TESTS_FILE="$WP_TESTS_DIR"/includes/functions.php -WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress} -WP_CORE_FILE="$WP_CORE_DIR"/wp-settings.php +WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} +WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} download() { - if command -v curl > /dev/null 2>&1; then - curl -L -s "$1" > "$2"; - return $? - elif command -v wget > /dev/null 2>&1; then + if [ `which curl` ]; then + curl -s "$1" > "$2"; + elif [ `which wget` ]; then wget -nv -O "$2" "$1" - return $? - else - echo -e "${RED}Error: Neither curl nor wget is installed.${RESET}" - exit 1 fi } -check_for_updates() { - local remote_url="https://raw.githubusercontent.com/wp-cli/scaffold-command/main/templates/install-wp-tests.sh" - local tmp_script="$TMPDIR/install-wp-tests.sh.latest" - - if ! download "$remote_url" "$tmp_script"; then - echo -e "${YELLOW}Warning: Failed to download the latest version of the script for update check.${RESET}" - return - fi - - if [ ! -f "$tmp_script" ] || [ ! -s "$tmp_script" ]; then - echo -e "${YELLOW}Warning: Downloaded script is missing or empty, cannot check for updates.${RESET}" - rm -f "$tmp_script" - return - fi - - local local_hash="" - local remote_hash="" - - if command -v shasum > /dev/null; then - local_hash=$(shasum -a 256 "$0" | awk '{print $1}') - remote_hash=$(shasum -a 256 "$tmp_script" | awk '{print $1}') - elif command -v sha256sum > /dev/null; then - local_hash=$(sha256sum "$0" | awk '{print $1}') - remote_hash=$(sha256sum "$tmp_script" | awk '{print $1}') - else - echo -e "${YELLOW}Warning: Could not find shasum or sha256sum to check for script updates.${RESET}" - rm "$tmp_script" - return - fi - - rm "$tmp_script" - - if [ "$local_hash" != "$remote_hash" ]; then - echo -e "${YELLOW}Warning: A newer version of this script is available at $remote_url${RESET}" - fi -} -# Allow disabling the update check by setting WP_INSTALL_TESTS_SKIP_UPDATE_CHECK=true in the environment. -if [ "${WP_INSTALL_TESTS_SKIP_UPDATE_CHECK:-false}" != "true" ]; then - check_for_updates -fi - -if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then - WP_BRANCH=${WP_VERSION%\-*} - WP_TESTS_TAG="branches/$WP_BRANCH" -elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then - WP_TESTS_TAG="branches/$WP_VERSION" -elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then - if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then - # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x - WP_TESTS_TAG="tags/${WP_VERSION%??}" - else - WP_TESTS_TAG="tags/$WP_VERSION" - fi +if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then + WP_TESTS_TAG="tags/$WP_VERSION" elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then WP_TESTS_TAG="trunk" else # http serves a single offer, whereas https serves multiple. we only want one download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json - LATEST_VERSION=$(grep -oE '"version":"[^"]*' /tmp/wp-latest.json | head -n 1 | sed 's/"version":"//') + grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json + LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') if [[ -z "$LATEST_VERSION" ]]; then - echo -e "${RED}Error: Latest WordPress version could not be found.${RESET}" + echo "Latest WordPress version could not be found" exit 1 fi - # The version-check endpoint returns major.minor (e.g., 6.9), but GitHub tags include the patch version (e.g., 6.9.0) - if [[ $LATEST_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then - LATEST_VERSION="${LATEST_VERSION}.0" - fi WP_TESTS_TAG="tags/$LATEST_VERSION" fi @@ -115,164 +43,62 @@ set -ex install_wp() { - if [ -f $WP_CORE_FILE ]; then - echo -e "${CYAN}WordPress is already installed.${RESET}" + if [ -d $WP_CORE_DIR ]; then return; fi - echo -e "${CYAN}Installing WordPress...${RESET}" - - rm -rf $WP_CORE_DIR mkdir -p $WP_CORE_DIR if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then - download https://github.com/WordPress/wordpress/archive/refs/heads/master.tar.gz $TMPDIR/wordpress.tar.gz - tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR + mkdir -p /tmp/wordpress-nightly + download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip + unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ + mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR else if [ $WP_VERSION == 'latest' ]; then local ARCHIVE_NAME='latest' - elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then - # https serves multiple offers, whereas http serves single. - download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json - if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then - # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x - LATEST_VERSION=${WP_VERSION%??} - else - # otherwise, scan the releases and get the most up to date minor version of the major release - local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` - LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) - fi - if [[ -z "$LATEST_VERSION" ]]; then - local ARCHIVE_NAME="wordpress-$WP_VERSION" - else - local ARCHIVE_NAME="wordpress-$LATEST_VERSION" - fi else local ARCHIVE_NAME="wordpress-$WP_VERSION" fi - download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz - tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR + download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz + tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR fi - echo -e "${GREEN}WordPress installed successfully.${RESET}" + + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php } install_test_suite() { # portable in-place argument for both GNU sed and Mac OSX sed if [[ $(uname -s) == 'Darwin' ]]; then - local ioption='-i.bak' + local ioption='-i .bak' else local ioption='-i' fi - # set up testing suite if it doesn't yet exist or only partially exists - if [ ! -f $WP_TESTS_FILE ]; then - echo -e "${CYAN}Installing test suite...${RESET}" + # set up testing suite if it doesn't yet exist + if [ ! -d $WP_TESTS_DIR ]; then # set up testing suite - rm -rf $WP_TESTS_DIR mkdir -p $WP_TESTS_DIR - - if [[ $WP_TESTS_TAG == 'trunk' ]]; then - ref=trunk - archive_url="https://github.com/WordPress/wordpress-develop/archive/refs/heads/${ref}.tar.gz" - elif [[ $WP_TESTS_TAG == branches/* ]]; then - ref=${WP_TESTS_TAG#branches/} - archive_url="https://github.com/WordPress/wordpress-develop/archive/refs/heads/${ref}.tar.gz" - else - ref=${WP_TESTS_TAG#tags/} - archive_url="https://github.com/WordPress/wordpress-develop/archive/refs/tags/${ref}.tar.gz" - fi - - if [ -z "$ref" ]; then - echo -e "${RED}Error:${RESET} Unable to determine git reference from WP_TESTS_TAG: $WP_TESTS_TAG" - exit 1 - fi - - download "${archive_url}" "$TMPDIR/wordpress-develop.tar.gz" - - # Validate that the tarball was downloaded correctly before extracting - if [ ! -s "$TMPDIR/wordpress-develop.tar.gz" ]; then - echo -e "${RED}Error:${RESET} Downloaded test suite archive is missing or empty: $TMPDIR/wordpress-develop.tar.gz" - exit 1 - fi - - if ! tar -tzf "$TMPDIR/wordpress-develop.tar.gz" >/dev/null 2>&1; then - echo -e "${RED}Error:${RESET} Downloaded test suite archive is not a valid tar.gz file: $TMPDIR/wordpress-develop.tar.gz" - exit 1 - fi - - tar -zxmf "$TMPDIR/wordpress-develop.tar.gz" -C "$TMPDIR" - mv "$TMPDIR/wordpress-develop-${ref}/tests/phpunit/includes" "$WP_TESTS_DIR"/ - mv "$TMPDIR/wordpress-develop-${ref}/tests/phpunit/data" "$WP_TESTS_DIR"/ - rm -rf "$TMPDIR/wordpress-develop-${ref}" - rm "$TMPDIR/wordpress-develop.tar.gz" - echo -e "${GREEN}Test suite installed.${RESET}" - else - echo -e "${CYAN}Test suite is already installed.${RESET}" + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data fi - if [ ! -f "$WP_TESTS_DIR"/wp-tests-config.php ]; then - echo -e "${CYAN}Configuring test suite...${RESET}" - if [[ $WP_TESTS_TAG == 'trunk' ]]; then - ref=trunk - elif [[ $WP_TESTS_TAG == branches/* ]]; then - ref=${WP_TESTS_TAG#branches/} - else - ref=${WP_TESTS_TAG#tags/} - fi - - if [ -z "$ref" ]; then - echo -e "${RED}Error:${RESET} Unable to determine git reference from WP_TESTS_TAG: $WP_TESTS_TAG" - exit 1 - fi - - download https://raw.githubusercontent.com/WordPress/wordpress-develop/${ref}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php + if [ ! -f wp-tests-config.php ]; then + download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php # remove all forward slashes in the end WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") - # escape special sed replacement characters in $WP_CORE_DIR (backslash, pipe, ampersand) - WP_CORE_DIR_ESCAPED=$(printf '%s' "$WP_CORE_DIR" | sed 's/[\\|&]/\\&/g') - sed $ioption "s|dirname( __FILE__ ) . '/src/'|'${WP_CORE_DIR_ESCAPED}/'|" "$WP_TESTS_DIR"/wp-tests-config.php - sed $ioption "s|__DIR__ . '/src/'|'${WP_CORE_DIR_ESCAPED}/'|" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php - echo -e "${GREEN}Test suite configured.${RESET}" - else - echo -e "${CYAN}Test suite is already configured.${RESET}" - fi - -} - -recreate_db() { - shopt -s nocasematch - if [[ $1 =~ ^(y|yes)$ ]] - then - echo -e "${CYAN}Recreating the database ($DB_NAME)...${RESET}" - if command -v mariadb-admin > /dev/null 2>&1; then - mariadb-admin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA - else - mysqladmin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA - fi - create_db - echo -e "${GREEN}Database ($DB_NAME) recreated.${RESET}" - else - echo -e "${YELLOW}Leaving the existing database ($DB_NAME) in place.${RESET}" fi - shopt -u nocasematch -} -create_db() { - if command -v mariadb-admin > /dev/null 2>&1; then - mariadb-admin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA - else - mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA - fi } install_db() { if [ ${SKIP_DB_CREATE} = "true" ]; then - echo -e "${YELLOW}Skipping database creation.${RESET}" return 0 fi @@ -293,24 +119,9 @@ install_db() { fi # create database - if command -v mariadb > /dev/null 2>&1; then - local DB_CLIENT='mariadb' - else - local DB_CLIENT='mysql' - fi - if $DB_CLIENT --user="$DB_USER" --password="$DB_PASS"$EXTRA --execute='show databases;' | grep -q "^$DB_NAME$"; - then - echo -e "${YELLOW}Reinstalling will delete the existing test database ($DB_NAME)${RESET}" - read -p 'Are you sure you want to proceed? [y/N]: ' DELETE_EXISTING_DB - recreate_db $DELETE_EXISTING_DB - else - echo -e "${CYAN}Creating database ($DB_NAME)...${RESET}" - create_db - echo -e "${GREEN}Database ($DB_NAME) created.${RESET}" - fi + mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA } install_wp install_test_suite install_db -echo -e "${GREEN}Done.${RESET}" diff --git a/templates/phpcs.ruleset.xml b/templates/phpcs.ruleset.xml new file mode 100644 index 000000000..210c25a14 --- /dev/null +++ b/templates/phpcs.ruleset.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<ruleset name="WordPress Coding Standards for Plugins"> + <description>Generally-applicable sniffs for WordPress plugins</description> + + <rule ref="WordPress-Core" /> + <rule ref="WordPress-Docs" /> + + <exclude-pattern>*/node_modules/*</exclude-pattern> + <exclude-pattern>*/vendor/*</exclude-pattern> +</ruleset> diff --git a/templates/phpunit.xml.dist b/templates/phpunit.xml.dist index 165396799..44f0fdb69 100644 --- a/templates/phpunit.xml.dist +++ b/templates/phpunit.xml.dist @@ -1,17 +1,14 @@ -<?xml version="1.0"?> <phpunit bootstrap="tests/bootstrap.php" backupGlobals="false" colors="true" convertErrorsToExceptions="true" - convertWarningsToExceptions="true" convertNoticesToExceptions="true" - convertDeprecationsToExceptions="true" + convertWarningsToExceptions="true" > <testsuites> - <testsuite name="testing"> + <testsuite> <directory prefix="test-" suffix=".php">./tests/</directory> - <exclude>./tests/test-sample.php</exclude> </testsuite> </testsuites> </phpunit> diff --git a/templates/plugin-bitbucket.mustache b/templates/plugin-bitbucket.mustache deleted file mode 100644 index 827e2fa73..000000000 --- a/templates/plugin-bitbucket.mustache +++ /dev/null @@ -1,132 +0,0 @@ -pipelines: - default: - - step: - image: php:7.4 - name: "PHP 7.4" - script: - # Install Dependencies - - apt-get update && apt-get install -y subversion git zip libzip-dev --no-install-recommends - - # PHP extensions - - docker-php-ext-install -j$(nproc) mysqli pdo_mysql zip - - # Setup WordPress tests - - bash bin/install-wp-tests.sh wordpress_tests root root 127.0.0.1 latest true - - # Install Composer - - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" - - php composer-setup.php --install-dir=/usr/local/bin --filename=composer - - php -r "unlink('composer-setup.php');" - - export PATH="$PATH:$HOME/.composer/vendor/bin" - - export COMPOSER_ALLOW_SUPERUSER=1 - - # Install PHPUnit - - PHPUNIT_VERSION=9.6.19 - - curl -o /usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" && chmod +x /usr/local/bin/phpunit - - composer global require yoast/phpunit-polyfills - - export WP_TESTS_PHPUNIT_POLYFILLS_PATH="$HOME/.composer/vendor/yoast/phpunit-polyfills" - - phpunit --version - - # Install PHPCS and WPCS - - composer global config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true - - composer global require "wp-coding-standards/wpcs" - - composer global require "phpcompatibility/phpcompatibility-wp" - - phpcs --version - - # Run PHPCS - - phpcs - - # Run PHPUnit - - phpunit - services: - - database - - - step: - image: php:8.0 - name: "PHP 8.0" - script: - # Install Dependencies - - apt-get update && apt-get install -y subversion git zip libzip-dev --no-install-recommends - - # PHP extensions - - docker-php-ext-install -j$(nproc) mysqli pdo_mysql zip - - # Setup WordPress tests - - bash bin/install-wp-tests.sh wordpress_tests root root 127.0.0.1 latest true - - # Install Composer - - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" - - php composer-setup.php --install-dir=/usr/local/bin --filename=composer - - php -r "unlink('composer-setup.php');" - - export PATH="$PATH:$HOME/.composer/vendor/bin" - - export COMPOSER_ALLOW_SUPERUSER=1 - - # Install PHPUnit - - PHPUNIT_VERSION=9.6.19 - - curl -o /usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" && chmod +x /usr/local/bin/phpunit - - composer global require yoast/phpunit-polyfills - - export WP_TESTS_PHPUNIT_POLYFILLS_PATH="$HOME/.composer/vendor/yoast/phpunit-polyfills" - - phpunit --version - - # Install PHPCS and WPCS - - composer global config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true - - composer global require "wp-coding-standards/wpcs" - - composer global require "phpcompatibility/phpcompatibility-wp" - - phpcs --version - - # Run PHPCS - - phpcs - - # Run PHPUnit - - phpunit - services: - - database - - - step: - image: php:8.2 - name: "PHP 8.2" - script: - # Install Dependencies - - apt-get update && apt-get install -y subversion git zip libzip-dev --no-install-recommends - - # PHP extensions - - docker-php-ext-install -j$(nproc) mysqli pdo_mysql zip - - # Setup WordPress tests - - bash bin/install-wp-tests.sh wordpress_tests root root 127.0.0.1 latest true - - # Install Composer - - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" - - php composer-setup.php --install-dir=/usr/local/bin --filename=composer - - php -r "unlink('composer-setup.php');" - - export PATH="$PATH:$HOME/.composer/vendor/bin" - - export COMPOSER_ALLOW_SUPERUSER=1 - - # Install PHPUnit - - PHPUNIT_VERSION=9.6.19 - - curl -o /usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" && chmod +x /usr/local/bin/phpunit - - composer global require yoast/phpunit-polyfills - - export WP_TESTS_PHPUNIT_POLYFILLS_PATH="$HOME/.composer/vendor/yoast/phpunit-polyfills" - - phpunit --version - - # Install PHPCS and WPCS - - composer global config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true - - composer global require "wp-coding-standards/wpcs" - - composer global require "phpcompatibility/phpcompatibility-wp" - - phpcs --version - - # Run PHPCS - - phpcs - - # Run PHPUnit - - phpunit - services: - - database - -definitions: - services: - database: - image: mysql:latest - environment: - MYSQL_DATABASE: 'wordpress_tests' - MYSQL_ROOT_PASSWORD: 'root' diff --git a/templates/plugin-bootstrap.mustache b/templates/plugin-bootstrap.mustache index 0b21e2e91..05c21bf1e 100644 --- a/templates/plugin-bootstrap.mustache +++ b/templates/plugin-bootstrap.mustache @@ -1,38 +1,25 @@ <?php /** - * PHPUnit bootstrap file. + * PHPUnit bootstrap file * * @package {{plugin_package}} */ $_tests_dir = getenv( 'WP_TESTS_DIR' ); - if ( ! $_tests_dir ) { - $_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib'; -} - -// Forward custom PHPUnit Polyfills configuration to PHPUnit bootstrap file. -$_phpunit_polyfills_path = getenv( 'WP_TESTS_PHPUNIT_POLYFILLS_PATH' ); -if ( false !== $_phpunit_polyfills_path ) { - define( 'WP_TESTS_PHPUNIT_POLYFILLS_PATH', $_phpunit_polyfills_path ); -} - -if ( ! file_exists( "{$_tests_dir}/includes/functions.php" ) ) { - echo "Could not find {$_tests_dir}/includes/functions.php, have you run bin/install-wp-tests.sh ?" . PHP_EOL; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - exit( 1 ); + $_tests_dir = '/tmp/wordpress-tests-lib'; } // Give access to tests_add_filter() function. -require_once "{$_tests_dir}/includes/functions.php"; +require_once $_tests_dir . '/includes/functions.php'; /** * Manually load the plugin being tested. */ function _manually_load_plugin() { - require dirname( __DIR__ ) . '/{{plugin_main_file}}'; + require dirname( dirname( __FILE__ ) ) . '/{{plugin_slug}}.php'; } - tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' ); // Start up the WP testing environment. -require "{$_tests_dir}/includes/bootstrap.php"; +require $_tests_dir . '/includes/bootstrap.php'; diff --git a/templates/plugin-circle.mustache b/templates/plugin-circle.mustache index 6f16d526c..c63a17f2b 100644 --- a/templates/plugin-circle.mustache +++ b/templates/plugin-circle.mustache @@ -1,106 +1,26 @@ -workflows: - version: 2 - main: - jobs: - - php56-build - - php70-build - - php71-build - - php72-build - - php73-build - - php74-build - -version: 2 - -job-references: - mysql_image: &mysql_image - circleci/mysql:5.6 - - setup_environment: &setup_environment - name: "Setup Environment Variables" - command: | - echo "export PATH=$HOME/.composer/vendor/bin:$PATH" >> $BASH_ENV - source /home/circleci/.bashrc - - install_dependencies: &install_dependencies - name: "Install Dependencies" - command: | - sudo apt-get update && sudo apt-get install subversion - sudo -E docker-php-ext-install mysqli - sudo sh -c "printf '\ndeb http://ftp.us.debian.org/debian sid main\n' >> /etc/apt/sources.list" - sudo apt-get update && sudo apt-get install mysql-client-5.7 - - php_job: &php_job - environment: - - WP_TESTS_DIR: "/tmp/wordpress-tests-lib" - - WP_CORE_DIR: "/tmp/wordpress/" - steps: - - checkout - - run: *setup_environment - - run: *install_dependencies - - run: - name: "Run Tests" - command: | - composer global require "phpunit/phpunit=5.7.*" - composer global require wp-coding-standards/wpcs - phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs - phpcs - rm -rf $WP_TESTS_DIR $WP_CORE_DIR - bash bin/install-wp-tests.sh wordpress_test root '' 127.0.0.1 latest - phpunit - WP_MULTISITE=1 phpunit - -jobs: - php56-build: - <<: *php_job - docker: - - image: circleci/php:5.6 - - image: *mysql_image - steps: - - checkout - - run: *setup_environment - - run: *install_dependencies - - run: - name: "Run Tests" - command: | - composer global require "phpunit/phpunit=5.7.*" - composer global require wp-coding-standards/wpcs - phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs - phpcs - SKIP_DB_CREATE=false - {{#wp_versions_to_test}} - rm -rf $WP_TESTS_DIR $WP_CORE_DIR - bash bin/install-wp-tests.sh wordpress_test root '' 127.0.0.1 {{.}} $SKIP_DB_CREATE - phpunit - WP_MULTISITE=1 phpunit - SKIP_DB_CREATE=true - {{/wp_versions_to_test}} - - php70-build: - <<: *php_job - docker: - - image: circleci/php:7.0 - - image: *mysql_image - - php71-build: - <<: *php_job - docker: - - image: circleci/php:7.1 - - image: *mysql_image - - php72-build: - <<: *php_job - docker: - - image: circleci/php:7.2 - - image: *mysql_image - - php73-build: - <<: *php_job - docker: - - image: circleci/php:7.3 - - image: *mysql_image - - php74-build: - <<: *php_job - docker: - - image: circleci/php:7.4 - - image: *mysql_image +machine: + php: + version: 5.6.22 + environment: + WP_TESTS_DIR: /tmp/wordpress-tests-lib + WP_CORE_DIR: /tmp/wordpress/ + PATH: $HOME/.composer/vendor/bin:$PATH + +dependencies: + pre: + - sudo apt-get update; sudo apt-get install subversion + +test: + pre: + - | + composer global require wp-coding-standards/wpcs + phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs + override: + - phpcs --standard=phpcs.ruleset.xml $(find . -name '*.php') + {{#wp_versions_to_test}} + - | + rm -rf $WP_TESTS_DIR $WP_CORE_DIR + bash bin/install-wp-tests.sh wordpress_test ubuntu '' 127.0.0.1 {{.}} + phpunit + WP_MULTISITE=1 phpunit + {{/wp_versions_to_test}} diff --git a/templates/plugin-composer.mustache b/templates/plugin-composer.mustache deleted file mode 100644 index aa3ad7147..000000000 --- a/templates/plugin-composer.mustache +++ /dev/null @@ -1,8 +0,0 @@ -{ - "require-dev": { - "wp-cli/i18n-command": "^2" - }, - "scripts": { - "makepot": "wp i18n make-pot ." - } -} diff --git a/templates/plugin-distignore.mustache b/templates/plugin-distignore.mustache index af58f8c7b..8132804c8 100644 --- a/templates/plugin-distignore.mustache +++ b/templates/plugin-distignore.mustache @@ -1,40 +1,26 @@ # A set of files you probably don't want in your WordPress.org distribution -.babelrc -.deployignore .distignore .editorconfig -.eslintignore -.eslintrc .git .gitignore -.github .gitlab-ci.yml .travis.yml .DS_Store -.*~ Thumbs.db behat.yml -bitbucket-pipelines.yml bin -.circleci/config.yml +circle.yml composer.json composer.lock -dependencies.yml Gruntfile.js package.json -package-lock.json phpunit.xml phpunit.xml.dist multisite.xml multisite.xml.dist -.phpcs.xml -phpcs.xml -.phpcs.xml.dist -phpcs.xml.dist +phpcs.ruleset.xml README.md -webpack.config.js wp-cli.local.yml -yarn.lock tests vendor node_modules diff --git a/templates/plugin-github.mustache b/templates/plugin-github.mustache deleted file mode 100644 index f8662591d..000000000 --- a/templates/plugin-github.mustache +++ /dev/null @@ -1,42 +0,0 @@ -{{=<% %>=}} -name: Testing - -on: - pull_request: - branches: - - main - - master - -jobs: - phpunit: - name: Run tests - runs-on: ubuntu-latest - strategy: - matrix: - php-version: ['8.2', '8.0', '7.4'] - services: - database: - image: mysql:latest - env: - MYSQL_DATABASE: wordpress_tests - MYSQL_ROOT_PASSWORD: root - ports: - - 3306:3306 - steps: - - name: Check out source code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 - with: - php-version: ${{ matrix.php-version }} - tools: phpunit-polyfills:1.1 - - - name: Install SVN - run: sudo apt-get update && sudo apt-get install -y subversion - - - name: Setup tests - run: bash bin/install-wp-tests.sh wordpress_tests root root 127.0.0.1 latest true - - - name: Run tests - run: phpunit diff --git a/templates/plugin-gitignore.mustache b/templates/plugin-gitignore.mustache index 6cc91535c..7b4c57a39 100644 --- a/templates/plugin-gitignore.mustache +++ b/templates/plugin-gitignore.mustache @@ -1,10 +1,7 @@ .DS_Store -phpcs.xml -phpunit.xml Thumbs.db wp-cli.local.yml node_modules/ -vendor/ *.sql *.tar.gz *.zip diff --git a/templates/plugin-gitlab.mustache b/templates/plugin-gitlab.mustache index 5d6871eb0..1e30cc5f6 100644 --- a/templates/plugin-gitlab.mustache +++ b/templates/plugin-gitlab.mustache @@ -5,56 +5,37 @@ variables: before_script: # Install dependencies - - # Update the docker + + # update the docker + - apt-get clean - apt-get -yqq update - - apt-get -yqqf install zip unzip subversion default-mysql-client default-libmysqlclient-dev --fix-missing + + # instll the required packages for the running CI tests + - apt-get -yqqf install zip unzip subversion mysql-client libmysqlclient-dev --fix-missing # PHP extensions - - docker-php-ext-install -j$(nproc) mysqli pdo_mysql + - docker-php-ext-enable mbstring mcrypt mysqli pdo_mysql intl gd zip bz2 - # Setup WordPress tests + # Set up WordPress tests - bash bin/install-wp-tests.sh wordpress_tests root mysql mysql latest true - # Install Composer - - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" - - php composer-setup.php --install-dir=/usr/local/bin --filename=composer - - php -r "unlink('composer-setup.php');" - - export PATH="$PATH:$HOME/.composer/vendor/bin" - - # Install PHPUnit - - PHPUNIT_VERSION=9.6.19 - - curl -o /usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" && chmod +x /usr/local/bin/phpunit - - composer global require yoast/phpunit-polyfills - - export WP_TESTS_PHPUNIT_POLYFILLS_PATH="$HOME/.composer/vendor/yoast/phpunit-polyfills" - - phpunit --version - # Install PHPCS and WPCS - - composer global config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true + - composer global require "squizlabs/php_codesniffer=*" - composer global require "wp-coding-standards/wpcs" - - composer global require "phpcompatibility/phpcompatibility-wp" - - phpcs --version - -PHPunit:PHP7.4:MySQL: - image: php:7.4-bullseye - services: - - mysql:5.7 - script: - - phpcs - - phpunit + - phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs -PHPunit:PHP8.0:MySQL: - image: php:8.0-bullseye +PHPunit:PHP5.3:MySQL: + image: tetraweb/php:5.3 services: - - mysql:5.7 + - mysql:5.6 script: - - phpcs - - phpunit + - phpcs --standard=phpcs.ruleset.xml $(find . -name '*.php') + - phpunit --configuration phpunit.xml.dist -PHPunit:PHP8.2:MySQL: - image: php:8.2-bullseye +PHPunit:PHP5.6:MySQL: + image: tetraweb/php:5.6 services: - - mysql:5.7 + - mysql:5.6 script: - - phpcs - - phpunit + - phpcs --standard=phpcs.ruleset.xml $(find . -name '*.php') + - phpunit --configuration phpunit.xml.dist \ No newline at end of file diff --git a/templates/plugin-gruntfile.mustache b/templates/plugin-gruntfile.mustache new file mode 100644 index 000000000..86a021bfb --- /dev/null +++ b/templates/plugin-gruntfile.mustache @@ -0,0 +1,53 @@ +module.exports = function( grunt ) { + + 'use strict'; + var banner = '/**\n * <%= pkg.homepage %>\n * Copyright (c) <%= grunt.template.today("yyyy") %>\n * This file is generated automatically. Do not edit.\n */\n'; + // Project configuration + grunt.initConfig( { + + pkg: grunt.file.readJSON( 'package.json' ), + + addtextdomain: { + options: { + textdomain: '{{textdomain}}', + }, + target: { + files: { + src: [ '*.php', '**/*.php', '!node_modules/**', '!php-tests/**', '!bin/**' ] + } + } + }, + + wp_readme_to_markdown: { + your_target: { + files: { + 'README.md': 'readme.txt' + } + }, + }, + + makepot: { + target: { + options: { + domainPath: '/languages', + mainFile: '{{plugin_slug}}.php', + potFilename: '{{plugin_slug}}.pot', + potHeaders: { + poedit: true, + 'x-poedit-keywordslist': true + }, + type: 'wp-plugin', + updateTimestamp: true + } + } + }, + } ); + + grunt.loadNpmTasks( 'grunt-wp-i18n' ); + grunt.loadNpmTasks( 'grunt-wp-readme-to-markdown' ); + grunt.registerTask( 'i18n', ['addtextdomain', 'makepot'] ); + grunt.registerTask( 'readme', ['wp_readme_to_markdown'] ); + + grunt.util.linefeed = '\n'; + +}; diff --git a/templates/plugin-packages.mustache b/templates/plugin-packages.mustache new file mode 100644 index 000000000..84a60ca66 --- /dev/null +++ b/templates/plugin-packages.mustache @@ -0,0 +1,12 @@ + +{ + "name": "{{plugin_slug}}", + "version": "0.1.0", + "main": "Gruntfile.js", + "author": "{{plugin_author}}", + "devDependencies": { + "grunt": "~0.4.5", + "grunt-wp-i18n": "~0.5.0", + "grunt-wp-readme-to-markdown": "~1.0.0" + } +} diff --git a/templates/plugin-readme.mustache b/templates/plugin-readme.mustache index c2392661b..79a8ea993 100644 --- a/templates/plugin-readme.mustache +++ b/templates/plugin-readme.mustache @@ -1,13 +1,12 @@ === {{plugin_name}} === Contributors: (this should be a list of wordpress.org userid's) -Donate link: https://example.com/ +Donate link: http://example.com/ Tags: comments, spam -Requires at least: 4.5 +Requires at least: 4.4 Tested up to: {{plugin_tested_up_to}} -Requires PHP: 5.6 Stable tag: 0.1.0 License: GPLv2 or later -License URI: https://www.gnu.org/licenses/gpl-2.0.html +License URI: http://www.gnu.org/licenses/gpl-2.0.html Here is a short description of the plugin. This should be no more than 150 characters. No markup here. @@ -103,10 +102,10 @@ Unordered list: * something else * third thing -Here's a link to [WordPress](https://wordpress.org/ "Your favorite software") and one to [Markdown's Syntax Documentation][markdown syntax]. +Here's a link to [WordPress](http://wordpress.org/ "Your favorite software") and one to [Markdown's Syntax Documentation][markdown syntax]. Titles are optional, naturally. -[markdown syntax]: https://daringfireball.net/projects/markdown/syntax +[markdown syntax]: http://daringfireball.net/projects/markdown/syntax "Markdown is what the parser uses to process much of the readme file" Markdown uses email style notation for blockquotes and I've been told: diff --git a/templates/plugin-test-sample.mustache b/templates/plugin-test-sample.mustache index 1fe7bf4f7..2e897d13d 100644 --- a/templates/plugin-test-sample.mustache +++ b/templates/plugin-test-sample.mustache @@ -13,7 +13,7 @@ class SampleTest extends WP_UnitTestCase { /** * A single example test. */ - public function test_sample() { + function test_sample() { // Replace this with some actual testing code. $this->assertTrue( true ); } diff --git a/templates/plugin-travis.mustache b/templates/plugin-travis.mustache new file mode 100644 index 000000000..72536b948 --- /dev/null +++ b/templates/plugin-travis.mustache @@ -0,0 +1,59 @@ +sudo: false + +language: php + +notifications: + email: + on_success: never + on_failure: change + +branches: + only: + - master + +cache: + - composer + - $HOME/.composer/cache + +matrix: + include: + - php: 7.1 + env: WP_VERSION=latest + - php: 7.0 + env: WP_VERSION=latest + {{#wp_versions_to_test}} + - php: 5.6 + env: WP_VERSION={{.}} + {{/wp_versions_to_test}} + - php: 5.6 + env: WP_TRAVISCI=phpcs + - php: 5.3 + env: WP_VERSION=latest + +before_script: + - export PATH="$HOME/.composer/vendor/bin:$PATH" + - | + if [[ ! -z "$WP_VERSION" ]] ; then + bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION + if [[ ${TRAVIS_PHP_VERSION:0:2} == "7." ]]; then + composer global require "phpunit/phpunit=5.7.*" + else + composer global require "phpunit/phpunit=4.8.*" + fi + fi + - | + if [[ "$WP_TRAVISCI" == "phpcs" ]] ; then + composer global require wp-coding-standards/wpcs + phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs + fi + +script: + - | + if [[ ! -z "$WP_VERSION" ]] ; then + phpunit + WP_MULTISITE=1 phpunit + fi + - | + if [[ "$WP_TRAVISCI" == "phpcs" ]] ; then + phpcs --standard=phpcs.ruleset.xml $(find . -name '*.php') + fi diff --git a/templates/plugin.mustache b/templates/plugin.mustache index 097fc41e6..fc502b67a 100644 --- a/templates/plugin.mustache +++ b/templates/plugin.mustache @@ -11,8 +11,3 @@ * * @package {{plugin_package}} */ - -// Your code starts here. -if ( ! defined( 'ABSPATH' ) ) { - exit; -} diff --git a/templates/post_type.mustache b/templates/post_type.mustache index 7115162a6..02f96e197 100644 --- a/templates/post_type.mustache +++ b/templates/post_type.mustache @@ -1,45 +1,29 @@ - register_post_type( - '{{slug}}', - array( - 'labels' => array( - 'name' => __( '{{label_plural_ucfirst}}', '{{textdomain}}' ), - 'singular_name' => __( '{{label_ucfirst}}', '{{textdomain}}' ), - 'all_items' => __( 'All {{label_plural_ucfirst}}', '{{textdomain}}' ), - 'archives' => __( '{{label_ucfirst}} Archives', '{{textdomain}}' ), - 'attributes' => __( '{{label_ucfirst}} Attributes', '{{textdomain}}' ), - 'insert_into_item' => __( 'Insert into {{label}}', '{{textdomain}}' ), - 'uploaded_to_this_item' => __( 'Uploaded to this {{label}}', '{{textdomain}}' ), - 'featured_image' => _x( 'Featured Image', '{{slug}}', '{{textdomain}}' ), - 'set_featured_image' => _x( 'Set featured image', '{{slug}}', '{{textdomain}}' ), - 'remove_featured_image' => _x( 'Remove featured image', '{{slug}}', '{{textdomain}}' ), - 'use_featured_image' => _x( 'Use as featured image', '{{slug}}', '{{textdomain}}' ), - 'filter_items_list' => __( 'Filter {{label_plural}} list', '{{textdomain}}' ), - 'items_list_navigation' => __( '{{label_plural_ucfirst}} list navigation', '{{textdomain}}' ), - 'items_list' => __( '{{label_plural_ucfirst}} list', '{{textdomain}}' ), - 'new_item' => __( 'New {{label_ucfirst}}', '{{textdomain}}' ), - 'add_new' => __( 'Add New', '{{textdomain}}' ), - 'add_new_item' => __( 'Add New {{label_ucfirst}}', '{{textdomain}}' ), - 'edit_item' => __( 'Edit {{label_ucfirst}}', '{{textdomain}}' ), - 'view_item' => __( 'View {{label_ucfirst}}', '{{textdomain}}' ), - 'view_items' => __( 'View {{label_plural_ucfirst}}', '{{textdomain}}' ), - 'search_items' => __( 'Search {{label_plural}}', '{{textdomain}}' ), - 'not_found' => __( 'No {{label_plural}} found', '{{textdomain}}' ), - 'not_found_in_trash' => __( 'No {{label_plural}} found in trash', '{{textdomain}}' ), - 'parent_item_colon' => __( 'Parent {{label_ucfirst}}:', '{{textdomain}}' ), - 'menu_name' => __( '{{label_plural_ucfirst}}', '{{textdomain}}' ), - ), - 'public' => true, - 'hierarchical' => false, - 'show_ui' => true, - 'show_in_nav_menus' => true, - 'supports' => array( 'title', 'editor' ), - 'has_archive' => true, - 'rewrite' => true, - 'query_var' => true, - 'menu_position' => null, - 'menu_icon' => 'dashicons-{{dashicon}}', - 'show_in_rest' => true, - 'rest_base' => '{{slug}}', - 'rest_controller_class' => 'WP_REST_Posts_Controller', - ) - ); + register_post_type( '{{slug}}', array( + 'labels' => array( + 'name' => __( '{{label_plural_ucfirst}}', '{{textdomain}}' ), + 'singular_name' => __( '{{label_ucfirst}}', '{{textdomain}}' ), + 'all_items' => __( 'All {{label_plural_ucfirst}}', '{{textdomain}}' ), + 'new_item' => __( 'New {{label}}', '{{textdomain}}' ), + 'add_new' => __( 'Add New', '{{textdomain}}' ), + 'add_new_item' => __( 'Add New {{label}}', '{{textdomain}}' ), + 'edit_item' => __( 'Edit {{label}}', '{{textdomain}}' ), + 'view_item' => __( 'View {{label}}', '{{textdomain}}' ), + 'search_items' => __( 'Search {{label_plural}}', '{{textdomain}}' ), + 'not_found' => __( 'No {{label_plural}} found', '{{textdomain}}' ), + 'not_found_in_trash' => __( 'No {{label_plural}} found in trash', '{{textdomain}}' ), + 'parent_item_colon' => __( 'Parent {{label}}', '{{textdomain}}' ), + 'menu_name' => __( '{{label_plural_ucfirst}}', '{{textdomain}}' ), + ), + 'public' => true, + 'hierarchical' => false, + 'show_ui' => true, + 'show_in_nav_menus' => true, + 'supports' => array( 'title', 'editor' ), + 'has_archive' => true, + 'rewrite' => true, + 'query_var' => true, + 'menu_icon' => 'dashicons-{{dashicon}}', + 'show_in_rest' => true, + 'rest_base' => '{{slug}}', + 'rest_controller_class' => 'WP_REST_Posts_Controller', + ) ); diff --git a/templates/post_type_extended.mustache b/templates/post_type_extended.mustache index 288fa1c9d..b8250fde9 100644 --- a/templates/post_type_extended.mustache +++ b/templates/post_type_extended.mustache @@ -1,81 +1,32 @@ <?php -/** - * Custom post type - * - * @package {{prefix}} - */ -/** - * Registers the `{{machine_name}}` post type. - */ -function {{prefix}}_init() { +function {{machine_name}}_init() { {{output}} } +add_action( 'init', '{{machine_name}}_init' ); -add_action( 'init', '{{prefix}}_init' ); - -/** - * Sets the post updated messages for the `{{machine_name}}` post type. - * - * @param array $messages Post updated messages. - * @return array Messages for the `{{machine_name}}` post type. - */ -function {{prefix}}_updated_messages( $messages ) { +function {{machine_name}}_updated_messages( $messages ) { global $post; $permalink = get_permalink( $post ); $messages['{{slug}}'] = array( - 0 => '', // Unused. Messages start at index 1. - /* translators: %s: post permalink */ - 1 => sprintf( __( '{{label_ucfirst}} updated. <a target="_blank" href="%s">View {{label}}</a>', '{{textdomain}}' ), esc_url( $permalink ) ), - 2 => __( 'Custom field updated.', '{{textdomain}}' ), - 3 => __( 'Custom field deleted.', '{{textdomain}}' ), - 4 => __( '{{label_ucfirst}} updated.', '{{textdomain}}' ), + 0 => '', // Unused. Messages start at index 1. + 1 => sprintf( __('{{label_ucfirst}} updated. <a target="_blank" href="%s">View {{label}}</a>', '{{textdomain}}'), esc_url( $permalink ) ), + 2 => __('Custom field updated.', '{{textdomain}}'), + 3 => __('Custom field deleted.', '{{textdomain}}'), + 4 => __('{{label_ucfirst}} updated.', '{{textdomain}}'), /* translators: %s: date and time of the revision */ - 5 => isset( $_GET['revision'] ) ? sprintf( __( '{{label_ucfirst}} restored to revision from %s', '{{textdomain}}' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false, // phpcs:ignore WordPress.Security.NonceVerification.Recommended - /* translators: %s: post permalink */ - 6 => sprintf( __( '{{label_ucfirst}} published. <a href="%s">View {{label}}</a>', '{{textdomain}}' ), esc_url( $permalink ) ), - 7 => __( '{{label_ucfirst}} saved.', '{{textdomain}}' ), - /* translators: %s: post permalink */ - 8 => sprintf( __( '{{label_ucfirst}} submitted. <a target="_blank" href="%s">Preview {{label}}</a>', '{{textdomain}}' ), esc_url( add_query_arg( 'preview', 'true', $permalink ) ) ), - /* translators: 1: Publish box date format, see https://secure.php.net/date 2: Post permalink */ - 9 => sprintf( __( '{{label_ucfirst}} scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview {{label}}</a>', '{{textdomain}}' ), date_i18n( __( 'M j, Y @ G:i', '{{textdomain}}' ), strtotime( $post->post_date ) ), esc_url( $permalink ) ), - /* translators: %s: post permalink */ - 10 => sprintf( __( '{{label_ucfirst}} draft updated. <a target="_blank" href="%s">Preview {{label}}</a>', '{{textdomain}}' ), esc_url( add_query_arg( 'preview', 'true', $permalink ) ) ), + 5 => isset($_GET['revision']) ? sprintf( __('{{label_ucfirst}} restored to revision from %s', '{{textdomain}}'), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false, + 6 => sprintf( __('{{label_ucfirst}} published. <a href="%s">View {{label}}</a>', '{{textdomain}}'), esc_url( $permalink ) ), + 7 => __('{{label_ucfirst}} saved.', '{{textdomain}}'), + 8 => sprintf( __('{{label_ucfirst}} submitted. <a target="_blank" href="%s">Preview {{label}}</a>', '{{textdomain}}'), esc_url( add_query_arg( 'preview', 'true', $permalink ) ) ), + 9 => sprintf( __('{{label_ucfirst}} scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview {{label}}</a>', '{{textdomain}}'), + // translators: Publish box date format, see http://php.net/date + date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( $permalink ) ), + 10 => sprintf( __('{{label_ucfirst}} draft updated. <a target="_blank" href="%s">Preview {{label}}</a>', '{{textdomain}}'), esc_url( add_query_arg( 'preview', 'true', $permalink ) ) ), ); return $messages; } - -add_filter( 'post_updated_messages', '{{prefix}}_updated_messages' ); - -/** - * Sets the bulk post updated messages for the `{{machine_name}}` post type. - * - * @param array $bulk_messages Arrays of messages, each keyed by the corresponding post type. Messages are - * keyed with 'updated', 'locked', 'deleted', 'trashed', and 'untrashed'. - * @param int[] $bulk_counts Array of item counts for each message, used to build internationalized strings. - * @return array Bulk messages for the `{{machine_name}}` post type. - */ -function {{prefix}}_bulk_updated_messages( $bulk_messages, $bulk_counts ) { - global $post; - - $bulk_messages['{{slug}}'] = array( - /* translators: %s: Number of {{label_plural}}. */ - 'updated' => _n( '%s {{label}} updated.', '%s {{label_plural}} updated.', $bulk_counts['updated'], '{{textdomain}}' ), - 'locked' => ( 1 === $bulk_counts['locked'] ) ? __( '1 {{label}} not updated, somebody is editing it.', '{{textdomain}}' ) : - /* translators: %s: Number of {{label_plural}}. */ - _n( '%s {{label}} not updated, somebody is editing it.', '%s {{label_plural}} not updated, somebody is editing them.', $bulk_counts['locked'], '{{textdomain}}' ), - /* translators: %s: Number of {{label_plural}}. */ - 'deleted' => _n( '%s {{label}} permanently deleted.', '%s {{label_plural}} permanently deleted.', $bulk_counts['deleted'], '{{textdomain}}' ), - /* translators: %s: Number of {{label_plural}}. */ - 'trashed' => _n( '%s {{label}} moved to the Trash.', '%s {{label_plural}} moved to the Trash.', $bulk_counts['trashed'], '{{textdomain}}' ), - /* translators: %s: Number of {{label_plural}}. */ - 'untrashed' => _n( '%s {{label}} restored from the Trash.', '%s {{label_plural}} restored from the Trash.', $bulk_counts['untrashed'], '{{textdomain}}' ), - ); - - return $bulk_messages; -} - -add_filter( 'bulk_post_updated_messages', '{{prefix}}_bulk_updated_messages', 10, 2 ); +add_filter( 'post_updated_messages', '{{machine_name}}_updated_messages' ); diff --git a/templates/taxonomy.mustache b/templates/taxonomy.mustache index 6f5ea8958..cb00da9d1 100644 --- a/templates/taxonomy.mustache +++ b/templates/taxonomy.mustache @@ -1,46 +1,36 @@ - register_taxonomy( - '{{slug}}', - array( {{post_types}} ), - array( - 'hierarchical' => false, - 'public' => true, - 'show_in_nav_menus' => true, - 'show_ui' => true, - 'show_admin_column' => false, - 'query_var' => true, - 'rewrite' => true, - 'capabilities' => array( - 'manage_terms' => 'edit_posts', - 'edit_terms' => 'edit_posts', - 'delete_terms' => 'edit_posts', - 'assign_terms' => 'edit_posts', - ), - 'labels' => array( - 'name' => __( '{{label_plural_ucfirst}}', '{{textdomain}}' ), - 'singular_name' => _x( '{{label_ucfirst}}', 'taxonomy general name', '{{textdomain}}' ), - 'search_items' => __( 'Search {{label_plural_ucfirst}}', '{{textdomain}}' ), - 'popular_items' => __( 'Popular {{label_plural_ucfirst}}', '{{textdomain}}' ), - 'all_items' => __( 'All {{label_plural_ucfirst}}', '{{textdomain}}' ), - 'parent_item' => __( 'Parent {{label_ucfirst}}', '{{textdomain}}' ), - 'parent_item_colon' => __( 'Parent {{label_ucfirst}}:', '{{textdomain}}' ), - 'edit_item' => __( 'Edit {{label_ucfirst}}', '{{textdomain}}' ), - 'update_item' => __( 'Update {{label_ucfirst}}', '{{textdomain}}' ), - 'view_item' => __( 'View {{label_ucfirst}}', '{{textdomain}}' ), - 'add_new_item' => __( 'Add New {{label_ucfirst}}', '{{textdomain}}' ), - 'new_item_name' => __( 'New {{label_ucfirst}}', '{{textdomain}}' ), - 'separate_items_with_commas' => __( 'Separate {{label_plural}} with commas', '{{textdomain}}' ), - 'add_or_remove_items' => __( 'Add or remove {{label_plural}}', '{{textdomain}}' ), - 'choose_from_most_used' => __( 'Choose from the most used {{label_plural}}', '{{textdomain}}' ), - 'not_found' => __( 'No {{label_plural}} found.', '{{textdomain}}' ), - 'no_terms' => __( 'No {{label_plural}}', '{{textdomain}}' ), - 'menu_name' => __( '{{label_plural_ucfirst}}', '{{textdomain}}' ), - 'items_list_navigation' => __( '{{label_plural_ucfirst}} list navigation', '{{textdomain}}' ), - 'items_list' => __( '{{label_plural_ucfirst}} list', '{{textdomain}}' ), - 'most_used' => _x( 'Most Used', '{{slug}}', '{{textdomain}}' ), - 'back_to_items' => __( '← Back to {{label_plural_ucfirst}}', '{{textdomain}}' ), - ), - 'show_in_rest' => true, - 'rest_base' => '{{slug}}', - 'rest_controller_class' => 'WP_REST_Terms_Controller', - ) - ); + register_taxonomy( '{{slug}}', array( {{post_types}} ), array( + 'hierarchical' => false, + 'public' => true, + 'show_in_nav_menus' => true, + 'show_ui' => true, + 'show_admin_column' => false, + 'query_var' => true, + 'rewrite' => true, + 'capabilities' => array( + 'manage_terms' => 'edit_posts', + 'edit_terms' => 'edit_posts', + 'delete_terms' => 'edit_posts', + 'assign_terms' => 'edit_posts' + ), + 'labels' => array( + 'name' => __( '{{label_plural_ucfirst}}', '{{textdomain}}' ), + 'singular_name' => _x( '{{label_ucfirst}}', 'taxonomy general name', '{{textdomain}}' ), + 'search_items' => __( 'Search {{label_plural}}', '{{textdomain}}' ), + 'popular_items' => __( 'Popular {{label_plural}}', '{{textdomain}}' ), + 'all_items' => __( 'All {{label_plural}}', '{{textdomain}}' ), + 'parent_item' => __( 'Parent {{label}}', '{{textdomain}}' ), + 'parent_item_colon' => __( 'Parent {{label}}:', '{{textdomain}}' ), + 'edit_item' => __( 'Edit {{label}}', '{{textdomain}}' ), + 'update_item' => __( 'Update {{label}}', '{{textdomain}}' ), + 'add_new_item' => __( 'New {{label}}', '{{textdomain}}' ), + 'new_item_name' => __( 'New {{label}}', '{{textdomain}}' ), + 'separate_items_with_commas' => __( 'Separate {{label_plural}} with commas', '{{textdomain}}' ), + 'add_or_remove_items' => __( 'Add or remove {{label_plural}}', '{{textdomain}}' ), + 'choose_from_most_used' => __( 'Choose from the most used {{label_plural}}', '{{textdomain}}' ), + 'not_found' => __( 'No {{label_plural}} found.', '{{textdomain}}' ), + 'menu_name' => __( '{{label_plural_ucfirst}}', '{{textdomain}}' ), + ), + 'show_in_rest' => true, + 'rest_base' => '{{slug}}', + 'rest_controller_class' => 'WP_REST_Terms_Controller', + ) ); diff --git a/templates/taxonomy_extended.mustache b/templates/taxonomy_extended.mustache index fc71a6596..e2e584a11 100644 --- a/templates/taxonomy_extended.mustache +++ b/templates/taxonomy_extended.mustache @@ -1,39 +1,6 @@ <?php -/** - * Custom taxonomy - * - * @package {{prefix}} - */ -/** - * Registers the `{{machine_name}}` taxonomy, - * for use with {{post_types}}. - */ -function {{prefix}}_init() { +function {{machine_name}}_init() { {{output}} } - -add_action( 'init', '{{prefix}}_init' ); - -/** - * Sets the post updated messages for the `{{machine_name}}` taxonomy. - * - * @param array $messages Post updated messages. - * @return array Messages for the `{{machine_name}}` taxonomy. - */ -function {{prefix}}_updated_messages( $messages ) { - - $messages['{{slug}}'] = array( - 0 => '', // Unused. Messages start at index 1. - 1 => __( '{{label_ucfirst}} added.', '{{textdomain}}' ), - 2 => __( '{{label_ucfirst}} deleted.', '{{textdomain}}' ), - 3 => __( '{{label_ucfirst}} updated.', '{{textdomain}}' ), - 4 => __( '{{label_ucfirst}} not added.', '{{textdomain}}' ), - 5 => __( '{{label_ucfirst}} not updated.', '{{textdomain}}' ), - 6 => __( '{{label_plural_ucfirst}} deleted.', '{{textdomain}}' ), - ); - - return $messages; -} - -add_filter( 'term_updated_messages', '{{prefix}}_updated_messages' ); +add_action( 'init', '{{machine_name}}_init' ); diff --git a/templates/theme-bootstrap.mustache b/templates/theme-bootstrap.mustache index 04002b08c..82c81cfc0 100644 --- a/templates/theme-bootstrap.mustache +++ b/templates/theme-bootstrap.mustache @@ -1,49 +1,34 @@ <?php /** - * PHPUnit bootstrap file. + * PHPUnit bootstrap file * * @package {{theme_package}} */ $_tests_dir = getenv( 'WP_TESTS_DIR' ); - if ( ! $_tests_dir ) { - $_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib'; -} - -if ( ! file_exists( $_tests_dir . '/includes/functions.php' ) ) { - echo "Could not find {$_tests_dir}/includes/functions.php, have you run bin/install-wp-tests.sh ?" . PHP_EOL; - exit( 1 ); + $_tests_dir = '/tmp/wordpress-tests-lib'; } // Give access to tests_add_filter() function. -require_once "{$_tests_dir}/includes/functions.php"; +require_once $_tests_dir . '/includes/functions.php'; -/** - * Registers theme. - */ function _register_theme() { - $theme_dir = dirname( __DIR__ ); + $theme_dir = dirname( dirname( __FILE__ ) ); $current_theme = basename( $theme_dir ); - $theme_root = dirname( $theme_dir ); - add_filter( 'theme_root', function () use ( $theme_root ) { - return $theme_root; - } ); + register_theme_directory( dirname( $theme_dir ) ); - register_theme_directory( $theme_root ); - - add_filter( 'pre_option_template', function () use ( $current_theme ) { + add_filter( 'pre_option_template', function() use ( $current_theme ) { return $current_theme; - } ); - - add_filter( 'pre_option_stylesheet', function () use ( $current_theme ) { + }); + add_filter( 'pre_option_stylesheet', function() use ( $current_theme ) { return $current_theme; - } ); + }); } - tests_add_filter( 'muplugins_loaded', '_register_theme' ); + // Start up the WP testing environment. -require "{$_tests_dir}/includes/bootstrap.php"; +require $_tests_dir . '/includes/bootstrap.php'; diff --git a/templates/theme-status.mustache b/templates/theme-status.mustache deleted file mode 100644 index c53be6338..000000000 --- a/templates/theme-status.mustache +++ /dev/null @@ -1,5 +0,0 @@ -Theme %9{{slug}}%n details: - Name: {{name}} - Status: {{status}}%n - Version: {{version}} - Author: {{author}} diff --git a/templates/theme-test-sample.mustache b/templates/theme-test-sample.mustache index fb44cdc66..ec78927c6 100644 --- a/templates/theme-test-sample.mustache +++ b/templates/theme-test-sample.mustache @@ -13,7 +13,7 @@ class SampleTest extends WP_UnitTestCase { /** * A single example test. */ - public function test_sample() { + function test_sample() { // Replace this with some actual testing code. $this->assertTrue( true ); } diff --git a/utils/behat-tags.php b/utils/behat-tags.php new file mode 100644 index 000000000..df836b830 --- /dev/null +++ b/utils/behat-tags.php @@ -0,0 +1,66 @@ +<?php +/** + * Generate a list of tags to skip during the test run. + * + * Require a minimum version of WordPress: + * + * @require-wp-4.0 + * Scenario: Core translation CRUD + * + * Then use in bash script: + * + * BEHAT_TAGS=$(php behat-tags.php) + * vendor/bin/behat --format progress $BEHAT_TAGS + */ + +function version_tags( $prefix, $current, $operator = '<' ) { + if ( ! $current ) + return array(); + + exec( "grep '@{$prefix}-[0-9\.]*' -h -o features/*.feature | uniq", $existing_tags ); + + $skip_tags = array(); + + foreach ( $existing_tags as $tag ) { + $compare = str_replace( "@{$prefix}-", '', $tag ); + if ( version_compare( $current, $compare, $operator ) ) { + $skip_tags[] = $tag; + } + } + + return $skip_tags; +} + +$skip_tags = array_merge( + version_tags( 'require-wp', getenv( 'WP_VERSION' ), '<' ), + version_tags( 'require-php', PHP_VERSION, '<' ), + version_tags( 'less-than-php', PHP_VERSION, '>' ) +); + +# Skip Github API tests by default because of rate limiting. See https://github.com/wp-cli/wp-cli/issues/1612 +$skip_tags[] = '@github-api'; + +# Require PHP extension, eg 'imagick'. +function extension_tags() { + $extension_tags = array(); + exec( "grep '@require-extension-[A-Za-z_]*' -h -o features/*.feature | uniq", $extension_tags ); + + $skip_tags = array(); + + $substr_start = strlen( '@require-extension-' ); + foreach ( $extension_tags as $tag ) { + $extension = substr( $tag, $substr_start ); + if ( ! extension_loaded( $extension ) ) { + $skip_tags[] = $tag; + } + } + + return $skip_tags; +} + +$skip_tags = array_merge( $skip_tags, extension_tags() ); + +if ( !empty( $skip_tags ) ) { + echo '--tags=~' . implode( '&&~', $skip_tags ); +} +