global__r – errors in logs

If you’re using WordPress Multisite in a highly scalable environment using HyperDB or LudicrousDB, you may have seen global__r errors in your logs.

Can't select global__r... yada yada yada

The “global” part of “global__r” comes from these database drop-ins defaulting to a “global” dataset if nothing is found or explicitly passed in. The “__r” part comes from looking at databases intended for reading — databases designated as slaves (vs. master databases intended for writing “__w”).

So if a SELECT query is failing, why would that be?

The first and most logical reason is that the database is down. Check to make sure it’s not by attempting to communicate with the database directly via whatever you are most comfortable with (command line, SequalPRO, etc…)

The second most logical reason is that your web server (powering the PHP part of your application) is unable to reach your database server. Check to make sure fail2ban or some other firewall utility hasn’t erroneously blocked things, and then try to manually ping & connect the two servers together to ensure you receive a good response.

The final and less obvious reason this will occur, is harder to track down, and I think might be the source of your error log entries if everything else checks out and you’ve made it to this blog post after scouring the web for answers.

There are two queries that run inside of upgrade_network() and populate_options() respectfully, that try to delete all of the transients for a specific site and a specific network. These two queries are unlikely to get caught by the matching regex used to map database table names to what HyperDB or LudicrousDB use to route queries to their respective servers. They look something like this:

DELETE a, b FROM $wpdb->options a, $wpdb->options b

and

DELETE a, b FROM $wpdb->sitemeta a, $wpdb->sitemeta b

If you search all of WordPress, these are the only two places raw queries like this are done, and they’re only ran under specific conditions where WordPress is cleaning up after itself during a database upgrade. This means the conditions are perfect for a surprise entry in your error logs once in a blue moon when you aren’t hand-holding a huge WordPress multisite/multi-network database upgrade.

How do we prevent these, and what’s the repercussion? The solution is probably a regex fix upstream to these plugins and/or WordPress’s WPDB base class to properly match these queries. The repercussion is transients that don’t get deleted, which isn’t usually a huge problem unless it causes the database upgrade to continuously run; if that was the case, you’d have lots of entries in your error logs.

I have a hunch this issue is exacerbated by object caching plugins that store transients in memory and not in the database. In these types of installations, these raw queries are trying to delete data that never would have been there in the first place.

I’ve also been staring at this code back and forth for a few weeks now, and while there are a lot of moving parts, I haven’t identified any data corruption or loss issues, and these queries are properly escaped and prepared, so it’s unlikely HyperDB or LudicrousDB would introduce anything that might be harmful to existing data.

If you have these issues, hopefully this helps you isolate the root cause to identify whether this is a configuration issue, a caching issue, a communication issue, or an issue with the database itself. If you have more info, I’d love to hear about it in the comments below.❤

Mother Nature’s Toolbox Must Be Heavy

Running npm install for WordPress is a terrifying experience. It installs so many libraries and dependencies, it would take a lifetime to learn them all. If you’ve never had the pleasure, here’s what it looks like today:

