       for key, filter of tinyliquid.filters
         unless key of bundledPlugins.filters
           bundledPlugins.filters[key] = filter
-      callback()
-    .fail (err) ->
-      callback err
-
-loadSitePlugins = (config, callback) ->
+      return
+
+loadSitePlugins = (config) ->
   # Resolve directories relative to source
-  dirs = config.plugins.map (dir) ->
-    path.resolve config.source, dir
-  async.filter dirs, fs.exists, (results) ->
-    loadPlugins(results)
-      .then (plugins) ->
-        callback null, plugins
-      .fail (err) -> callback err
+  dirs = config.plugins.map (dir) -> path.resolve config.source, dir
+  # Only check directories that actually exist
+  Q.allSettled(dirs.map (dir) -> Q.nfcall fs.stat, dir)
+    .then (results) ->
+      pluginDirs = []
+      for result, i in results
+        continue if result.state isnt 'fulfilled'
+        pluginDirs.push dirs[i]
+
+      loadPlugins pluginDirs
 
 loadPlugins = (dirs) ->
   log.verbose "generate", "Looking for plugins in: %s", dirs.join ', '

  # First-run initialization
  postMask = ///^(\d{4})-(\d{2})-(\d{2})-(.+)\.(#{config.markdown_ext.join '|'}|html)$///
  time.tzset config.timezone
  async.parallel [
    # Check directories for existence
    (cb) -> checkDirectories config, cb
    # Built-in enfield plugins
    loadBundledPlugins
  ], (err) -> refreshContent config, (err) ->
    if err
      return callback err

    log.info "generate", "Generated %s -> %s", config.source, config.destination
    callback()
    return unless config.watch

    destinationPath = path.resolve config.destination
    gaze path.join(config.source, '**/*'), {debounceDelay: 500}, (err, watcher) ->
      log.info "watch", "Watching %s for changes", config.source
      watcher.on 'all', (event, filepath) ->
        # Ignore any path within the destination directory
        return if helpers.isWithinDirectory filepath, destinationPath

        # Ignore any path within hidden/ignored directories

        # TODO: Special case _config.yml
        # TODO: Reload plugins

        log.info "watch", "%s %s", event, filepath
        refreshContent config, ->
          log.info "watch", "Regenerated"

refreshContent = (config, callback) ->
  # Get content in parallel:
  async.parallel [
    # Mimic Jekyll behavior by clearing out destination first
    (cb) -> fs.remove config.destination, cb
    (cb) -> loadSitePlugins config, cb
    (cb) -> loadIncludes config, cb
    (cb) -> loadLayouts config, cb
    (cb) -> loadContents config, cb
  ], (err, [_, plugins, includes, layouts, { posts, pages, files }]) ->
    if err then return callback err
    log.verbose "generate", "Initial content load complete"
    processResults {config, plugins, includes, layouts, posts, pages, files}, callback

processResults = ({config, plugins, includes, layouts, posts, pages, files}, callback) ->
  # Create site data structure

        allFiles.map(path.basename), dirs

      for file in allFiles
        loadFileIntoPlugins file, plugins

      plugins

loadFileIntoPlugins = (file, plugins) ->
  log.verbose "generate", "Loading plugin: %s", file
  plugin = require file

  if 'filters' of plugin
    for key, filter of plugin['filters']
      log.silly "generate", "Found filter for %s", key
      plugins.filters[key] = filter
  if 'tags' of plugin
    for key, tag of plugin['tags']
      log.silly "generate", "Found tag for %s", key
      plugins.tags[key] = tag
  if 'converters' of plugin
    for key, converter of plugin['converters']
      log.silly "generate", "Found converter for %s", key
      plugins.converters.push converter
  if 'generators' of plugin
    for key, generator of plugin['generators']
      log.silly "generate", "Found generator for %s", key
      plugins.generators.push generator
