├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .mailmap ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE-MIT ├── README.md ├── RELEASE.md ├── bootstrap.js ├── build_docs.sh ├── docker-compose.yml ├── docs ├── 05_SETUP.md ├── 10_BUILD.md ├── 20_QUALITY.md ├── 30_FRONTEND.md ├── 40_OPERATIONS.md ├── 40_TESTING.md ├── 60_PACKAGE.md ├── 70_ADVANCED_CONFIG.md ├── 70_GIT_INTEGRATION.md ├── 80_CI.md ├── 80_CUSTOM_TASKS.md ├── 90_FAQ.md ├── Dockerfile ├── README.md ├── docker-compose.yml ├── index.md └── mkdocs.yml ├── example ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── Gruntconfig.json ├── Gruntfile.js ├── composer.json ├── gitignore ├── package.json ├── phpmd.xml ├── src │ ├── libraries │ │ └── .gitkeep │ ├── modules │ │ └── .gitkeep │ ├── profiles │ │ └── .gitkeep │ ├── sites │ │ └── default │ │ │ └── .gitkeep │ ├── static │ │ └── .gitkeep │ └── themes │ │ └── .gitkeep └── test │ ├── behat.yml │ └── features │ ├── bootstrap │ └── FeatureContext.php │ └── example.feature ├── lib ├── drupal.js ├── help.js ├── init.js ├── scripts.js └── util.js ├── npm-shrinkwrap.json ├── package.json ├── tasks ├── behat.js ├── clean.js ├── composer.js ├── copy.js ├── git.js ├── help.js ├── install.js ├── make.js ├── mkdir.js ├── notify.js ├── operations.js ├── package.js ├── quality.js ├── scaffold.js ├── serve.js ├── test.js ├── theme.js └── watch.js └── test ├── build.js ├── create_working_copy.sh ├── library.js ├── test.sh ├── test_assets ├── Gruntconfig.json ├── composer.json └── src │ ├── libraries │ └── example_lib │ │ └── example.md │ ├── modules │ ├── test.js │ └── test.php │ ├── project.make │ └── themes │ └── example_theme │ └── example_theme.info ├── test_assets_d8 ├── Gruntconfig.json ├── src │ ├── libraries │ │ └── example_lib │ │ │ └── example.md │ ├── modules │ │ ├── gdt_test │ │ │ ├── gdt_test.info.yml │ │ │ └── tests │ │ │ │ └── src │ │ │ │ └── Unit │ │ │ │ └── PassTest.php │ │ ├── test.js │ │ ├── test.php │ │ └── test.yml │ └── themes │ │ └── example_theme │ │ └── example_theme.info.yml └── test │ └── features │ └── example.feature └── travis.php.ini /.eslintignore: -------------------------------------------------------------------------------- 1 | /test/test_assets 2 | /test/test_assets_d8 3 | /test/working_copy 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "google", 3 | "rules": { 4 | "max-len": 0, 5 | "max-nested-callbacks": ["warn", {"max": 5}], 6 | "valid-jsdoc": 0 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /node_modules 3 | /test/working_copy 4 | /build 5 | /docs/site 6 | example/build/ 7 | example/composer.lock 8 | example/vendor/ 9 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # 2 | # This list is used by git-shortlog to consolidate variations of the same 3 | # committer identity into one. 4 | # 5 | # This done, you can run commands like `git shortlog -sne` and get the data 6 | # properly consolidated by author. 7 | # 8 | 9 | Adam Ross Adam R 10 | Adam Ross Adam Ross 11 | Adam Ross Grayside 12 | Adam Ross Grayside 13 | Joe Turgeon Joe Turgeon 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: php 3 | php: 4 | - 5.5 5 | - 5.6 6 | - 7 7 | addons: 8 | apt: 9 | sources: 10 | - ubuntu-toolchain-r-test 11 | packages: 12 | - gcc-4.8 13 | - g++-4.8 14 | env: 15 | - NVM_NODE_VERSION="4" GDT_DRUPAL_CORE="7" GDT_TEST_URL="http://127.0.0.1:8080/misc/drupal.js" CXX=g++-4.8 16 | - NVM_NODE_VERSION="7" GDT_DRUPAL_CORE="8" GDT_TEST_URL="http://127.0.0.1:8080/core/misc/drupal.js" CXX=g++-4.8 17 | - NVM_NODE_VERSION="6" GDT_DRUPAL_CORE="8" GDT_TEST_URL="http://127.0.0.1:8080/core/misc/drupal.js" CXX=g++-4.8 18 | matrix: 19 | exclude: 20 | # Skip Drupal 7 on PHP 7 21 | - php: 7 22 | env: NVM_NODE_VERSION="4" GDT_DRUPAL_CORE="7" GDT_TEST_URL="http://127.0.0.1:8080/misc/drupal.js" CXX=g++-4.8 23 | # Skip PHP 5.5 and 5.6 with Node.js 7 24 | - php: 5.5 25 | env: NVM_NODE_VERSION="6" GDT_DRUPAL_CORE="8" GDT_TEST_URL="http://127.0.0.1:8080/core/misc/drupal.js" CXX=g++-4.8 26 | # Skip PHP 5.5 and 5.6 with Node.js 7 27 | - php: 5.5 28 | env: NVM_NODE_VERSION="7" GDT_DRUPAL_CORE="8" GDT_TEST_URL="http://127.0.0.1:8080/core/misc/drupal.js" CXX=g++-4.8 29 | - php: 5.6 30 | env: NVM_NODE_VERSION="7" GDT_DRUPAL_CORE="8" GDT_TEST_URL="http://127.0.0.1:8080/core/misc/drupal.js" CXX=g++-4.8 31 | before_install: 32 | - rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install $NVM_NODE_VERSION 33 | - node --version 34 | - npm --version 35 | - composer self-update 36 | - echo "sendmail_path='true'" >> `php --ini | grep "Loaded Configuration" | awk '{print $4}'` 37 | - phpenv config-add test/travis.php.ini 38 | install: 39 | - npm set progress=false 40 | - npm install -g grunt-cli@^1.0.0 41 | - npm install -g mocha 42 | - npm install 43 | script: 44 | - echo "Code checkout disk usage"; du -chs . 45 | # Disable linting until outstanding PRs are merged. 46 | # Updating eslint has caused it to explode. 47 | # - npm run lint 48 | - ./test/create_working_copy.sh 49 | - cd test/working_copy/ 50 | - echo "Working copy disk usage"; du -chs . 51 | - grunt --version 52 | - grunt help 53 | - grunt --quiet --timer 54 | - echo "Fully built disk usage"; du -chs . 55 | - grunt install --db-url=sqlite:/`pwd`/.ht.sqlite --quiet 56 | - grunt serve:test >/dev/null & 57 | - until curl -I -XGET -s $GDT_TEST_URL 2>/dev/null | egrep -q '^HTTP.*200'; do sleep 0.5; done 58 | - vendor/bin/drush --root=build/html en -y simpletest 59 | - grunt test 60 | - sleep 1; while (ps aux | grep '[b]ehat' > /dev/null); do sleep 1; done 61 | - for pid in `ps aux | grep drush | grep runserver | awk '{print $2}'`; do echo "Stopping drush pid $pid"; kill -SIGINT $pid; done; 62 | - grunt package --quiet 63 | - mocha --timeout 30000 node_modules/grunt-drupal-tasks/test/build.js 64 | - mocha node_modules/grunt-drupal-tasks/test/library.js 65 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v1.1.0 [2018/04/25] 4 | 5 | Long overdue Spring Cleaning. 6 | 7 | Thanks to @markdboyd, @jhedstrom, @csegarra, @scottalan, and @spacepants for 8 | contributing. 9 | 10 | ### Changes 11 | 12 | * Drush 9 Compatibility: Only pass dbUrl to Drush if set. (#342) 13 | * For Drupal 8 packaging, loading composer.json from repo root. (#331) 14 | * Fixed githook configuration. (#321, #344) 15 | * Fix Behat failures: Add conflict with behat >= v3.4 (#343) 16 | * Fix eslint error in test regex. (#317) 17 | * Correct path of the config.serve.concurrent configuration (#334) 18 | * Use absolute path to HTML build directory. (#325) 19 | * expose eslint/phpcs report format option (#310) 20 | 21 | ### Project Plumbing 22 | 23 | * Added Greenkeeper for automated dependency update management. 24 | * Added Docker-based local development & testing environment. 25 | * Add mailmap for contributor aliasing. 26 | * Ignore build byproducts in examples directory. 27 | * Ignore the .idea directory for phpstorm (#329) 28 | * Minor tweaks to Travis testing around node/Drupal version compatibility. 29 | * Re-sequenced README badges 30 | 31 | ## v1.0.0 [2016/10/19] 32 | 33 | - No changes since the v1.0.0-rc1 pre-release. 34 | 35 | ## v1.0.0-rc1 [2016/10/14] 36 | 37 | - Adding scaffold step to ensure the profiles, modules, and libraries 38 | directories are created within the build output. 39 | - By default, exclude bower_components and node_modules paths from phpmd. 40 | - Fixed task error if phpcsConfig and eslintConfig are not defined. 41 | - Setting up generated documentation site, and documentation updates. 42 | - Removing Travis test coverage for Node.js v5. 43 | 44 | ## v1.0.0-alpha4 [2016/09/26] 45 | 46 | - Adding support to configure the packaging task's output location of the 47 | docroot and vendor directories for Drupal 8 projects using Composer. 48 | 49 | ## v1.0.0-alpha3 [2016/08/21] 50 | 51 | - Fixed missing composer:drupal-scaffold command error. 52 | - Fixed eslint installation issue. 53 | 54 | ## v1.0.0-alpha2 [2016/07/15] 55 | 56 | - Fixed regression issue for projects using Composer but not drupal-scaffold. 57 | - For Composer installs, rebuild package output to exclude dev dependencies. 58 | - Extended default PHPCS configuration to validate YAML files. 59 | - Removed deprecated code and documentation. 60 | - Updated dependencies. 61 | 62 | ## v1.0.0-alpha1 [2016/06/22] 63 | 64 | - Dropped support for Node.js v0.12 and earlier and PHP v5.4 and earlier. 65 | - Added support for a Composer build process for Drupal 8. 66 | - Removed built-in support for Compass theme compilation and Ruby bundle 67 | installation. 68 | - Added configuration for the Behat binary path. Add default configuration for 69 | the paths of the phpcs, phpmd, and Drush binaries. Changed configuration key 70 | from `drush.cmd` to `drush.path` for consistency. 71 | - Minor code refactoring and adopting code style standard. 72 | - Updated dependencies. 73 | 74 | ### Upgrade Notes 75 | 76 | - Node.js v4 or later is required. Grunt Drupal Tasks is now compatible with 77 | Node.js v4 and v6. 78 | - PHP v5.5 or later is required. 79 | - Use of the built-in Compass theme compilation steps must be replaced by custom 80 | handling at the project- or theme-level. 81 | - Change the Gruntconfig.json configuration key `cmd` under `drush` to `path`, 82 | or if using the default path of `vendor/bin/drush`, remove the setting entirely. 83 | 84 | ## v0.11.1 [2016/04/17] 85 | 86 | - Fixed `grunt package` regression that custom code from the packaged outputs. 87 | 88 | ## v0.11.0 [2016/04/12] 89 | 90 | - Updated example to use Grunt v1.x. Resolved peer dependency compatibility 91 | issues with dependencies. 92 | - Adding default options to use with `composer install` to improve performance 93 | and cacheability. 94 | - Added support for processing `*.make.yml` as Drush make files. 95 | - Fixed issue on Windows where copy:tempbuild was never called. 96 | - Updated dependencies in example project and for the plugin itself. 97 | 98 | ### Upgrade Notes 99 | 100 | - Grunt should be a dependency in the `package.json` file of projects that use 101 | Grunt Drupal Tasks. It is recommended to update this to use Grunt `^1.0.0`. If 102 | projects include any dependency that specifies Grunt as a peer dependency and is 103 | limited to version `< 0.5`, then you may encounter an incompatible peer 104 | dependency error when running `npm install`. The recommended solution is to 105 | upgrade dependencies to versions that are compatible with Grunt v1.x. 106 | 107 | ## v0.10.1 [2016/03/17] 108 | 109 | - Using Drush --root option by default to ensure the docroot is identified. 110 | - Updating example project to install Drush 8.x instead of dev-master. 111 | - Removing the theme validate step from the validate:staged special command, 112 | due to incompatibilities. 113 | 114 | ## v0.10.0 [2016/03/08] 115 | 116 | - Replaced copy with rsync to move the temporary build output as part of the 117 | default build process. For Windows environments, copy is still used. 118 | - Added support for running tests included with custom modules for Drupal 8. 119 | - Updated default phplint patterns to skip `*.panels_default.inc` files. 120 | - Removed peer dependency on Grunt in package.json. This prevents issues when 121 | updating to the forthcoming Grunt 1.0.0. 122 | - Updating Node.js engines requirement in package.json to v0.12.0 or later. 123 | - Updated dependencies. 124 | 125 | ### Upgrade Notes 126 | 127 | - Node.js v0.10.x is no longer supported. 128 | - Project configurations that override the default `drushmake` task should be 129 | updated to use `rsync:tempbuild` instead of `copy:tempbuild` on non-Windows 130 | environments. The `canRsync` function from `lib/util.js` should be used to 131 | determine if rsync is supported in an environment. 132 | 133 | ## v0.9.3 [2016/02/25] 134 | 135 | - Improved performance for code style quality checks by limiting file scans. 136 | - Updated default file patterns for code style quality checks. 137 | - Added Travis CI test coverage for more versions of Node.js. 138 | - Fixed Drupal 8 tests by adding a D8-specific composer.json file. 139 | 140 | ## v0.9.2 [2016/01/27] 141 | 142 | - Added `--no-validate` command line option to skip running the `grunt:validate` 143 | tasks, including PHPCS and eslint. 144 | - Fixed bug that prevented phpcs and eslint from running 145 | - Fixed bug that caused scope error for this.name. 146 | - Updated dependencies. Added npm shrinkwrap file to ensure consistency between 147 | installations. 148 | 149 | ## v0.9.1 [2015/12/10] 150 | 151 | - Add config option to specify command runner in git hook scripts. 152 | - Fixed Gruntconfig.json interpretation for disabling tasks and specifying port 153 | for the serve task. 154 | - Fixed error when using a theme's validate script. 155 | - Configured copy operations during build to ensure file modes are preserved. 156 | 157 | ## v0.9.0 [2015/11/18] 158 | 159 | - Added the ability to [define scripts for common project operations](https://github.com/phase2/grunt-drupal-tasks/blob/master/CONFIG.md#project-operations). 160 | - Added `grunt install` task to [easily install the site cleanly or with an 161 | imported database](https://github.com/phase2/grunt-drupal-tasks/blob/master/CONFIG.md#install-settings). 162 | - Added optional [integration with Git Hooks](https://github.com/phase2/grunt-drupal-tasks/blob/master/CONFIG.md#adding-git-hooks) 163 | to support running Grunt tasks when git operations are run. When enabled, the 164 | validation task is run against staged code before a commit is made. 165 | - If index.php is missing from the build destination, then the Drush make task 166 | is always run (and the "newer" feature is disabled). This facilitates certain VM 167 | configurations. 168 | - PHPCS and eslint tasks are skipped if there are no source files. 169 | 170 | ### Upgrade Notes 171 | 172 | - Themes with a validate task configured are excluded by default from the Grunt 173 | Drupal Tasks validate task. 174 | 175 | ## v0.8.0 [2015/07/24] 176 | 177 | - Fixed bug in theme proxying feature. 178 | - Updated Travis CI test configuration to use improved infrastructure and test 179 | additional PHP versions. 180 | - Added default scaffold task to symlink any directories in `src/libraries` to 181 | `build/html/sites/all/libraries` (for D7) or `build/html/libraries` (for D8). 182 | 183 | ## v0.7.1 [2015/06/24] 184 | 185 | - Fixing error with 'grunt serve' due to drush:serve not being defined. 186 | - Ensuring that Behat is run with the Grunt process's environment variables. 187 | 188 | ## v0.7.0 [2015/06/16] 189 | 190 | ### New Features 191 | 192 | - Added eslint JavaScript code quality checking to the validate and analyze 193 | tasks. 194 | - Added `grunt serve` task to [easily install and run the Drupal site](https://github.com/phase2/grunt-drupal-tasks/blob/master/CONFIG.md#serve-settings) 195 | without external dependencies like Apache. 196 | - Added theme scripts system to allow running [theme-provided build scripts](https://github.com/phase2/grunt-drupal-tasks/blob/master/CONFIG.md#theme-scripts) 197 | as part of the GDT build process. 198 | - Added `phplint.dir` setting to Gruntconfig to allow customization of linting 199 | paths. 200 | - Added `grunt validate:newer` to validate only files changed since the last 201 | run, and using it for the `grunt watch` task for a speed increase! 202 | - [New settings for `grunt package`](https://github.com/phase2/grunt-drupal-tasks/blob/master/CONFIG.md#package-settings) 203 | to prepare output ready for commit to Acquia- and Pantheon-style release 204 | repositories. 205 | - Added Drupal 8 test coverage, Travis and `npm test` will now run tests 206 | against D7 and D8. 207 | - Added support for actions against configured URLs for multiple environments. 208 | See documentation for more information: https://github.com/phase2/grunt-drupal-tasks/blob/master/CONFIG.md#core-settings 209 | - Additional unit & integration tests. 210 | 211 | ### Upgrade Notes 212 | 213 | - The GRUNT_DRUPAL_QUIET environment variable to enable quiet mode is renamed 214 | to GDT_QUIET. 215 | - `grunt package` no longer compresses by default. Use `grunt package:compress` 216 | to replicate former behavior. 217 | 218 | ## v0.6.1 [2015/05/17] 219 | 220 | - Adding documentation on Gadget, the Yeoman generator for Grunt Drupal Tasks. 221 | - Ensuring dot-files are copied from the temporary build (Drush output) and the 222 | static files directory. 223 | - Adding --notify option as a converse of --quiet. 224 | - Adding .editorconfig to the project example. 225 | 226 | ## v0.6.0 [2015/04/07] 227 | 228 | - **Added automatic support for Drupal 8 based on Drush detection of the Drupal 229 | version.** 230 | - Dynamically compute Drush Make concurrency based on system capability with a 231 | new concurrency detection service. 232 | - Ruby (bundler) will now install dependencies into `vendor/bundle`. 233 | - Ruby and PHP upstream binaries are placed or symlinked from `vendor/bin`. 234 | This frees up `bin/` for custom project scripts. 235 | - Adding default values for the buildPaths in the project Gruntconfig.json so 236 | the buildPaths config is no longer required. 237 | - Ensure reports directory is created before running analyze. 238 | - Support for \*.sass files in compass watch. 239 | - Refactored `grunt help` task to be extensible from separate projects. 240 | - The docroot assembly tasks (such as the symlinking) performed after drush make 241 | have been consolidated into a new `scaffold` task. 242 | - Due to npm's behavior that strips .gitignore files from packages, the example 243 | .gitignore is renamed to gitignore, and needs to be renamed manually after 244 | installation. 245 | 246 | ### Upgrade Notes 247 | 248 | - Remove the Drush Make `--concurrency` option from your Gruntconfig. It will no 249 | longer be respected. 250 | - The example `composer.json` and `Gruntconfig.json` have been both updated 251 | to support installing PHP component executables to `vendor/bin` instead 252 | of `bin`. If you want to continue using `bin`, use caution when updating 253 | these files. 254 | - Ruby bundle executables are moved to `vendor/bin` from `bin`. This change is 255 | intrinsic to v0.6.0. You may need to run `rm -Rf .bundle` to clear Bundler 256 | configuration to make way for the new install location. 257 | - Gruntconfig.json no longer needs the `buildPaths` config key. Elements of 258 | `buildPaths` added to your project Gruntconfig will override default behavior. 259 | - Configuration of `grunt help` for project-specific tasks via Gruntconfig.json 260 | removed in favor of new [Help API](https://github.com/phase2/grunt-drupal-tasks/blob/master/CONFIG.md#help-settings-help-api). 261 | 262 | ## v0.5.2 [2015/01/24] 263 | 264 | - Adding configuration for the Drush executable path, whether to trigger a fail 265 | for PHPCS warnings, and help text for custom tasks 266 | - Including dot files in `package` task results 267 | - Pinning npm dependencies to a specific version for stability 268 | - Adding tests for Compass theme compilation (and implicitly Bundler dependency 269 | installation) 270 | 271 | ### Upgrade Notes 272 | 273 | - The example project's configuration will install a copy of Drush in the 274 | project's `bin/` directory, and use this copy for all Drush operations. To 275 | adopt this practice on existing projects, add the Drush dependency to your 276 | project's `composer.json` and specify the path in Gruntconfig.json with: 277 | 278 | ```json 279 | "drush": { 280 | "cmd": "bin/drush" 281 | } 282 | ``` 283 | 284 | ## v0.5.1 [2015/01/10] 285 | 286 | - Critical bug fixes to Bundler and Drush support 287 | - Adding a script to support the `npm test` command for running an end-to-end 288 | test on Grunt Drupal Tasks functionality similar to the Travis CI script 289 | 290 | ### Upgrade Notes 291 | 292 | - Update your project's package.json to require at least v0.5.1 of Grunt Drupal 293 | Tasks by using the `~0.5.1` version field value. 294 | 295 | ## v0.5.0 [2015/01/05] 296 | 297 | - Moving main Grunt Drupal Tasks library code from Gruntfile.js to bootstrap.js 298 | - Adding integration with OS notification features with grunt-notify 299 | - Adding documentation for use with a continuous integration system 300 | - Wrapping `behat` task in a `test` alias and moving Behat-related files into 301 | - top-level `test/` directory to allow for multiple testing methods 302 | - Adding Zombie.js and Behat support for JavaScript testing 303 | - Replacing grunt-parallel with grunt-concurrent 304 | - Many dependency updates 305 | - Other minor improvements 306 | 307 | ### Upgrade Notes 308 | 309 | - Change your project's Gruntfile.js to include grunt-drupal-tasks using 310 | `require('grunt-drupal-tasks')(grunt);` instead of 311 | `require('grunt-drupal-tasks/Gruntfile')(grunt);` 312 | 313 | - By default, Behat expects test features and the behat.yml inside a top-level 314 | `test/` directory. You can either update your project structure by moving 315 | behat.yml and the features directory under `test/`, or you may continue to use 316 | the old structure by providing a `config` and `src` option for any configured 317 | sites. For example: 318 | 319 | ```json 320 | "behat": { 321 | "default": { 322 | "config": "./behat.yml", 323 | "src": "./features/**/*.feature" 324 | } 325 | } 326 | ``` 327 | 328 | - Custom tasks that execute steps in parallel should be updated to use the 329 | grunt-concurrent plugin. See grunt-drupal-tasks/tasks/behat.js for an example. 330 | 331 | - See the example composer.json for updated dependency versions, which you can 332 | manually apply to your project. 333 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you would like to contribute to this project, here are a few considerations. 4 | 5 | ## Change Guidance 6 | 7 | ### Adding a New Task 8 | 9 | Adding a new task should only be done if a majority of projects should use that 10 | functionality. Really cool tasks that seem useful for less than 80% of Drupal 11 | projects using this system should be a separate project. Different projects can 12 | still follow the philosophy of Grunt Drupal Tasks and will happily be linked 13 | if we think they have high value. 14 | 15 | A task should be defined in a file under the `tasks/` directory. We prefer to 16 | see `taskname.js` as the name, unless there is a group of closely related tasks, 17 | such as the existing `theme.js`. 18 | 19 | ### Modifying the Default Build Process 20 | 21 | If you want to propose a change to the default build process, this change should 22 | be: 23 | 24 | - Universally relevant to all projects using the tasks. 25 | 26 | - If unconditionally added to the build, based on documented, required 27 | configuration. 28 | 29 | - If conditionally added to the build, make sure that condition checks that the 30 | task is properly configured. Look at `bootstrap.js` for examples of 31 | conditional steps. 32 | 33 | ### Things to Consider 34 | 35 | - There can be multiple modules, themes, profiles, libraries, and sites. New 36 | tasks should take that into account. 37 | 38 | - The tasks in the main build process should provide command-line results for 39 | ease of developer troubleshooting. 40 | 41 | - Changes to the directory structure or subtractions/renames in the supported 42 | Grunt configuration are considered backwards compatibility breaks. 43 | 44 | ## Testing & Validation 45 | 46 | Run `npm test` to confirm your changes will not break the tests. It is okay if you 47 | expect the tests to pass to rely on Travis to fail your build, but if it does fail 48 | please avoid using Travis to explore passing solutions. 49 | 50 | ## Using Work-in-Progress on a Live Project 51 | 52 | 1. Create your project environment with [generator-gadget](https://github.com/phase2/generator-gadget) 53 | 2. Temporarily remove grunt-drupal-tasks from package.json to avoid unnecessarily installing it. 54 | 3. Run `npm install` for your project if you haven't yet and any npm dependencies remain. 55 | 4. Navigate to your node_modules directory, and create a symlink to your working directory: `ln -s ~/Projects/grunt-drupal-tasks .` 56 | 57 | ## Docker-based Development 58 | 59 | * **Running Tests**: `docker-compose run --rm test` 60 | * **Live Project**: Replace step 1 with [generator-outrigger-drupal](https://github.com/phase2/generator-outrigger-drupal), 61 | preferably via `rig project create`. Replace step 4 by one of the options below: 62 | 63 | ### Copy/Move Your Working Repo 64 | 65 | You may simply move your cloned grunt-drupal-tasks repo inside the node_modules 66 | directory of the project so it is synced into the container. 67 | 68 | ### Temporary Volume Mount 69 | 70 | In all containers which need access to grunt-drupal-tasks, add a volume mount 71 | to override the grunt-drupal-tasks directory structure with your working copy. 72 | In docker-compose.yml terms, that might look like the following: 73 | 74 | ```yaml 75 | - ~/Projects/grunt-drupal-tasks:/var/www/node_modules/grunt-drupal-tasks:ro 76 | ``` 77 | 78 | This uses the read-only flag to ensure any build actions do not overwrite your 79 | working code. 80 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Phase2 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grunt Drupal Tasks 2 | 3 | > A Grunt plugin to automate Drupal 7 and Drupal 8 build and testing tasks. 4 | 5 | [![npm version](https://badge.fury.io/js/grunt-drupal-tasks.svg)](https://www.npmjs.com/package/grunt-drupal-tasks) 6 | [![Travis CI status](https://travis-ci.org/phase2/grunt-drupal-tasks.svg?branch=master)](https://travis-ci.org/phase2/grunt-drupal-tasks) 7 | [![Dependency Status](https://david-dm.org/phase2/grunt-drupal-tasks.svg)](https://david-dm.org/phase2/grunt-drupal-tasks) 8 | [![Greenkeeper badge](https://badges.greenkeeper.io/phase2/grunt-drupal-tasks.svg)](https://greenkeeper.io/) 9 | 10 | ## Features 11 | 12 | This project is built on the tools of the Grunt community to provide scripted 13 | automation of a number of PHP & Drupal tasks. Here are a few examples of what it 14 | provides: 15 | 16 | * Configurable code structure that defaults to a clean development practice. 17 | * Composer [build workflow](docs/10_BUILD.md) 18 | (or Drush make-based build workflow for Drupal 7.x). 19 | * Optional use [Code Quality & Static Analysis](docs/20_QUALITY.md), 20 | and [Frontend tooling](docs/30_FRONTEND.md) 21 | to extend the build process. 22 | * [Behat and SimpleTest Testing](docs/40_TESTING.md) 23 | * [Deployment packaging](docs/60_PACKAGE.md) 24 | * [Git Hook management](docs/70_GIT_INTEGRATION.md) 25 | * Desktop Notifications 26 | * Local Development Friendly 27 | * [CI](docs/80_CI.md) Friendly 28 | 29 | We are continuously working to improve this toolchain, adding functionality that 30 | we see as common to our _continuous integration_ and everyday development 31 | practices. 32 | 33 | ## Requirements 34 | 35 | For requirements, installation, use, and customization instructions, see the [documentation](https://phase2.github.io/grunt-drupal-tasks). 36 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Releasing New Versions of Grunt Drupal Tasks 2 | 3 | The following documents the standard process to release a new version of Grunt 4 | Drupal Tasks: 5 | 6 | - Update the `CHANGELOG.md` file. 7 | 8 | Use `git log v0.9.2...HEAD --oneline` to get a summary of commit messages 9 | since the latest release. 10 | 11 | Summarize changes and note any modifications needed to upgrade existing 12 | instances. 13 | 14 | - Update the version in `package.json`. 15 | 16 | Decide whether the next release is a major, minor, or patch level according 17 | to the semver specification. 18 | 19 | If updating to a new major or minor version, then also update the version of 20 | Grunt Drupal Tasks required in `example/package.json`. 21 | 22 | - Update `npm-shrinkwrap.json`. 23 | 24 | This is most important when dependencies have been updated, but since this 25 | file contains the version of Grunt Drupal Tasks, it should be updated as 26 | part of the release process. 27 | 28 | First, delete `npm-shrinkwrap.json` and `node_modules`. Then, run: 29 | `npm i --prod && npm shrinkwrap`. 30 | 31 | Installing with the `--prod` option excludes dev dependencies, which avoids 32 | an issue with npm v2 where modules that are dev dependencies are not 33 | included in the shrinkwrap file. 34 | 35 | - Tag a new version. 36 | 37 | Once all updates are committed, tag a new version using `git tag -a v0.9.3 38 | -m "Version 0.9.3."` and push the tag. 39 | 40 | - Run `npm publish` to publish the release to npm. 41 | 42 | For pre-releases (alpha, non-stable), specify a release tag by running: 43 | `npm publish --tag alpha` 44 | -------------------------------------------------------------------------------- /bootstrap.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | // Initialize global configuration variables. 3 | var config = grunt.file.readJSON('Gruntconfig.json'); 4 | if (grunt.config.getRaw() === undefined) { 5 | grunt.initConfig({ 6 | config: config 7 | }); 8 | } else { 9 | grunt.config.set('config', config); 10 | } 11 | 12 | var GDT = require('./lib/init')(grunt); 13 | GDT.init(); 14 | 15 | // Wrap Grunt's loadNpmTasks() function to allow loading Grunt task modules 16 | // that are dependencies of Grunt Drupal Tasks. 17 | grunt._loadNpmTasks = grunt.loadNpmTasks; 18 | grunt.loadNpmTasks = function(mod) { 19 | var internalMod = grunt.file.exists(__dirname, 'node_modules', mod); 20 | var pathOrig; 21 | if (internalMod) { 22 | pathOrig = process.cwd(); 23 | process.chdir(__dirname); 24 | } 25 | grunt._loadNpmTasks(mod); 26 | if (internalMod) { 27 | process.chdir(pathOrig); 28 | } 29 | }; 30 | 31 | // Load all tasks from grunt-drupal-tasks. 32 | var path = require('path'); 33 | grunt.loadTasks(path.join(__dirname, '/tasks')); 34 | 35 | // Define the default task to fully build and configure the project. 36 | var tasksDefault = []; 37 | 38 | // If the "--no-validate" option is given, skip adding "validate" to default 39 | // tasks array. 40 | if (!grunt.option('no-validate')) { 41 | tasksDefault.push('validate'); 42 | } 43 | 44 | // Process .make files if configured. 45 | if (grunt.config.get('config.srcPaths.make')) { 46 | // If build/html exists, but is empty, skip the newer check. 47 | // This facilitates situations where the build/html is generated as a mounted 48 | // directory point with a newer timestamp than the Drush Makefiles. 49 | // 50 | // We do not use the grunt-newer .cache with drushmake so skipping newer for 51 | // any one run does not impact later behavior. 52 | if (grunt.file.exists(grunt.config.get('config.buildPaths.html') + '/index.php')) { 53 | tasksDefault.push('newer:drushmake:default'); 54 | } else { 55 | tasksDefault.push('drushmake:default'); 56 | } 57 | } 58 | 59 | // Wire up the generated docroot to our custom code. 60 | tasksDefault.push('scaffold'); 61 | 62 | if (grunt.file.exists('./composer.lock') && grunt.config.get(['composer', 'install'])) { 63 | if (grunt.config.get(['composer', 'drupal-scaffold'])) { 64 | // Manually run `composer drupal-scaffold` since this is only automatically run on update. 65 | tasksDefault.unshift('composer:drupal-scaffold'); 66 | } 67 | // Run `composer install` if there is already a lock file. Updates should be explicit once this file exists. 68 | tasksDefault.unshift('composer:install'); 69 | } else if (grunt.config.get(['composer', 'update'])) { 70 | // Run `composer update` if no lock file exists. This forces `composer drupal-scaffold` to run. 71 | tasksDefault.unshift('composer:update'); 72 | } 73 | 74 | if (grunt.task.exists('compile-theme')) { 75 | tasksDefault.push('compile-theme'); 76 | } 77 | 78 | grunt.registerTask('default', tasksDefault); 79 | 80 | // If the "--timer" option is given, enable time-grunt to show how long each 81 | // task takes. 82 | if (grunt.option('timer')) { 83 | require('time-grunt')(grunt); 84 | } 85 | 86 | require('grunt-log-headers')(grunt); 87 | }; 88 | -------------------------------------------------------------------------------- /build_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Script to build mkdocs site if mkdocs is installed locally 3 | # 4 | cd docs 5 | mkdocs build --clean --site-dir ../../phase2.github.io/grunt-drupal-tasks -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | x-custom: 4 | environment: &environment 5 | COMPOSER_CACHE_DIR: /root/.cache/composer 6 | NODE_VERSION: 6 7 | NPM_CONFIG_CACHE: /root/.cache/npm 8 | GDT_QUIET: 1 9 | volumes: &volumes 10 | - .:/code 11 | # Persist the cache directories associated with various tools. 12 | # The first volume mount covers: npm, composer, bower, fontconfig, & yarn 13 | - /data/gdt/cache:/root/.cache 14 | - /data/gdt/cache/drush:/root/.drush/cache 15 | - /data/gdt/cache/behat_gherkin:/tmp/behat_gherkin_cache 16 | 17 | services: 18 | cli: 19 | image: outrigger/build:php56 20 | container_name: gdt 21 | environment: *environment 22 | entrypoint: ["/init"] 23 | command: ["bash"] 24 | network_mode: bridge 25 | volumes: *volumes 26 | working_dir: /code 27 | 28 | test: 29 | image: outrigger/build:php56 30 | container_name: gdt 31 | environment: *environment 32 | entrypoint: ["/init", "npm", "test"] 33 | network_mode: bridge 34 | volumes: *volumes 35 | working_dir: /code 36 | -------------------------------------------------------------------------------- /docs/05_SETUP.md: -------------------------------------------------------------------------------- 1 | # Setup Your Environment & Project 2 | 3 | > Get Node, Grunt, & Composer squared away, then use Gadget to spin up new projects. 4 | 5 | ## Requirements 6 | 7 | Note that Grunt Drupal Tasks is intended to be compatible with Node.js v4 and 8 | later and PHP v5.5 and later. See the version 0.x series for compatibility with 9 | earlier versions of Node.js and PHP. 10 | 11 | ### Node.js 12 | 13 | Install _Node.js v4.2.x or later_ using [nvm](https://github.com/creationix/nvm), 14 | a [package manager](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager), 15 | or a [standalone installer](http://nodejs.org/download/). 16 | 17 | ### Grunt 18 | 19 | Once _Node.js_ is installed, use _npm_ to install 20 | [grunt-cli](https://github.com/gruntjs/grunt-cli), the Grunt command line interface, 21 | by running: 22 | 23 | ``` 24 | npm install -g grunt-cli 25 | ``` 26 | 27 | ### Composer 28 | 29 | Some optional features, used in the included example and end-to-end test suite 30 | require additional tools, like [Composer](https://getcomposer.org/download/). 31 | 32 | The example template bundles a **composer.json** file that installs supporting 33 | tools, like Behat with the Drupal Extension, PHPMD (PHP Mess Detector), and the 34 | PHP Code Sniffer, among others. By default, the build runs "composer install" to 35 | install these tools. You can modify composer.json to include other dependencies 36 | for your project. 37 | 38 | ## Start using Gadget 39 | 40 | The easiest way to start a new Drupal project with Grunt Drupal Tasks is to use 41 | **[Gadget](https://github.com/phase2/generator-gadget)**, a tool which offers an 42 | interactive, text-based approach to building an initial project template. 43 | 44 | Once you have Gadget installed, start a new Drupal project by running 45 | `yo gadget` in an empty directory and answer it's prompts. 46 | 47 | ``` 48 | $> yo gadget 49 | 50 | ? Machine-name of your project? gadget-test 51 | ? One-line project description? My new project. 52 | ``` 53 | 54 | If you wish to customize this structure, you can [override the default practices 55 | with your project configuration](10_BUILD.md), 56 | but will need to manually adjusted generated files to match. 57 | 58 | For an example of the default behavior, take a look at the 59 | [example project](https://github.com/phase2/grunt-drupal-tasks/tree/master/example). 60 | -------------------------------------------------------------------------------- /docs/10_BUILD.md: -------------------------------------------------------------------------------- 1 | # Code Structure & Build Process 2 | 3 | > What's your codebase and how is it used in a working Drupal site? 4 | 5 | Continuing discussing of code structure begun in the 6 | [Setup section](05_SETUP.md), Grunt Drupal Tasks is a build system driven by a 7 | known "scaffolding" configuration to assemble a working Drupal site from custom 8 | code and dependency manifests. 9 | 10 | ## The Build 11 | 12 | ``` 13 | $> grunt 14 | ``` 15 | 16 | The build process is managed by running `grunt`. If there are no errors, this 17 | will result in assembling all the code and assets needed to run your Drupal site 18 | in **build/html**. The full process includes a number of steps, which assumes 19 | the checkout of the codebase has already run `npm install` to retrieve all Node 20 | dependencies. 21 | 22 | 1. **Composer Install:** Retrieve all development dependencies in the project 23 | composer.json. For Drupal 8.x, this also includes the management of Drupal 24 | dependencies via Composer. 25 | 2. **Validate:** Run static analysis and code quality checks against custom code. 26 | 3. **Drush Make:** For Drupal 7.x projects, if the Drush makefiles are newer 27 | than the built codebase, Drush Make will run to assemble upstream 28 | dependencies. For Drupal 8.x projects, Composer is used instead of Drush Make. 29 | 4. **Scaffold:** Copy and symlink custom code into the assembled codebase. 30 | 5. **Theme Triggers:** Run any theme triggers to validate code or build assets 31 | on a per-theme basis. 32 | 33 | ## The Scaffold 34 | 35 | The scaffold, which can be thought of as the root directory and the conventions 36 | around having discrete `src/`, `build/`, `test/`, and other folders, focuses on 37 | providing clear context to developers for the different pieces of the codebase. 38 | 39 | Operational code and build artifacts are contained in the `build/` directory, 40 | the other parts of the repository are intuitively discovered for easy editing. 41 | 42 | Examples in the documentation and the default behavior of Grunt Drupal Tasks, 43 | use a standardized convention that is significantly configurable for individual 44 | project needs. 45 | 46 | ### Structure of the Code Repository 47 | 48 | On looking at the code repository, there is no sign of Drupal core. That is 49 | because Drupal core, contributed modules, and any other upstream dependency or 50 | generated code is not part of the code repository. This structure organizes 51 | custom code, configuration, and manifests of dependencies which are downloaded 52 | as-needed by the *build process*. 53 | 54 | ``` 55 | src/ 56 | ↳ libraries/ 57 | ↳ modules/ 58 | ↳ profiles/ 59 | ↳ sites/ 60 | ↳ static/ 61 | ↳ themes/ 62 | ↳ project.make 63 | test/ 64 | ↳ behat.yml 65 | ↳ features/ 66 | composer.json 67 | Gruntconfig.json 68 | Gruntfile.js 69 | package.json 70 | phpmd.xml 71 | ``` 72 | 73 | ### Setting up Source Code 74 | 75 | - Place custom modules in **src/modules/**. When the project is built, the 76 | contents of src/modules/ become part of the Drupal's sites/all/modules/ 77 | directory (via a symlink from sites/all/modules/custom/ to src/modules/). 78 | 79 | - Place custom installation profiles in **src/profiles/**. When the project is 80 | built, the contents of src/profiles become part of Drupal's sites/all/modules/ 81 | directory (via symlink from profiles/ to each profile in src/profiles/). 82 | 83 | - For Drupal 8.x projects, customize the `composer.json` file to add module 84 | dependencies. Patches can also be specified. 85 | 86 | - For Drupal 7.x projects, customize the Drush make file that is used at the 87 | start of the build process. The example includes `project.make` but this 88 | file can be replaced or renamed with a setting change in Gruntconfig.json 89 | (see below). 90 | 91 | - Include any sites directories (like "default"), optionally with settings.php 92 | or other files, and if needed a multi-site sites.php in **src/sites/**. (The 93 | contents of src/sites/ are copied into sites/.) 94 | 95 | - Include any static files that should be copied into the Drupal docroot on 96 | build in **src/static/**. This allows for overriding files like `.htaccess`. 97 | 98 | - Place custom themes in **src/themes/**. When the project is built, the 99 | contents of src/themes/ become part of the Drupal's sites/all/themes/ 100 | directory (via a symlink from sites/all/themes/custom/ to src/themes/). 101 | 102 | ### Build Directory Structure 103 | 104 | ``` 105 | build/ 106 | ↳ cache 107 | ↳ html 108 | ↳ packages 109 | ↳ reports 110 | ↳ temp 111 | node_modules/ 112 | vendor/ 113 | ``` 114 | 115 | ## Customizing the Build 116 | 117 | Grunt Drupal Tasks is designed to provide sensible default behaviors for Drupal 118 | projects, but allow these assumptions to be overridden. 119 | 120 | **Gruntconfig.json** is a settings file that allows certain paths and optional 121 | features to be configured on a project-specific basis. 122 | 123 | ### Configuration Options 124 | 125 | The core set of configuration options for Gruntconfig.json specify the basic 126 | build parameters: the code to build, and the output directories to use. 127 | 128 | This is the minimum set of configuration options: 129 | 130 | ``` 131 | { 132 | "srcPaths": { 133 | "drupal": "src", 134 | } 135 | } 136 | ``` 137 | 138 | **srcPaths.drupal**: The directory that contains all project-specific Drupal 139 | code and configuration. Grunt Drupal Tasks assumes this directory has the 140 | following structure: 141 | 142 | ``` 143 | src/ 144 | modules/ 145 | profiles/ 146 | sites/ 147 | static/ 148 | themes/ 149 | ``` 150 | 151 | The following build output paths are optional to specify in the project's 152 | `Gruntconfig.json` file. 153 | 154 | **srcPaths.make**: The Drush make file used to assemble the Drupal project. 155 | This is only used for Drupal 7.x projects. Example is `src/project.make`. 156 | 157 | **buildPaths.build**: The directory that should be used for miscellaneous build 158 | artifacts. This can be the parent directory of the following build paths. 159 | 160 | **buildPaths.html**: The directory that should be used for the Drupal docroot 161 | build destination generated by the default build operation. 162 | 163 | **buildPaths.package**: The directory that should be used to store packages 164 | generated on demand by the *package* operation. 165 | 166 | **buildPaths.reports**: The directory that should be used for output from the 167 | analysis and validation tools. 168 | 169 | **buildPaths.temp**: The directory that should be used for temporary build 170 | artifacts. This can be a subdirectory of `buildPaths.html`. 171 | 172 | ### Developer Modification 173 | 174 | There are two ways to change the default build process (which is run when simply 175 | typing `grunt` into the command-line.) 176 | 177 | ### Fork the Build Process 178 | Add the following code snippet after the Grunt Drupal Tasks `bootstrap.js` file 179 | is loaded in your `gruntfile.js`. 180 | 181 | ``` 182 | grunt.registerTask('default', [ 183 | 'alternate', 184 | 'list', 185 | 'of', 186 | 'tasks' 187 | ]); 188 | ``` 189 | 190 | You may use any of the tasks available to Grunt when doing this, though 191 | significant changes to the build process may make it difficult to get support. 192 | 193 | ### Prepend or Append New Tasks 194 | 195 | If you want to avoid forking the build process, but have some additional tasks 196 | that need to be done, you can use this trick to add new tasks to the build. 197 | 198 | This trick can be done multiple times after the initial load of `bootstrap.js`, 199 | which allows for modular customizations. 200 | 201 | ```js 202 | grunt.task.renameTask('default', 'default-pre-custom'); 203 | grunt.registerTask('default', ['shell:custom', 'default-pre-custom']); 204 | ``` 205 | 206 | ### Hacking Drush Make 207 | 208 | ### Drush Settings 209 | 210 | This is an example of the settings for Drush tasks: 211 | 212 | ``` 213 | { 214 | "drush": { 215 | "path": "/usr/bin/drush", 216 | "make": { 217 | "args": ["--force-complete", "--working-copy"] 218 | } 219 | } 220 | } 221 | ``` 222 | 223 | **drush.path**: The path to the Drush executable that should be used for all 224 | Drush operations. If none is specified, the Drush executable found in the 225 | default PATH will be used. 226 | 227 | **drush.make.args**: An array of arguments to pass to Drush for the make 228 | operation. 229 | -------------------------------------------------------------------------------- /docs/20_QUALITY.md: -------------------------------------------------------------------------------- 1 | # Quality 2 | 3 | > Maintaining code quality and managing static analysis. 4 | 5 | Grunt Drupal Tasks makes a point of easing management of good coding practices 6 | by building code quality checks into the default build process, and applying 7 | several best-of-breed tools to evaluate code changes. 8 | 9 | * The `grunt validate` task runs the same operation the build process executes. 10 | * The `grunt analyze` task runs the same, along with the PHP Mess Detector, and 11 | * outputs all results in checkstyle XML format for consumption by Jenkins or 12 | * other reporting tools. 13 | 14 | If you are interested in applying validation against files changed in your git 15 | repository, read more on 16 | [how Grunt Drupal Tasks can manage your Git Hooks](70_GIT_INTEGRATION.md). 17 | 18 | ### Validate Settings 19 | 20 | The `validate` task runs eslint, phpcs, phplint against all custom code. If a 21 | theme has it's own `validate` operation configured for grunt, it will skip 22 | eslint. 23 | 24 | **validate.ignoreError**: Set to `true` to prevent failing the build if code 25 | quality validation fails (which also prevents other tasks from executing). 26 | 27 | #### eslint 28 | 29 | Example of eslint configuration: 30 | 31 | ``` 32 | "eslint": { 33 | "dir": [ 34 | '<%= config.srcPaths.drupal %>/themes/*/js/**/*.js', 35 | '<%= config.srcPaths.drupal %>/{modules,profiles,libraries}/**/*.js' 36 | ] 37 | }, 38 | ``` 39 | **eslint**: To enable eslint, set to `true` to use default options or an object 40 | with the following optional settings. 41 | 42 | **eslint.configFile**: The path to the eslint config file to use. If no value 43 | is specified, then `.eslintrc` in the project root is used. 44 | 45 | **eslint.dir**: An array of glob patterns to include/exclude files for 46 | review by eslint. The following is used by default: 47 | 48 | #### PHPLint 49 | 50 | **phplint.dir**: An array of globbing patterns which phplint should include or 51 | exclude from PHP code syntax validation. 52 | 53 | ``` 54 | "phplint": { 55 | "dir": [ 56 | '<%= config.srcPaths.drupal %>/sites/**/*.{php,inc}', 57 | '<%= config.srcPaths.drupal %>/themes/*/template.php', 58 | '<%= config.srcPaths.drupal %>/themes/*/templates/**/*.php', 59 | '<%= config.srcPaths.drupal %>/themes/*/includes/**/*.{inc,php}', 60 | '<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/*.{php,module,inc,install,profile}', 61 | '!<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/*.{box,pages_default,views_default,panelizer,strongarm}.inc', 62 | '!<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/*.features.*inc', 63 | '!<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/vendor/**' 64 | ] 65 | } 66 | ``` 67 | 68 | #### PHPCS 69 | 70 | **phpcs.path**: The path to the PHPCS executable. 71 | 72 | **phpcs.standard**: The PHPCS coding standard to use. The example composer.json 73 | installs the Drupal Coder's standard, the path of which is shown above. 74 | 75 | **phpcs.dir**: An array of globbing pattern where phpcs should search for files. 76 | This can be used to replace the defaults supplied by grunt-drupal-tasks. 77 | 78 | This example placed in the Gruntconfig.json file ignores directories named 79 | "pattern-lab" and a "bower_components" in addition to the defaults that come 80 | with grunt-drupal-tasks: 81 | 82 | ``` 83 | { 84 | "phpcs": { 85 | "path": "vendor/bin/phpcs", 86 | "dir": [ 87 | '<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/*.{php,module,inc,install,profile}', 88 | '!<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/*.{box,pages_default,views_default,panelizer,strongarm}.inc', 89 | '!<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/*.features.*inc', 90 | '!<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/vendor/**', 91 | '!<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/*.tpl.php' 92 | ] 93 | } 94 | } 95 | ``` 96 | 97 | > If there is no `phpcs` key in the configuration, the system will assume you 98 | are not using PHPCS and will suppress it from the system. 99 | 100 | > The formal coding standards for PHPTemplate files (ending in `.tpl.php`) 101 | seem to be rarely followed or enforced. By default we skip PHPCS processing 102 | of these files. 103 | 104 | ### Analyze Settings 105 | 106 | In addition to all the behaviors under the Validate section, Analyze adds 107 | additional reporting that does not make sense as part of the build process, 108 | such as tools that are too time-consuming to run on a regular basis. 109 | 110 | #### PHP Mess Detector (PHPMD) 111 | 112 | This tool is less focused on codified practice, and more on the architectural 113 | implications of code complexity, though there is some overlap in the rulesets. 114 | 115 | ``` 116 | { 117 | "phpmd": { 118 | "configPath": "phpmd.xml", 119 | "path": "bin/phpmd" 120 | "excludePaths": [ 121 | "bower_components", 122 | "node_modules" 123 | ] 124 | } 125 | } 126 | ``` 127 | 128 | **phpmd.configPath**: The configuration file to use with PHPMD. Defaults to 129 | *phpmd.xml*. 130 | 131 | **phpmd.excludePaths**: An array of string path patterns that should be skipped 132 | by PHPMD. 133 | 134 | **phpmd.path**: The path to the PHPMD executable. 135 | 136 | > If there is no `phpmd` key in the configuration, the system will assume you 137 | are not using PHPMD and will suppress it from the system. 138 | -------------------------------------------------------------------------------- /docs/30_FRONTEND.md: -------------------------------------------------------------------------------- 1 | # Frontend & Theme Operations 2 | 3 | > Facilitate integration with Drupal themes as owners of their own script-driven 4 | tooling. 5 | 6 | ## Making Grunt Theme-Aware 7 | 8 | For each theme Grunt should be aware of, it will need an entry in the 9 | `Gruntconfig.json` under the themes property. This might look like the 10 | following: 11 | 12 | ``` 13 | { 14 | "themes": { 15 | "spartan": { 16 | "path": "<%= config.srcPaths.drupal %>/themes/spartan" 17 | } 18 | } 19 | } 20 | ``` 21 | 22 | **themes**: Defines each custom Drupal theme and enables features, like 23 | integration with theme scripts. 24 | 25 | **themes.\.path**: Specify the path to the theme. Defaults to 26 | `<%= config.srcPaths.drupal %>/themes/\`. 27 | 28 | ## Theme Scripts 29 | 30 | Many themes come with their own tooling, possibly grunt-based as well. In those 31 | cases you can configure Grunt-Drupal-Tasks with the ability to trigger those 32 | tasks, and by magic naming attach some theme-specific functions to the more 33 | general project tasks. 34 | 35 | ``` 36 | "legionaire": { 37 | "path": "<%= config.srcPaths.drupal %>/themes/legionaire", 38 | "scripts": { 39 | "compile-theme": "grunt compile" 40 | "validate": "grunt eslint", 41 | "analyze": "echo 'No theme-specific analysis tools here!'", 42 | "ls": "ls -lR" 43 | } 44 | } 45 | ``` 46 | 47 | `grunt themes` may be run to list all themes setup in your project 48 | Gruntconfig.json. `grunt themes:\` will list all the options in the 49 | theme's scripts config as a means of providing usage documentation. 50 | `grunt themes:\:\` will run the script keyed by `\` from 51 | the theme's directory. 52 | 53 | Three of the key names are magic: 'compile-theme', 'validate', and 'analyze'. 54 | When one of those three is specified, Grunt Drupal Tasks will automatically 55 | run the command as part of the actions it takes for `grunt compile-theme`, 56 | `grunt validate`, and `grunt analyze`. 57 | 58 | Note that Theme Scripts support the `pre-` and `post-` operations explained in 59 | Project Operations. This means if you configure a `compile-theme` task in 60 | `Gruntconfig.json`, you may also configure a `pre-compile-theme` and a 61 | `post-compile-theme`. 62 | -------------------------------------------------------------------------------- /docs/40_OPERATIONS.md: -------------------------------------------------------------------------------- 1 | # Project Operations 2 | 3 | > Operations other than testing involved in the creation, hosting, and 4 | management of a running Drupal site. 5 | 6 | ## Invoking Ad Hoc Operations 7 | 8 | Some of the most common use cases for any Drupal site are the basic operations 9 | of installation, running database updates, and clearing the cache. These are not 10 | difficult operations, but many developers appreciate the common shorthand a 11 | little grunt magic can provide. 12 | 13 | To keep this flexible the new Operations system offers nothing out of the box, 14 | but facilitates creating lightweight commands or callouts to the shell for more 15 | advanced scripts you want easily run on the project by the entire team. 16 | 17 | This should remind you of the [Frontend & Theming Operations](30_FRONTEND.md). 18 | 19 | ``` 20 | { 21 | "scripts": { 22 | "update": "drush <%= config.alias %> updatedb -yv && drush <%= config.alias %> features-revert-all -yv", 23 | "reset": "drush <%= config.alias %> registry-rebuild && drush <%= config.alias %> cc all" 24 | } 25 | } 26 | ``` 27 | 28 | Here you see two commands defined inside the top-level `scripts` configuration. 29 | 30 | * `grunt update` demonstrates running database updates and reverting all 31 | [Features](http://www.drupal.org/project/features). 32 | * `grunt reset` demonstrates a thorough reset of the file registry and Drupal 33 | caches. 34 | 35 | The Alias and Profile is specified per the rules for **Magic Configuration**. 36 | 37 | **alias**: The Drush Site Alias. This is computed as a helper for use in 38 | project operations, but is not yet applied as part of other tasks. Defaults to 39 | `@`. The environment variable to override is `GDT_SITE_ALIAS`. 40 | 41 | ### Pre and Post Operations 42 | 43 | Similar to `npm run`, the operations defined in your Gruntconfig will support 44 | `pre-` and `post-` variants. If the main script exists the pre-script will be 45 | triggered first and the post-script will be triggered after. This allows you to 46 | easily extend any project operation with separate grunt, bash, or other tasks. 47 | 48 | For example, if you define a new script `update`, you may also configure 49 | `pre-update` and `post-update`. 50 | 51 | ## Install settings 52 | 53 | The `install` task will either install Drupal via the configured `profile`, or 54 | if a path to a database is configured, load this database: 55 | 56 | To specify a profile other than the `standard` profile: 57 | 58 | ```json 59 | { 60 | "project": { 61 | "profile": "openatrium" 62 | } 63 | } 64 | ``` 65 | 66 | to specify a database to load: 67 | 68 | ```json 69 | { 70 | "project": { 71 | "db": "path/to/project.sql.gz" 72 | } 73 | } 74 | ``` 75 | 76 | **project.profile** The Drupal installation profile, this will be used to 77 | configure the behavior of the `grunt install` and `grunt serve` tasks. Defaults 78 | to `standard`. The environment variable is `GDT_INSTALL_PROFILE` 79 | 80 | **project.db** If specified, this database will be loaded instead of running a 81 | site installation. If the file referenced is not present `grunt install` will 82 | fall back to a standard `drush site-install`. 83 | 84 | ### Pre and Post Installation 85 | 86 | The install task has a unique implementation of support for the pre and post 87 | scripts otherwise made available for themes and project operations. This allows 88 | you to implement simple directives or scripts to prepare for installation or 89 | perform non-standardized operations after the main installation routine. 90 | 91 | ## Serve Settings 92 | 93 | The Serve task allows you to run Drupal using PHP's built-in webserver. This 94 | facilitates quick demos and low-overhead development for projects with simple 95 | infrastructure requirements. When using this task it will take over the 96 | terminal window. 97 | 98 | `grunt serve` will not install the Drupal site. Run with `grunt serve:demo` to 99 | skip starting up watch tasks. 100 | 101 | ```json 102 | { 103 | "project": { 104 | "profile": 'project_profile_name' 105 | }, 106 | "serve": { 107 | "port": 9043, 108 | "concurrent": [ 109 | "watch-theme" 110 | ] 111 | } 112 | } 113 | ``` 114 | 115 | **serve.port**: The port number to bind for the webserver. Only one service may 116 | occupy a port on a machine, so a project-specific port may be worthwhile. 117 | Defaults to `8080`. 118 | 119 | **serve.concurrent**: An array of grunt tasks to be run concurrently to the 120 | server. When run with `serve:demo` or `serve:test` these tasks are not used. By 121 | default they include 'watch-test' and 'watch-theme'. If you use this 122 | configuration to add tasks be sure to include these as they will be suppressed 123 | by any configuration. 124 | -------------------------------------------------------------------------------- /docs/40_TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | > Unit, Functional, Integration, and End-to-End test running. 4 | 5 | ## Configuration 6 | 7 | **domain**: The domain of the site without protocol. It may also be a Base URL 8 | (with a path prefix) so long as the protocol is excluded. This domain is used 9 | to construct complete URLs for testing purposes. This may be overridden by the 10 | `GDT_DOMAIN` environment variable, and if neither is set will fall back to the 11 | environment's hostname. If a domain is specified, we recommend it is lined up 12 | with any standard set for local development, as development processes should 13 | not default to hitting a production or staging environment. 14 | 15 | **siteUrls**: (Optional) A map of Drupal subsite names to the URLs by which 16 | each can be accessed for end-to-end testing by tools such as Behat. For clarity 17 | please align the site names (keys) with any Drush Site Aliases. If no siteUrls 18 | are specified it will default to `http://<%= config.domain %>`. The environment 19 | variable override is `GDT_SITEURLS`. 20 | 21 | ``` 22 | "siteUrls": { 23 | "default": "http://<%= config.domain %>", 24 | "subsite": "http://sub.<%= config.domain %>.local", 25 | "external": "http://example.com" 26 | }, 27 | ``` 28 | 29 | ### Behat Settings 30 | 31 | To support Behat tests, the example includes a basic **behat.yml** configuration 32 | file and a **features/** directory for test cases inside the general **test/** 33 | directory. 34 | 35 | This is an example of the settings for Behat tasks: 36 | 37 | ``` 38 | { 39 | "behat": { 40 | "flags": "--tags '~@javascript'", 41 | "path": "/usr/local/bin/behat", 42 | "subsite": { 43 | "src": "./test/features/subsite/*.feature", 44 | "debug": false 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | **behat.\**: A map of Drupal subsite names to a configuration object, 51 | which will extend the defaults passed to 52 | [grunt-parallel-behat](https://github.com/linusnorton/grunt-parallel-behat) 53 | 54 | **behat.flags**: A string with any command-line arguments and options that 55 | should be used while invoking Behat. These flags can be overridden by using the 56 | `--behat-flags` option when running `grunt`. Common use cases are to include or 57 | exclude tests according to tags or to use an alternative profile defined in 58 | `behat.yml`. 59 | 60 | **behat.path**: A string with the path to the Behat executable. By default, this 61 | is set to `vendor/bin/behat` to use the version of Behat installed by Composer. 62 | Only set this option if you need to use a specific version of Behat that is 63 | installed elsewhere. 64 | -------------------------------------------------------------------------------- /docs/60_PACKAGE.md: -------------------------------------------------------------------------------- 1 | # Deployment Packaging 2 | 3 | > Package up the operational codebase to use outside the development scaffolding. 4 | 5 | The `grunt package` task allows you to assemble and independently exported 6 | Drupal codebase. This is used to facilitate deployment of the minimal code 7 | needed to run Drupal in a formal environment such as Production. 8 | 9 | ## Default Packaging 10 | 11 | You can find the resulting package in `build/packages/package` by default as a 12 | standard directory, all symlinks from the grunt scaffolding dereferenced. If 13 | run with `grunt package:compress` it will also output 14 | `build/packages/package.tgz` as an easily stored archive. **Remember, this 15 | directory is wiped by `grunt clean` unless you configure your package directory 16 | to be outside the build directory.** 17 | 18 | This is an example of the settings for package tasks: 19 | 20 | ``` 21 | { 22 | "packages": { 23 | "srcFiles": ["!sites/*/files/**", "!xmlrpc.php", "!modules/php/*"], 24 | "projFiles": ["README*", "bin/**"] 25 | } 26 | } 27 | ``` 28 | 29 | ## Packaging Customization 30 | 31 | **packages.srcFiles**: An array of files or file patterns to include or exclude 32 | from the build output when building a package. The above excludes files within 33 | any `sites/*/files` directory, and Drupal's `xmlrpc.php` file and PHP Filter 34 | module. For more on this format, see: http://gruntjs.com/configuring-tasks#files 35 | 36 | **packages.projFiles**: An array of files or file patterns to include or exclude 37 | from the project directory when building a package. The above includes README 38 | files and files under `bin/` in the project's package. 39 | 40 | **packages.dest.docroot**: Specify where within the package directory the 41 | `srcFiles` should be placed. Defaults to the package root. For Acquia set this 42 | to `/docroot`. 43 | 44 | **packages.dest.vendor**: Specify where to place the composer.json and vendor 45 | directory. Defaults to the docroot. 46 | 47 | **packages.dest.devResources**: Specify where within the package directory the 48 | `projFiles` should be placed. Defaults to package root. 49 | 50 | **packages.name**: The default name of the package, used as the path within the 51 | packages directory. This can be overridden by calling grunt package with the 52 | `--name` parameter. 53 | 54 | ## Packaging for Acquia 55 | 56 | The `package` command has the flexibility to support many different use cases, 57 | include structure for PaaS services such as Acquia. This is currently handled 58 | by convention rather than specific coded support. 59 | 60 | The example configuration below for your Gruntconfig.json file structures an 61 | Acquia repository with support for custom hooks and scripts. 62 | 63 | ``` 64 | { 65 | "packages": { 66 | "srcFiles": ["!sites/*/files/**", "!xmlrpc.php", "!modules/php/*"], 67 | "projFiles": ["README*", "bin/**", "hooks/**"], 68 | "dest": { 69 | "docroot": "docroot" 70 | } 71 | } 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/70_ADVANCED_CONFIG.md: -------------------------------------------------------------------------------- 1 | # Advanced Configuration 2 | 3 | > Tips and tricks in customizing Grunt Drupal Tasks, contextually adjusting 4 | behaviors, and more. 5 | 6 | ## Desktop Notification Settings 7 | 8 | This is an example of the settings for the notify feature: 9 | 10 | ``` 11 | { 12 | "notify": { 13 | "threshold": 3 14 | } 15 | } 16 | ``` 17 | 18 | **notify.threshold**: Minimum number of seconds a task must execute for a 19 | notification to be triggered when the task ends. 20 | 21 | ## Magic Configuration 22 | 23 | The domain, project.profile, alias, and siteUrls configurations are all built 24 | using the same system that allows for maximum flexibility in project and 25 | environment configuration. Note that all configuration is subject to the Project 26 | Gruntfile override. 27 | 28 | In order of precedence it checks the following: 29 | 30 | ### Priority 1: Project Gruntfile.js 31 | 32 | The value of the configuration prefixed with `gdt.`. This allows a project 33 | Gruntfile to dynamically specify the value before bootstrapping Grunt Drupal 34 | Tasks. This is valuable because the code of grunt-drupal-tasks sets up many 35 | behaviors based on that configuration that would be difficult to replicate 36 | after the bootstrap. 37 | 38 | This can be done with any configuration you might place into your 39 | Gruntconfig.json, and is merged recursively as an override to whatever you place 40 | in that file. 41 | 42 | For example: 43 | 44 | * **alias**: `gdt.alias` 45 | * **domain**: `gdt.domain` 46 | * **profile**: `gdt.profile` 47 | * **siteUrls**: `gdt.siteUrls` 48 | 49 | Suppose your environment is set up with the following directory structure: 50 | 51 | > * project.example.com 52 | * project-repository 53 | * Gruntfile.js 54 | * Gruntconfig.json 55 | * src/ 56 | * ... 57 | * project2.example.net 58 | * ... 59 | 60 | Given this structure, you will not want to use environment variables because 61 | each of your projects might need different values in the host machine. However, 62 | a little logic in the project Gruntfile.js can allow you to dynamically derive 63 | your development domain or Drush site alias. 64 | 65 | For example, here is a Gruntfile that derives the site alias and domain 66 | based on the parent directory name of the project repository. 67 | 68 | ```js 69 | var path = require('path'); 70 | module.exports = function(grunt) { 71 | var name = path.basename(path.resolve(__dirname, '..')); 72 | // Set domain to "project.example.com". If nothing else sets the `alias` 73 | // it will default to @project.example.com. 74 | grunt.initConfig({ gdt: { domain: name }); 75 | // Load all plugins and tasks defined by the grunt-drupal-tasks package. 76 | require('grunt-drupal-tasks')(grunt); 77 | }; 78 | ``` 79 | 80 | ### Priority 2: Environment Variable 81 | 82 | Each magic configuration property can also be set via an Environment Variable. 83 | 84 | Environment Variables are extremely powerful, but only useful where the 85 | environment contains only a single project using Grunt Drupal Tasks. This is 86 | more effective for dedicated environments, such as official production and 87 | staging servers, or virtual and containerized environments. 88 | 89 | The specific environment variable name is covered as part of the property 90 | descriptions. 91 | 92 | ### Priority 3: Project Gruntconfig.json 93 | 94 | The configuration placed in Gruntconfig.json. This is good for straightforward 95 | defaults and is how all *non-magical* configuration works. 96 | 97 | If you want to include your task in one of the existing groups, copy the text 98 | exactly as seen in the output of the `grunt help` task. 99 | -------------------------------------------------------------------------------- /docs/70_GIT_INTEGRATION.md: -------------------------------------------------------------------------------- 1 | # Git Integrations 2 | 3 | > Tricks to better manage your project using it's Git version control. 4 | 5 | ## Adding Git Hooks 6 | 7 | The [Git Hook system](http://git-scm.com/docs/githooks) (not to be confused with 8 | the Drupal Hook system), triggers events on certain git interactions. The git 9 | **pre-commit** hook allows us to add a validation step to the commit process 10 | that will block developer's from committing work if it does not get approved by 11 | a script. 12 | 13 | Git hook scripts need to be manually installed on a per-repository basis, so 14 | Grunt Drupal Tasks facilitates by allowing easy configuration of the 15 | grunt-githooks plugin as part of our `grunt git-setup` task. 16 | 17 | ### Default Task 18 | 19 | By default, Grunt Drupal Tasks installs a special variant of the `validate` 20 | task. This version, specifically invoked by `validate:staged` attempts to 21 | minimize the number of files scanned by checking only those that have changed 22 | since the last successful commit. 23 | 24 | ### Configuring Git Hooks 25 | 26 | **config.git.hooks**: An array of grunt tasks to run on pre-commit in addition 27 | to `validate:staged`. 28 | 29 | **config.git.hook-command**: A substitute for a simple `grunt` command to handle 30 | the individual grunt tasks. Allows specification of more complex bin paths or 31 | execution wrappers such as `time` or `docker-compose`. 32 | 33 | ### Overriding a Commit Rejection 34 | 35 | Using the `-f` flag with your git commit will override the pre-commit script 36 | even if it fails the commit will proceed. 37 | 38 | ## Further Git Reading 39 | 40 | * [Customizing Git - Git Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) 41 | * [githooks.com](http://githooks.com) 42 | -------------------------------------------------------------------------------- /docs/80_CI.md: -------------------------------------------------------------------------------- 1 | # Continuous Integration 2 | 3 | One of the key advantages of the `grunt-drupal-tasks` system is the ability to 4 | easily roll a broad suite of generalizable tests into your CI process. In order 5 | to integrate with your CI system, there is often small snippets of code to be 6 | written. 7 | 8 | ## Jenkins 9 | 10 | In order to best leverage Jenkins for your CI process, you will want to use 11 | the grunt tools for their full range of testing. 12 | 13 | ```bash 14 | trap 'exit' ERR 15 | cd /opt/development/example.com 16 | git fetch origin 17 | git rebase origin/develop 18 | npm install 19 | grunt 20 | grunt test 21 | ``` 22 | 23 | ### Reports 24 | 25 | In addition to the core CI routine, you may configure Jenkins to run the 26 | `analyze` task to collect reports on build behavior. Properly configured, you 27 | will get charts and the ability to inspect error messages within the Jenkins UI. 28 | 29 | ```bash 30 | grunt analyze 31 | 32 | if [ -f "build/reports/phpmd.xml" ]; then 33 | cp build/reports/phpmd.xml $WORKSPACE 34 | fi 35 | 36 | if [ -f "build/reports/phpcs.xml" ]; then 37 | cp build/reports/phpcs.xml $WORKSPACE 38 | fi 39 | ``` 40 | 41 | ### Configure PHPCS 42 | 43 | * If you have not yet, install the [Checkstyle Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Checkstyle+Plugin) 44 | * In **Post-build Actions** add "Publish Checkstyle analysis results" 45 | * Configure the results to be phpcs.xml 46 | 47 | ### Configure PHPMD 48 | 49 | * If you have not yet, install the [PMD Plugin](https://wiki.jenkins-ci.org/display/JENKINS/PMD+Plugin) 50 | * In **Post-build Actions** add "Publish PHPMD analysis results" 51 | * Configure the results to be phpmd.xml 52 | -------------------------------------------------------------------------------- /docs/80_CUSTOM_TASKS.md: -------------------------------------------------------------------------------- 1 | # Extending Grunt Drupal Tasks 2 | 3 | > Guide for advanced project-specific extensions to Grunt Drupal Tasks. 4 | 5 | ## Adding new tasks 6 | 7 | To add additional tasks to your project's grunt implementation, you may define 8 | them directly in your Gruntfile.js or include them from separate files. 9 | 10 | ## Load a plugin managed by npm 11 | 12 | ```js 13 | grunt.loadNpmTasks('grunt-plugin-name'); 14 | ``` 15 | 16 | ### Load a local grunt plugin 17 | 18 | ```js 19 | grunt.loadTasks(__dirname + '/grunt/tasks'); 20 | ``` 21 | 22 | ## Overriding existing tasks 23 | 24 | To override an existing task the trick is to add code that will be the final 25 | piece to adjust the configuration of the task. This may be a specific 26 | configuration element, or a redefinition of the entire task. 27 | 28 | ### Override a task's configuration 29 | 30 | In your project's `Gruntfile.js`, use the `grunt.config.set()` function to 31 | override the task's default configuration provided by Grunt Drupal Tasks. 32 | 33 | The following example changes the list of files scanned by the `phplint:all` 34 | command: 35 | 36 | ```js 37 | grunt.config.set('phplint', { 38 | all: [ 39 | '<%= config.srcPaths.drupal %>/**/*.php' 40 | ] 41 | }); 42 | ``` 43 | 44 | ## Re-register the validate task 45 | 46 | In your project's `Gruntfile.js`, use the `grunt.registerTask()` function to 47 | override task aliases, like default, validate, and analyze. 48 | 49 | The following example changes the list of tasks run by the `validate` command: 50 | 51 | ```js 52 | var taskList = ['phpcs:full']; 53 | grunt.registerTask('validate', taskList); 54 | ``` 55 | 56 | ## Customizing Help Output (Help API) 57 | 58 | If you add custom tasks to your project and want them exposed as part of the 59 | `help` task, you may add a simple code snippet to your Gruntfile.js or any 60 | loaded task file. 61 | 62 | ```js 63 | var Help = require('grunt-drupal-tasks/lib/help'); 64 | 65 | Help.addItem('existing-task', 'Named Group', 'Optional description that avoids the default task description.'); 66 | 67 | Help.add({ 68 | task: 'existing-task', 69 | group: 'Named Group', 70 | description: 'Optional description that avoids the default task description.' 71 | }); 72 | 73 | Help.add([ 74 | { 75 | task: 'existing-task', 76 | group: 'Named Group', 77 | description: 'Optional description that avoids the default task description.' 78 | }, 79 | { 80 | task: 'second-task', 81 | group: 'Named Group', 82 | description: 'A second registered task to register with the help system.' 83 | } 84 | ]); 85 | ``` 86 | 87 | If you want to include your task in one of the existing groups, copy the text 88 | exactly as seen in the output of the `grunt help` task. 89 | 90 | ## Leveraging Bash Scripts 91 | 92 | If you have an existing project or a kit of useful project utilities, you may 93 | have some Bash scripts laying around that you want to keep. Sometimes Bash 94 | or PHP are simply better for managing a Drupal system, other times you want to 95 | keep the up-front time of integrating the Grunt tools minimal. 96 | 97 | The preferred approach is to configure your scripts via the **Project 98 | Operations** system. This allows you to run your script via 99 | `grunt custom-install`, and pass any grunt-derived configuration you might need 100 | to the script. 101 | 102 | If you want to automatically load your scripts, here is a script based on 103 | [grunt-shell](https://github.com/sindresorhus/grunt-shell) you can use. 104 | 105 | ```js 106 | module.exports = function(grunt) { 107 | 108 | /** 109 | * Define "bin" wrapper tasks. 110 | */ 111 | var files = grunt.file.expand("bin/*.sh"); 112 | if (files) { 113 | for (var f in files) { 114 | var name = files[f].split('/').pop().split('.').shift(); 115 | grunt.config(['shell', name], { 116 | command: 'bash ' + files[f] 117 | }); 118 | grunt.registerTask(name, ['shell:' + name]); 119 | } 120 | } 121 | }; 122 | ``` 123 | 124 | This particular snippet requires your bash scripts end in `.sh` and be located 125 | in the directory `root:bin/`. 126 | -------------------------------------------------------------------------------- /docs/90_FAQ.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | > Answers to common questions from conversation. 4 | 5 | ## Will you be switching to Gulp or another Task Runner? 6 | 7 | Gulp's significant advantage of Grunt is it's ability to easily pipeline a 8 | single set of files through multiple operations. That is not a common element of 9 | this project, so a port to Gulp is not planned. If you have a compelling 10 | argument and the time for significant contributions, please open or join a 11 | relevant issue. 12 | -------------------------------------------------------------------------------- /docs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.4 2 | 3 | RUN pip install mkdocs 4 | 5 | RUN mkdir /documents 6 | WORKDIR /documents 7 | 8 | CMD mkdocs build --clean --site-dir /site 9 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/grunt-drupal-tasks/4a9a009a4aa97260fbe954239fdc415d8a0efccd/docs/README.md -------------------------------------------------------------------------------- /docs/docker-compose.yml: -------------------------------------------------------------------------------- 1 | docs: 2 | build: . 3 | working_dir: /documents 4 | volumes: 5 | - .:/documents 6 | - ../build:/site 7 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Grunt Drupal Tasks 2 | 3 | This project brings the tooling energy of the Node.js and Grunt communities 4 | together with best practices in Drupal development to accelerate Drupal 7 and 5 | Drupal 8 development. 6 | 7 | ## Features 8 | 9 | * [Generator Gadget](https://github.com/phase2/generator-gadget), a 10 | Yeoman-generator to assemble new codebases with best practices configuration. 11 | * Configurable code structure that defaults to a clean development practice. 12 | * Drush make-based [build workflow](10_BUILD.md). 13 | * Optional use of Composer, 14 | [Code Quality & Static Analysis](20_QUALITY.md), and [Frontend tooling](30_FRONTEND.md) 15 | to extend the build process. 16 | * [Behat and SimpleTest Testing](40_TESTING.md) 17 | * [Deployment packaging](60_PACKAGE.md) 18 | * [Git Hook management](70_GIT_INTEGRATION.md) 19 | * Desktop Notifications 20 | * Local Development Friendly 21 | * [CI](80_CI.md) Friendly 22 | 23 | We are continuously working to improve this toolchain, adding functionality that 24 | we see as common to our _continuous integration_ and everyday development 25 | practices. 26 | 27 | ## Usage 28 | 29 | Working on a project that's integrated **grunt-drupal-tasks**? Run `grunt help` 30 | to view documentation tailored for your project. 31 | 32 | To build your Drupal site, run `grunt`. 33 | 34 | ### Environment Options 35 | 36 | These environment variables will override other options. 37 | 38 | * `GDT_DOMAIN`: Specify the base URL for live system testing. Falls back to a 39 | setting in the project's Gruntconfig.json, then hostname if not set. 40 | * `GDT_INSTALL_PROFILE`: Overrides the install profile specified in the 41 | project's Gruntconfig.json to be used by the `install` task. 42 | * `GDT_QUIET`: If evaluated truthy, will suppress all desktop notifications. 43 | * `GDT_SITE_ALIAS`: Configure the default Drush site alias for the project. 44 | * `GDT_SITEURLS`: Overrides the URL(s) for the project's site(s) specified in 45 | the Gruntconfig.json by which each can be accessed for end-to-end testing by 46 | tools such as Behat. Use instead of `GDT_DOMAIN` if the project has multiple 47 | subsites or URLs. 48 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Grunt-Drupal-Tasks 2 | repo_url: https://github.com/phase2/grunt-drupal-tasks/ 3 | site_author: Phase2 4 | theme: readthedocs 5 | docs_dir: . 6 | pages: 7 | - 'Home': 'index.md' 8 | - 'Project Setup': '05_SETUP.md' 9 | - 'Build Process': '10_BUILD.md' 10 | - 'Code Quality': '20_QUALITY.md' 11 | - 'Frontend Ties': '30_FRONTEND.md' 12 | - 'Project Operations': '40_OPERATIONS.md' 13 | - 'Testing': '40_TESTING.md' 14 | - 'Deploy Packaging': '60_PACKAGE.md' 15 | - 'Git Integration': '70_GIT_INTEGRATION.md' 16 | - 'Advanced Options': '70_ADVANCED_CONFIG.md' 17 | - 'Continuous Integration': '80_CI.md' 18 | - 'Extending GDT': '80_CUSTOM_TASKS.md' 19 | - 'FAQ': '90_FAQ.md' 20 | markdown_extensions: 21 | - smarty 22 | -------------------------------------------------------------------------------- /example/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /example/.eslintignore: -------------------------------------------------------------------------------- 1 | ## 2 | # This file instructs eslint to skip certain directories in it's analysis. 3 | # 4 | # It is not needed by grunt-drupal-tasks but is intended to make direct use 5 | # of eslint more effective. 6 | # 7 | # @see http://eslint.org/docs/user-guide/configuring.html 8 | ## 9 | 10 | # node_modules ignored by default 11 | 12 | # Ignore composer and bundler managed dependencies 13 | vendor/**/* 14 | 15 | # Ignore generated files and upstream dependencies via Drush Make 16 | build/**/* 17 | 18 | # Ignore JS in the Drupal files directory such as aggregations. 19 | src/sites/**/files/**/*.js 20 | -------------------------------------------------------------------------------- /example/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | }, 5 | "globals": { 6 | "Drupal": true, 7 | "jQuery": true, 8 | "tinyMCE": true 9 | }, 10 | "rules": { 11 | "eqeqeq": [2, "smart"], 12 | "guard-for-in": 2, 13 | "no-undef": 2, 14 | //"no-unused-vars": [2, {"vars": "local", "args": "none"}], 15 | "no-unused-vars": 0, 16 | "strict": 0, 17 | "new-cap": 0, 18 | "quotes": 0, 19 | "camelcase": 0, 20 | "no-underscore-dangle": 0, 21 | "no-new": 0, 22 | "no-alert": 0, 23 | "no-use-before-define": 0, 24 | "consistent-return": 0, 25 | "no-constant-condition": 0, 26 | "no-catch-shadow" : 2 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/Gruntconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "srcPaths": { 3 | "make": "src/project.make", 4 | "drupal": "src" 5 | }, 6 | "domain": "project.vm", 7 | "buildPaths": { 8 | "packages": "build/packages" 9 | }, 10 | "packages": { 11 | "srcFiles": ["!sites/*/files/**", "!xmlrpc.php", "!modules/php/*"], 12 | "projFiles": ["README*", "bin/**", "hooks/**", "src/*.make", "vendor/**"], 13 | "dest": { 14 | "docroot": "html", 15 | "devResources": "" 16 | } 17 | }, 18 | "phpcs": true, 19 | "phpmd": true, 20 | "behat": { 21 | "flags": "--tags ~@wip" 22 | }, 23 | "eslint": true, 24 | "scripts": { 25 | "update": "<%= config.drush.cmd %> <%= config.alias %> features-revert-all -yv" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | // Load all plugins and tasks defined by the grunt-drupal-tasks package. 3 | require('grunt-drupal-tasks')(grunt); 4 | }; 5 | -------------------------------------------------------------------------------- /example/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client/project", 3 | "description": "{Project} drupal codebase for {client}.", 4 | "repositories": [ 5 | { 6 | "type": "composer", 7 | "url": "https://packages.drupal.org/8" 8 | }, 9 | { 10 | "type": "git", 11 | "url": "https://github.com/minkphp/MinkZombieDriver" 12 | } 13 | ], 14 | "require": { 15 | "php": ">= 5.6", 16 | "composer/installers": "^1.0.20", 17 | "drupal-composer/drupal-scaffold": "^2.0.1", 18 | "cweagans/composer-patches": "~1.0", 19 | "drupal/core": "~8.5", 20 | "roave/security-advisories": "dev-master" 21 | }, 22 | "require-dev": { 23 | "behat/mink-zombie-driver": "dev-master#dd32fbd6988881dbab583225b65b787a893bb131", 24 | "drupal/drupal-extension": "~3.3.0", 25 | "drush/drush": "^9", 26 | "drupal/console": "~1.7", 27 | "phpmd/phpmd": "~2.1", 28 | "phpunit/phpunit": "~4.8", 29 | "drupal/coder": "^8.2" 30 | }, 31 | "conflict": { 32 | "drupal/drupal": "*", 33 | "behat/behat": ">= 3.4.0" 34 | }, 35 | "minimum-stability": "dev", 36 | "prefer-stable": true, 37 | "scripts": { 38 | "drupal-scaffold": "DrupalComposer\\DrupalScaffold\\Plugin::scaffold" 39 | }, 40 | "extra": { 41 | "installer-paths": { 42 | "build/html/core": [ 43 | "type:drupal-core" 44 | ], 45 | "build/html/modules/contrib/{$name}": [ 46 | "type:drupal-module" 47 | ], 48 | "build/html/profiles/contrib/{$name}": [ 49 | "type:drupal-profile" 50 | ], 51 | "build/html/themes/contrib/{$name}": [ 52 | "type:drupal-theme" 53 | ], 54 | "drush/contrib/{$name}": [ 55 | "type:drupal-drush" 56 | ] 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /example/gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | vendor 3 | 4 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-drupal-tasks-example", 3 | "description": "Example package.json for a Drupal project built with grunt-drupal-tasks.", 4 | "version": "1.0.0", 5 | "private": true, 6 | "dependencies": { 7 | "grunt": "^1.0.0", 8 | "grunt-drupal-tasks": "~1.0.0", 9 | "zombie": "^4.2.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/phpmd.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | Ruleset for PHPMD analysis of Drupal projects. Excludes coding issues 10 | handled better by PHPCS and rules which have too many false positives 11 | in a typical Drupal codebase. 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/src/libraries/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/grunt-drupal-tasks/4a9a009a4aa97260fbe954239fdc415d8a0efccd/example/src/libraries/.gitkeep -------------------------------------------------------------------------------- /example/src/modules/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/grunt-drupal-tasks/4a9a009a4aa97260fbe954239fdc415d8a0efccd/example/src/modules/.gitkeep -------------------------------------------------------------------------------- /example/src/profiles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/grunt-drupal-tasks/4a9a009a4aa97260fbe954239fdc415d8a0efccd/example/src/profiles/.gitkeep -------------------------------------------------------------------------------- /example/src/sites/default/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/grunt-drupal-tasks/4a9a009a4aa97260fbe954239fdc415d8a0efccd/example/src/sites/default/.gitkeep -------------------------------------------------------------------------------- /example/src/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/grunt-drupal-tasks/4a9a009a4aa97260fbe954239fdc415d8a0efccd/example/src/static/.gitkeep -------------------------------------------------------------------------------- /example/src/themes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phase2/grunt-drupal-tasks/4a9a009a4aa97260fbe954239fdc415d8a0efccd/example/src/themes/.gitkeep -------------------------------------------------------------------------------- /example/test/behat.yml: -------------------------------------------------------------------------------- 1 | default: 2 | suites: 3 | default: 4 | contexts: 5 | - FeatureContext 6 | - Drupal\DrupalExtension\Context\DrupalContext 7 | - Drupal\DrupalExtension\Context\MinkContext 8 | extensions: 9 | Behat\MinkExtension: 10 | goutte: ~ 11 | selenium2: ~ 12 | zombie: ~ 13 | base_url: http://127.0.0.1:8080 14 | javascript_session: 'zombie' 15 | Drupal\DrupalExtension: 16 | blackbox: ~ 17 | drush: 18 | alias: 'local' 19 | drupal: 20 | drupal_root: './build/html' 21 | api_driver: 'drupal' 22 | 23 | ci: 24 | formatter: 25 | name: junit 26 | parameters: 27 | output_path: behat_junit 28 | -------------------------------------------------------------------------------- /example/test/features/bootstrap/FeatureContext.php: -------------------------------------------------------------------------------- 1 | "; 20 | }; 21 | 22 | module.buildPaths = function() { 23 | // Set implicit global configuration. 24 | var buildPaths = grunt.config('config.buildPaths'); 25 | buildPaths = _.extend({ 26 | build: 'build', 27 | html: 'build/html', 28 | packages: 'build/packages', 29 | reports: 'build/reports', 30 | temp: 'build/temp' 31 | }, buildPaths); 32 | 33 | return buildPaths; 34 | }; 35 | 36 | module.themes = function(themes) { 37 | // If the theme path is not set give it a sane default. 38 | for (var key in themes) { 39 | if (!themes[key].path) { 40 | grunt.config(['themes', key, 'path'], "<%= config.srcPaths.drupal %>/themes/" + key); 41 | } 42 | } 43 | }; 44 | 45 | module.init = function() { 46 | if (grunt.config('config.siteUrls') === undefined) { 47 | grunt.config('config.siteUrls', {}); 48 | } 49 | 50 | grunt.config('config.domain', module.magic('domain') || require('os').hostname()); 51 | grunt.config('config.alias', module.magic('alias', 'SITE_ALIAS') || '@' + grunt.config('config.domain')); 52 | grunt.config('config.project.profile', module.magic(['project', 'profile'], 'INSTALL_PROFILE') || 'standard'); 53 | grunt.config('config.siteUrls', module.magic('siteUrls') || {default: module.siteUrls}); 54 | grunt.config('config.siteUrls.default', module.siteUrls()); 55 | grunt.config('config.buildPaths', module.buildPaths()); 56 | 57 | module.buildPaths(grunt.config('config.themes')); 58 | }; 59 | 60 | return module; 61 | }; 62 | -------------------------------------------------------------------------------- /lib/scripts.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | var module = {}; 3 | 4 | var buildCmd = function(name, cmd, execOptions) { 5 | grunt.loadNpmTasks('grunt-shell'); 6 | grunt.config(['shell', name + '-dispatch'], { 7 | command: cmd, 8 | options: { 9 | gruntLogHeader: false 10 | } 11 | }); 12 | 13 | if (execOptions) { 14 | grunt.config(['shell', name + '-dispatch', 'options', 'execOptions'], execOptions); 15 | } 16 | 17 | return 'shell:' + name + '-dispatch'; 18 | }; 19 | 20 | /** 21 | * Generate configuration and retrieve commands for an arbitrary task name. 22 | * Useful to add configuration-driven "events" to other tasks. 23 | */ 24 | module.eventify = function(scripts, task, namespace, tasks) { 25 | if (!scripts) { 26 | return tasks; 27 | } 28 | 29 | var command; 30 | if (scripts['pre-' + task]) { 31 | command = buildCmd('pre-' + namespace, scripts['pre-' + task]); 32 | tasks.unshift(command); 33 | } 34 | if (scripts['post-' + task]) { 35 | command = buildCmd('post-' + namespace, scripts['post-' + task]); 36 | tasks.push(command); 37 | } 38 | 39 | return tasks; 40 | }; 41 | 42 | // Generate configuration and retrieve commands for a defined script task. 43 | module.handle = function(config, task, namespace) { 44 | var command = ''; 45 | 46 | if (task === undefined) { 47 | if (config.scripts === undefined) { 48 | grunt.log.writeln("No scripts have been configured."); 49 | } else { 50 | module.usage(config.scripts); 51 | } 52 | return command; 53 | } 54 | 55 | if (config.scripts[task]) { 56 | command = []; 57 | if (config.scripts['pre-' + task]) { 58 | command.push(buildCmd( 59 | 'pre-' + namespace, 60 | config.scripts['pre-' + task], 61 | { 62 | cwd: config.path || process.cwd() 63 | } 64 | )); 65 | } 66 | command.push(buildCmd( 67 | namespace, 68 | config.scripts[task], 69 | { 70 | cwd: config.path || process.cwd() 71 | } 72 | )); 73 | if (config.scripts['post-' + task]) { 74 | command.push(buildCmd( 75 | 'post-' + namespace, 76 | config.scripts['post-' + task], 77 | { 78 | cwd: config.path || process.cwd() 79 | } 80 | )); 81 | } 82 | } else { 83 | grunt.log.writeln('The ' + namespace + ' command "' + task + '"" is not available. Select from the following:'); 84 | module.usage(config.scripts); 85 | } 86 | 87 | return command; 88 | }; 89 | 90 | module.usage = function(scripts) { 91 | var _ = require('lodash'); 92 | _.each(scripts, function(script, key) { 93 | grunt.log.writeln(key + '\t:\t' + scripts[key]); 94 | }); 95 | grunt.log.writeln('Scripts named with "pre-" or "post-" are automatically run before or after their respective operation.'); 96 | }; 97 | 98 | return module; 99 | }; 100 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Dynamically determine the ideal concurrency for parallel operations. 3 | // Algorithm courtesy of grunt-concurrent. 4 | concurrency: Math.max((require('os').cpus().length || 1) * 2, 2), 5 | // Assemble an array of absolute URLs 6 | expandUrls: function(baseUrl, paths) { 7 | return paths.map(function(value) { 8 | return baseUrl + value; 9 | }); 10 | }, 11 | canRsync: function(options) { 12 | // Enable process.platform to be mocked in options for testing. 13 | var platform = options && options.platform ? options.platform : process.platform; 14 | return platform !== "win32"; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-drupal-tasks", 3 | "description": "A Grunt plugin to automate Drupal build and testing tasks.", 4 | "version": "1.1.0", 5 | "main": "bootstrap.js", 6 | "dependencies": { 7 | "fs-extra": "~3.0.1", 8 | "grunt": "^1.0.1", 9 | "grunt-available-tasks": "~0.6.2", 10 | "grunt-composer": "~0.4.4", 11 | "grunt-concurrent": "^2.1.0", 12 | "grunt-contrib-clean": "^1.0.0", 13 | "grunt-contrib-compress": "^1.0.0", 14 | "grunt-contrib-copy": "^1.0.0", 15 | "grunt-contrib-symlink": "^1.0.0", 16 | "grunt-contrib-watch": "^1.0.0", 17 | "grunt-drush": "~0.0.7", 18 | "grunt-eslint": "^20.0.0", 19 | "grunt-force-task": "^2.0.0", 20 | "grunt-githooks": "~0.6.0", 21 | "grunt-log-headers": "^1.0.1", 22 | "grunt-mkdir": "^1.0.0", 23 | "grunt-newer": "^1.1.1", 24 | "grunt-notify": "git+https://github.com/grayside/grunt-notify.git#d4b9adf1", 25 | "grunt-parallel-behat": "^1.0.0", 26 | "grunt-phpcs": "git+https://github.com/grayside/grunt-phpcs.git#0.5.0-fork2", 27 | "grunt-phplint": "~0.1.0", 28 | "grunt-phpmd": "~0.1.1", 29 | "grunt-rsync": "^2.0.1", 30 | "grunt-shell": "^2.1.0", 31 | "grunt-staged": "~0.1.0", 32 | "lodash": "^4.5.0", 33 | "time-grunt": "^1.3.0" 34 | }, 35 | "devDependencies": { 36 | "eslint": "^4.18.0", 37 | "eslint-config-google": "^0.9.1", 38 | "eslint-config-xo": "^0.20.1" 39 | }, 40 | "engines": { 41 | "node": ">=4.0.0" 42 | }, 43 | "keywords": [ 44 | "build", 45 | "drupal", 46 | "grunt", 47 | "gruntplugin" 48 | ], 49 | "bugs": "https://github.com/phase2/grunt-drupal-tasks/issues", 50 | "homepage": "https://github.com/phase2/grunt-drupal-tasks", 51 | "license": "MIT", 52 | "repository": { 53 | "type": "git", 54 | "url": "https://github.com/phase2/grunt-drupal-tasks.git" 55 | }, 56 | "scripts": { 57 | "test": "./test/test.sh", 58 | "predocs": "rm -Rf build; mkdir build", 59 | "docs": "docker-compose run docs", 60 | "lint": "eslint ." 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tasks/behat.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define "behat" tasks. 4 | * 5 | * Dynamically adds Behat testing tasks based on configuration sets in the 6 | * Gruntconfig.json file. 7 | * 8 | * For a complete understanding of the behat task, please review CONFIG.md. 9 | * 10 | * Example: 11 | * "config": { 12 | * "buildPaths": { 13 | * "html": "build/html" 14 | * }, 15 | * "siteUrls": { 16 | * "default": "http://dev-site.local", 17 | * "subsite": "http://sub.dev-site.local" 18 | * }, 19 | * "behat": { 20 | * "flags": "--tags ~@wip", 21 | * "path": "/usr/local/bin/behat", 22 | * "subsite": { 23 | * "src": "./features/subsite/*.feature", 24 | * "debug": false 25 | * } 26 | * } 27 | * } 28 | * 29 | * If the command line option `--behat_flags="flags"` is set, the `flags` 30 | * specified will be passed to behat and override any flags from the config. 31 | */ 32 | grunt.loadNpmTasks('grunt-parallel-behat'); 33 | var config = grunt.config.get('config'); 34 | var _ = require('lodash'); 35 | 36 | if (config.buildPaths.html && config.siteUrls) { 37 | for (var key in config.siteUrls) { 38 | if (config.siteUrls.hasOwnProperty(key)) { 39 | var options = {}; 40 | var path = 'vendor/bin/behat'; 41 | 42 | // Allow configuring the path to the behat binary. 43 | if (config.behat && config.behat.path) { 44 | path = config.behat.path; 45 | } 46 | 47 | // Check for per-site behat options. 48 | if (config.behat && config.behat[key]) { 49 | var siteOptions = config.behat[key]; 50 | options = (typeof siteOptions === 'object') ? siteOptions : options; 51 | } 52 | 53 | // Support for global behat flags config. 54 | if (!options.flags && config.behat && config.behat.flags) { 55 | options.flags = config.behat.flags; 56 | } 57 | 58 | // Override with flags at runtime. 59 | if (grunt.option('behat_flags')) { 60 | options.flags = grunt.option('behat_flags'); 61 | } 62 | 63 | grunt.config(['behat', 'site-' + key], 64 | { 65 | src: options.src || './test/features/**/*.feature', 66 | options: _.extend({ 67 | config: './test/behat.yml', 68 | maxProcesses: 5, 69 | bin: path, 70 | debug: true, 71 | env: _.extend({}, process.env, { 72 | BEHAT_PARAMS: "{\"extensions\": {\"Drupal\\\\DrupalExtension\": {\"drupal\": {\"drupal_root\": \"./" + config.buildPaths.html + "\"}}, \"Behat\\\\MinkExtension\": {\"base_url\": \"" + config.siteUrls[key] + "\", \"zombie\": {\"node_modules_path\": \"" + process.cwd() + "/node_modules/\"}}}}" 73 | }) 74 | }, options) 75 | } 76 | ); 77 | } 78 | } 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /tasks/clean.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define "clean" tasks to remove files and directories. 4 | * 5 | * grunt clean 6 | * Removes build directory. 7 | * 8 | * grunt clean:sites 9 | * Removes sites/default in the build/html directory. 10 | */ 11 | grunt.loadNpmTasks('grunt-contrib-clean'); 12 | var Help = require('../lib/help')(grunt); 13 | 14 | grunt.config('clean', { 15 | default: [ 16 | '<%= config.buildPaths.html %>' 17 | ], 18 | sites: [ 19 | '<%= config.buildPaths.html %>/sites/default' 20 | ], 21 | temp: [ 22 | '<%= config.buildPaths.temp %>' 23 | ] 24 | }); 25 | 26 | Help.add({ 27 | task: 'clean:default', 28 | group: 'Utilities', 29 | description: 'Use "clean" to remove the build output directory.' 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /tasks/composer.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define "composer" tasks. 4 | * 5 | * Dynamically adds a Composer install task if a composer.json file 6 | * exists in the project directory. 7 | */ 8 | if (require('fs').existsSync('./composer.json')) { 9 | grunt.loadNpmTasks('grunt-composer'); 10 | var Help = require('../lib/help')(grunt); 11 | 12 | grunt.config(['composer', 'install'], { 13 | options: { 14 | flags: [ 15 | 'no-interaction', 16 | 'no-progress', 17 | 'prefer-dist' 18 | ] 19 | } 20 | }); 21 | grunt.config(['composer', 'update'], { 22 | options: { 23 | flags: [ 24 | 'no-interaction', 25 | 'no-progress', 26 | 'prefer-dist' 27 | ] 28 | } 29 | }); 30 | 31 | // Add the drupal-scaffold task if it is defined in the `composer.json`. 32 | var composer = JSON.parse(require('fs').readFileSync('./composer.json', 'utf8')); 33 | if (typeof composer.scripts !== 'undefined' && 'drupal-scaffold' in composer.scripts) { 34 | grunt.config(['composer', 'drupal-scaffold'], {}); 35 | } 36 | 37 | Help.add({ 38 | task: 'composer', 39 | group: 'Dependency Management', 40 | description: 41 | "Install dependencies defined in this project's composer.json file." 42 | }); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /tasks/copy.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define "copy" tasks. 4 | * 5 | * grunt copy:static 6 | * Copies all files from src/static to the build/html directory. 7 | * grunt rsync:tempbuild 8 | * Duplicate Drupal docroot from temporary location to the final build 9 | * target. 10 | * grunt copy:tempbuild 11 | * Original implementation of rsync:tempbuild, preserved for backwards 12 | * compatibility and Windows support. 13 | * grunt copy:defaults 14 | * Copies Drupal's sites/default directory into the custom codebase. 15 | */ 16 | grunt.loadNpmTasks('grunt-contrib-copy'); 17 | grunt.loadNpmTasks('grunt-rsync'); 18 | 19 | grunt.config('copy.static', { 20 | options: { 21 | mode: true 22 | }, 23 | files: [ 24 | { 25 | expand: true, 26 | cwd: '<%= config.srcPaths.drupal %>/static', 27 | src: ['**', '.**'], 28 | dest: '<%= config.buildPaths.html %>', 29 | dot: true, 30 | follow: true 31 | } 32 | ] 33 | }); 34 | 35 | grunt.config('copy.tempbuild', { 36 | options: { 37 | mode: true 38 | }, 39 | files: [ 40 | { 41 | expand: true, 42 | cwd: '<%= config.buildPaths.temp %>', 43 | src: ['**', '.**'], 44 | dest: '<%= config.buildPaths.html %>', 45 | dot: true, 46 | follow: true 47 | } 48 | ] 49 | }); 50 | 51 | grunt.config('rsync.tempbuild', { 52 | options: { 53 | args: [ 54 | '-ahW', 55 | '--stats' 56 | ], 57 | src: '<%= config.buildPaths.temp %>/', 58 | dest: '<%= config.buildPaths.html %>' 59 | } 60 | }); 61 | 62 | grunt.config('copy.defaults', { 63 | options: { 64 | mode: true 65 | }, 66 | files: [ 67 | { 68 | expand: true, 69 | cwd: '<%= config.buildPaths.html %>/sites/default', 70 | src: ['default*'], 71 | dest: '<%= config.srcPaths.drupal %>/sites/default' 72 | } 73 | ] 74 | }); 75 | }; 76 | -------------------------------------------------------------------------------- /tasks/git.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | module.exports = function(grunt) { 4 | /** 5 | * Define tasks for automatic setup of a local project git repo. 6 | * 7 | * These are tasks that cannot be initialized into the repository via Yeoman, 8 | * such as githooks. 9 | */ 10 | 11 | if (!grunt.file.exists('build') && !grunt.file.exists('.git/hooks/pre-comit') && !grunt.config('config.validate.ignoreError')) { 12 | grunt.log.writeln('Run `grunt git-setup` to perform one-time repo upgrades, such as adding a pre-commit code scanner.'); 13 | } 14 | 15 | grunt.registerTask('git-setup', 'Set up the project git repository with some one-time configurations.', function() { 16 | grunt.loadNpmTasks('grunt-githooks'); 17 | 18 | var tasks = grunt.config('config.git.hooks') || []; 19 | tasks.unshift('validate:staged'); 20 | 21 | // Githooks task may be configured via Gruntconfig. 22 | grunt.config('githooks', { 23 | options: { 24 | command: grunt.config('config.git.hook-command') || 'grunt' 25 | }, 26 | gdt: { 27 | 'pre-commit': _.uniq(tasks).join(' ') 28 | } 29 | }); 30 | 31 | grunt.task.run([ 32 | 'githooks' 33 | ]); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /tasks/help.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define "help" task, a more purposeful and structured help than -h. 4 | * 5 | * grunt help 6 | * Provides an overview of intended and useful grunt tasks. 7 | */ 8 | var _ = require('lodash'); 9 | 10 | var Help = require('../lib/help')(grunt); 11 | 12 | Help.add({ 13 | task: 'default', 14 | group: 'Build Process', 15 | description: 'The default build process that executes when "grunt" runs, which includes verifying dependencies and assembling the build directory.' 16 | }); 17 | 18 | // Specify that only configured help entries should be displayed. 19 | grunt.config('availabletasks.help.options.filter', 'include'); 20 | 21 | // Suppress task headings for help to keep it lean. 22 | grunt.config('help.options.gruntLogHeader', false); 23 | grunt.config('availabletasks.help.options.gruntLogHeader', false); 24 | grunt.config('help-after.options.gruntLogHeader', false); 25 | 26 | grunt.registerTask('help', 'Output grunt-drupal-tasks specialized help.', function() { 27 | var gdt = {}; 28 | var gruntHelp = require('grunt/lib/grunt/help'); 29 | var process = function(queue) { 30 | queue.forEach(function(cb) { 31 | cb(); 32 | }); 33 | }; 34 | 35 | gdt.cleanOptions = function() { 36 | _.each(gruntHelp._options, function(item, i) { 37 | if (item[0] === '--base' || item[0] === '--gruntfile' || item[0] === '--tasks' || item[0] === '--npm') { 38 | delete gruntHelp._options[i]; 39 | } 40 | if (item[0] === '--help, -h') { 41 | gruntHelp._options[i][1] = 'Display the default Grunt help text.'; 42 | } 43 | }); 44 | }; 45 | 46 | gdt.header = function() { 47 | grunt.log.writeln('Used with Grunt Drupal Tasks (v' + require('../package.json').version + ')'); 48 | grunt.log.writeln().writeln('To display this help, run `grunt help`'); 49 | }; 50 | 51 | gdt.options = function() { 52 | var options = [ 53 | ['--concurrency', 'Override the dynamic concurrency parameter used by Drush Make.'], 54 | ['--db-url', 'Pass thru your Drupal database credentials for site installation.'], 55 | ['--name', 'Override the name of the package exported by "grunt package".'], 56 | ['--notify', 'Request a desktop notification despite timing or environment settings.'], 57 | ['--no-validate', 'Skip `grunt:validate` in the default `grunt` task.'], 58 | ['--quiet', 'Suppress desktop notifications.'], 59 | ['--timer', 'Output task execution timing info.'] 60 | ]; 61 | 62 | if (grunt.config('config.project.db')) { 63 | options.push('--no-db-load', 'Perform a clean site installation even if a database restore path is configured'); 64 | } 65 | 66 | gruntHelp.table(options); 67 | }; 68 | 69 | gdt.tasks = function() { 70 | grunt.loadNpmTasks('grunt-available-tasks'); 71 | grunt.task.run('availabletasks:help', 'help-after'); 72 | }; 73 | 74 | gdt.footer = function() { 75 | grunt.log.writeln('For help with Grunt Drupal Tasks, see ' + require('../package.json').homepage); 76 | }; 77 | 78 | // Dispatch to grunt tasks is asynchronous, in order to display text after 79 | // dispatching to availabletasks we need a dedicated task so grunt's internal 80 | // task sequencing will ensure the proper order of execution. 81 | grunt.registerTask('help-after', 'Generate a footer to follow primary help text.', function() { 82 | var queue = [ 83 | gruntHelp.footer, 84 | gdt.footer 85 | ]; 86 | process(queue); 87 | }); 88 | 89 | var queue = [ 90 | gruntHelp.initOptions, 91 | gdt.cleanOptions, 92 | gruntHelp.initWidths, 93 | gruntHelp.header, 94 | gdt.header, 95 | gruntHelp.usage, 96 | gruntHelp.options, 97 | gdt.options, 98 | gruntHelp.optionsFooter, 99 | gdt.tasks 100 | ]; 101 | 102 | process(queue); 103 | }); 104 | }; 105 | -------------------------------------------------------------------------------- /tasks/install.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define tasks to quickly get Drupal running. 4 | * 5 | * grunt serve 6 | * Run Drupal via the PHP webserver and activate watch tasks. 7 | * grunt serve:demo 8 | * Run Drupal via the PHP webserver without running watch tasks. 9 | * grunt serve:test 10 | * Run Drupal via the PHP webserver without running watch tasks or opening 11 | * the browser. 12 | */ 13 | grunt.loadNpmTasks('grunt-drush'); 14 | 15 | var Help = require('../lib/help')(grunt); 16 | var Drupal = require('../lib/drupal')(grunt); 17 | 18 | var _ = require('lodash'); 19 | 20 | // If no path is configured for Drush, fallback to the system path. 21 | var cmd = {cmd: Drupal.drushPath()}; 22 | var args = ['--root=<%= config.buildPaths.html %>']; 23 | var profile = grunt.config('config.project.profile') || 'standard'; 24 | var dbUrl = grunt.option('db-url') || ''; 25 | 26 | if (dbUrl) { 27 | dbUrl = '--db-url=' + dbUrl; 28 | args.push(dbUrl); 29 | } 30 | 31 | grunt.config(['drush', 'install'], { 32 | args: args.concat(['site-install', '-yv', profile]), 33 | options: _.extend({ 34 | }, cmd) 35 | }); 36 | grunt.config(['drush', 'sql-drop'], { 37 | args: args.concat(['sql-drop', '-y']), 38 | options: _.extend({ 39 | }, cmd) 40 | }); 41 | grunt.config(['drush', 'updb'], { 42 | args: args.concat(['updatedb', '-y']), 43 | options: _.extend({ 44 | }, cmd) 45 | }); 46 | grunt.config(['shell', 'loaddb'], { 47 | command: 'gzip -dc <%= config.project.db %> | <%= config.project.dbConnection %>' 48 | }); 49 | 50 | grunt.registerTask('install', 'Install Drupal with the configured profile, or from a database dump file if one exists.', function() { 51 | var done = this.async(); 52 | var name = this.name; 53 | 54 | // Check for a database file first. 55 | var dbPath = grunt.config('config.project.db'); 56 | if (!grunt.option('no-db-load') && dbPath && grunt.file.exists(dbPath)) { 57 | Drupal.loadDatabaseConnection(function(error) { 58 | if (error) { 59 | grunt.fail.fatal(error); 60 | done(); 61 | } 62 | 63 | var tasks = [ 64 | // Drop current database. 65 | 'drush:sql-drop', 66 | // Load new database. 67 | 'shell:loaddb', 68 | // Run any pending database updates. 69 | 'drush:updb' 70 | ]; 71 | 72 | tasks = require('../lib/scripts')(grunt) 73 | .eventify(grunt.config('config.scripts'), name, 'ops', tasks); 74 | 75 | grunt.task.run(tasks); 76 | done(); 77 | }); 78 | } else { 79 | var tasks = [ 80 | // Run site install. 81 | 'drush:install' 82 | ]; 83 | 84 | tasks = require('../lib/scripts')(grunt) 85 | .eventify(grunt.config('config.scripts'), name, 'ops', tasks); 86 | 87 | grunt.task.run(tasks); 88 | done(); 89 | } 90 | }); 91 | 92 | Help.add([ 93 | { 94 | task: 'install', 95 | group: 'Operations' 96 | } 97 | ]); 98 | }; 99 | -------------------------------------------------------------------------------- /tasks/make.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define "drush" tasks. 4 | * 5 | * grunt drush:make 6 | * Builds the Drush make file to the build/html directory. 7 | */ 8 | grunt.loadNpmTasks('grunt-drush'); 9 | grunt.loadNpmTasks('grunt-newer'); 10 | 11 | var Help = require('../lib/help')(grunt); 12 | var Drupal = require('../lib/drupal')(grunt); 13 | var gdt = require('../lib/util'); 14 | 15 | var _ = require('lodash'); 16 | 17 | // If no path is configured for Drush, fallback to the system path. 18 | var cmd = {cmd: Drupal.drushPath()}; 19 | 20 | // Allow extra arguments for drush to be supplied. 21 | var makeArgs = [ 22 | 'make', 23 | '<%= config.srcPaths.make %>', 24 | '<%= config.buildPaths.temp %>' 25 | ]; 26 | var extraArgs = grunt.config.get('config.drush.make.args'); 27 | 28 | if (extraArgs && extraArgs.length) { 29 | extraArgs.unshift(makeArgs[0]); 30 | extraArgs.push(makeArgs[1]); 31 | extraArgs.push(makeArgs[2]); 32 | makeArgs = extraArgs; 33 | } 34 | 35 | var limit = grunt.option('concurrency') || require('../lib/util').concurrency; 36 | makeArgs.push('--concurrency=' + limit); 37 | grunt.verbose.writeln('Configured for concurrency=' + limit); 38 | 39 | grunt.config(['drush', 'make'], { 40 | args: makeArgs, 41 | options: _.extend({}, cmd) 42 | }); 43 | 44 | grunt.registerTask('drushmake', 'Prepare the build directory and run "drush make"', function() { 45 | grunt.task.run(this.options().tasks); 46 | }); 47 | 48 | // The "drushmake" task will run make only if the src file specified here is 49 | // newer than the dest file specified. This includes scanning recursively into 50 | // the source directory for common places included makefiles might be located. 51 | grunt.config('drushmake', { 52 | default: { 53 | src: [ 54 | // Check the configured makefile. 55 | '<%= config.srcPaths.make %>', 56 | // Check files at the immediate root of the Drupal source files. 57 | '<%= config.srcPaths.drupal %>/*.{make,make.yml}', 58 | // Recursively check inside modules, profiles, and libraries. 59 | '<%= config.srcPaths.drupal %>/{modules,profiles,libraries}/**/*.{make,make.yml}', 60 | // Check at the immediate root of each theme. 61 | '<%= config.srcPaths.drupal %>/themes/*/*.{make,make.yml}' 62 | ], 63 | dest: '<%= config.buildPaths.html %>' 64 | }, 65 | options: { 66 | tasks: [ 67 | 'mkdir:init', 68 | 'clean:temp', 69 | 'drush:make', 70 | 'clean:default', 71 | gdt.canRsync() ? 'rsync:tempbuild' : 'copy:tempbuild', 72 | 'clean:temp' 73 | ] 74 | } 75 | }); 76 | 77 | Help.add([ 78 | { 79 | task: 'drushmake', 80 | group: 'Dependency Management' 81 | }, 82 | { 83 | task: 'newer', 84 | group: 'Dependency Management', 85 | description: 'Use "newer:drushmake" to run the drushmake task only if the make file was updated.' 86 | } 87 | ]); 88 | }; 89 | -------------------------------------------------------------------------------- /tasks/mkdir.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define "mkdir" tasks. 4 | * 5 | * grunt mkdir:init 6 | * Creates directories expected in the build directory. 7 | * 8 | * grunt mkdir:files 9 | * Creates sites/default/files in the build/html directory. 10 | */ 11 | grunt.loadNpmTasks('grunt-mkdir'); 12 | grunt.config('mkdir', { 13 | init: { 14 | options: { 15 | create: [ 16 | '<%= config.buildPaths.build %>/cache', 17 | '<%= config.buildPaths.build %>/docs', 18 | '<%= config.buildPaths.reports %>' 19 | ] 20 | } 21 | }, 22 | files: { 23 | options: { 24 | create: [ 25 | '<%= config.buildPaths.html %>/sites/default/files' 26 | ] 27 | } 28 | } 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /tasks/notify.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define "notify" tasks. 4 | */ 5 | if ((grunt.option('quiet') || process.env.GDT_QUIET) && !grunt.option('notify')) { 6 | return; 7 | } 8 | 9 | grunt.loadNpmTasks('grunt-notify'); 10 | var guessProjectName = require('grunt-notify/lib/util/guessProjectName'); 11 | 12 | // Set the default threshold. 13 | var threshold = grunt.config('config.notify.threshold') || 10; 14 | // If the notify flag is used, drop the threshold to ensure notifications are 15 | // triggered. 16 | if (grunt.option('notify')) { 17 | threshold = 0; 18 | } 19 | 20 | grunt.config('notify_hooks', { 21 | options: { 22 | // title needs to be reset to the same as the grunt-notify default because 23 | // the guessing mechanism thinks that grunt-drupal-tasks is the project. 24 | title: guessProjectName(), 25 | // Only trigger success messages if the process has taken longer than the 26 | // configured notifyThreshold. 27 | success: true, 28 | // The 'threshold' option is currently implemented to be triggered if 29 | // enabled is falsy. If enabled is truthy the threshold is moot. 30 | enabled: grunt.option('notify'), 31 | threshold: threshold, 32 | duration: 5, 33 | // Supposed to suppress notify_hooks log header, but not working. 34 | gruntLogHeader: false 35 | } 36 | }); 37 | 38 | // This is required if you use any options. 39 | grunt.task.run('notify_hooks'); 40 | }; 41 | -------------------------------------------------------------------------------- /tasks/operations.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define operations tasks as configured via scripts in Gruntconfig.json 4 | * 5 | * These are ad hoc scripts with minimal facilitation from Grunt. 6 | */ 7 | var _ = require('lodash'); 8 | 9 | if (grunt.config('config.scripts')) { 10 | var scripts = grunt.config('config.scripts'); 11 | _.each(scripts, function(script, key) { 12 | grunt.registerTask(key, 'Perform the configured "' + key + '" task.', function() { 13 | var task = require('../lib/scripts')(grunt) 14 | .handle(grunt.config('config'), this.name, 'ops'); 15 | 16 | if (task) { 17 | grunt.task.run(task); 18 | } 19 | }); 20 | 21 | require('../lib/help')(grunt).add({ 22 | task: key, 23 | group: 'Operations' 24 | }); 25 | }); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /tasks/package.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define "package" tasks. 4 | * 5 | * grunt package 6 | * Builds a deployment package in the build/package directory. 7 | */ 8 | 9 | var Help = require('../lib/help')(grunt); 10 | var path = require('path'); 11 | 12 | grunt.registerTask('packageRewriteComposer', '', function() { 13 | var pathBuild = grunt.config('config.buildPaths.html'); 14 | var pathPackage = grunt.config('config.packages.dest.docroot'); 15 | var pathVendor = grunt.config('config.packages.dest.vendor'); 16 | // Check if we are packaging to a custom destination. 17 | if ((pathBuild !== pathPackage) || pathVendor) { 18 | // Load `composer.json` as JSON, convert to object. 19 | var destPath = grunt.option('package-dest'); 20 | var composer = grunt.file.readJSON('composer.json'); 21 | // Determine new installer-paths 22 | var pathInstall = pathPackage ? pathPackage + '/' : ''; 23 | if (pathVendor) { 24 | // Make sure install path is relative to vendor location. 25 | var regexVendor = new RegExp('^' + pathVendor + '/'); 26 | pathInstall = pathInstall.replace(regexVendor, ''); 27 | } 28 | var regex = new RegExp('^' + pathBuild + '/'); 29 | for (var key in composer.extra['installer-paths']) { 30 | // Add an unnecessary if check just for eslint rules. 31 | if (composer.extra['installer-paths'].hasOwnProperty(key)) { 32 | var newKey = key.replace(regex, pathInstall); 33 | if (newKey !== key) { 34 | // Alter keys in `extra.installer-paths` object to change `build/html` 35 | // to `html` or an alternative path from the config. 36 | var value = composer.extra['installer-paths'][key]; 37 | delete composer.extra['installer-paths'][key]; 38 | composer.extra['installer-paths'][newKey] = value; 39 | } 40 | } 41 | } 42 | 43 | // Next, generate the composer.json in the correct path. 44 | var destComposer = (pathVendor) ? destPath + '/' + pathVendor : destPath; 45 | // Write out data to `composer.json` in the package output. 46 | var composerString = JSON.stringify(composer, null, 2); 47 | grunt.file.write(destComposer + '/composer.json', composerString); 48 | if (pathVendor) { 49 | // Remove the original file if we moved it. 50 | grunt.file.delete(destPath + '/composer.json'); 51 | // Change working directory for later `composer install`. 52 | grunt.config(['composer'], { 53 | options: { 54 | flags: ['no-dev'], 55 | cwd: destComposer 56 | } 57 | }); 58 | } 59 | } 60 | }); 61 | 62 | grunt.registerTask('package', 'Package the operational codebase for deployment. Use package:compress to create an archive.', function() { 63 | grunt.loadNpmTasks('grunt-contrib-copy'); 64 | 65 | var config = grunt.config.get('config.packages'); 66 | var srcFiles = ['**', '!**/.gitkeep'].concat((config && config.srcFiles && config.srcFiles.length) ? config.srcFiles : '**'); 67 | var projFiles = (config && config.projFiles && config.projFiles.length) ? config.projFiles : []; 68 | 69 | // Look for a package target spec, build destination path. 70 | var packageName = grunt.option('name') || config.name || 'package'; 71 | var destPath = grunt.config.get('config.buildPaths.packages') + '/' + packageName; 72 | var tasks = []; 73 | grunt.option('package-dest', destPath); 74 | 75 | grunt.config('copy.package', { 76 | files: [ 77 | { 78 | expand: true, 79 | cwd: '<%= config.buildPaths.html %>', 80 | src: srcFiles, 81 | dest: path.resolve(destPath, grunt.config.get('config.packages.dest.docroot') || ''), 82 | dot: true, 83 | follow: true 84 | }, 85 | { 86 | expand: true, 87 | src: projFiles, 88 | dest: path.resolve(destPath, grunt.config.get('config.packages.dest.devResources') || ''), 89 | dot: true, 90 | follow: true 91 | } 92 | ], 93 | options: { 94 | gruntLogHeader: false, 95 | mode: true 96 | } 97 | }); 98 | 99 | grunt.config.set('clean.packages', [destPath]); 100 | 101 | tasks.push('clean:packages'); 102 | tasks.push('copy:package'); 103 | 104 | // If the `composer.json` file is being packaged, rebuild composer dependencies without dev. 105 | if (projFiles.find(function(pattern) { 106 | return pattern.startsWith('composer'); 107 | })) { 108 | tasks.push('packageRewriteComposer'); 109 | grunt.config(['composer'], { 110 | options: { 111 | flags: ['no-dev'], 112 | cwd: destPath 113 | } 114 | }); 115 | tasks.push('composer:install'); 116 | grunt.config(['composer', 'drupal-scaffold'], {}); 117 | tasks.push('composer:drupal-scaffold'); 118 | } 119 | 120 | if (this.args[0] && this.args[0] === 'compress') { 121 | grunt.loadNpmTasks('grunt-contrib-compress'); 122 | grunt.config('compress.package', { 123 | options: { 124 | archive: destPath + '.tgz', 125 | mode: 'tgz', 126 | gruntLogHeader: false 127 | }, 128 | files: [ 129 | { 130 | expand: true, 131 | dot: true, 132 | cwd: grunt.config.get('config.buildPaths.packages') + '/' + packageName, 133 | src: ['**'] 134 | } 135 | ] 136 | }); 137 | 138 | tasks.push('compress:package'); 139 | } 140 | 141 | grunt.task.run(tasks); 142 | }); 143 | 144 | Help.add({ 145 | task: 'package', 146 | group: 'Operations' 147 | }); 148 | }; 149 | -------------------------------------------------------------------------------- /tasks/quality.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define "validate" and "analyze" tasks, and include required plugins. 4 | * 5 | * These tasks are used to ensure code quality. 6 | * 7 | * grunt validate 8 | * Evaluate the quality of all custom code to make sure it is safe to push. 9 | * Should be reasonably fast and display on the command-line. 10 | * 11 | * grunt analyze 12 | * Deeper inspection & analyze of codebase, not done on every build. 13 | * Produces reports for Jenkins. May be a long-running task. 14 | */ 15 | grunt.loadNpmTasks('grunt-eslint'); 16 | grunt.loadNpmTasks('grunt-force-task'); 17 | grunt.loadNpmTasks('grunt-phplint'); 18 | grunt.loadNpmTasks('grunt-phpcs'); 19 | grunt.loadNpmTasks('grunt-phpmd'); 20 | 21 | var Help = require('../lib/help')(grunt); 22 | var _ = require('lodash'); 23 | 24 | // Task set aliases are registered at the end of the file based on these values. 25 | var validate = []; 26 | var analyze = []; 27 | 28 | var defaultPatterns = [ 29 | '<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/*.{php,module,inc,install,profile}', 30 | '!<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/*.{box,pages_default,panels_default,views_default,panelizer,strongarm}.inc', 31 | '!<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/*.features.*inc', 32 | '!<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/vendor/**' 33 | ]; 34 | 35 | // Include common sites and theme locations in phplint validation. 36 | var phplintPatterns = defaultPatterns.slice(0); 37 | phplintPatterns.unshift([ 38 | '<%= config.srcPaths.drupal %>/sites/*/*.{php,inc}', 39 | '<%= config.srcPaths.drupal %>/sites/*/*/*.{php,inc}', 40 | '<%= config.srcPaths.drupal %>/themes/*/template.php', 41 | '<%= config.srcPaths.drupal %>/themes/*/templates/**/*.php', 42 | '<%= config.srcPaths.drupal %>/themes/*/includes/**/*.{inc,php}' 43 | ]); 44 | 45 | grunt.config('phplint', { 46 | all: grunt.config.get('config.phplint.dir') ? grunt.config.get('config.phplint.dir') : phplintPatterns 47 | }); 48 | validate.push('phplint:all'); 49 | 50 | // Exclude templates by default from phpcs validation. 51 | var phpcsPatterns = defaultPatterns.slice(0); 52 | phpcsPatterns.unshift('<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/*.yml'); 53 | phpcsPatterns.push('!<%= config.srcPaths.drupal %>/{modules,profiles,libraries,static}/**/*.tpl.php'); 54 | 55 | var phpcsConfig = grunt.config.get('config.phpcs'); 56 | if (phpcsConfig) { 57 | var phpcs = phpcsConfig.dir || phpcsPatterns; 58 | var phpStandard = phpcsConfig.standard || 59 | 'vendor/drupal/coder/coder_sniffer/Drupal,vendor/drupal/coder/coder_sniffer/DrupalPractice'; 60 | var ignoreError = grunt.config('config.validate.ignoreError'); 61 | 62 | grunt.config('phpcs', { 63 | analyze: { 64 | src: phpcs 65 | }, 66 | drupal: { 67 | src: phpcs 68 | }, 69 | validate: { 70 | src: phpcs, 71 | options: { 72 | report: phpcsConfig.validateReport || 'full', 73 | reportFile: false 74 | } 75 | }, 76 | full: { 77 | src: phpcs, 78 | options: { 79 | report: 'full', 80 | reportFile: false 81 | } 82 | }, 83 | summary: { 84 | src: phpcs, 85 | options: { 86 | report: 'summary', 87 | reportFile: false 88 | } 89 | }, 90 | gitblame: { 91 | src: phpcs, 92 | options: { 93 | report: 'gitblame', 94 | reportFile: false 95 | } 96 | }, 97 | options: { 98 | bin: phpcsConfig.path || 'vendor/bin/phpcs', 99 | standard: phpStandard, 100 | ignoreExitCode: ignoreError, 101 | report: phpcsConfig.format || 'checkstyle', 102 | reportFile: '<%= config.buildPaths.reports %>/phpcs.xml' 103 | } 104 | }); 105 | } 106 | 107 | var phpmdConfig = grunt.config.get('config.phpmd'); 108 | if (phpmdConfig) { 109 | var excludePaths = grunt.config('config.phpmd.excludePaths'); 110 | excludePaths = (excludePaths && excludePaths.length) ? excludePaths : []; 111 | excludePaths = excludePaths.concat([ 112 | "<%= config.srcPaths.drupal %>/sites", 113 | 'bower_components', 114 | 'node_modules' 115 | ]); 116 | 117 | grunt.config('phpmd', { 118 | custom: { 119 | dir: '<%= config.srcPaths.drupal %>/' 120 | }, 121 | options: { 122 | bin: phpmdConfig.path || 'vendor/bin/phpmd', 123 | rulesets: phpmdConfig.configPath || 'phpmd.xml', 124 | suffixes: "php,module,inc,install,profile", 125 | exclude: excludePaths.join(), 126 | reportFormat: 'xml', 127 | reportFile: '<%= config.buildPaths.reports %>/phpmd.xml' 128 | } 129 | }); 130 | analyze.push('phpmd:custom'); 131 | } 132 | 133 | var themes = grunt.config('config.themes'); 134 | var eslintConfig = grunt.config.get('config.eslint'); 135 | if (eslintConfig) { 136 | var eslintTarget = eslintConfig.dir || [ 137 | '<%= config.srcPaths.drupal %>/themes/*/js/**/*.js', 138 | '<%= config.srcPaths.drupal %>/{modules,profiles,libraries}/**/*.js' 139 | ]; 140 | var eslintTargetAnalyze = eslintTarget; 141 | var eslintConfigFile = eslintConfig.configFile || process.cwd() + '/.eslintrc'; 142 | 143 | _.each(themes, function(theme, key) { 144 | if (theme.scripts && theme.scripts.validate) { 145 | // If the theme has a validate task of it's own, then exclude its 146 | // javascript files from our validate process. 147 | eslintTarget.push('!<%= config.srcPaths.drupal %>/themes/' + key + '/js/**/*.js'); 148 | } 149 | if (theme.scripts && theme.scripts.analyze) { 150 | // If the theme has an analyze task of it's own, then exclude its 151 | // javascript files from our analyze process. 152 | eslintTargetAnalyze.push('!<%= config.srcPaths.drupal %>/themes/' + key + '/js/**/*.js'); 153 | } 154 | }); 155 | 156 | grunt.config('eslint', { 157 | options: { 158 | configFile: eslintConfigFile 159 | }, 160 | validate: eslintTarget, 161 | analyze: { 162 | options: { 163 | format: eslintConfig.format || 'checkstyle', 164 | outputFile: '<%= config.buildPaths.reports %>/eslint.xml' 165 | }, 166 | src: eslintTargetAnalyze 167 | } 168 | }); 169 | } 170 | 171 | // If any of the themes have code quality commands, attach them here. 172 | for (var key in themes) { 173 | if (themes[key].scripts && themes[key].scripts.validate) { 174 | validate.push('themes:' + key + ':validate'); 175 | } 176 | if (themes[key].scripts && themes[key].scripts.analyze) { 177 | validate.push('themes:' + key + ':analyze'); 178 | } 179 | } 180 | 181 | var filesToProcess = function(patterns) { 182 | var paths = _.map(patterns, function(item) { 183 | return grunt.template.process(item); 184 | }); 185 | 186 | // If length is evaluated to truthy at least one file matched. 187 | return grunt.file.expand(paths); 188 | }; 189 | 190 | grunt.registerTask('validate', 'Validate the quality of custom code.', function(mode) { 191 | phpcsConfig = grunt.config.get('phpcs'); 192 | var files; 193 | if (phpcsConfig && phpcsConfig.validate) { 194 | files = filesToProcess(phpcsConfig.validate.src); 195 | if (files.length) { 196 | grunt.config.set('phpcs.validate.src', files); 197 | validate.push('phpcs:validate'); 198 | } 199 | } 200 | eslintConfig = grunt.config.get('eslint'); 201 | var eslintIgnoreError = grunt.config.get('config.validate.ignoreError') === undefined ? false : grunt.config.get('config.validate.ignoreError'); 202 | var eslintName = eslintIgnoreError ? 'force:eslint' : 'eslint'; 203 | if (eslintConfig && eslintConfig.validate) { 204 | files = filesToProcess(eslintConfig.validate); 205 | if (files.length) { 206 | grunt.config.set('eslint.validate', files); 207 | validate.push(eslintName + ':validate'); 208 | } 209 | } 210 | 211 | if (mode === 'newer' || mode === 'staged') { 212 | // This works because grunt-newer and grunt-staged have consisting naming. 213 | grunt.loadNpmTasks('grunt-' + mode); 214 | // Wrap each task configured for validate in the newer command. 215 | // grunt-phplint contains complex caching that does the same thing. 216 | validate = validate.filter(function(item) { 217 | return item.indexOf('themes:') !== 0; 218 | }).map(function(item) { 219 | return item === 'phplint:all' ? item : mode + ':' + item; 220 | }); 221 | } 222 | 223 | if (validate.length) { 224 | grunt.task.run(validate); 225 | } 226 | }); 227 | 228 | grunt.registerTask('analyze', 'Generate reports on code quality for use by Jenkins or other visualization tools.', function() { 229 | phpcsConfig = grunt.config.get('phpcs'); 230 | var files; 231 | if (phpcsConfig.analyze) { 232 | files = filesToProcess(phpcsConfig.analyze.src); 233 | if (files.length) { 234 | grunt.config.set('phpcs.analyze', files); 235 | analyze.push('phpcs:analyze'); 236 | } 237 | } 238 | eslintConfig = grunt.config.get('eslint'); 239 | var eslintIgnoreError = grunt.config.get('config.validate.ignoreError') === undefined ? false : grunt.config.get('config.validate.ignoreError'); 240 | var eslintName = eslintIgnoreError ? 'force:eslint' : 'eslint'; 241 | if (eslintConfig.analyze) { 242 | // The eslint:analyze task has a deeper configuration structure than 243 | // eslint:validate. 244 | files = filesToProcess(eslintConfig.analyze.src); 245 | if (files.length) { 246 | grunt.config.set('eslint.analyze', files); 247 | analyze.push(eslintName + ':analyze'); 248 | } 249 | } 250 | 251 | if (analyze.length) { 252 | var tasks = analyze; 253 | if (analyze.length > 1) { 254 | grunt.loadNpmTasks('grunt-concurrent'); 255 | grunt.config(['concurrent', 'analyze'], { 256 | tasks: analyze, 257 | options: { 258 | logConcurrentOutput: true 259 | } 260 | }); 261 | tasks = ['concurrent:analyze']; 262 | } 263 | 264 | tasks.unshift('mkdir:init'); 265 | grunt.task.run(tasks); 266 | } 267 | }); 268 | 269 | Help.add([ 270 | { 271 | task: 'validate', 272 | group: 'Testing & Code Quality', 273 | description: 'Quick code health check for syntax errors and basic practices. (e.g. PHPCS w/ Drush Coder rules)' 274 | }, 275 | { 276 | task: 'analyze', 277 | group: 'Testing & Code Quality', 278 | description: 'Static codebase analysis to detect problems. Outputs Jenkins-compatible reports. (e.g. PHPMD)' 279 | } 280 | ]); 281 | }; 282 | -------------------------------------------------------------------------------- /tasks/scaffold.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Apply the scaffolding to the Drupal docroot. 4 | */ 5 | grunt.registerTask('scaffold', 'Apply the scaffolding from ' + 6 | grunt.config('config.srcPaths.drupal') + '/ to the Drupal docroot.', 7 | function() { 8 | var path = require('path'); 9 | if (!require('fs').existsSync(path.join( 10 | grunt.config('config.buildPaths.html'), 'index.php'))) { 11 | grunt.fail.fatal('Cannot apply scaffolding until after Drupal has' + 12 | ' been built.'); 13 | } 14 | 15 | var done = this.async(); 16 | var drupal = require('../lib/drupal')(grunt); 17 | drupal.loadDrushStatus(function(err) { 18 | if (err) { 19 | grunt.fail.fatal('Cannot load Drush status for built Drupal ' + 20 | 'docroot.\n\n' + err); 21 | return done(); 22 | } 23 | 24 | grunt.loadNpmTasks('grunt-contrib-symlink'); 25 | 26 | grunt.config.set('mkdir.drupal', { 27 | options: { 28 | create: [ 29 | drupal.libraryPath(), 30 | drupal.modulePath(), 31 | drupal.profilePath() 32 | ] 33 | } 34 | }); 35 | 36 | grunt.config(['symlink', 'libraries'], { 37 | expand: true, 38 | cwd: '<%= config.srcPaths.drupal %>/libraries', 39 | src: ['*'], 40 | dest: drupal.libraryPath() 41 | }); 42 | grunt.config(['symlink', 'modules'], { 43 | src: '<%= config.srcPaths.drupal %>/modules', 44 | dest: path.join(drupal.modulePath(), 'custom') 45 | }); 46 | grunt.config(['symlink', 'profiles'], { 47 | expand: true, 48 | cwd: '<%= config.srcPaths.drupal %>/profiles', 49 | src: ['*'], 50 | dest: drupal.profilePath(), 51 | filter: 'isDirectory' 52 | }); 53 | grunt.config(['symlink', 'sites'], { 54 | expand: true, 55 | cwd: '<%= config.srcPaths.drupal %>/sites', 56 | src: ['*'], 57 | dest: drupal.sitePath(), 58 | filter: function(path) { 59 | return (path !== '<%= config.srcPaths.drupal %>/sites/all'); 60 | } 61 | }); 62 | grunt.config(['symlink', 'themes'], { 63 | src: '<%= config.srcPaths.drupal %>/themes', 64 | dest: path.join(drupal.themePath(), 'custom') 65 | }); 66 | 67 | grunt.task.run([ 68 | 'mkdir:drupal', 69 | 'symlink:profiles', 70 | 'symlink:libraries', 71 | 'symlink:modules', 72 | 'symlink:themes', 73 | 'copy:defaults', 74 | 'clean:sites', 75 | 'symlink:sites', 76 | 'mkdir:files', 77 | 'copy:static' 78 | ]); 79 | 80 | done(); 81 | }); 82 | }); 83 | 84 | require('../lib/help')(grunt).add({ 85 | task: 'scaffold', 86 | group: 'Build Process' 87 | }); 88 | }; 89 | -------------------------------------------------------------------------------- /tasks/serve.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define tasks to quickly get Drupal running. 4 | * 5 | * grunt serve 6 | * Run Drupal via the PHP webserver and activate watch tasks. 7 | * grunt serve:demo 8 | * Run Drupal via the PHP webserver without running watch tasks. 9 | * grunt serve:test 10 | * Run Drupal via the PHP webserver without running watch tasks or opening 11 | * the browser. 12 | */ 13 | grunt.loadNpmTasks('grunt-drush'); 14 | 15 | var Help = require('../lib/help')(grunt); 16 | var Drupal = require('../lib/drupal')(grunt); 17 | 18 | var path = require('path'); 19 | var _ = require('lodash'); 20 | 21 | // If no path is configured for Drush, fallback to the system path. 22 | var cmd = {cmd: Drupal.drushPath()}; 23 | 24 | var profile = grunt.config('config.project.profile') || 'standard'; 25 | 26 | grunt.config(['drush', 'liteinstall'], { 27 | args: ['site-install', '-yv', profile, '--db-url=sqlite:/' + 28 | path.join(path.resolve(grunt.config('config.buildPaths.build')), 29 | 'drupal.sqlite')], 30 | options: _.extend({ 31 | cwd: '<%= config.buildPaths.html %>' 32 | }, cmd) 33 | }); 34 | 35 | grunt.config(['drush', 'serve'], { 36 | args: ['runserver', ':' + (grunt.config('config.serve.port') || 8080)], 37 | options: _.extend({ 38 | cwd: '<%= config.buildPaths.html %>' 39 | }, cmd) 40 | }); 41 | 42 | grunt.registerTask('serve', 'Run Drupal using the PHP built-in webserver. ' + 43 | 'Use serve:demo to run without watch tasks. Use serve:test for G-D-T ' + 44 | 'development.', function() { 45 | if (this.args[0] !== 'test') { 46 | // Unlike the test version, this one allows port overriding and opens 47 | // the site in a new tab. 48 | grunt.config('drush.serve.args', ['runserver', ':' + 49 | (grunt.config('config.serve.port') || 8080) + '/']); 50 | } 51 | 52 | if (this.args.length) { 53 | grunt.task.run('drush:serve'); 54 | } else { 55 | grunt.loadNpmTasks('grunt-concurrent'); 56 | 57 | // Allow overriding the tasks run concurrently to the Drupal server. 58 | var serveTasks = grunt.config('config.serve.concurrent'); 59 | if (!serveTasks) { 60 | serveTasks = ['watch-test']; 61 | if (grunt.task.exists('watch-theme')) { 62 | serveTasks.push('watch-theme'); 63 | } 64 | } 65 | serveTasks.push('drush:serve'); 66 | 67 | grunt.config('concurrent.serve', { 68 | tasks: serveTasks, 69 | options: { 70 | logConcurrentOutput: true 71 | } 72 | }); 73 | 74 | grunt.task.run('concurrent:serve'); 75 | } 76 | }); 77 | 78 | Help.add([ 79 | { 80 | task: 'serve', 81 | group: 'Operations' 82 | } 83 | ]); 84 | }; 85 | -------------------------------------------------------------------------------- /tasks/test.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | var Drupal = require('../lib/drupal')(grunt); 3 | var Help = require('../lib/help')(grunt); 4 | 5 | grunt.config('test', { 6 | behat: true, 7 | drupal: { 8 | command: [] 9 | } 10 | }); 11 | 12 | // @todo Would rather display as 'alias for test:behat, test:drupal, etc'. 13 | grunt.registerMultiTask('test', 'Run all configured tests.', function() { 14 | var self = this; 15 | var done = this.async(); 16 | 17 | Drupal.loadDrushStatus(function(err) { 18 | if (err) { 19 | grunt.fail.fatal('Cannot load Drush status for built Drupal docroot.\n\n'); 20 | return done(); 21 | } 22 | 23 | // Only run Drupal tests on D8 and higher. For D7, there is no option to 24 | // run tests in a directory, so projects wishing to do this would add a 25 | // separate task and hard-code the test groups that need to run. 26 | var majorVersion = Drupal.majorVersion(); 27 | if (majorVersion !== 8 && self.target === 'drupal') { 28 | grunt.verbose.write('Running Drupal tests by directory is ' + 29 | 'unsupported in Drupal 7. Create a custom task to run test groups ' + 30 | 'as needed.'); 31 | return done(); 32 | } 33 | 34 | var script = './core/scripts/run-tests.sh'; 35 | var testLocation = './modules/custom'; 36 | var domain = process.env.GDT_DOMAIN || grunt.config('config.domain') || 37 | require('os').hostname(); 38 | 39 | grunt.loadNpmTasks('grunt-shell'); 40 | grunt.config(['shell', 'testdrupal'], { 41 | command: grunt.config.get('config.testing.drupal.command') || 'php ' + 42 | script + ' --verbose --color --concurrency 4 --directory ' + 43 | testLocation + ' --url ' + domain + ' --php `which php`', 44 | options: { 45 | execOptions: { 46 | cwd: '<%= config.buildPaths.html %>' 47 | } 48 | } 49 | }); 50 | 51 | switch (self.target) { 52 | case 'behat': 53 | grunt.task.run('behat'); 54 | break; 55 | 56 | case 'drupal': 57 | grunt.task.run('shell:testdrupal'); 58 | break; 59 | 60 | default: 61 | } 62 | 63 | done(); 64 | }); 65 | }); 66 | 67 | Help.add({ 68 | task: 'test', 69 | group: 'Testing & Code Quality', 70 | description: 'Run custom module and Behat tests included with this project.' 71 | }); 72 | }; 73 | -------------------------------------------------------------------------------- /tasks/theme.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define "compile-theme" task. 4 | * 5 | * Individual themes "register" with grunt by adding a configuration object 6 | * the themes section of configuration. Each theme may specify a non-default 7 | * path. From there they may specify commands that should be passed to scripts 8 | * provided with the theme. 9 | * 10 | * Example: 11 | * 12 | * "themes": { 13 | * // Legionare relies on the default path and attaches a task to our 14 | * // `compile-theme` task. 15 | * "legionaire": { 16 | * "scripts": { 17 | * "compile-theme": "grunt compile" 18 | * } 19 | * }, 20 | * // Gladiator specifies a path and a validate task to be run by 21 | * // `grunt themes:gladiator:validate`. 22 | * "gladiator": { 23 | * "path": "<%= config.srcPaths.drupal %>/themes/gladiator", 24 | * "scripts": { 25 | * "validate": "gulp eslint", 26 | * } 27 | * }, 28 | * // Trojan does not specify a path or define functionality. It is a stub 29 | * // the system will recognize and list as part of `grunt themes`. 30 | * "trojan": { 31 | * } 32 | * } 33 | */ 34 | var config = grunt.config.get('config'); 35 | var steps = []; 36 | 37 | if (config.themes) { 38 | var Help = require('../lib/help')(grunt); 39 | 40 | for (var key in config.themes) { 41 | if (config.themes.hasOwnProperty(key)) { 42 | var theme = config.themes[key]; 43 | 44 | // If the compile-theme delegation script is truthy trigger it as part 45 | // of the compile theme task. This ensures developers not working 46 | // directly with the theme and CI systems can still use the primary 47 | // grunt implementation as a single authority for the build process. 48 | if (theme.scripts && theme.scripts['compile-theme']) { 49 | steps.push('themes:' + key + ':compile-theme'); 50 | } 51 | } 52 | } 53 | 54 | if (steps) { 55 | grunt.registerTask('compile-theme', steps); 56 | Help.add({ 57 | task: 'compile-theme', 58 | group: 'Asset & Code Compilation', 59 | description: 'Run compilers for all themes.' 60 | }); 61 | } 62 | 63 | // Register a passthru task that calls out to theme-specific Grunt tooling. 64 | grunt.registerTask('themes', 'Proxies tasks to theme-specific, pre-defined command-line operations.', function() { 65 | var themes = grunt.config('config.themes'); 66 | if (!this.args[0]) { 67 | var _ = require('lodash'); 68 | _.each(themes, function(theme, key) { 69 | grunt.log.writeln(key + ' (' + theme.path + ')'); 70 | }); 71 | } 72 | 73 | var theme = this.args[0]; 74 | this.requiresConfig(['config', 'themes', theme, 'path']); 75 | var scripts = require('../lib/scripts')(grunt); 76 | var task = scripts.handle(themes[theme], this.args[1], 'theme'); 77 | 78 | if (task) { 79 | grunt.task.run(task); 80 | } 81 | }); 82 | 83 | Help.add({ 84 | task: 'themes', 85 | group: 'Utilities' 86 | }); 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /tasks/watch.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | /** 3 | * Define "watch" tasks. 4 | * 5 | * Add a watch task that automatically runs the test suite when a file in 6 | * the Drupal docroot changes (except for files in sites/.../files) or when 7 | * a file in the testing features directory changes. 8 | */ 9 | grunt.loadNpmTasks('grunt-contrib-watch'); 10 | var Help = require('../lib/help')(grunt); 11 | 12 | grunt.config(['watch', 'test'], { 13 | files: [ 14 | '<%= config.srcPaths.drupal %>/**/*', 15 | '!<%= config.srcPaths.drupal %>/sites/*/files/**/*', 16 | 'features/**/*' 17 | ], 18 | tasks: ['test'] 19 | }); 20 | grunt.config(['watch', 'validate'], { 21 | files: [ 22 | '<%= config.srcPaths.drupal %>/**/*', 23 | '!<%= config.srcPaths.drupal %>/**/*.features.*inc', 24 | '!<%= config.srcPaths.drupal %>/sites/**' 25 | ], 26 | tasks: ['validate:newer'] 27 | }); 28 | 29 | grunt.config(['concurrent', 'watch-test'], { 30 | tasks: ['watch:validate', 'watch:test'], 31 | options: { 32 | logConcurrentOutput: true 33 | } 34 | }); 35 | 36 | grunt.registerTask('watch-test', 'Backend operations watch task.', 37 | function() { 38 | grunt.loadNpmTasks('grunt-concurrent'); 39 | grunt.task.run('concurrent:watch-test'); 40 | }); 41 | 42 | Help.add({ 43 | task: 'watch-test', 44 | group: 'Real-time Tooling', 45 | description: 'Watch for changes that should trigger new testing and validation of the codebase.' 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /test/build.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, before, after */ 2 | 3 | var assert = require('assert'); 4 | var fs = require('fs-extra'); 5 | var exec = require('child_process').exec; 6 | 7 | describe('grunt', function() { 8 | describe('default', function() { 9 | var drupalCore = process.env.GDT_DRUPAL_CORE; 10 | 11 | // Ensure the vendor directory exists. 12 | it('it should create the vendor directory', function(done) { 13 | fs.exists('vendor', function(exists) { 14 | assert.ok(exists); 15 | done(); 16 | }); 17 | }); 18 | 19 | // Ensure the build/html directory exists. 20 | it('it should create the build/html directory', function(done) { 21 | fs.exists('build/html', function(exists) { 22 | assert.ok(exists); 23 | done(); 24 | }); 25 | }); 26 | 27 | // Ensure build/html/index.php exists and that index.php can be executed 28 | // with PHP. 29 | it('it should build a runnable Drupal docroot', function(done) { 30 | fs.exists('build/html/index.php', function(exists) { 31 | assert.ok(exists); 32 | 33 | if (exists) { 34 | process.chdir('build/html'); 35 | exec('php index.php', function(error) { 36 | var testDrupalRan = (error === null); 37 | process.chdir('../..'); 38 | assert.ok(testDrupalRan, "Drupal's index.php could be executed with PHP"); 39 | done(); 40 | }); 41 | } else { 42 | done(); 43 | } 44 | }); 45 | }); 46 | 47 | // Ensure there is a symlink to src/modules. 48 | var modulesSrc = (drupalCore === '8') ? '../../../src/modules' : '../../../../../src/modules'; 49 | var modulesDest = (drupalCore === '8') ? 'build/html/modules/custom' : 'build/html/sites/all/modules/custom'; 50 | it('it should link the ' + modulesDest + ' directory', function(done) { 51 | fs.lstat(modulesDest, function(err, stats) { 52 | assert.ok(!err); 53 | assert.ok(stats.isSymbolicLink()); 54 | 55 | if (stats.isSymbolicLink()) { 56 | fs.readlink(modulesDest, function(err, linkString) { 57 | assert.ok(!err); 58 | assert.equal(linkString, modulesSrc); 59 | done(); 60 | }); 61 | } else { 62 | done(); 63 | } 64 | }); 65 | }); 66 | 67 | // Ensure there is a symlink for libs in src/libraries. 68 | var librariesSrc = (drupalCore === '8') ? '../../../src/libraries' : '../../../../../src/libraries'; 69 | var exampleLibSrc = librariesSrc + '/example_lib'; 70 | var librariesDest = (drupalCore === '8') ? 'build/html/libraries' : 'build/html/sites/all/libraries'; 71 | var exampleLibDest = librariesDest + '/example_lib'; 72 | it('it should link libraries in the ' + librariesDest + ' directory', function(done) { 73 | fs.lstat(exampleLibDest, function(err, stats) { 74 | assert.ok(!err); 75 | assert.ok(stats.isSymbolicLink()); 76 | 77 | if (stats.isSymbolicLink()) { 78 | fs.readlink(exampleLibDest, function(err, linkString) { 79 | assert.ok(!err); 80 | assert.equal(linkString, exampleLibSrc); 81 | done(); 82 | }); 83 | } else { 84 | done(); 85 | } 86 | }); 87 | }); 88 | 89 | // Ensure a custom library file is available under build. 90 | var librariesBuildDest = (drupalCore === '8') ? 'build/html/libraries/example_lib/example.md' : 'build/html/sites/all/libraries/example_lib/example.md'; 91 | it('custom library file should exist in build', function(done) { 92 | fs.exists(librariesBuildDest, function(exists) { 93 | assert.ok(exists); 94 | done(); 95 | }); 96 | }); 97 | 98 | // Ensure a custom module file is available under build. 99 | var modulesBuildDest = (drupalCore === '8') ? 'build/html/modules/custom/test.php' : 'build/html/sites/all/modules/custom/test.php'; 100 | it('custom module file should exist in build', function(done) { 101 | fs.exists(modulesBuildDest, function(exists) { 102 | assert.ok(exists); 103 | done(); 104 | }); 105 | }); 106 | 107 | // Ensure a custom theme file is available under build. 108 | var themesBuildDest = (drupalCore === '8') ? 'build/html/themes/custom/example_theme/example_theme.info.yml' : 'build/html/sites/all/themes/custom/example_theme/example_theme.info'; 109 | it('custom theme file should exist in build', function(done) { 110 | fs.exists(themesBuildDest, function(exists) { 111 | assert.ok(exists); 112 | done(); 113 | }); 114 | }); 115 | 116 | // Ensure a custom library file is available under package. 117 | var librariesPackageDest = (drupalCore === '8') ? 'build/packages/package/html/libraries/example_lib/example.md' : 'build/packages/package/sites/all/libraries/example_lib/example.md'; 118 | it('custom library file should exist in package', function(done) { 119 | fs.exists(librariesPackageDest, function(exists) { 120 | assert.ok(exists); 121 | done(); 122 | }); 123 | }); 124 | 125 | // Ensure a custom module file is available under package. 126 | var modulesPackageDest = (drupalCore === '8') ? 'build/packages/package/html/modules/custom/test.php' : 'build/packages/package/sites/all/modules/custom/test.php'; 127 | it('custom module file should exist in package', function(done) { 128 | fs.exists(modulesPackageDest, function(exists) { 129 | assert.ok(exists); 130 | done(); 131 | }); 132 | }); 133 | 134 | // Ensure a custom theme file is available under package. 135 | var themesPackageDest = (drupalCore === '8') ? 'build/packages/package/html/themes/custom/example_theme/example_theme.info.yml' : 'build/packages/package/sites/all/themes/custom/example_theme/example_theme.info'; 136 | it('custom theme file should exist in package', function(done) { 137 | fs.exists(themesPackageDest, function(exists) { 138 | assert.ok(exists); 139 | done(); 140 | }); 141 | }); 142 | }); 143 | 144 | describe('Script dispatching', function() { 145 | it('should pass commands along to themes', function(done) { 146 | exec('grunt themes:example_theme:echo', function(error, stdout) { 147 | var status = !error && stdout && stdout.match(/theme\sscripts\srun/)[0] && stdout.match(/run in example_theme/)[0]; 148 | assert.ok(status); 149 | done(); 150 | }); 151 | }); 152 | 153 | it('should run project operation scripts', function(done) { 154 | exec('grunt echo', function(error, stdout) { 155 | var status = !error && stdout && stdout.match(/operational\sscripts\srun/)[0]; 156 | assert.ok(status); 157 | done(); 158 | }); 159 | }); 160 | 161 | it('should run pre-op and post-op scripts', function(done) { 162 | exec('grunt echo', function(error, stdout) { 163 | var status = !error && stdout && stdout.match(/pre-op script/)[0] && stdout.match(/post-op script/)[0]; 164 | assert.ok(status); 165 | done(); 166 | }); 167 | }); 168 | 169 | it('should include project operation scripts in help output', function(done) { 170 | exec('grunt help', function(error, stdout) { 171 | var status = !error && stdout && stdout.match(/Perform\sthe\sconfigured\s"echo"\stask/)[0]; 172 | assert.ok(status); 173 | done(); 174 | }); 175 | }); 176 | }); 177 | 178 | describe('grunt task', function() { 179 | it('"git-setup" may be run to install git hooks.', function(done) { 180 | exec('grunt git-setup', function(error) { 181 | fs.exists('./.git/hooks/pre-commit', function(exists) { 182 | assert.ok(!error && exists); 183 | done(); 184 | }); 185 | }); 186 | }); 187 | }); 188 | 189 | describe('Packaging', function() { 190 | var drupalCore = process.env.GDT_DRUPAL_CORE; 191 | 192 | // Package commands can take excessive time (> 10 minutes for D8). 193 | // Before testing them, remove the bulk of the Drupal codebase. 194 | before(function(done) { 195 | fs.move('build/html/core', 'build/cache/core', function() { 196 | fs.move('build/html/modules', 'build/cache/modules', function() { 197 | done(); 198 | }); 199 | }); 200 | }); 201 | 202 | it('should place the build codebase in build/packages/package by default', function(done) { 203 | this.timeout(180000); 204 | exec('grunt package', function(error) { 205 | var indexPackageDest = (drupalCore === '8') ? 'build/packages/package/html/index.php' : 'build/packages/package/index.php'; 206 | fs.exists(indexPackageDest, function(exists) { 207 | assert.ok(!error && exists); 208 | done(); 209 | }); 210 | }); 211 | }); 212 | 213 | it('should allow override of grunt package destination with --name', function(done) { 214 | this.timeout(180000); 215 | exec('grunt package --name=upstream', function(error) { 216 | var indexPackageDest = (drupalCore === '8') ? 'build/packages/upstream/html/index.php' : 'build/packages/upstream/index.php'; 217 | fs.exists(indexPackageDest, function(exists) { 218 | assert.ok(!error && exists); 219 | done(); 220 | }); 221 | }); 222 | }); 223 | 224 | it('should compress the package with grunt package:compress', function(done) { 225 | this.timeout(180000); 226 | exec('grunt package:compress --name=archive', function(error) { 227 | fs.exists('build/packages/archive.tgz', function(exists) { 228 | assert.ok(!error && exists); 229 | done(); 230 | }); 231 | }); 232 | }); 233 | 234 | it('should only clean the package with the current name', function(done) { 235 | // Two package operations have occurred since this was created. 236 | fs.exists('build/packages/package', function(exists) { 237 | assert.ok(exists); 238 | done(); 239 | }); 240 | }); 241 | 242 | after(function(done) { 243 | fs.move('build/cache/core', 'build/html/core', function() { 244 | fs.move('build/cache/modules', 'build/html/modules', function() { 245 | done(); 246 | }); 247 | }); 248 | }); 249 | }); 250 | }); 251 | -------------------------------------------------------------------------------- /test/create_working_copy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script creates a working copy of grunt-drupal-tasks by initializing a 4 | # project using the example in the test/working_copy directory. 5 | 6 | if [ ! -f "test/create_working_copy.sh" ]; then echo "This script should be run from the grunt-drupal-tasks directory."; exit 1; fi; 7 | 8 | # If an old install exists, reset permissions on src/sites/default. 9 | if [ -d "test/working_copy/src/sites/default" ]; then chmod 755 test/working_copy/src/sites/default/; fi; 10 | 11 | # Initialize the working_copy directory 12 | rm -rf test/working_copy 13 | mkdir test/working_copy 14 | PATH_WORKING_COPY="`pwd`/test/working_copy" 15 | 16 | # Copy the example skeleton into working_copy 17 | cp -r example/. $PATH_WORKING_COPY 18 | 19 | # Copy the test assets into working_copy 20 | PATH_TEST_ASSETS="test/test_assets" 21 | if [[ $GDT_DRUPAL_CORE == "8" ]]; then PATH_TEST_ASSETS="test/test_assets_d8"; fi; 22 | cd $PATH_TEST_ASSETS 23 | for file in `find . -type f`; do mkdir -p $PATH_WORKING_COPY/`dirname $file`; cp $file $PATH_WORKING_COPY/$file; done; 24 | 25 | # Install dependencies of working_copy 26 | cd $PATH_WORKING_COPY 27 | npm install 28 | 29 | # Force installation of the checked out version of grunt-drupal-tasks 30 | npm install ../.. 31 | -------------------------------------------------------------------------------- /test/library.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | var assert = require('assert'); 4 | var util = require('../lib/util'); 5 | 6 | describe('Utility Functions', function() { 7 | describe('provides a capability to determine effective concurrency', function() { 8 | it('should have "concurrency" to recommend a safe limit greater than 2', function() { 9 | var limit = util.concurrency; 10 | assert(limit >= 2, 'concurrency limit not >= 2'); 11 | }); 12 | }); 13 | describe('provides a capability to construct absolute URLs', function() { 14 | it('allows expansion of a root path', function() { 15 | var items = util.expandUrls('http://example.com', ['/']); 16 | assert.equal('http://example.com/', items[0]); 17 | }); 18 | it('allows expansion of multiple paths', function() { 19 | var items = util.expandUrls('http://example.com', ['/a', '/b']); 20 | assert.equal('http://example.com/a', items[0]); 21 | assert.equal('http://example.com/b', items[1]); 22 | }); 23 | it('allows expansion of a base URL with a base path', function() { 24 | var items = util.expandUrls('http://example.com/subsite', ['/a', '/b']); 25 | assert.equal('http://example.com/subsite/a', items[0]); 26 | assert.equal('http://example.com/subsite/b', items[1]); 27 | }); 28 | }); 29 | }); 30 | 31 | describe('Initialization', function() { 32 | var grunt = require('grunt'); 33 | 34 | describe('of magic configuration', function() { 35 | it('should defer to the "GDT" namespace', function(done) { 36 | grunt.config.init({ 37 | config: {testA: 'Enterprise'}, 38 | gdt: {testA: 'Victory'} 39 | }); 40 | process.env.GDT_TEST_A = 'Yamaguchi'; 41 | 42 | var init = require('../lib/init')(grunt); 43 | assert.equal(init.magic('testA'), 'Victory'); 44 | done(); 45 | }); 46 | it('should defer to environment over normal project configuration', function(done) { 47 | grunt.config.init({ 48 | config: {testB: 'Enterprise'} 49 | }); 50 | process.env.GDT_TEST_B = 'Yamaguchi'; 51 | 52 | var init = require('../lib/init')(grunt); 53 | assert.equal(init.magic('test_b'), 'Yamaguchi'); 54 | done(); 55 | }); 56 | it('should fall back to the project configuration', function(done) { 57 | grunt.config.init({ 58 | config: {testC: 'Enterprise'} 59 | }); 60 | 61 | var init = require('../lib/init')(grunt); 62 | assert.equal(init.magic('testC'), 'Enterprise'); 63 | done(); 64 | }); 65 | it('should allow override of the environment variable name', function(done) { 66 | grunt.config.init({ 67 | config: {testD: 'Enterprise'} 68 | }); 69 | process.env.GDT_TEST_DELTA = 'Yamaguchi'; 70 | 71 | var init = require('../lib/init')(grunt); 72 | assert.equal(init.magic('testD', 'test_delta'), 'Yamaguchi'); 73 | done(); 74 | }); 75 | it('should allow override of the environment variable name without affecting priority', function(done) { 76 | grunt.config.init({ 77 | config: {testE: 'Enterprise'} 78 | }); 79 | 80 | var init = require('../lib/init')(grunt); 81 | assert.equal(init.magic('testE', 'test_epsilon'), 'Enterprise'); 82 | done(); 83 | }); 84 | it('should allow a nested value to be overridden', function(done) { 85 | grunt.config.init({ 86 | config: {nested: {item: 'a'}} 87 | }); 88 | 89 | var init = require('../lib/init')(grunt); 90 | assert.equal(init.magic(['nested', 'item'], 'nested_item'), 'a'); 91 | done(); 92 | }); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | run_tests () { 6 | echo "*** Running tests for Drupal $GDT_DRUPAL_CORE ***" 7 | ./test/create_working_copy.sh 8 | cd test/working_copy/ 9 | grunt --quiet 10 | grunt drush:liteinstall --quiet 11 | grunt serve:test >/dev/null & 12 | until curl -I -XGET -s $GDT_TEST_URL 2>/dev/null | egrep -q '^HTTP.*200'; do sleep 0.5; done 13 | grunt test 14 | sleep 1; while (ps aux | grep '[b]ehat' > /dev/null); do sleep 1; done 15 | for pid in `ps aux | grep drush | grep runserver | awk '{print $2}'`; do echo "Stopping drush pid $pid"; kill -SIGINT $pid; done; 16 | grunt package --quiet 17 | # end-to-end tests 18 | mocha --timeout 10000 node_modules/grunt-drupal-tasks/test/build.js 19 | # unit tests 20 | mocha node_modules/grunt-drupal-tasks/test/library.js 21 | } 22 | 23 | # Drupal 8 24 | export GDT_DRUPAL_CORE="8" GDT_TEST_URL="http://127.0.0.1:8080/core/misc/drupal.js" 25 | run_tests 26 | 27 | # Rinse and repeat 28 | cd ../.. 29 | 30 | # Drupal 7 31 | export GDT_DRUPAL_CORE="7" GDT_TEST_URL="http://127.0.0.1:8080/misc/drupal.js" 32 | run_tests 33 | -------------------------------------------------------------------------------- /test/test_assets/Gruntconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "srcPaths": { 3 | "make": "src/project.make", 4 | "drupal": "src" 5 | }, 6 | "siteUrls": { 7 | "default": "http://project.local" 8 | }, 9 | "packages": { 10 | "srcFiles": ["!sites/*/files/**", "!xmlrpc.php", "!modules/php/*"], 11 | "projFiles": ["README*", "bin/**"] 12 | }, 13 | "phpcs": true, 14 | "phpmd": true, 15 | "behat": { 16 | "flags": "--tags ~@wip" 17 | }, 18 | "validate": { 19 | "ignoreError": true 20 | }, 21 | "eslint": true, 22 | "scripts": { 23 | "pre-echo": "echo 'pre-op script'", 24 | "echo": "echo 'operational scripts run'", 25 | "post-echo": "echo 'post-op script'" 26 | }, 27 | "themes": { 28 | "example_theme": { 29 | "path": "<%= config.srcPaths.drupal %>/themes/example_theme", 30 | "scripts": { 31 | "echo": "echo 'theme scripts run in '$(basename $(pwd))" 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/test_assets/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client/project", 3 | "description": "{Project} drupal codebase for {client}.", 4 | "repositories": [ 5 | { 6 | "type": "git", 7 | "url": "https://github.com/arithmetric/MinkZombieDriver" 8 | } 9 | ], 10 | "require-dev": { 11 | "behat/mink-zombie-driver": "dev-feature/test_remove_tough_cookie", 12 | "drupal/drupal-extension": "~3.3.0", 13 | "drush/drush": "^8", 14 | "drupal/coder": "^8.2", 15 | "phpmd/phpmd": "~2.1" 16 | }, 17 | "require": { 18 | "roave/security-advisories": "dev-master" 19 | }, 20 | "conflict": { 21 | "behat/behat": ">= 3.4.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/test_assets/src/libraries/example_lib/example.md: -------------------------------------------------------------------------------- 1 | # Example library 2 | This is just a placeholder file to verify that grunt can symlink libraries into 3 | the appropriate place. 4 | -------------------------------------------------------------------------------- /test/test_assets/src/modules/test.js: -------------------------------------------------------------------------------- 1 | 2 | var test = "hi"; 3 | 4 | if (test == 'hello') { 5 | } 6 | 7 | -------------------------------------------------------------------------------- /test/test_assets/src/modules/test.php: -------------------------------------------------------------------------------- 1 | /themes/example_theme", 34 | "scripts": { 35 | "echo": "echo 'theme scripts run in '$(basename $(pwd))" 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/test_assets_d8/src/libraries/example_lib/example.md: -------------------------------------------------------------------------------- 1 | # Example library 2 | This is just a placeholder file to verify that grunt can symlink libraries into 3 | the appropriate place. 4 | -------------------------------------------------------------------------------- /test/test_assets_d8/src/modules/gdt_test/gdt_test.info.yml: -------------------------------------------------------------------------------- 1 | core: 8.x 2 | name: GDT Test 3 | type: module 4 | -------------------------------------------------------------------------------- /test/test_assets_d8/src/modules/gdt_test/tests/src/Unit/PassTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(TRUE, 'True is true'); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /test/test_assets_d8/src/modules/test.js: -------------------------------------------------------------------------------- 1 | 2 | var test = "hi"; 3 | 4 | if (test == 'hello') { 5 | } 6 | 7 | -------------------------------------------------------------------------------- /test/test_assets_d8/src/modules/test.php: -------------------------------------------------------------------------------- 1 |