WordPress@4.5.0 /Users/johnjamesjacoby/Work/VVV/www/wordpress-develop
├─┬ autoprefixer@6.1.2
│ ├── browserslist@1.0.1
│ ├── caniuse-db@1.0.30000384
│ ├── num2fraction@1.2.2
│ ├─┬ postcss@5.0.14
│ │ ├── js-base64@2.1.9
│ │ ├── source-map@0.5.3
│ │ └─┬ supports-color@3.1.2
│ │   └── has-flag@1.0.0
│ └── postcss-value-parser@3.2.3
├─┬ grunt@0.4.5
│ ├── async@0.1.22
│ ├── coffee-script@1.3.3
│ ├── colors@0.6.2
│ ├── dateformat@1.0.2-1.2.3
│ ├── eventemitter2@0.4.14
│ ├── exit@0.1.2
│ ├─┬ findup-sync@0.1.3
│ │ ├─┬ glob@3.2.11
│ │ │ └── minimatch@0.3.0
│ │ └── lodash@2.4.2
│ ├── getobject@0.1.0
│ ├─┬ glob@3.1.21
│ │ ├── graceful-fs@1.2.3
│ │ └── inherits@1.0.2
│ ├─┬ grunt-legacy-log@0.1.3
│ │ ├─┬ grunt-legacy-log-utils@0.1.1
│ │ │ ├── lodash@2.4.2
│ │ │ └── underscore.string@2.3.3
│ │ ├── lodash@2.4.2
│ │ └── underscore.string@2.3.3
│ ├── hooker@0.2.3
│ ├── iconv-lite@0.2.11
│ ├─┬ js-yaml@2.0.5
│ │ └─┬ argparse@0.1.16
│ │   ├── underscore@1.7.0
│ │   └── underscore.string@2.4.0
│ ├── lodash@0.9.2
│ ├─┬ minimatch@0.2.14
│ │ ├── lru-cache@2.7.3
│ │ └── sigmund@1.0.1
│ ├─┬ nopt@1.0.10
│ │ └── abbrev@1.0.7
│ ├── rimraf@2.2.8
│ ├── underscore.string@2.2.1
│ └── which@1.0.9
├─┬ grunt-browserify@4.0.1
│ ├── async@0.9.2
│ ├─┬ browserify@11.2.0
│ │ ├── assert@1.3.0
│ │ ├─┬ browser-pack@5.0.1
│ │ │ ├─┬ combine-source-map@0.6.1
│ │ │ │ ├── convert-source-map@1.1.3
│ │ │ │ ├─┬ inline-source-map@0.5.0
│ │ │ │ │ └── source-map@0.4.4
│ │ │ │ ├── lodash.memoize@3.0.4
│ │ │ │ └── source-map@0.4.4
│ │ │ └── umd@3.0.1
│ │ ├── browser-resolve@1.11.0
│ │ ├─┬ browserify-zlib@0.1.4
│ │ │ └── pako@0.2.8
│ │ ├─┬ buffer@3.6.0
│ │ │ ├── base64-js@0.0.8
│ │ │ ├── ieee754@1.1.6
│ │ │ └── isarray@1.0.0
│ │ ├── builtins@0.0.7
│ │ ├── commondir@0.0.1
│ │ ├─┬ concat-stream@1.4.10
│ │ │ ├── readable-stream@1.1.13
│ │ │ └── typedarray@0.0.6
│ │ ├─┬ console-browserify@1.1.0
│ │ │ └── date-now@0.1.4
│ │ ├── constants-browserify@0.0.1
│ │ ├─┬ crypto-browserify@3.11.0
│ │ │ ├─┬ browserify-cipher@1.0.0
│ │ │ │ ├─┬ browserify-aes@1.0.5
│ │ │ │ │ └── buffer-xor@1.0.3
│ │ │ │ ├─┬ browserify-des@1.0.0
│ │ │ │ │ └─┬ des.js@1.0.0
│ │ │ │ │   └── minimalistic-assert@1.0.0
│ │ │ │ └── evp_bytestokey@1.0.0
│ │ │ ├─┬ browserify-sign@4.0.0
│ │ │ │ ├── bn.js@4.6.2
│ │ │ │ ├── browserify-rsa@4.0.0
│ │ │ │ ├─┬ elliptic@6.0.2
│ │ │ │ │ ├── brorand@1.0.5
│ │ │ │ │ └── hash.js@1.0.3
│ │ │ │ └─┬ parse-asn1@5.0.0
│ │ │ │   └── asn1.js@4.3.0
│ │ │ ├── create-ecdh@4.0.0
│ │ │ ├─┬ create-hash@1.1.2
│ │ │ │ ├── cipher-base@1.0.2
│ │ │ │ └── ripemd160@1.0.1
│ │ │ ├── create-hmac@1.1.4
│ │ │ ├─┬ diffie-hellman@5.0.0
│ │ │ │ └── miller-rabin@4.0.0
│ │ │ ├── pbkdf2@3.0.4
│ │ │ ├── public-encrypt@4.0.0
│ │ │ └── randombytes@2.0.1
│ │ ├── defined@1.0.0
│ │ ├── deps-sort@1.3.9
│ │ ├── domain-browser@1.1.7
│ │ ├─┬ duplexer2@0.0.2
│ │ │ └── readable-stream@1.1.13
│ │ ├── events@1.0.2
│ │ ├─┬ glob@4.5.3
│ │ │ └── minimatch@2.0.10
│ │ ├─┬ has@1.0.1
│ │ │ └── function-bind@1.0.2
│ │ ├── htmlescape@1.1.0
│ │ ├── https-browserify@0.0.1
│ │ ├── inherits@2.0.1
│ │ ├─┬ insert-module-globals@6.6.3
│ │ │ ├── is-buffer@1.1.1
│ │ │ └─┬ lexical-scope@1.2.0
│ │ │   └── astw@2.0.0
│ │ ├── isarray@0.0.1
│ │ ├─┬ JSONStream@1.0.7
│ │ │ ├── jsonparse@1.2.0
│ │ │ └── through@2.3.8
│ │ ├─┬ labeled-stream-splicer@1.0.2
│ │ │ └─┬ stream-splicer@1.3.2
│ │ │   └── readable-stream@1.1.13
│ │ ├─┬ module-deps@3.9.1
│ │ │ ├── detective@4.3.1
│ │ │ ├── readable-stream@1.1.13
│ │ │ └─┬ stream-combiner2@1.0.2
│ │ │   └─┬ through2@0.5.1
│ │ │     ├── readable-stream@1.0.33
│ │ │     └── xtend@3.0.0
│ │ ├── os-browserify@0.1.2
│ │ ├─┬ parents@1.0.1
│ │ │ └── path-platform@0.11.15
│ │ ├── path-browserify@0.0.0
│ │ ├── process@0.11.2
│ │ ├── punycode@1.4.0
│ │ ├── querystring-es3@0.2.1
│ │ ├─┬ read-only-stream@1.1.1
│ │ │ ├── readable-stream@1.1.13
│ │ │ └─┬ readable-wrap@1.0.0
│ │ │   └── readable-stream@1.1.13
│ │ ├─┬ readable-stream@2.0.5
│ │ │ ├── core-util-is@1.0.2
│ │ │ ├── process-nextick-args@1.0.6
│ │ │ └── util-deprecate@1.0.2
│ │ ├─┬ shasum@1.0.2
│ │ │ ├─┬ json-stable-stringify@0.0.1
│ │ │ │ └── jsonify@0.0.0
│ │ │ └── sha.js@2.4.4
│ │ ├── shell-quote@0.0.1
│ │ ├── stream-browserify@2.0.1
│ │ ├─┬ stream-http@1.7.1
│ │ │ ├── builtin-status-codes@1.0.0
│ │ │ ├── foreach@2.0.5
│ │ │ ├── indexof@0.0.1
│ │ │ └── object-keys@1.0.9
│ │ ├── string_decoder@0.10.31
│ │ ├─┬ subarg@1.0.0
│ │ │ └── minimist@1.2.0
│ │ ├─┬ syntax-error@1.1.4
│ │ │ └── acorn@1.2.2
│ │ ├─┬ through2@1.1.1
│ │ │ └── readable-stream@1.1.13
│ │ ├── timers-browserify@1.4.2
│ │ ├── tty-browserify@0.0.0
│ │ ├─┬ url@0.10.3
│ │ │ ├── punycode@1.3.2
│ │ │ └── querystring@0.2.0
│ │ ├── util@0.10.3
│ │ ├── vm-browserify@0.0.4
│ │ └── xtend@4.0.1
│ ├─┬ glob@5.0.15
│ │ ├─┬ inflight@1.0.4
│ │ │ └── wrappy@1.0.1
│ │ ├─┬ minimatch@3.0.0
│ │ │ └─┬ brace-expansion@1.1.2
│ │ │   ├── balanced-match@0.3.0
│ │ │   └── concat-map@0.0.1
│ │ ├── once@1.3.3
│ │ └── path-is-absolute@1.0.0
│ ├── lodash@3.10.1
│ ├── resolve@1.1.6
│ └─┬ watchify@3.6.1
│   ├─┬ anymatch@1.3.0
│   │ ├── arrify@1.0.1
│   │ └─┬ micromatch@2.3.7
│   │   ├─┬ arr-diff@2.0.0
│   │   │ └── arr-flatten@1.0.1
│   │   ├── array-unique@0.2.1
│   │   ├─┬ braces@1.8.3
│   │   │ ├─┬ expand-range@1.8.1
│   │   │ │ └─┬ fill-range@2.2.3
│   │   │ │   ├── is-number@2.1.0
│   │   │ │   ├── isobject@2.0.0
│   │   │ │   ├── randomatic@1.1.5
│   │   │ │   └── repeat-string@1.5.2
│   │   │ ├── preserve@0.2.0
│   │   │ └── repeat-element@1.1.2
│   │   ├── expand-brackets@0.1.4
│   │   ├─┬ extglob@0.3.1
│   │   │ ├─┬ ansi-green@0.1.1
│   │   │ │ └── ansi-wrap@0.1.0
│   │   │ └── success-symbol@0.1.0
│   │   ├── filename-regex@2.0.0
│   │   ├── is-extglob@1.0.0
│   │   ├── kind-of@3.0.2
│   │   ├── normalize-path@2.0.1
│   │   ├─┬ object.omit@2.0.0
│   │   │ ├─┬ for-own@0.1.3
│   │   │ │ └── for-in@0.1.4
│   │   │ └── is-extendable@0.1.1
│   │   ├─┬ parse-glob@3.0.4
│   │   │ ├── glob-base@0.3.0
│   │   │ └── is-dotfile@1.0.2
│   │   └─┬ regex-cache@0.4.2
│   │     ├── is-equal-shallow@0.1.3
│   │     └── is-primitive@2.0.0
│   ├─┬ browserify@12.0.1
│   │ ├─┬ browser-pack@6.0.1
│   │ │ └─┬ combine-source-map@0.7.1
│   │ │   ├── inline-source-map@0.6.1
│   │ │   └── source-map@0.4.2
│   │ ├── concat-stream@1.5.1
│   │ ├── constants-browserify@1.0.0
│   │ ├── deps-sort@2.0.0
│   │ ├── duplexer2@0.1.4
│   │ ├── events@1.1.0
│   │ ├─┬ glob@5.0.15
│   │ │ └── minimatch@3.0.0
│   │ ├── insert-module-globals@7.0.1
│   │ ├─┬ labeled-stream-splicer@2.0.0
│   │ │ └── stream-splicer@2.0.0
│   │ ├─┬ module-deps@4.0.5
│   │ │ └── stream-combiner2@1.1.1
│   │ ├── read-only-stream@2.0.0
│   │ ├── shell-quote@1.4.3
│   │ ├── stream-http@2.0.2
│   │ └─┬ url@0.11.0
│   │   └── punycode@1.3.2
│   ├─┬ chokidar@1.4.2
│   │ ├── async-each@0.1.6
│   │ ├─┬ fsevents@1.0.6
│   │ │ └─┬ node-pre-gyp@0.6.17
│   │ │   ├─┬ mkdirp@0.5.1
│   │ │   │ └── minimist@0.0.8
│   │ │   ├─┬ nopt@3.0.6
│   │ │   │ └── abbrev@1.0.7
│   │ │   ├─┬ npmlog@2.0.0
│   │ │   │ ├── ansi@0.3.0
│   │ │   │ ├─┬ are-we-there-yet@1.0.4
│   │ │   │ │ ├── delegates@0.1.0
│   │ │   │ │ └─┬ readable-stream@1.1.13
│   │ │   │ │   ├── core-util-is@1.0.2
│   │ │   │ │   ├── isarray@0.0.1
│   │ │   │ │   └── string_decoder@0.10.31
│   │ │   │ └─┬ gauge@1.2.2
│   │ │   │   ├── has-unicode@1.0.1
│   │ │   │   ├─┬ lodash.pad@3.1.1
│   │ │   │   │ ├── lodash._basetostring@3.0.1
│   │ │   │   │ └─┬ lodash._createpadding@3.6.1
│   │ │   │   │   └── lodash.repeat@3.0.1
│   │ │   │   ├── lodash.padleft@3.1.1
│   │ │   │   └── lodash.padright@3.1.1
│   │ │   ├─┬ rc@1.1.5
│   │ │   │ ├── ini@1.3.4
│   │ │   │ ├── minimist@1.2.0
│   │ │   │ └── strip-json-comments@1.0.4
│   │ │   ├─┬ request@2.67.0
│   │ │   │ ├── aws-sign2@0.6.0
│   │ │   │ ├─┬ bl@1.0.0
│   │ │   │ │ └─┬ readable-stream@2.0.4
│   │ │   │ │   ├── core-util-is@1.0.2
│   │ │   │ │   ├── inherits@2.0.1
│   │ │   │ │   ├── isarray@0.0.1
│   │ │   │ │   ├── string_decoder@0.10.31
│   │ │   │ │   └── util-deprecate@1.0.2
│   │ │   │ ├── caseless@0.11.0
│   │ │   │ ├─┬ combined-stream@1.0.5
│   │ │   │ │ └── delayed-stream@1.0.0
│   │ │   │ ├── extend@3.0.0
│   │ │   │ ├── forever-agent@0.6.1
│   │ │   │ ├── form-data@1.0.0-rc3
│   │ │   │ ├─┬ har-validator@2.0.3
│   │ │   │ │ ├─┬ chalk@1.1.1
│   │ │   │ │ │ ├── ansi-styles@2.1.0
│   │ │   │ │ │ ├─┬ has-ansi@2.0.0
│   │ │   │ │ │ │ └── ansi-regex@2.0.0
│   │ │   │ │ │ ├── strip-ansi@3.0.0
│   │ │   │ │ │ └── supports-color@2.0.0
│   │ │   │ │ ├─┬ commander@2.9.0
│   │ │   │ │ │ └── graceful-readlink@1.0.1
│   │ │   │ │ ├─┬ is-my-json-valid@2.12.3
│   │ │   │ │ │ ├── generate-function@2.0.0
│   │ │   │ │ │ ├─┬ generate-object-property@1.2.0
│   │ │   │ │ │ │ └── is-property@1.0.2
│   │ │   │ │ │ ├── jsonpointer@2.0.0
│   │ │   │ │ │ └── xtend@4.0.1
│   │ │   │ │ └─┬ pinkie-promise@2.0.0
│   │ │   │ │   └── pinkie@2.0.1
│   │ │   │ ├─┬ hawk@3.1.2
│   │ │   │ │ ├── boom@2.10.1
│   │ │   │ │ ├── cryptiles@2.0.5
│   │ │   │ │ ├── hoek@2.16.3
│   │ │   │ │ └── sntp@1.0.9
│   │ │   │ ├─┬ http-signature@1.1.0
│   │ │   │ │ ├── assert-plus@0.1.5
│   │ │   │ │ ├─┬ jsprim@1.2.2
│   │ │   │ │ │ ├── extsprintf@1.0.2
│   │ │   │ │ │ ├── json-schema@0.2.2
│   │ │   │ │ │ └── verror@1.3.6
│   │ │   │ │ └─┬ sshpk@1.7.0
│   │ │   │ │   ├── asn1@0.2.3
│   │ │   │ │   ├── assert-plus@0.2.0
│   │ │   │ │   ├── ecc-jsbn@0.1.1
│   │ │   │ │   ├── jodid25519@1.0.2
│   │ │   │ │   └── jsbn@0.1.0
│   │ │   │ ├── is-typedarray@1.0.0
│   │ │   │ ├── isstream@0.1.2
│   │ │   │ ├── json-stringify-safe@5.0.1
│   │ │   │ ├── node-uuid@1.4.7
│   │ │   │ ├── oauth-sign@0.8.0
│   │ │   │ ├── qs@5.2.0
│   │ │   │ ├── stringstream@0.0.5
│   │ │   │ └── tough-cookie@2.2.1
│   │ │   ├─┬ rimraf@2.4.4
│   │ │   │ └─┬ glob@5.0.15
│   │ │   │   ├─┬ inflight@1.0.4
│   │ │   │   │ └── wrappy@1.0.1
│   │ │   │   ├── inherits@2.0.1
│   │ │   │   ├─┬ minimatch@3.0.0
│   │ │   │   │ └─┬ brace-expansion@1.1.1
│   │ │   │   │   └── concat-map@0.0.1
│   │ │   │   ├─┬ once@1.3.3
│   │ │   │   │ └── wrappy@1.0.1
│   │ │   │   └── path-is-absolute@1.0.0
│   │ │   ├── semver@5.1.0
│   │ │   ├─┬ tar@2.2.1
│   │ │   │ ├── block-stream@0.0.8
│   │ │   │ ├── fstream@1.0.8
│   │ │   │ └── inherits@2.0.1
│   │ │   └─┬ tar-pack@3.1.0
│   │ │     ├── debug@0.7.4
│   │ │     ├─┬ fstream-ignore@1.0.3
│   │ │     │ └─┬ minimatch@3.0.0
│   │ │     │   └─┬ brace-expansion@1.1.1
│   │ │     │     └── concat-map@0.0.1
│   │ │     ├── graceful-fs@4.1.2
│   │ │     ├─┬ readable-stream@1.0.33
│   │ │     │ ├── core-util-is@1.0.2
│   │ │     │ ├── inherits@2.0.1
│   │ │     │ ├── isarray@0.0.1
│   │ │     │ └── string_decoder@0.10.31
│   │ │     └── rimraf@2.2.8
│   │ ├── glob-parent@2.0.0
│   │ ├─┬ is-binary-path@1.0.1
│   │ │ └── binary-extensions@1.4.0
│   │ ├── is-glob@2.0.1
│   │ └─┬ readdirp@2.0.0
│   │   ├── graceful-fs@4.1.2
│   │   └── minimatch@2.0.10
│   ├─┬ outpipe@1.1.1
│   │ └─┬ shell-quote@1.4.3
│   │   ├── array-filter@0.0.1
│   │   ├── array-map@0.0.0
│   │   └── array-reduce@0.0.0
│   └── through2@2.0.0
├── grunt-contrib-clean@0.6.0
├─┬ grunt-contrib-compress@0.14.0
│ ├─┬ archiver@0.16.0
│ │ ├── async@1.4.2
│ │ ├── buffer-crc32@0.2.5
│ │ ├─┬ glob@5.0.15
│ │ │ └── minimatch@3.0.0
│ │ ├─┬ lazystream@0.1.0
│ │ │ └── readable-stream@1.0.33
│ │ ├── lodash@3.10.1
│ │ ├── readable-stream@1.0.33
│ │ ├─┬ tar-stream@1.2.2
│ │ │ ├── bl@1.0.0
│ │ │ └── end-of-stream@1.1.0
│ │ └─┬ zip-stream@0.6.0
│ │   ├─┬ compress-commons@0.3.0
│ │   │ ├─┬ crc32-stream@0.3.4
│ │   │ │ └── readable-stream@1.0.33
│ │   │ ├── node-int64@0.4.0
│ │   │ └── readable-stream@1.0.33
│ │   ├── lodash@3.10.1
│ │   └── readable-stream@1.0.33
│ ├─┬ chalk@1.1.1
│ │ ├── ansi-styles@2.1.0
│ │ ├── escape-string-regexp@1.0.4
│ │ ├─┬ has-ansi@2.0.0
│ │ │ └── ansi-regex@2.0.0
│ │ ├── strip-ansi@3.0.0
│ │ └── supports-color@2.0.0
│ └─┬ pretty-bytes@2.0.1
│   ├── get-stdin@4.0.1
│   ├─┬ meow@3.7.0
│   │ ├─┬ camelcase-keys@2.0.0
│   │ │ └── camelcase@2.0.1
│   │ ├── decamelize@1.1.2
│   │ ├─┬ loud-rejection@1.2.0
│   │ │ └── signal-exit@2.1.2
│   │ ├── map-obj@1.0.1
│   │ ├─┬ normalize-package-data@2.3.5
│   │ │ ├── hosted-git-info@2.1.4
│   │ │ ├─┬ is-builtin-module@1.0.0
│   │ │ │ └── builtin-modules@1.1.1
│   │ │ └─┬ validate-npm-package-license@3.0.1
│   │ │   ├─┬ spdx-correct@1.0.2
│   │ │   │ └── spdx-license-ids@1.1.0
│   │ │   └─┬ spdx-expression-parse@1.0.2
│   │ │     └── spdx-exceptions@1.0.4
│   │ ├─┬ read-pkg-up@1.0.1
│   │ │ ├─┬ find-up@1.1.0
│   │ │ │ └── path-exists@2.1.0
│   │ │ └─┬ read-pkg@1.1.0
│   │ │   ├─┬ load-json-file@1.1.0
│   │ │   │ ├── graceful-fs@4.1.2
│   │ │   │ ├─┬ parse-json@2.2.0
│   │ │   │ │ └─┬ error-ex@1.3.0
│   │ │   │ │   └── is-arrayish@0.2.1
│   │ │   │ └── pify@2.3.0
│   │ │   └─┬ path-type@1.1.0
│   │ │     └── graceful-fs@4.1.2
│   │ ├─┬ redent@1.0.0
│   │ │ ├─┬ indent-string@2.1.0
│   │ │ │ └─┬ repeating@2.0.0
│   │ │ │   └── is-finite@1.0.1
│   │ │ └── strip-indent@1.0.1
│   │ └── trim-newlines@1.0.0
│   └── number-is-nan@1.0.0
├─┬ grunt-contrib-concat@0.5.1
│ ├─┬ chalk@0.5.1
│ │ ├── ansi-styles@1.1.0
│ │ ├─┬ has-ansi@0.1.0
│ │ │ └── ansi-regex@0.2.1
│ │ ├── strip-ansi@0.3.0
│ │ └── supports-color@0.2.0
│ └─┬ source-map@0.3.0
│   └── amdefine@1.0.0
├─┬ grunt-contrib-copy@0.8.2
│ └── file-sync-cmp@0.1.1
├─┬ grunt-contrib-cssmin@0.14.0
│ ├─┬ clean-css@3.4.9
│ │ ├─┬ commander@2.8.1
│ │ │ └── graceful-readlink@1.0.1
│ │ └── source-map@0.4.4
│ └─┬ maxmin@1.1.0
│   ├── figures@1.4.0
│   ├── gzip-size@1.0.0
│   └── pretty-bytes@1.0.4
├─┬ grunt-contrib-imagemin@1.0.0
│ ├── async@0.9.2
│ ├── gulp-rename@1.2.2
│ ├─┬ imagemin@4.0.0
│ │ ├─┬ buffer-to-vinyl@1.1.0
│ │ │ ├── file-type@3.4.0
│ │ │ ├── uuid@2.0.1
│ │ │ └─┬ vinyl@1.1.0
│ │ │   ├── clone@1.0.2
│ │ │   ├── clone-stats@0.0.1
│ │ │   └── replace-ext@0.0.1
│ │ ├─┬ imagemin-gifsicle@4.2.0
│ │ │ ├─┬ gifsicle@3.0.3
│ │ │ │ ├─┬ bin-build@2.2.0
│ │ │ │ │ ├── archive-type@3.2.0
│ │ │ │ │ ├─┬ decompress@3.0.0
│ │ │ │ │ │ ├─┬ decompress-tar@3.1.0
│ │ │ │ │ │ │ ├── is-tar@1.0.0
│ │ │ │ │ │ │ ├── object-assign@2.1.1
│ │ │ │ │ │ │ ├─┬ strip-dirs@1.1.1
│ │ │ │ │ │ │ │ ├── is-natural-number@2.0.0
│ │ │ │ │ │ │ │ └── sum-up@1.0.2
│ │ │ │ │ │ │ ├─┬ through2@0.6.5
│ │ │ │ │ │ │ │ └── readable-stream@1.0.33
│ │ │ │ │ │ │ └─┬ vinyl@0.4.6
│ │ │ │ │ │ │   └── clone@0.2.0
│ │ │ │ │ │ ├─┬ decompress-tarbz2@3.1.0
│ │ │ │ │ │ │ ├── is-bzip2@1.0.0
│ │ │ │ │ │ │ ├── object-assign@2.1.1
│ │ │ │ │ │ │ ├── seek-bzip@1.0.5
│ │ │ │ │ │ │ ├─┬ through2@0.6.5
│ │ │ │ │ │ │ │ └── readable-stream@1.0.33
│ │ │ │ │ │ │ └─┬ vinyl@0.4.6
│ │ │ │ │ │ │   └── clone@0.2.0
│ │ │ │ │ │ ├─┬ decompress-targz@3.1.0
│ │ │ │ │ │ │ ├── is-gzip@1.0.0
│ │ │ │ │ │ │ ├── object-assign@2.1.1
│ │ │ │ │ │ │ ├─┬ through2@0.6.5
│ │ │ │ │ │ │ │ └── readable-stream@1.0.33
│ │ │ │ │ │ │ └─┬ vinyl@0.4.6
│ │ │ │ │ │ │   └── clone@0.2.0
│ │ │ │ │ │ ├─┬ decompress-unzip@3.4.0
│ │ │ │ │ │ │ ├── is-zip@1.0.0
│ │ │ │ │ │ │ ├── stat-mode@0.2.1
│ │ │ │ │ │ │ ├── through2@2.0.0
│ │ │ │ │ │ │ └─┬ yauzl@2.4.1
│ │ │ │ │ │ │   └─┬ fd-slicer@1.0.1
│ │ │ │ │ │ │     └── pend@1.2.0
│ │ │ │ │ │ ├─┬ stream-combiner2@1.1.1
│ │ │ │ │ │ │ └── duplexer2@0.1.4
│ │ │ │ │ │ └── vinyl-assign@1.2.1
│ │ │ │ │ ├─┬ download@4.4.3
│ │ │ │ │ │ ├─┬ caw@1.2.0
│ │ │ │ │ │ │ ├─┬ get-proxy@1.0.1
│ │ │ │ │ │ │ │ └─┬ rc@0.5.5
│ │ │ │ │ │ │ │   ├── deep-extend@0.2.11
│ │ │ │ │ │ │ │   ├── minimist@0.0.10
│ │ │ │ │ │ │ │   └── strip-json-comments@0.1.3
│ │ │ │ │ │ │ ├── is-obj@1.0.0
│ │ │ │ │ │ │ └── object-assign@3.0.0
│ │ │ │ │ │ ├─┬ filenamify@1.2.0
│ │ │ │ │ │ │ ├── filename-reserved-regex@1.0.0
│ │ │ │ │ │ │ ├── strip-outer@1.0.0
│ │ │ │ │ │ │ └── trim-repeated@1.0.0
│ │ │ │ │ │ ├─┬ got@5.3.0
│ │ │ │ │ │ │ ├─┬ create-error-class@2.0.1
│ │ │ │ │ │ │ │ └── capture-stack-trace@1.0.0
│ │ │ │ │ │ │ ├── is-plain-obj@1.1.0
│ │ │ │ │ │ │ ├── is-redirect@1.0.0
│ │ │ │ │ │ │ ├── lowercase-keys@1.0.0
│ │ │ │ │ │ │ ├── node-status-codes@1.0.0
│ │ │ │ │ │ │ ├── timed-out@2.0.0
│ │ │ │ │ │ │ ├── unzip-response@1.0.0
│ │ │ │ │ │ │ └─┬ url-parse-lax@1.0.0
│ │ │ │ │ │ │   └── prepend-http@1.0.3
│ │ │ │ │ │ ├─┬ gulp-decompress@1.2.0
│ │ │ │ │ │ │ └─┬ gulp-util@3.0.7
│ │ │ │ │ │ │   ├── array-differ@1.0.0
│ │ │ │ │ │ │   ├── array-uniq@1.0.2
│ │ │ │ │ │ │   ├── beeper@1.1.0
│ │ │ │ │ │ │   ├── dateformat@1.0.12
│ │ │ │ │ │ │   ├─┬ fancy-log@1.1.0
│ │ │ │ │ │ │   │ └── dateformat@1.0.12
│ │ │ │ │ │ │   ├─┬ gulplog@1.0.0
│ │ │ │ │ │ │   │ └── glogg@1.0.0
│ │ │ │ │ │ │   ├─┬ has-gulplog@0.1.0
│ │ │ │ │ │ │   │ └── sparkles@1.0.0
│ │ │ │ │ │ │   ├── lodash._reescape@3.0.0
│ │ │ │ │ │ │   ├── lodash._reevaluate@3.0.0
│ │ │ │ │ │ │   ├── lodash._reinterpolate@3.0.0
│ │ │ │ │ │ │   ├─┬ lodash.template@3.6.2
│ │ │ │ │ │ │   │ ├── lodash._basecopy@3.0.1
│ │ │ │ │ │ │   │ ├── lodash._basevalues@3.0.0
│ │ │ │ │ │ │   │ ├── lodash._isiterateecall@3.0.9
│ │ │ │ │ │ │   │ ├── lodash.escape@3.0.0
│ │ │ │ │ │ │   │ ├─┬ lodash.keys@3.1.2
│ │ │ │ │ │ │   │ │ ├── lodash._getnative@3.9.1
│ │ │ │ │ │ │   │ │ ├── lodash.isarguments@3.0.4
│ │ │ │ │ │ │   │ │ └── lodash.isarray@3.0.4
│ │ │ │ │ │ │   │ ├── lodash.restparam@3.6.1
│ │ │ │ │ │ │   │ └── lodash.templatesettings@3.1.0
│ │ │ │ │ │ │   ├── multipipe@0.1.2
│ │ │ │ │ │ │   ├── object-assign@3.0.0
│ │ │ │ │ │ │   ├── through2@2.0.0
│ │ │ │ │ │ │   └── vinyl@0.5.3
│ │ │ │ │ │ ├── is-url@1.2.1
│ │ │ │ │ │ ├─┬ read-all-stream@3.0.1
│ │ │ │ │ │ │ └─┬ pinkie-promise@1.0.0
│ │ │ │ │ │ │   └── pinkie@1.0.0
│ │ │ │ │ │ ├─┬ stream-combiner2@1.1.1
│ │ │ │ │ │ │ └── duplexer2@0.1.4
│ │ │ │ │ │ └─┬ ware@1.3.0
│ │ │ │ │ │   └─┬ wrap-fn@0.1.4
│ │ │ │ │ │     └── co@3.1.0
│ │ │ │ │ ├─┬ exec-series@1.0.2
│ │ │ │ │ │ └── async-each-series@1.1.0
│ │ │ │ │ └─┬ url-regex@3.1.0
│ │ │ │ │   └── ip-regex@1.0.3
│ │ │ │ ├─┬ bin-wrapper@3.0.2
│ │ │ │ │ ├─┬ bin-check@2.0.0
│ │ │ │ │ │ └── executable@1.1.0
│ │ │ │ │ ├─┬ bin-version-check@2.1.0
│ │ │ │ │ │ ├─┬ bin-version@1.0.4
│ │ │ │ │ │ │ └─┬ find-versions@1.2.1
│ │ │ │ │ │ │   └── semver-regex@1.0.0
│ │ │ │ │ │ ├── semver@4.3.6
│ │ │ │ │ │ └── semver-truncate@1.1.0
│ │ │ │ │ ├── lazy-req@1.1.0
│ │ │ │ │ └── os-filter-obj@1.0.3
│ │ │ │ └─┬ logalot@2.1.0
│ │ │ │   └─┬ squeak@1.3.0
│ │ │ │     ├── console-stream@0.1.1
│ │ │ │     └─┬ lpad-align@1.1.0
│ │ │ │       ├── longest@1.0.1
│ │ │ │       └── lpad@2.0.1
│ │ │ ├── is-gif@1.0.0
│ │ │ └─┬ through2@0.6.5
│ │ │   └── readable-stream@1.0.33
│ │ ├─┬ imagemin-jpegtran@4.3.2
│ │ │ ├── is-jpg@1.0.0
│ │ │ ├── jpegtran-bin@3.0.6
│ │ │ └── through2@2.0.0
│ │ ├─┬ imagemin-optipng@4.3.0
│ │ │ ├─┬ exec-buffer@2.0.1
│ │ │ │ └── tempfile@1.1.1
│ │ │ ├── is-png@1.0.0
│ │ │ ├── optipng-bin@3.0.4
│ │ │ └─┬ through2@0.6.5
│ │ │   └── readable-stream@1.0.33
│ │ ├─┬ imagemin-svgo@4.2.0
│ │ │ ├── is-svg@1.1.1
│ │ │ ├─┬ svgo@0.6.1
│ │ │ │ ├─┬ coa@1.0.1
│ │ │ │ │ └── q@1.4.1
│ │ │ │ ├── colors@1.1.2
│ │ │ │ ├─┬ csso@1.4.4
│ │ │ │ │ └── clap@1.0.10
│ │ │ │ ├─┬ js-yaml@3.4.6
│ │ │ │ │ ├─┬ argparse@1.0.3
│ │ │ │ │ │ ├── lodash@3.10.1
│ │ │ │ │ │ └── sprintf-js@1.0.3
│ │ │ │ │ ├── esprima@2.7.1
│ │ │ │ │ └── inherit@2.2.2
│ │ │ │ ├── sax@1.1.4
│ │ │ │ └── whet.extend@0.9.9
│ │ │ └── through2@2.0.0
│ │ ├── optional@0.1.3
│ │ ├─┬ stream-combiner2@1.1.1
│ │ │ └── duplexer2@0.1.4
│ │ └─┬ vinyl-fs@2.2.1
│ │   ├─┬ duplexify@3.4.2
│ │   │ └── end-of-stream@1.0.0
│ │   ├─┬ glob-stream@5.3.1
│ │   │ ├─┬ glob@5.0.15
│ │   │ │ └── minimatch@3.0.0
│ │   │ ├─┬ ordered-read-streams@0.3.0
│ │   │ │ └── is-stream@1.0.1
│ │   │ ├─┬ through2@0.6.5
│ │   │ │ └── readable-stream@1.0.33
│ │   │ ├─┬ to-absolute-glob@0.1.1
│ │   │ │ └── extend-shallow@2.0.1
│ │   │ └── unique-stream@2.2.0
│ │   ├── graceful-fs@4.1.2
│ │   ├─┬ gulp-sourcemaps@1.6.0
│ │   │ ├── graceful-fs@4.1.2
│ │   │ └── through2@2.0.0
│ │   ├── is-valid-glob@0.3.0
│ │   ├── merge-stream@1.0.0
│ │   ├─┬ strip-bom@2.0.0
│ │   │ └── is-utf8@0.2.1
│ │   ├─┬ strip-bom-stream@1.0.0
│ │   │ └── first-chunk-stream@1.0.0
│ │   ├── through2@2.0.0
│ │   └─┬ through2-filter@2.0.0
│ │     └── through2@2.0.0
│ └── pretty-bytes@1.0.4
├─┬ grunt-contrib-jshint@0.11.3
│ └─┬ jshint@2.8.0
│   ├─┬ cli@0.6.6
│   │ └─┬ glob@3.2.11
│   │   └── minimatch@0.3.0
│   ├─┬ htmlparser2@3.8.3
│   │ ├── domelementtype@1.3.0
│   │ ├── domhandler@2.3.0
│   │ ├─┬ domutils@1.5.1
│   │ │ └─┬ dom-serializer@0.1.0
│   │ │   ├── domelementtype@1.1.3
│   │ │   └── entities@1.1.1
│   │ ├── entities@1.0.0
│   │ └── readable-stream@1.1.13
│   ├── lodash@3.7.0
│   ├── minimatch@2.0.10
│   ├── shelljs@0.3.0
│   └── strip-json-comments@1.0.4
├─┬ grunt-contrib-qunit@0.7.0
│ └─┬ grunt-lib-phantomjs@0.6.0
│   ├─┬ phantomjs@1.9.19
│   │ ├── adm-zip@0.4.4
│   │ ├─┬ fs-extra@0.23.1
│   │ │ ├── graceful-fs@4.1.2
│   │ │ └── jsonfile@2.2.3
│   │ ├── kew@0.4.0
│   │ ├─┬ md5@2.0.0
│   │ │ ├── charenc@0.0.1
│   │ │ ├── crypt@0.0.1
│   │ │ └── is-buffer@1.0.2
│   │ ├─┬ npmconf@2.1.1
│   │ │ ├── nopt@3.0.6
│   │ │ └── semver@4.3.6
│   │ ├── progress@1.1.8
│   │ ├─┬ request@2.42.0
│   │ │ ├── aws-sign2@0.5.0
│   │ │ ├─┬ bl@0.9.4
│   │ │ │ └── readable-stream@1.0.33
│   │ │ ├── caseless@0.6.0
│   │ │ ├── hawk@1.1.1
│   │ │ ├── mime-types@1.0.2
│   │ │ ├── oauth-sign@0.4.0
│   │ │ └── qs@1.2.2
│   │ └─┬ request-progress@0.3.1
│   │   └── throttleit@0.0.2
│   ├── semver@1.0.14
│   └─┬ temporary@0.0.8
│     └── package@1.0.1
├─┬ grunt-contrib-uglify@0.10.1
│ ├─┬ chalk@1.0.0
│ │ ├─┬ has-ansi@1.0.3
│ │ │ └── ansi-regex@1.1.1
│ │ ├── strip-ansi@2.0.1
│ │ └── supports-color@1.3.1
│ ├── lodash@3.2.0
│ ├─┬ maxmin@1.0.1
│ │ └── pretty-bytes@1.0.4
│ ├─┬ uglify-js@2.5.0
│ │ ├── async@0.2.10
│ │ ├── uglify-to-browserify@1.0.2
│ │ └─┬ yargs@3.5.4
│ │   ├── camelcase@1.2.1
│ │   ├── window-size@0.1.0
│ │   └── wordwrap@0.0.2
│ └── uri-path@1.0.0
├─┬ grunt-contrib-watch@0.6.1
│ ├── async@0.2.10
│ ├─┬ gaze@0.5.2
│ │ └─┬ globule@0.1.0
│ │   └── lodash@1.0.2
│ ├── lodash@2.4.2
│ └─┬ tiny-lr-fork@0.0.5
│   ├── debug@0.7.4
│   ├── faye-websocket@0.4.4
│   ├─┬ noptify@0.0.3
│   │ └── nopt@2.0.0
│   └── qs@0.5.6
├── grunt-includes@0.5.2
├─┬ grunt-jsvalidate@0.2.2
│ └── esprima@1.0.4
├── grunt-legacy-util@0.2.0
├─┬ grunt-patch-wordpress@0.3.0
│ ├─┬ inquirer@0.2.5
│ │ ├── async@0.2.10
│ │ ├─┬ cli-color@0.2.3
│ │ │ ├── es5-ext@0.9.2
│ │ │ └─┬ memoizee@0.2.6
│ │ │   ├── event-emitter@0.2.2
│ │ │   └── next-tick@0.1.0
│ │ ├── lodash@1.2.1
│ │ └── mute-stream@0.0.3
│ ├─┬ request@2.27.0
│ │ ├── aws-sign@0.3.0
│ │ ├── cookie-jar@0.3.0
│ │ ├── forever-agent@0.5.2
│ │ ├─┬ form-data@0.1.4
│ │ │ ├── async@0.9.2
│ │ │ └─┬ combined-stream@0.0.7
│ │ │   └── delayed-stream@0.0.5
│ │ ├─┬ hawk@1.0.0
│ │ │ ├── boom@0.4.2
│ │ │ ├── cryptiles@0.2.2
│ │ │ ├── hoek@0.9.1
│ │ │ └── sntp@0.2.4
│ │ ├─┬ http-signature@0.10.1
│ │ │ ├── asn1@0.1.11
│ │ │ ├── assert-plus@0.1.5
│ │ │ └── ctype@0.5.3
│ │ ├── json-stringify-safe@5.0.1
│ │ ├── mime@1.2.11
│ │ ├── node-uuid@1.4.7
│ │ ├── oauth-sign@0.3.0
│ │ ├── qs@0.6.6
│ │ └── tunnel-agent@0.3.0
│ ├── underscore@1.5.2
│ └── underscore.string@2.3.3
├─┬ grunt-postcss@0.7.1
│ ├── diff@2.2.1
│ └── es6-promise@3.0.2
├─┬ grunt-rtlcss@1.6.0
│ └─┬ rtlcss@1.7.2
│   ├─┬ findup@0.1.5
│   │ └── commander@2.1.0
│   ├─┬ mkdirp@0.5.0
│   │ └── minimist@0.0.8
│   └── strip-json-comments@1.0.4
├─┬ grunt-sass@1.1.0
│ ├─┬ each-async@1.1.1
│ │ ├── onetime@1.1.0
│ │ └── set-immediate-shim@1.0.1
│ ├─┬ node-sass@3.4.2
│ │ ├── async-foreach@0.1.3
│ │ ├─┬ cross-spawn@2.1.4
│ │ │ ├─┬ cross-spawn-async@2.1.6
│ │ │ │ ├─┬ lru-cache@4.0.0
│ │ │ │ │ ├── pseudomap@1.0.2
│ │ │ │ │ └── yallist@2.0.0
│ │ │ │ └─┬ which@1.2.1
│ │ │ │   └─┬ is-absolute@0.1.7
│ │ │ │     └── is-relative@0.1.3
│ │ │ └─┬ spawn-sync@1.0.15
│ │ │   └── os-shim@0.1.3
│ │ ├─┬ glob@5.0.15
│ │ │ └── minimatch@3.0.0
│ │ ├─┬ mkdirp@0.5.1
│ │ │ └── minimist@0.0.8
│ │ ├── nan@2.1.0
│ │ ├─┬ node-gyp@3.2.1
│ │ │ ├─┬ fstream@1.0.8
│ │ │ │ └── graceful-fs@4.1.2
│ │ │ ├── graceful-fs@4.1.2
│ │ │ ├── minimatch@1.0.0
│ │ │ ├── nopt@3.0.6
│ │ │ ├─┬ npmlog@1.2.1
│ │ │ │ ├── ansi@0.3.0
│ │ │ │ ├─┬ are-we-there-yet@1.0.5
│ │ │ │ │ └── delegates@0.1.0
│ │ │ │ └─┬ gauge@1.2.2
│ │ │ │   ├── has-unicode@1.0.1
│ │ │ │   ├─┬ lodash.pad@3.1.1
│ │ │ │   │ ├── lodash._basetostring@3.0.1
│ │ │ │   │ └─┬ lodash._createpadding@3.6.1
│ │ │ │   │   └── lodash.repeat@3.0.1
│ │ │ │   ├── lodash.padleft@3.1.1
│ │ │ │   └── lodash.padright@3.1.1
│ │ │ ├─┬ osenv@0.1.3
│ │ │ │ ├── os-homedir@1.0.1
│ │ │ │ └── os-tmpdir@1.0.1
│ │ │ ├─┬ path-array@1.0.0
│ │ │ │ └── array-index@0.1.1
│ │ │ ├── semver@5.1.0
│ │ │ └─┬ tar@2.2.1
│ │ │   └── block-stream@0.0.8
│ │ ├─┬ npmconf@2.1.2
│ │ │ ├─┬ config-chain@1.1.9
│ │ │ │ └── proto-list@1.2.4
│ │ │ ├── ini@1.3.4
│ │ │ ├── nopt@3.0.6
│ │ │ ├── semver@4.3.6
│ │ │ └── uid-number@0.0.5
│ │ ├─┬ request@2.67.0
│ │ │ ├── aws-sign2@0.6.0
│ │ │ ├── caseless@0.11.0
│ │ │ ├─┬ combined-stream@1.0.5
│ │ │ │ └── delayed-stream@1.0.0
│ │ │ ├── extend@3.0.0
│ │ │ ├── forever-agent@0.6.1
│ │ │ ├─┬ form-data@1.0.0-rc3
│ │ │ │ └── async@1.5.1
│ │ │ ├─┬ har-validator@2.0.3
│ │ │ │ ├── commander@2.9.0
│ │ │ │ ├─┬ is-my-json-valid@2.12.3
│ │ │ │ │ ├── generate-function@2.0.0
│ │ │ │ │ ├─┬ generate-object-property@1.2.0
│ │ │ │ │ │ └── is-property@1.0.2
│ │ │ │ │ └── jsonpointer@2.0.0
│ │ │ │ └─┬ pinkie-promise@2.0.0
│ │ │ │   └── pinkie@2.0.1
│ │ │ ├─┬ hawk@3.1.2
│ │ │ │ ├── boom@2.10.1
│ │ │ │ ├── cryptiles@2.0.5
│ │ │ │ ├── hoek@2.16.3
│ │ │ │ └── sntp@1.0.9
│ │ │ ├─┬ http-signature@1.1.0
│ │ │ │ ├─┬ jsprim@1.2.2
│ │ │ │ │ ├── extsprintf@1.0.2
│ │ │ │ │ ├── json-schema@0.2.2
│ │ │ │ │ └── verror@1.3.6
│ │ │ │ └─┬ sshpk@1.7.2
│ │ │ │   ├── asn1@0.2.3
│ │ │ │   ├── assert-plus@0.2.0
│ │ │ │   ├── dashdash@1.11.0
│ │ │ │   ├── ecc-jsbn@0.1.1
│ │ │ │   ├── jodid25519@1.0.2
│ │ │ │   ├── jsbn@0.1.0
│ │ │ │   └── tweetnacl@0.13.3
│ │ │ ├── is-typedarray@1.0.0
│ │ │ ├── isstream@0.1.2
│ │ │ ├─┬ mime-types@2.1.9
│ │ │ │ └── mime-db@1.21.0
│ │ │ ├── oauth-sign@0.8.0
│ │ │ ├── qs@5.2.0
│ │ │ ├── stringstream@0.0.5
│ │ │ ├── tough-cookie@2.2.1
│ │ │ └── tunnel-agent@0.4.2
│ │ └─┬ sass-graph@2.0.1
│ │   ├─┬ glob@5.0.15
│ │   │ └── minimatch@3.0.0
│ │   ├── lodash@3.10.1
│ │   └─┬ yargs@3.31.0
│ │     ├─┬ cliui@3.1.0
│ │     │ └── wrap-ansi@1.0.0
│ │     ├─┬ os-locale@1.4.0
│ │     │ └─┬ lcid@1.0.0
│ │     │   └── invert-kv@1.0.0
│ │     ├─┬ string-width@1.0.1
│ │     │ ├── code-point-at@1.0.0
│ │     │ └── is-fullwidth-code-point@1.0.0
│ │     ├── window-size@0.1.4
│ │     └── y18n@3.2.0
│ └── object-assign@4.0.1
└─┬ matchdep@1.0.0
  ├─┬ findup-sync@0.3.0
  │ └─┬ glob@5.0.15
  │   └── minimatch@3.0.0
  ├─┬ globule@0.2.0
  │ ├─┬ glob@3.2.11
  │ │ └── minimatch@0.3.0
  │ ├── lodash@2.4.2
  │ └── minimatch@0.2.14
  └── stack-trace@0.0.9

And this is just for WordPress core; it doesn’t include the tools we use for BuddyPress or bbPress.

I don’t like this. I don’t like not knowing what these tools do or why they’re necessary. It feels like having one-thousand hammers instead of drill-bits and screwdrivers and power-tools. It’s not immediately obvious what the return on learning each or any of these unique tools is.

And when a tool becomes obsolete or out-of-date, the rabbit hole is full of eels:

npm WARN deprecated lodash@0.9.2: lodash@<2.0.0 is no longer maintained. Upgrade to lodash@^3.0.0
npm WARN deprecated npmconf@2.1.1: this package has been reintegrated into npm and is now out of date with respect to npm
npm WARN deprecated lodash@1.0.2: lodash@<2.0.0 is no longer maintained. Upgrade to lodash@^3.0.0
npm WARN deprecated lodash@1.2.1: lodash@<2.0.0 is no longer maintained. Upgrade to lodash@^3.0.0
npm WARN deprecated npmconf@2.1.2: this package has been reintegrated into npm and is now out of date with respect to npm
npm WARN deprecated lodash@2.4.2: lodash@<3.0.0 is no longer maintained. Upgrade to lodash@^3.0.0.
n

These relatively helpful messages may be outside of your control. They might be directly your fault. They might be globally installed modules or locally installed ones. Upgrading might be good for WordPress but break everything else you work on without warning.

Here’s usually what happens:

  • Something in the mystery toolbox breaks and complains about it
  • Try to upgrade the broken tool according to the feedback message(s)
  • The tool is still broken
  • Delete the entire `node_modules` directory and `npm install` again
  • Fixed!

Now, I understand what’s going on here is a miracle of modern software engineering. An enormous amount of automation is going on here, and the fact it actually works most of the time I’ll consider another closely coupled miracle. It still feels like there must be a better way, even though I can’t claim the fame of knowing right now what that might actually be.

I get that this is all awesome. I get that this process, and having & using these tools, is better than smashing things with rocks and hoping for the best. I get that a ton of work has gone into making this as seamless and wrinkle-free as possible.

I accept it, and work with it, and try not to think about it, but the trend of installing and trusting hundreds of tiny unknown libraries feels a little too organic and alive for a man-made computing machine. Like millions of nerve-endings and neurons and vessels and muscles working in unison to blink your eyes and sip on some coffee, the line between being a software developer or a software doctor is an increasingly jagged one.

If we aren’t careful, we’ll end up as lost amongst our own creations as we are inside of mother nature’s.

The NSA’s Guidelines for Implementation of REST

NSA REST

Direct Link and Mirrored Here, from March 25, 2011:

The key point to remember is that in order to ensure that an operation / implementation is RESTful, these methods must be used as they were defined in RFC 2616, the HTTP 1.1 specification. If implementations abuse these methods, they not only depart from RESTful behavior, but also jeopardize the application’s ability to interoperate with other RESTful capabilities.

Chosen & Pretty Filters

In the past month I’ve released two WordPress plugins that I think ended up complementing each other rather nicely:

Together they make filtering posts in WordPress a much nicer experience:

Chosen + Pretty Filters

If you’re a plugin author that’s bundling the Chosen library or doing something interesting with post filters, I hope you’ll consider referring your users to install these instead.

Good plugins are small, purposeful, and they naturally blend into WordPress like bananas in a smoothie, but when you bundle libraries inside your large plugin, you will collide with other plugins that use that same library, and WordPress just isn’t designed to handle this very well quite yet.

Chances are, your plugin works great without Chosen. Users can still drop down a select box and type in it to find what they’re looking for, it’s just not a fancy experience. If you think your users would enjoy a fancy experience, maybe consider pointing them to WP Chosen instead, and update your plugin to support it and avoid the additional support burden while you’re at it.

P.S. All sites on Flox.io now have both of these active by default.❤

I’m difficult to work with

When I was in third grade, my elementary school guidance counselor setup a meeting with my parents and I to talk about my behavior. I’ll spare you the details, but the gist is that “John has a high comprehension level and enormous potential but does not apply himself.” Whether or not that was or is actually true is debatable on some days and a ludicrous notion on others, but this interaction stuck with me, and possibly accidentally influenced the rest of my life, up to this point at least.

I’ve made it around the sun 36 times now, and in the past 28 revolutions since being told that my ability to grok how the world works was a super human ability yet to be seen in reality, I’ve identified several commonalities that boil down to one inalienable truth:

You’re difficult to work with.

I’ve been told this directly several times in my life, and twice recently, so let’s assume that it’s true.

  • I’m stubborn; I get that from my dad who is always right even after you have definitively proven him wrong with factual evidence to refute his theories. I’ve always found this endearing in a way; “prove that I’m wrong” was a fun challenge growing up and learning how the world worked, and I also actively try never to operate in that capacity towards others because as an adult, it’s hugely frustrating.
  • I’m observant; I get that from both of my parents who both were always living on the brink of poverty and needing to keep an inventory of every scrap, every opportunity, and every potential threat at what they had already accomplished or accumulated.
  • I’m passionate; I get this from my mom; her heart is bigger than her head, and her head is growing increasingly fuzzy. I want to make sure that people and things are taken care of, and I actively put forth my best effort to ensure the most positiver outcome occurs.
  • I “think too much.” I’m not sure when exactly this started or if it’s always been this way, or what exactly influenced my brain to work this way, but learning is my addiction and being fluent enough in everything to be able to hold down a conversation is a way for me to dodge any social anxiety I might have.
  • I expect too much from people. I expect people to understand my perspective as much as I understand theirs. I expect people to be as patient with me as I am with them. I expect people to be polite, and communicative, and respectful. I expect people to be considerate, kind, and compassionate. I’m constantly disappointed when they aren’t any of these things.

(Edit: I should note here that I think my parents are both amazing individuals. They’re brilliant in their own unique ways. They are savants that sacrificed their opportunities so that I could have mine, and I love and appreciate them immensely.)

This last one is (in my self-diagnosed opinion) ultimately the issue that makes me difficult to work with. I try not to offer unsolicited advice, but I desperately want to be helpful so when someone does ask for my opinion I have a well thought-out perspective to offer. That requires an education, which requires research, and doing this at scale with all the cool shit in the world requires an ability to comprehend something quickly and filter out anything that isn’t relevant.

In reality, though, what’s happened numerous times is someone asks for my opinion, and I blow their question out of the water with several layers deeper worth of feedback than they were probably asking for. Here’s an example based on a real life experience:

  • “What do you think of this new soup we are trying out?”
  • “I like it, but I don’t think it matches the rest of the lunch offerings.”
  • “Oh, okay. But the soup is good though?”
  • “It’s not bad, but it’s heavy on the spices and thicker than I expected it to be. And I think if I came in for an iced-tea on a hot summer day, that I wouldn’t want to pair it with a cup of tomato soup.”
  • “I suppose. We have a few days worth of ingredients so we’ll see how it goes. Thanks.”

Now, me… I don’t find this interaction off-putting at all, but the chef definitely does, and the manager who worked hard to make the decision to order the ingredients and put together the pairings and design the menu and bring out the ladder and chalk and write the specials on the board and convince everyone this was the right thing to do, doesn’t want to hear this feedback.

This type of scenario carries over to my current career, where interactions are largely public, relationships are largely friendly, interactions are usually with individuals I’ve known in some capacity for several years, but I still manage to piss off despite a lifetime of preparation to try and avoid conflict and accomplish cool stuff with people.

My hunch is that they’re probably right, and that working with me is difficult. Ironically, I don’t think it’s because I’m stubborn like my dad, or over-observant, or passionate, or think too much, but because I’m so fluent and familiar with every aspect and angle of every problem that needs solving under my umbrella of influence, that I’ve already:

  • Deeply assessed the entire situation
  • Tested several theories about what’s wrong
  • Cross-checked the results of my conclusions
  • Considered the social implications of communicating my feedback
  • Formulated a response catered to being direct, polite, jovial, and light-hearted enough to convey humor in whatever flaw it is we’re diagnosing and repairing

Ironically, even with all of this preparation, and time, and knowledge, and consideration, I’m still difficult to work with.  And they’re right, they must be, because it’s fairly consistent feedback spanning several years and groups of friends and relationships and what-not.

My conclusion is that, in one sense, I’m over-applying myself to compensate for a conversation that happened when I was 8 years old. I’ve become addicted to learning things and applying what I learn to prove to myself that I can. I learned how to build, tune, and race cars when I was a teenager. I learned how to write code and make video games. I learned about making wine, brewing coffee, working on the house, auto-cross, electrical, plumbing, accounting, hiring, firing, small engines, milling wood flooring, drywall, pressure washing, video production, mixing music, turntablism, art history, design, typography, security, microwave emitters, steam cleaning, public speaking, community service, whatever…

Basically, I unknowingly fueled the depression and anxiety of primarily inattentive ADHD. I included a link, but you can just search the web for it if you care to learn. Basically, my brain is a hummingbird that never lands, and is constantly on high-alert trying to observe and absorb, and there is no off switch within reach. When it’s time to communicate to someone else what’s been rattling around in my head for however long, it’s already been too long and I’ve worked too far ahead. The effort it takes for me to slow down to bring everyone else up-to-my-speed, means me sacrificing my momentum just so that people can think I’m difficult to work with anyways.

This doesn’t happen very frequently, but when it does it’s painful… it hurts my head to stop thinking so I can write down everything I just learned, with the knowledge that the recipient isn’t going to consider all of the angles that I did, and I’m too anxious about being perceived negatively to concentrate on communicating the depths of my thoughts effectively.

If I wasn’t a cargo-shorts wearing pizza-eating white-dude that looks and acts pretty normal most of the time, and if it wasn’t something I felt I could control enough to navigate the world with relative ease, I’d call it a disability. It’s like being blind, and having people tell you that you’re difficult to work with because you can’t see.

When you consider the perspective of a self-aware recipient, being told that you’re difficult to work with is not feedback, it’s a personal attack, it’s dismissive, and it’s insulting. Combine that feedback with your efforts being voluntary, and it starts to look like management is actively sabotaging your experience.

It’s perpetually negatively self-fulfilling. If you tell someone they are a jerk, they’re going to get defensive which heightens their anxiety and excites them into acting like a jerk, and then you get to say they’re a jerk. It’s unfair, manipulative, and not indicative of true leadership ability or spirit.

What should happen in these cases, and what I actively put mucho effort to convey in BuddyPress, bbPress, and other open-source endeavors, is an appreciation for everyone’s efforts and perspectives, particularly if I initially disagree, because it’s important to me and the projects I represent that I fully understand all perspectives before I can rightfully come to any conclusion, and it’s important that the delivery of my conclusion be respectful of their time & feelings related to the matter.

So, fine… I’m difficult to work with. I’m probably difficult to work for, too. And difficult to be married to. And I’m confident Paul the dog thinks I’m a difficult puppy-master because I spent 2 hours drafting this all up instead of walking him around the block this afternoon. If you know me, or you think you want to, or you’re forced to interact with me somewhere for some reason, please try to give me the benefit of the doubt, and if you aren’t able to, expect for me to be pretty frustrated about it, because I’m trying my best and I expect you to do the same.

WordPress is a Conduit

When people talk about WordPress, they call it various things:

In the past, I’ve gone on the record and called it something even bigger, but none of these descriptions truly accurately describe what WordPress actually is, at least to an ever-increasing number of people.

WordPress, the software, is a conduit for creativity, discussion, and innovation. You are introduced to it in such a simple way, but it’s potential is immediately recognizable so you can’t help but tinker with it and wonder what else it’s capable of.

WordPress.org, the website, is a conduit for discovery, a beacon in the fog, and ultimately the preferred outlet for tinkerers like myself. In my opinion, this is the single most important part of the entire system – the kingpin keeping all of it together that without it would all fall down (hat-tip to the GPL, also.)

WordPress.com, the anti-social network, is a conduit for writing, and a way to be introduced to the great suite of software Automattic produces to help make online publishing a more enjoyable experience.

WordPress, all of it, draws you in and funnels you through it’s finely tuned interface, introducing you back to yourself in a way that’s intentionally intimate and private, provoking you to invent something great and inviting you to learn more about what both you and it are capable of together.

I’ve experienced no other software and no other community with such an immense, almost gravitational pull. Once I was in, all I could do was orbit and enjoy the view from as many perspectives as I could. From inside Automattic and WordPress.com; from inside 10up and client services; from helping improve WordPress.org; to starting my own journey with Flox – WordPress was there, making sure I knew everything was going to be alright, and helping me discover where I can best fit and why.

WordPress is a conduit for positivity, for enabling greatness, and for generating joy. To everyone who has contributed influence to the WordPress community, working tirelessly to keep it’s spirit shimmering, thank you for your immense passion, intense attention to detail, and for allowing me to float along and be helpful where I’m able.

“I’m too busy”

TL;DR – If you ping me directly for help with something, you will probably get it.

BuddyPress & bbPress recently switched from IRC to Slack for real time synchronous communication. Philosophically, I prefer the openness of IRC, but I do appreciate how convenient Slack is for everyone, and it’s anecdotally a more inviting and active environment & experience. For some, Slack might actually be overly convenient to the point of annoying or obtrusive, but I think like any communication tool, it requires a bit of wrangling and tuning to suit your needs.

What won’t happen, in Slack or otherwise, is any response from me that boils down to “I’m too busy” even if it’s true.

Let’s imagine that I am actually too busy to help you; you still won’t hear me say so. Instead, you’ll get any number of different replies that are considerate of you and your time:

  • “I’d love to help but won’t be able to for X number of hours.”
  • “Sorry; I have a bunch of things going on and won’t be able to get to this for a while.”
  • “I took a quick look and found this; check it out and let me know how it goes.”
  • “I can’t help right now, but maybe Frank is around and can take a look with you.”

My emphasis is on being polite and considerate of how the recipient could perceive my attitude & demeanor, and making sure to convey genuine concern for their situation even if it does not directly influence my life or priorities in any way.

Conversely, when I reach out to ask for help, it’s only because I’ve reached a point where it is no longer efficient or appropriate to continue on my own; I’ve done everything I am aware of and is in my scope of influence to do, and for any number of reasons I’m choosing to include someone else into my situation.

One place this happens quite frequently is WordPress Trac. As an example, let’s again imagine that I’m working on BuddyPress or bbPress, and I believe I’ve identified a bug in WordPress core. There’s a priority-ordered list of things I’ll go through before I include anyone else into my problem:

  • Run the PHPUnit tests to ensure I didn’t break something on my own
  • Duplicate the bug in a completely vanilla installation without any modifications
  • Confirm bug is real
  • Attempt to fix bug in WordPress core codebase by modifying as few lines as possible
  • Test and retest fix to confirm results, and confirm PHPUnit tests still pass
  • Search the internet for anyone else with this problem, and drill down into any relevant results to learn as much as I can about what anyone else is experiencing
  • Search WordPress Trac for any existing tickets related to any surrounding code, usually by searching for function or method names, variables, component names, etc…

All this, and no one else but me has any idea I’ve been hunting down Carmen Sandiego for the past hour or two. What happens next generally depends on the severity of the issue, and whether or not I feel comfortable pinging someone directly and risk interrupting whatever they’re working on for a consult. In my experience, anytime the urgency and prioritization of two separate parties converges, it requires even more clear & concise communication than normal. If someone gets frustrated by being interrupted, or you haven’t come fully prepared, the person(s) you ping are not going to be receptive now, and will be less receptive in the future.

Even after all of this research, preparation, and with years of experience doing this in a professional setting, it still isn’t easy, and positive & productive results are not guaranteed. I think if you value other people’s time more than your own, do your due-diligence, and are considerate of how your interruptions may impact their lives, you will generally get the same in return. And if you’re always constantly too busy to be interrupted (to the point of forgetting to be polite about it) it might not be the outside world and their lack of so-and-so that’s the problem.