Beispiel #1
0
 return function(_data) {
   _data = _data || {};
   if (data) {
     _.defaults(_data, data);
   }
   return self.partial(name, _data);
 };
Beispiel #2
0
  before: function (scope, cb) {

    // scope.args are the raw command line arguments.
    //
    // e.g. if someone runs:
    // $ sails generate hook user find create update
    // then `scope.args` would be `['user', 'find', 'create', 'update']`
    if (!scope.args[0]) {
      return cb( new Error('Please provide a name for this hook.') );
    }

    // Attach defaults
    _.defaults(scope, {
      createdAt: new Date()
    });

    // Decide the output id for use in targets below:
    scope.id = scope.args[0];

    // Lop off the `sails-hook-` prefix for the hook name.
    scope.hookName = scope.id.replace(/^sails-hook-/, '');

    // Determine path to hook
    scope.hookPath = path.join(scope.standalone ? '.' : 'api/hooks', scope.id);

    // When finished, we trigger a callback with no error
    // to begin generating files/folders as specified by
    // the `targets` below.
    return cb();
  },
Beispiel #3
0
 return function(req, _data) {
   _data = _data || {};
   if (data) {
     _.defaults(_data, data);
   }
   return self.render(req, name, _data);
 };
Beispiel #4
0
module.exports = function(options, sb) {

  // provide default values for switchback
  sb = reportback.extend(sb, {
    alreadyExists: 'error'
  });


  // Validate options and provide defaults.
  if (_.isUndefined(options.contents)) {
    return sb.invalid(new Error('Consistency violation: `contents` is required'));
  }
  if (_.isUndefined(options.rootPath)) {
    return sb.invalid(new Error('Consistency violation: `rootPath` is required'));
  }
  _.defaults(options, {
    force: false
  });

  // In case we ended up here w/ a relative path,
  // resolve it using the process's CWD
  var rootPath = path.resolve(process.cwd(), options.rootPath);

  // Only override an existing file if `options.force` is true
  fsx.exists(rootPath, function(exists) {
    if (exists && !options.force) {
      return sb.alreadyExists('Something else already exists at ::' + rootPath);
    }

    // Don't actually write the file if this is a dry run.
    if (options.dry) { return sb.success(); }

    // Delete existing file if necessary
    (function _deleteExistingFileMaybe(proceed){
      if (!exists) { return proceed(); }

      fsx.remove(rootPath, function (err) {
        if (err) { return proceed(err); }
        return proceed();
      });

    })(function (err) {
      if (err) { return sb(err); }

      // console.log('about to generate a file @ `'+rootPath+'`:',options.contents);

      fsx.outputFile(rootPath, options.contents, function (err){
        if (err) { return sb(err); }

        return sb();

      });//</fsx.outputFile()>

    });//</self-calling function :: _deleteExistingFileMaybe()>

  });//</fsx.exists()>

};
Beispiel #5
0
    _.each(options.models, function (userlandModelDef, key){

      if (_.isUndefined(userlandModelDef.identity)) {
        userlandModelDef.identity = key;
      }
      else if (userlandModelDef.identity !== key) {
        throw new Error('If `identity` is explicitly defined on a model definition, it should exactly match the key under which it is passed in to `Waterline.start()`.  But the model definition passed in for key `'+key+'` has an identity that does not match: `'+userlandModelDef.identity+'`');
      }

      _.defaults(userlandModelDef, _.omit(options.defaultModelSettings, 'attributes'));
      if (options.defaultModelSettings.attributes) {
        userlandModelDef.attributes = userlandModelDef.attributes || {};
        _.defaults(userlandModelDef.attributes, options.defaultModelSettings.attributes);
      }

      orm.registerModel(Waterline.Model.extend(userlandModelDef));

    });//</_.each>
Beispiel #6
0
 return _.map(choices, function(item) {
   var object, relationship;
   if (item.item) {
     object = item.item;
     relationship = item.relationship;
   } else {
     object = item;
     relationship = {};
   }
   var choice = {
     value: object._id
   };
   if (item.__removed) {
     choice.__removed = true;
   }
   _.defaults(choice, relationship);
   return choice;
 });
Beispiel #7
0
      sails.modules.loadResponses(function loadedRuntimeErrorModules(err, responseDefs) {
        if (err) { return cb(err); }

        // Check that none of the custom responses provided from userland collie with
        // reserved response methods/properties.
        //
        // Note: this could be made more flexible in the future-- I've found it to be
        // helpful to sometimes override res.view() in apps.  That said, in those circumstances,
        // I've been able to accomplish this by manually overriding res.view in a custom hook.
        // That said, if that won't work for your use case, please let me know (tweet @mikermcneil).
        var reservedResKeys = [
          'view',
          'status', 'set', 'get', 'cookie', 'clearCookie', 'redirect',
          'location', 'charset', 'send', 'json', 'jsonp', 'type', 'format',
          'attachment', 'sendfile', 'download', 'links', 'locals', 'render'
        ];
        _.each(responseDefs, function (responseDef, customResponseKey) {
          if ( _.contains(reservedResKeys, customResponseKey) ) {
            Err.invalidCustomResponse(customResponseKey);
          }
        });

        // Mix in the built-in default definitions for custom responses.
        _.defaults(responseDefs, {
          ok: require('./defaults/ok'),
          negotiate: require('./defaults/negotiate'),
          notFound: require('./defaults/notFound'),
          serverError: require('./defaults/serverError'),
          forbidden: require('./defaults/forbidden'),
          badRequest: require('./defaults/badRequest')
        });

        // Expose combined custom/default response method definitions on the hook.
        // (e.g. `serverError`, `notFound`, `ok`, etc.)
        // TODO: use this instead of exposing as "middleware", since that's confusing naming.

        // Register blueprint actions as middleware of this hook.
        hook.middleware = responseDefs;

        return cb();
      });
Beispiel #8
0
module.exports = function(options, sb) {
  sb = reportback.extend(sb, {
    alreadyExists: 'error',
    invalid: 'error'
  });

  // Validate options and provide defaults.
  if (_.isUndefined(options.templatePath)) {
    return sb.invalid(new Error('Consistency violation: `templatePath` is required'));
  }
  if (_.isUndefined(options.templatesDirectory)) {
    return sb.invalid(new Error('Consistency violation: `templatesDirectory` is required'));
  }
  if (_.isUndefined(options.rootPath)) {
    return sb.invalid(new Error('Consistency violation: `rootPath` is required'));
  }
  _.defaults(options, {
    force: false
  });

  // Compute the absolute path to copy from, given its relative path
  // from its source generator's `templates/` directory.
  // > `templatesDirectory` should be provided as an absolute path!
  var absSrcPath = path.resolve(options.templatesDirectory, options.templatePath);

  // Note that we don't read in the file as utf8 here,
  // since it's not necessarily text (e.g. could be a binary file)
  //
  // So instead, we use fsx.copy(), which takes care of this for us.
  fsx.copy(absSrcPath, options.rootPath, {
    clobber: !!options.force
  }, function(err) {
    if (err) { return sb(err); }

    return sb();

  });//</fsx.copy()>

};
Beispiel #9
0
module.exports = function afterGenerate(scope, cb) {

  // Validate scope and provide defaults.
  if (_.isUndefined(scope.rootPath) || !_.isString(scope.rootPath)) {
    return cb(new Error('Consistency violation: `rootPath` is a required string.'));
  }
  if (_.isUndefined(scope.sailsRoot) || !_.isString(scope.sailsRoot)) {
    return cb(new Error('Consistency violation: `sailsRoot` is a required string.'));
  }
  if (_.isUndefined(scope.appName) || !_.isString(scope.appName)) {
    return cb(new Error('Consistency violation: `appName` is a required string.'));
  }

  _.defaults(scope, {

    // Whether to skip `npm installing deps automatically.
    fast: false,

  });



  // Skip installing dependencies if instructed by the `--fast` flag.
  if (scope.fast) {
    // FUTURE: use `path.basename(scope.rootPath)` for improved readability
    cb.log.info('Created a new Sails app `' + scope.appName + '`!');
    console.log(chalk.gray('(you will need to cd in and run `npm install`)'));
    return cb();
  }//--•


  // Otherwise IWMIH, we'll be automatically installing dependencies from NPM.

  // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  // MAYBE just install the relevant deps.  It's a lot simpler this way.
  // (Note that we can prby just omit `sails`, since it'll automatically use the global version.)
  // ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  cb.log.info('Installing dependencies...');
  console.log(chalk.gray('Press CTRL+C to cancel.'));
  console.log(chalk.gray('(to skip this step in the future, use --fast)'));
  // console.log(chalk.gray('(if you do that, you\'ll need to cd in and run `rm -rf node_modules && npm install`)'));
  // console.log(chalk.gray(''));
  // console.log(chalk.gray('To skip this next time, use `sails new '+scope.appName+' --fast`.'));
  // console.log(chalk.gray('To skip this next time, use the --fast option.'));


  // Bind a SIGINT listener (i.e. CTRL+C)
  // > Note that this is defined as an inline function so that it works properly
  // > with `process.removeListener()`.
  var wasSigintTriggered;
  var sigintListener = function (){
    wasSigintTriggered = true;
    console.log();
    console.log();
    console.log(chalk.dim('------------------------------------------------------------'));
    console.log(chalk.yellow.bold('Cancelled `npm install`.'));
    console.log(chalk.white.bold('New app was generated successfully, but some dependencies'));
    console.log(chalk.white.bold('in node_modules/ are probably still incomplete or missing.'));
    console.log();
    console.log(chalk.white('Before you can lift this app, you\'ll need to cd in and run:'));
    console.log(chalk.white('    rm -rf node_modules && npm install'));
    console.log();
    console.log(chalk.gray('For more help, visit https://sailsjs.com/support.'));
    console.log();
  };
  process.once('SIGINT', sigintListener);

  // Install dependencies.
  Process.executeCommand({
    command: 'npm install',
    dir: scope.rootPath
  }).exec(function (err) {
    if (wasSigintTriggered) { return; }
    else {
      process.removeListener('SIGINT', sigintListener);
    }

    if (err) {
      console.log(chalk.dim('------------------------------------------------------------'));
      console.log(chalk.red.bold('Failed to install dependencies.'));
      console.log(chalk.white.bold('New app was generated successfully, but some dependencies'));
      console.log(chalk.white.bold('in node_modules/ are probably still incomplete or missing.'));
      console.log();
      console.log(chalk.white('Before you continue, please make sure you are connected to the'));
      console.log(chalk.white('internet, and that your NPM registry hasn\'t gone down.'));
      console.log(chalk.white('Then cd into the new app and run:'));
      console.log(chalk.white('    rm -rf node_modules && npm install'));
      console.log();
      console.log(chalk.gray('See below for complete error details.'));
      console.log(chalk.gray('For more help, visit https://sailsjs.com/support.'));
      console.log();
      return cb(err);
    }

    // SUCCESS!
    cb.log.info('Created a new Sails app `' + scope.appName + '`!');

    return cb();

  });//</ Process.executeCommand>

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // The above code replaced the old `npm install` implementation (including the bit
  // about symlinking for NPM <= v2).  For future reference, that original code can be
  // found here:
  // https://github.com/balderdashy/sails-generate/commits/57186db994fbf226cf14eb268ee0c9854efa81d8
  // https://github.com/balderdashy/sails-generate/commits/4f757316b9732d1cbff3d5705651cbc17233a23d
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

};
Beispiel #10
0
  })(function(err) {
    if (err) { return done(err); }

    try {

      // Now set up defaults for the rest of our scope:
      var author = process.env.USER || 'anonymous node/sails user';

      var owner = author;

      _.defaults(scope, {

        // Specific to new Sails apps
        appName: defaultAppName,
        linker: true,
        adapter: 'sails-disk',
        without: [],
        traditional: true,
        caviar: false,
        generatedWithSailsVersion: scope.sailsPackageJSON.version,
        generatedWithSailsGenerateVersion: sailsGeneratePj.version,
        verbose: scope.verbose||false,

        // Generic
        // (for any generated Node package)
        IS_CURRENT_NODE_VERSION_CAPABLE_OF_AWAIT: IS_CURRENT_NODE_VERSION_CAPABLE_OF_AWAIT,
        author: author,
        owner: owner,
        year: (new Date()).getFullYear(),
        github: {
          username: author,
          owner: owner
        },
        modules: {},
        website: util.format('https://github.com/%s', owner)
      });

      // Expand `without` into an array.
      // (assumes it is a comma-separated list)
      if (_.isString(scope.without)) {
        scope.without = scope.without.split(',');

        // Strip empty strings just in case there is a trailing comma.
        scope.without = _.without(scope.without, '');

      }//>-

      // Validate `without`
      if (!_.isArray(scope.without)) {
        return done(new Error('Couldn\'t create a new Sails app.  The provided `without` option (`'+util.inspect(scope.without, {depth:null})+'`) is invalid.'));
      }//-•

      // To avoid confusion, disable the `async` dependency by default.
      if (!_.contains(scope.without, 'async')) {
        scope.without = _.uniq((scope.without||[]).concat(['async']));
      }

      // Provide a shortcut for generating an extremely minimal Sails app.
      if (scope.minimal) {
        scope.without = _.union(scope.without, ['i18n','orm','sockets','grunt','lodash','async','session','views']);
        scope.frontend = false;// FUTURE: replace --frontend option and instead implement the same thing as another kind of "--without".  e.g. "--without=frontend".  OR we may just be able to get rid of it altogether.
        delete scope.minimal;
      }//fi


      // Handle `caviar` vs. `traditional`
      if (scope.caviar) {
        // `caviar` takes precedence over `traditional` and `minimal`
        scope.traditional = false;

        // Ensure there are no conflicts with disabled features vs. --caviar.
        if (scope.frontend === false || _.intersection(scope.without, ['orm', 'sockets', 'grunt', 'lodash', 'session', 'views']).length > 0) {
          return done(new Error('Invalid usage: Cannot generate a new app with the expanded feature set (`--caviar`) while also excluding some of the features that it needs.'));
        }

        // Override targets:
        scope.modules.layout = {
          targets: {
            './': { copy: 'views/caviar-layout.ejs' }
          }
        };
        scope.modules.homepage = {
          targets: {
            './': { copy: 'views/pages/caviar-homepage.ejs' }
          }
        };
        scope.modules['404'] = {
          targets: {
            './': { copy: 'views/404-caviar.ejs' }
          }
        };
        scope.modules['500'] = {
          targets: {
            './': { copy: 'views/500-caviar.ejs' }
          }
        };
      } else if (scope.traditional === false) {
        // remember, at this point, "scope.minimal" should have already been taken care of above.
        return done(new Error('Invalid usage: Cannot set `traditional: false` without providing some other flag (e.g. `caviar: true`)'));
      } else {
        // If `caviar` is not enabled, then don't run the "parasails" sub-generator.
        scope.modules['parasails'] = false;

        // And don't generate .htmlhintrc or .lesshintrc files.
        scope.modules['.htmlhintrc'] = false;
        scope.modules['.lesshintrc'] = false;
      }


      // Reject unrecognized "without" entries
      var LEGAL_WITHOUT_IDENTIFIERS = ['lodash', 'async', 'orm', 'sockets', 'grunt', 'i18n', 'session', 'views'];
      var unrecognizedEntries = _.difference(scope.without, LEGAL_WITHOUT_IDENTIFIERS);
      if (unrecognizedEntries.length > 0) {
        return done(new Error('Couldn\'t create a new Sails app.  The provided `without` option contains '+(unrecognizedEntries.length===1?'an':unrecognizedEntries.length)+' unrecognized entr'+(unrecognizedEntries.length===1?'y':'ies')+': '+unrecognizedEntries));
      }//-•

      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      // Note: An earlier version of `onlyIf` support was implemented here, but
      // then moved into core in f324b8763a7a62510310ea636dc815c0b629f6ad and removed
      // from here in the following commit.
      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


      // --without=views
      //
      // Don't include the `views/` folder, or the `config/views.js`
      // and `config/security.js` files, or HTML and LESS-specific syntax files.
      if (_.contains(scope.without, 'views')) {
        scope.modules['views'] = false;
        scope.modules['config/views.js'] = false;
        scope.modules['config/security.js'] = false;
        scope.modules['.htmlhintrc'] = false;
        scope.modules['.lesshintrc'] = false;
      }//-•

      // --without=session
      //
      // Don't include the `config/session.js` file, and don't include policies
      // directory or the policies config file.
      // (while policies work fine without a session, the default policy doesn't
      // make sense without one, and generating an empty folder without a gitkeep
      // would be weird.)
      if (_.contains(scope.without, 'session')) {
        scope.modules['config/session.js'] = false;
        scope.modules['config/policies.js'] = false;
        scope.modules['api/policies'] = false;
      }//-•

      // --without=i18n
      //
      // Don't include the `config/locales/` directory if this is being generated
      // without `i18n`.  Also don't include `config/i18n.js`.
      if (_.contains(scope.without, 'i18n')) {
        scope.modules['config/locales/de.json'] = false;
        scope.modules['config/locales/en.json'] = false;
        scope.modules['config/locales/es.json'] = false;
        scope.modules['config/locales/fr.json'] = false;
        scope.modules['config/i18n.js'] = false;
      }//-•

      // --without=grunt
      //
      // Don't include the `tasks/` directory or Gruntfile.js if this is being generated
      // without `grunt`.  Also don't include `assets/`.
      if (_.contains(scope.without, 'grunt')) {
        scope.modules.grunttasks = false;
        scope.modules.gruntfile = false;
        scope.modules.assets = false;
        scope.modules.layout = {
          targets: {
            './': { copy: 'views/gruntless-layout.ejs' }
          }
        };
      }//-•

      // --without=sockets
      //
      // If this is being generated without `sockets`, do a different, stripped-down homepage instead.
      if (_.contains(scope.without, 'sockets')) {
        scope.modules.homepage = {
          targets: {
            './': { copy: 'views/stripped-down-homepage.ejs' }
          }
        };

        scope.modules['config/sockets.js'] = false;
        scope.modules['sails.io.js'] = false;
      }

      // --without=orm
      //
      if (_.contains(scope.without, 'orm')) {
        scope.modules.homepage = {
          targets: {
            './': { copy: 'views/stripped-down-homepage.ejs' }
          }
        };
        scope.modules['config/models.js'] = false;
        scope.modules['config/datastores.js'] = false;
        scope.modules['api/models'] = false;
        scope.modules['api/models/.gitkeep'] = false;

        // Also disable blueprints
        scope.modules['config/blueprints.js'] = false;
      }

      // Prevent individual sub-generators from logging their "afterwards" log message.
      scope.suppressFinalLog = true;

      // --------------------------------------------------------------------------------------------------------------
      // > FUTURE: convert this to work based on `--without` instead (e.g. you'd be able to do --without=lodash,async,frontend)
      // (but note this generator needs to be grepped for the word "frontend" first - there are other places in here where `scope.frontend` is used!!!)

      // Alias for `--no-front-end` or `--front-end`
      // (instead of "--no-frontend" or "--frontend")
      if (!_.isUndefined(scope['front-end'])) {
        scope.frontend = scope['front-end'];
        delete scope['front-end'];
      }

      // If this is an app being generated with `--no-frontend` (which is equivalent
      // to `--frontend=false`), then disable some of our targets.
      //
      if (scope.frontend === false) {
        scope.modules.frontend = false;
        scope.modules.gruntfile = false;
        scope.modules.views = false;
        scope.modules.views = false;
        scope.modules['sails.io.js'] = false;
        scope.modules['.htmlhintrc'] = false;
        scope.modules['.lesshintrc'] = false;
      }//>-
      // --------------------------------------------------------------------------------------------------------------

      // Ensure there aren't any insurmountable conflicts at the generator's root path.
      // > This is so that we don't inadvertently delete anything, unless the `force`
      // > flag was enabled.
      (function _ensureNoInsurmountableConflictsAtRootPath(proceed){

        // Try to read a list of the direct children of this directory.
        var dirContents;
        try {

          dirContents = fs.readdirSync(scope.rootPath);

        }
        catch (errFromReadDirSync) {

          switch (errFromReadDirSync.code) {

            // If the directory doesn't exist, no sweat.  (We'll keep going.)
            case 'ENOENT':
              return proceed();

            // If the target path is actually a file, instead of a directory,
            // then we'll check to see if the `force` flag was enabled.  If not,
            // then we'll bail with a fatal error.  But if `force` WAS enabled,
            // then we'll go ahead and continue onwards.
            case 'ENOTDIR':
              if (scope.force) {
                return proceed();
              }//-•
              return proceed(flaverr('alreadyExists', new Error('Couldn\'t create a new Sails app at "'+scope.rootPath+'" because something other than an empty directory already exists at that path.')));

            // Otherwise, there was some other issue, so bail w/ a fatal error.
            default:
              return proceed(new Error('Couldn\'t create a new Sails app at "'+scope.rootPath+'" because an unexpected error occurred: '+errFromReadDirSync.stack));

          }//</switch :: errFromReadDirSync.code>
        }//</catch :: errFromReadDirSync>

        //--•
        // If this directory is empty, then we're good.
        if (dirContents.length === 0){
          return proceed();
        }

        //--•
        // But otherwise, this directory is NOT empty.

        // If the `force` flag is enabled, we can proceed.
        // (Otherwise we have to fail w/ a fatal error.)
        if (scope.force) {
          return proceed();
        } else { return proceed(flaverr('alreadyExists', new Error('Couldn\'t create a new Sails app at "'+scope.rootPath+'" because a non-empty directory already exists at that path.'))); }

      })(function (err){
        if (err) {
          switch (err.code) {
            case 'alreadyExists': return done(err);
            default: return done(err);
          }
        }

        return done();

      });//</self-calling function :: ensure there aren't any insurmountable conflicts at the generator's root path>
    } catch (err) { return done(err); }
  });//_∏_  (potentially prompted)
  return function receiveIncomingSailsIOMsg(options) {
    app.log.verbose('Receiving incoming message from Socket.io: ', options.incomingSailsIOMsg);

    // If invalid callback function specified, freak out
    // (it's ok to NOT include a callback, but if it exists, it should be a function)
    if (options.socketIOClientCallback && !_.isFunction(options.socketIOClientCallback)) {
      delete options.socketIOClientCallback;
      return respondWithParseError('Could not parse request- callback may be omitted... but if provided, it must be a function.');
    }

    // Check that URL is specified
    if (!options.incomingSailsIOMsg.url) {
      return respondWithParseError(util.format('No url provided in request: %s',options.incomingSailsIOMsg));
    }

    // Check that URL is valid
    if (!_.isString(options.incomingSailsIOMsg.url)) {
      return respondWithParseError(util.format('Invalid url provided: %s',options.incomingSailsIOMsg.url));
    }

    // Grab the metadata for the SDK
    var sdk = parseSdkMetadata(options.socket.handshake);

    // No more backwards-compatibility for clients < v0.11.0
    // > Note that we round off the prerelease part of the SDK version,
    // > in case there is one.  Otherwise, this check will always fail,
    // > no matter the version.  For more information on this, see:
    // > • https://github.com/npm/node-semver/issues/130#issuecomment-120988399
    // > • https://github.com/npm/node-semver/tree/44cbc8482ac4f0f8d2de0abb7f8808056d2d55f9#prerelease-tags
    var closestStableSdkVersion = semver.major(sdk.version)+'.'+semver.minor(sdk.version)+'.'+semver.patch(sdk.version);
    if (!semver.satisfies(closestStableSdkVersion, '>=0.11.0')) {
      return respondWithParseError(util.format('This version of Sails is not compatible with the socket.io/sails.io.js client SDK version you are using (%s). Please see https://sailsjs.com/documentation/upgrading/to-v-0-11 for more information-- then please continue upgrading all the way to the installed version of Sails.',sdk.version));
    }

    // Try to get the express 'trust proxy' setting value
    var expressTrust;
    try {
      expressTrust = app.hooks.http.app.get('trust proxy fn');
    }
    // Something went wrong while retrieving the express settings. Default's to no Trust
    catch (e) {
      expressTrust = function () { return false };
    }

    // Dummy express req object so we can use proxy-addr
    var reqDummy = {
      connection: { remoteAddress: options.socket.handshake.address },
      headers: options.socket.handshake.headers
    };

    var ip = proxyaddr(reqDummy, expressTrust);

    var ips = proxyaddr.all(reqDummy, expressTrust);

    // reverse the order (to farthest -> closest)
    // and remove socket address
    ips.reverse().pop();

    // Start building up the request context which we'll pass into the interpreter in Sails core:
    var requestContext = {

      transport: 'socket.io', // TODO: consider if this is really helpful or just a waste of LoC

      protocol: 'ws', // TODO: consider if this is really helpful or just a waste of LoC

      isSocket: true,

      ip      : ip,

      ips     : ips,

      port    : null,

      // Access to underlying SIO socket
      socket  : options.socket,

      url     : options.incomingSailsIOMsg.url,

      path    : url.parse(options.incomingSailsIOMsg.url).pathname || '/',
      // ^^ Uses || '/' because otherwise url.parse returns `null`,
      // which is not a string and thus bad when you try to check
      // .match() of it.

      method  : options.eventName,

      // Attached data becomes simulated HTTP body (`req.body`)
      // (allow `params` or `data` to be specified for backwards/sideways-compatibility)
      body    : _.isArray(options.incomingSailsIOMsg.data) ? options.incomingSailsIOMsg.data : _.extend({}, options.incomingSailsIOMsg.params || {}, options.incomingSailsIOMsg.data || {}),

      // Allow optional headers
      headers: _.defaults({

        host: app.config.host,

        // Default the "cookie" request header to what was provided in the handshake.
        cookie: (function (){
          var _cookie;
          try {
            _cookie = options.socket.handshake.headers.cookie;
          }
          catch (e) {}
          // console.log('REQUEST to "%s %s" IS USING COOKIE:', options.eventName, options.incomingSailsIOMsg.url, _cookie);
          return _cookie;
        })(),

        nosession: options.socket.handshake.headers.nosession ? true : undefined,

      }, options.incomingSailsIOMsg.headers || {})

    };

    // app.log.verbose('Interpreting socket.io message as virtual request to "%s %s"...', requestContext.method, requestContext.url);
    // app.log.verbose('(cookie: %s)', requestContext.headers.cookie);

    // Set the `origin` header to what was provided in the handshake
    // (the origin header CANNOT BE OVERRIDDEN by sockets at virtual request-time-- only
    //  upon first connection.)
    if (requestContext.headers.origin){

      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      // FUTURE: Further document security reasons why `origin` may not be passed manually
      // at VR (virtual request) time.  Has to do w/ xdomain security concerns.
      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      app.log.verbose('Ignoring provided `origin` header in VR (virtual request) from socket.io: It would not be safe to change `origin` for this socket now!');
    }
    requestContext.headers.origin = (function (){
      var _origin;
      try {
        _origin = options.socket.handshake.headers.origin;
      }
      catch (e){}
      return _origin;
    })();


    // console.log('handshake:',options.socket.handshake);
    app.log.verbose('Interpreting socket.io message as VR (virtual request) to "%s %s"...', requestContext.method, requestContext.url);



    // Start building up the response context which we'll pass into the interpreter in Sails core:
    var responseContext = {

      /**
       * This `_clientCallback` function we provide here will be used by Sails core as a final step
       * when trying to run methods like `res.send()`.
       *
       * Since Socket.io v1.x does not support streaming socket messages out of the box,
       * currently we'll just use this callback vs passing in a stream (so the client response
       * stream will just be buffered to build up clientRes.body)
       *
       * IMPORTANT:
       * The `clientRes` provided here is a Readable client response stream, not the same `res`
       * that is available in userland code.
       */

      _clientCallback: function _clientCallback(clientRes) {

        // If no cookie was sent initially on the handshake, and a 'set-cookie' exists in response
        // headers, then save the cookie on the handshake (no need to send extra data over the wire
        // since we're maintaining a persistent connection on this side, plus this prevents client-side
        // js from accessing the cookie)
        //
        // This allows for anything relying on cookies (e.g. default `req.session` support)
        // to last as long as the socket connection (i.e. until the browser tab is closed)
        //
        // Note that we **STILL GENERATE A COOKIE** using socket.io middleware when the socket
        // initially connects.  This is so that by the time we run the `onConnect` lifecycle event,
        // it has access to the real session.  So in general, this should never fire.
        //
        // In the future, we may want to always reset the handshake's cookie based on the `set-cookie`
        // response header to allow for custom HTTP cookies to work completely via sockets, but that
        // should be evaluated carefully to avoid unexpected race conditions.
        try {
          if (!options.socket.handshake.headers.cookie && clientRes.headers['set-cookie']){
              options.socket.handshake.headers.cookie = clientRes.headers['set-cookie'][0];
          }
        }
        catch (e) {
          app.log.warn('Could not persist res.headers["set-cookie"] into the socket handshake:',e);
        }

        // If socket.io callback does not exist as a valid function, don't bother responding.
        if (!_.isFunction(options.socketIOClientCallback)) {
          return;
        }

        // Modern behavior
        // (builds a complete simulation of an HTTP response.)
        var jwr = {
          body: clientRes.body
        };

        // Allow headers and status code to be disabled to allow for squeezing
        // out a little more performance when relevant (and reducing bandwidth usage).
        // To achieve this, set `sails.config.sockets.sendResponseHeaders=false` and/or
        // `sails.config.sockets.sendStatusCode=false`.
        if (app.config.sockets.sendResponseHeaders) {
          jwr.headers = clientRes.headers;
        }
        if (app.config.sockets.sendStatusCode) {
          jwr.statusCode = clientRes.statusCode;
        }

        // Remove 'set-cookie' header
        // (to prevent cookie from reaching client-side js)
        delete jwr.headers['set-cookie'];

        // TODO:
        // Try out http://socket.io/blog/introducing-socket-io-1-0/#socket.io-stream
        // to explore how we could make it work with Sails.
        // (the old way in 0.9 was streams1 style, just emitting `data` and `end` messages)

        // Send down response.
        options.socketIOClientCallback(jwr);
        return;
      }

    };


    // Finally, lob a virtual request at the interpreter
    app.router.route(requestContext, responseContext);










    /**
     * Send a parse error back over the socket.
     * If a callback was provided by the socket.io client, it will be used,
     * but otherwise a low-level event will be emitted (since otherwise there's
     * no way to communicate with the client)
     *
     * Relies on closure scope for `options` and `app`.
     */

    function respondWithParseError (detailedErrorMsg) {

      var error = ERRORPACK.PARSE_VIRTUAL_REQ('Failed to parse incoming socket.io request.');
      error.details = detailedErrorMsg;

      // Log parse error
      app.log.error(error);

      // If callback is invalid or non-existent:
      if ( !_.isFunction(options.socketIOClientCallback) ) {
        // Emit parse error
        options.socket.emit('sails:parseError', error);
        return;
      }

      // Otherwise just send the error directly to the callback...
      return options.socketIOClientCallback(error);
    }

  };
Beispiel #12
0
  before: function (scope, done){

    var roughName;

    // Use actions2 by default, since actions are being generated individually.
    if (scope.actions2 === undefined) {
      scope.actions2 = true;
    }

    // The default template to use.
    scope.templateName = 'actions2.template';

    // If `--no-actions2` was used, switch the default template and easter-egg template suffix.
    if (!scope.actions2) {
      scope.templateName = 'action.template';
    }

    // Accept --name
    if (scope.name) {
      roughName = scope.name;
    }
    // Or otherwise the first serial arg is the "roughName" of the action we want to create.
    else if (_.isArray(scope.args)) {

      // If more than one serial args were provided, they'll constitute the action friendly name
      // and we'll combine them to form the base name.
      if (scope.args.length >= 2) {
        // However, note that we first check to be sure no dots or slashes were used
        // (if so, this would throw things off)
        if (scope.args[0].match(/[\/\.\\]/)) {
          return done(new Error(
            'Too many serial arguments: A "." or "/" was specified in the name for this new action, but extra words were provided too.\n' +
            '(should be provided like either `foo-bar/baz/view-profile` or `View profile`).'
          ));
        }

        scope.friendlyName = scope.args.join(' ');
        roughName = _.camelCase(scope.friendlyName);
      }
      // Otherwise, we'll infer an appropriate friendly name further on down, and just use the
      // first serial arg as our "roughName".
      else if (scope.args[0] && _.isString(scope.args[0])) {
        roughName = scope.args[0];
      }

    }//>-

    if (!roughName) {
      return done(new Error(
        'Missing argument: Please provide a name for the new action.\n' +
        '(should be "verby" and use the imperative mood; e.g. `view-profile` or `sign-up`).'
      ));
    }

    // Replace backslashes with proper slashes.
    // (This is crucial for Windows compatibility.)
    roughName = roughName.replace(/\\/g, '/');

    // Now get our actionName.
    // Be kind -- transform slashes to dots when creating the actionName.
    scope.actionName = roughName.replace(/\/+/g, '.');
    // Then split on dots and make sure each segment is using camel-case before recombining.
    // (We'll unwind this later for most of the things.)
    scope.actionName = _.map(scope.actionName.split('.'), function (segment){
      return _.camelCase(segment);
    }).join('.');

    // After transformation, the actionName must contain only letters, numbers, and dots--
    // and start with a lowercased letter.
    if (!scope.actionName.match(/^[a-z][a-zA-Z0-9\.]*$/)) {
      return done( new Error('The name `' + scope.actionName + '` is invalid. '+
                           'Action names must start with a lower-cased letter and contain only '+
                           'letters, numbers, and dots.'));
    }

    // If the action name contains something like "FooController", then simplify that.
    scope.actionName = scope.actionName.replace(/(\.?)(.+)controller\./i, '$1$2.');

    // Then get our `relPath` (filename / path).
    // When building the relPath, we convert any dots to slashes.
    scope.relPath = scope.actionName.replace(/\.+/g, '/');
    // Then split on slashes and make sure each segment is using kebab-case before recombining.
    scope.relPath = _.map(scope.relPath.split('/'), function (segment){
      // One exception: Don't kebab-case words like "v1" within this segment, if any.
      return _.map(_.words(segment), function(word){
        if (word.match(/[a-z]+[0-9]+/)) { return word; }
        return _.kebabCase(word);
      }).join('-');
    }).join('/');
    // (And of course, finally, we tack on `.js` at the end-- we're not barbarians after all.)
    scope.relPath += '.js';

    // Grab the filename for potential use in our template.
    scope.filename = path.basename(scope.relPath);


    // Identify the action "stem" for use below.
    // (e.g. "view-stuff-and-things" from "v1.pages.view-stuff-and-things")
    var stem = _.last(scope.actionName.split('.'));
    var sentenceCaseStem = _.map(_.words(stem), function (word, i){ if (i===0) { return _.capitalize(word); } else { return word[0].toLowerCase() + word.slice(1); } }).join(' ');

    // Attempt to invent a description, if there isn't one already.
    if (_.isUndefined(scope.description)) {
      scope.description = inventDescription(scope.actionName, true);
    }//>-

    // Infer other defaults.
    _.defaults(scope, {
      friendlyName: scope.friendlyName || sentenceCaseStem,
      description: scope.description || '',
      inferredViewTemplatePath: '',
      verbose: false,
      IS_CURRENT_NODE_VERSION_CAPABLE_OF_AWAIT: IS_CURRENT_NODE_VERSION_CAPABLE_OF_AWAIT,
      // getAlternativeTemplate: null << FUTURE: a configurable function
    });



    //  ┌┐ ┬ ┬┬┬ ┌┬┐  ┬┌┐┌  ┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐  ┌─┐┬─┐┌─┐┌┬┐  ┬ ┬┌─┐┌─┐┬┌─┌─┐
    //  ├┴┐│ │││  │───││││  ├─┤│   │ ││ ││││└─┐  ├┤ ├┬┘│ ││││  ├─┤│ ││ │├┴┐└─┐
    //  └─┘└─┘┴┴─┘┴   ┴┘└┘  ┴ ┴└─┘ ┴ ┴└─┘┘└┘└─┘  └  ┴└─└─┘┴ ┴  ┴ ┴└─┘└─┘┴ ┴└─┘

    // SECURITY HOOK
    // ================================================
    // FUTURE: If the specified relative path is "security/grant-csrf-token", then make an assumption and go
    // ahead and generate a simple exampl override of the corresponding built-in action from the `security` hook.

    // BLUEPRINTS HOOK
    // ================================================
    // FUTURE: If action stem is "find", and the name of a resource can be inferred from the specified relative
    // path, then make an assumption and go ahead and generate a reified version of the corresponding blueprint action.

    // FUTURE: If action stem is "findOne", and the name of a resource can be inferred from the specified relative
    // path, then make an assumption and go ahead and generate a reified version of the corresponding blueprint action.

    // FUTURE: If action stem is "create", and the name of a resource can be inferred from the specified relative
    // path, then make an assumption and go ahead and generate a reified version of the corresponding blueprint action.

    // FUTURE: If action stem is "update", and the name of a resource can be inferred from the specified relative
    // path, then make an assumption and go ahead and generate a reified version of the corresponding blueprint action.

    // FUTURE: If action stem is "destroy", and the name of a resource can be inferred from the specified relative
    // path, then make an assumption and go ahead and generate a reified version of the corresponding blueprint action.



    //  ┌─┐┌┬┐┬ ┬┌─┐┬─┐  ┌─┐┬┌┬┐┌─┐┬  ┌─┐  ┌┬┐┬┌┬┐┌─┐  ┌─┐┌─┐┬  ┬┌─┐┬─┐┌─┐
    //  │ │ │ ├─┤├┤ ├┬┘  └─┐││││├─┘│  ├┤    │ ││││├┤───└─┐├─┤└┐┌┘├┤ ├┬┘└─┐
    //  └─┘ ┴ ┴ ┴└─┘┴└─  └─┘┴┴ ┴┴  ┴─┘└─┘   ┴ ┴┴ ┴└─┘  └─┘┴ ┴ └┘ └─┘┴└─└─┘

    // ACTION THAT SERVES A WEB PAGE
    // ================================================
    // If action stem begins with "view" (and if it's not just... "view" by itself--
    // which would be weird, but whatever), then make an assumption about what to generate.
    // (set up the success exit with `viewTemplatePath` ready to go)
    if (stem.match(/^view/i) && stem !== 'view') {
      // console.log('stem!',_.words(stem).slice(1));
      var inferredViewTemplateBasename = _.reduce(_.words(stem).slice(1), function (memo, segment){
        // One exception: Don't kebab-case stuff like "v1".
        if (segment.match(/[a-z]+[0-9]+/)) {
          memo += segment;
        }
        else {
          memo += _.kebabCase(segment);
        }

        // Attach a hyphen at the end, if there isn't one already.
        memo = memo.replace(/\-*$/, '-');

        return memo;
      }, '');

      // Trim the last hyphen off the end.
      inferredViewTemplateBasename = inferredViewTemplateBasename.replace(/\-*$/, '');
      // console.log('inferredViewTemplateBasename!',inferredViewTemplateBasename);

      // If this action is in a subdirectory, apply that subdirectory to our inferred
      // view template path:
      var intermediateSubdirs = scope.actionName.split('.');
      intermediateSubdirs.pop();
      // console.log('intermediateSubdirs', intermediateSubdirs);
      intermediateSubdirs = intermediateSubdirs.map(function(subdirName){
        // One exception: Don't kebab-case words like "v1" within this subdir name, if any.
        return _.map(_.words(subdirName), function(word){
          if (word.match(/[a-z]+[0-9]+/)) { return word; }
          return _.kebabCase(word);
        }).join('-');
      });//∞

      scope.inferredViewTemplatePath = (
        path.join('pages/', intermediateSubdirs.join('/'), inferredViewTemplateBasename)
        .replace(/\\/g,'/')//« because Windows
      );

      // Adjust description:
      scope.description = (function _gettingAdjustedDescription(){
        var words = _.words(sentenceCaseStem).slice(1);
        if (_.last(words).match(/page/i)){
          words = words.slice(0, -1);
        }
        var friendlyPageName = words.join(' ');
        if (friendlyPageName.length > 1) {
          friendlyPageName = friendlyPageName[0].toUpperCase() + friendlyPageName.slice(1);
        }
        return 'Display "'+friendlyPageName+'" page.';
      })();//= (†)
    }//>-

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Note: For an example of how to swap out the template dynamically
    // (e.g. to inject smarter defaults based on context, or fun easter eggs)
    // take a look at the approach here:
    // https://github.com/balderdashy/sails-generate/commit/9b8cb6e85bdcc8b19f51976cd3a395d4b2512161
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


    return done();

  },
module.exports = function getBuiltInHttpMiddleware (expressRouterMiddleware, sails) {

  // Note that the environment of a Sails app is officially determined by
  // `sails.config.environment`. Normally, that is identical to what you'll
  // find inside `process.env.NODE_ENV`.
  //
  // However it is possible for NODE_ENV and `sails.config.environment to vary
  // (e.g. `sails.config.environment==='staging'` and `process.env.NODE_ENV==='production'`).
  //
  // Some middleware _depends on the NODE_ENV environment variable_ to determine
  // its behavior.  Since NODE_ENV may have been set automatically, this is why the
  // relevant requires are included _within_ this function, rather than up top.
  //
  // This is also why the NODE_ENV environment variable is used here to determine
  // whether or not to consider the app "in production".  This way, if you set
  // `NODE_ENV=production` explicitly, you can still use something like "staging"
  // or "sandbox" for your `sails.config.environment` in order to take advantage
  // of env-specific config files; while still having dependencies work like they
  // will in production (since NODE_ENV is set).
  //
  var IS_NODE_ENV_PRODUCTION = (process.env.NODE_ENV === 'production');



  return _.defaults(sails.config.http.middleware || {}, {

    // Configure flat file server to serve static files
    // (By default, all explicit+shadow routes take precedence over flat files)
    www: (function() {
      var flatFileMiddleware = require('serve-static')(sails.config.paths['public'], {
        maxAge: sails.config.http.cache
      });

      return flatFileMiddleware;
    })(),

    // If a Connect session store is configured, hook it up to Express
    session: (function() {
      // Silently do nothing if there's no session hook.
      // You can still have session middleware without the session hook enabled,
      // you just have to provide it yourself by configuring sails.config.http.middleware.session.
      if (!sails.hooks.session) {
        sails.log.verbose('Cannot load default HTTP session middleware when Sails session hook is disabled!');
        return;
      }
      // Complain a bit louder if the session hook is enabled, but not configured.
      if (!sails.config.session) {
        sails.log.error('Cannot load default HTTP session middleware without `sails.config.session` configured!');
        return;
      }

      var configuredSessionMiddleware = sails._privateSessionMiddleware;

      return function (req, res, next){

        // --•
        // Run the session middleware.
        configuredSessionMiddleware(req,res,function (err) {
          if (!err) {
            return next();
          }

          var errMsg = 'Error occurred in session middleware :: ' + util.inspect((err&&err.stack)?err.stack:err, false, null);
          sails.log.error(errMsg);

          // If headers have already been sent (e.g. because of timing issues in application-level code),
          // then don't attempt to send another response.
          // (but still log a warning)
          if (res.headersSent) {
            sails.log.warn('The session middleware encountered an error and triggered its callback, but response headers have already been sent.  Rather than attempting to send another response, failing silently...');
            return;
          }

          // --•
          // Otherwise, we can go ahead and send a response.
          return res.send(400, errMsg);
        });
      };

    })(),


    // Build configured favicon mwr function.
    favicon: (function (){
      var toServeFavicon = require('serve-favicon');
      var pathToDefaultFavicon = Path.resolve(__dirname,'./default-favicon.ico');
      var serveFaviconMwr = toServeFavicon(pathToDefaultFavicon);
      return serveFaviconMwr;
    })(),


    /**
     * Track request start time as soon as possible
     *
     * TODO: make this implicit (we can just tack it on to the beginning of the
     * middleware stack automatically--it's confusing that it appears to be configurable)
     */
    startRequestTimer: !IS_NODE_ENV_PRODUCTION && function startRequestTimer(req, res, next) {
      req._startTime = new Date();
      next();
    },

    cookieParser: (function() {
      // backwards compatibility for old express.cookieParser config
      var cookieParser =
        sails.config.http.cookieParser || sails.config.http.middleware.cookieParser;
      var sessionSecret = sails.config.session && sails.config.session.secret;

      // If available, Sails uses the configured session secret for signing cookies.
      if (sessionSecret) {
        // Ensure secret is a string.  This check happens in the session hook as well,
        // but sails.config.session.secret may still be provided even if the session hook
        // is turned off, so to be extra anal we'll check here as well.
        if (!_.isString(sessionSecret)) {
          throw new Error('If provided, sails.config.session.secret should be a string.');
        }
        return cookieParser && cookieParser(sessionSecret);
      }
      // If no session secret was provided in config
      // (e.g. if session hook is disabled and config/session.js is removed)
      // then we do not enable signed cookies by providing a cookie secret.
      // (note that of course signed cookies can still be enabled in a Sails app:
      // see conceptual docs on disabling the session hook for info)
      else {
        return cookieParser && cookieParser();
      }
    })(),

    compress: IS_NODE_ENV_PRODUCTION && require('compression')(),

    // Use body parser, if enabled
    bodyParser: (function() {

      var opts = {};
      var fn;

      opts.onBodyParserError = function (err, req, res, next) {
        var bodyParserFailureErrorMsg = 'Unable to parse HTTP body- error occurred :: ' + util.inspect((err&&err.stack)?err.stack:err, false, null);
        sails.log.error(bodyParserFailureErrorMsg);
        if (IS_NODE_ENV_PRODUCTION && sails.config.keepResponseErrors !== true) {
          return res.status(400).send();
        }
        return res.status(400).send(bodyParserFailureErrorMsg);
      };

      // Handle original bodyParser config:
      ////////////////////////////////////////////////////////
      // If a body parser was configured, use it
      if (sails.config.http.bodyParser) {
        fn = sails.config.http.bodyParser;
        return fn(opts);
      } else if (sails.config.http.bodyParser === false) {
        // Allow for explicit disabling of bodyParser using traditional
        // `express.bodyParser` conf
        return undefined;
      }

      // Default to built-in bodyParser:
      fn = require('skipper');
      return fn(opts);

    })(),

    // Allow simulation of PUT and DELETE HTTP methods for user agents
    // which don't support it natively (looks for a `_method` param)
    methodOverride: (function() {
      if (sails.config.http.methodOverride) {
        return sails.config.http.methodOverride();
      }
    })(),

    // Add powered-by Sails header
    poweredBy: function xPoweredBy(req, res, next) {
      res.header('X-Powered-By', 'Sails <sailsjs.org>');
      next();
    },

    // 404 and 500 middleware should be attached at the very end
    // (after `router`, `www`, and `favicon`)
    404: function handleUnmatchedRequest(req, res, next) {

      // Explicitly ignore error arg to avoid inadvertently
      // turning this into an error handler
      sails.emit('router:request:404', req, res);
    },
    500: function handleError(err, req, res, next) {
      sails.emit('router:request:500', err, req, res);
    }
  });
};
Beispiel #14
0
module.exports = function _join(options) {


  // Usage
  var invalid = false;
  invalid = invalid || anchor(options).to({
    type: 'object'
  });

  // Tolerate `right` and `left` usage
  _.defaults(options, {
    parent: options.left,
    child: options.right,
    parentKey: options.leftKey,
    childKey: options.rightKey,
    childNamespace: options.childNamespace || '.'
  });

  invalid = invalid || anchor(options.parent).to({
    type: 'array'
  });
  invalid = invalid || anchor(options.child).to({
    type: 'array'
  });
  invalid = invalid || anchor(options.parentKey).to({
    type: 'string'
  });
  invalid = invalid || anchor(options.childKey).to({
    type: 'string'
  });

  invalid = invalid || (options.outer === 'right' ?
    new Error('Right joins not supported yet.') : false);

  if (invalid) throw invalid;


  var resultSet = _.reduce(options.parent, function eachParentRow(memo, parentRow) {

    // For each childRow whose childKey matches
    // this parentRow's parentKey...
    var foundMatch = _.reduce(options.child, function eachChildRow(hasFoundMatchYet, childRow) {

      var newRow = partialJoin({
        parentRow: parentRow,
        childRow: childRow,
        parentKey: options.parentKey,
        childKey: options.childKey,
        childNamespace: options.childNamespace
      });

      // console.log('PARENT ROW: ', parentRow);
      // console.log('CHILD ROW: ', childRow);
      // console.log('JOIN ROW: ', newRow);

      // Save the new row for the join result if it exists
      // and mark the match as found
      if (newRow) {
        memo.push(newRow);
        return true;
      }
      return hasFoundMatchYet;
    }, false);

    // If this is a left outer join and we didn't find a match
    // for this parentRow, add it to the result set anyways
    if (!foundMatch && options.outer === 'left') {
      memo.push(_.cloneDeep(parentRow));
    }

    return memo;
  }, []);

  // console.log('JOIN RESULT SET::', resultSet);
  return resultSet;

};
Beispiel #15
0
  before: function(scope, sb) {

    // Make sure we're in the root of a Sails project.
    var pathToPackageJSON = path.resolve(scope.rootPath, 'package.json');
    var pkgJson;
    try {
      pkgJson = require(pathToPackageJSON);
    } catch (e) {
      // If there is an actual parsing error, display a special message.  If there is no package.json at all,
      // it'll fall through to the "Sorry, this command can only be used in the root directory of a Sails project" error below.
      if (e.code !== 'MODULE_NOT_FOUND') {
        return sb.invalid('Could not parse this directory\'s package.json file.  Could there be a typo... maybe a trailing comma?');
      }
    }

    if (!_.isObject(pkgJson) || !_.isObject(pkgJson.dependencies) || !_.contains(_.keys(pkgJson.dependencies), 'sails')) {
      return sb.invalid('Sorry, this command can only be used in the root directory of a Sails project.');
    }

    // scope.args are the raw command line arguments.
    //
    // e.g. if you run:
    // sails generate controlller user find create update
    // then:
    // scope.args = ['user', 'find', 'create', 'update']
    //
    _.defaults(scope, {
      id: _.capitalize(scope.args[0]),
      attributes: scope.args.slice(1)
    });

    if (!scope.rootPath) {
      return sb.invalid('Usage: sails generate model <modelname> [attribute|attribute:type ...]');
    }
    if (!scope.id) {
      return sb.invalid('Usage: sails generate model <modelname> [attribute|attribute:type ...]');
    }


    // If the model identity seems problematic, warn about it.
    // (For example "String" or good old "Uint8ClampedArray")
    // • https://github.com/balderdashy/sails/issues/3080
    var GRAYLIST = [
      'Array',
      'ArrayBuffer',
      'Boolean',
      'Buffer',
      'DTRACE_HTTP_CLIENT_REQUEST',
      'DTRACE_HTTP_CLIENT_RESPONSE',
      'DTRACE_HTTP_SERVER_REQUEST',
      'DTRACE_HTTP_SERVER_RESPONSE',
      'DTRACE_NET_SERVER_CONNECTION',
      'DTRACE_NET_STREAM_END',
      'DataView',
      'Date',
      'Error',
      'EvalError',
      'Float32Array',
      'Float64Array',
      'Function',
      'GLOBAL',
      'Infinity',
      'Int16Array',
      'Int32Array',
      'Int8Array',
      'Intl',
      'JSON',
      'Map',
      'Math',
      'NaN',
      'Number',
      'Object',
      'Promise',
      'RangeError',
      'ReferenceError',
      'RegExp',
      'Set',
      'String',
      'Symbol',
      'SyntaxError',
      'TypeError',
      'URIError',
      'Uint16Array',
      'Uint32Array',
      'Uint8Array',
      'Uint8ClampedArray',
      'WeakMap',
      'WeakSet'
    ];
    if (_.contains(GRAYLIST, scope.id) || _.contains(GRAYLIST, scope.args[0])) {
      console.warn('*•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•*');
      console.warn('Warning: That model identity might be problematic when/if globals are enabled!');
      console.warn('(Please consider renaming the new file to `the'+scope.args[0]+'.js`');
      console.warn('*•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•*');
    }//>-

    // If the model identity seems spooksome, or altogether nutty, then prevent it entirely.
    var BLACKLIST = [
      '__defineGetter__',
      '__defineSetter__',
      '__lookupGetter__',
      '__lookupSetter__',
      '__proto__',
      'constructor',
      'hasOwnProperty',
      'isPrototypeOf',
      'propertyIsEnumerable',
      'toLocaleString',
      'toString',
      'valueOf',
    ];
    if (_.contains(BLACKLIST, scope.id) || _.contains(BLACKLIST, scope.args[0]) || !scope.args[0].match(/^[a-z]/i)) {
      return sb.invalid('Whoops!  Unfortunately, that model identity conflicts with the name of a core feature of JavaScript or Node.js, so it won\'t do.  Would you try a different model identity?  (e.g. `The'+scope.args[0]+'`)');
    }//-•


    // If the identity potentially won't work because of conflicts in our dependencies, then let the user know.
    //
    // > This last one is only an issue until the changes from this PR get published to NPM:
    // > https://github.com/socketio/has-binary/pull/4
    // > (as well as all of the dependents, including socket.io and then, lastly, sails-hook-sockets)
    var BROWNLIST = [
      'File',
      'Blob'
    ];
    if (_.contains(BROWNLIST, scope.id) || _.contains(BROWNLIST, scope.args[0])) {
      console.warn('*•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•*');
      console.warn('Warning: That model identity might be problematic when/if globals are enabled.');
      console.warn('For example, it can conflict with expectations of 3rd party NPM packages that');
      console.warn('are designed to be usable on both the client and the server (e.g. socket.io).');
      console.warn('(It might be a good idea to delete this model file and instead re-generate');
      console.warn('the model with a different name like `UploadedFile`.)');
      console.warn('*•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•* *•~°~•*');
    }//>-



    // Validate optional attribute arguments
    var attributes = scope.attributes || [];
    var invalidAttributes = [];
    attributes = _.map(attributes, function(attribute) {

      var parts = attribute.split(':');

      if (parts[1] === undefined) {
        parts[1] = 'string';
      }

      // Handle invalidAttributes
      if (!parts[1] || !parts[0]) {
        invalidAttributes.push(
          'Invalid attribute notation:   "' + attribute + '"');
        return;
      }
      return {
        name: parts[0],
        type: parts[1]
      };

    });

    // Handle invalid action arguments
    // Send back invalidActions
    if (invalidAttributes.length) {
      return sb.invalid(invalidAttributes);
    }

    // Make sure there aren't duplicates
    if (_.uniq(_.pluck(attributes, 'name')).length !== attributes.length) {
      return sb.invalid('Duplicate attributes not allowed!');
    }

    //
    // Determine default values based on the
    // available scope.
    //
    _.defaults(scope, {
      globalId: _.capitalize(scope.id),
      ext: (scope.coffee) ? '.coffee' : '.js',
    });

    // Take another pass to take advantage of
    // the defaults absorbed in previous passes.
    _.defaults(scope, {
      filename: scope.globalId + scope.ext,
      lang: scope.coffee ? 'coffee' : 'js',
    });



    //
    // Transforms
    //

    // Render some stringified code from the action template
    var compiledAttrDefs = _.map(attributes, function (attrDef) {
      return _.trimRight(
        ATTR_DEF_TEMPLATE_FN({
          name: attrDef.name,
          type: attrDef.type,
          lang: scope.coffee ? 'coffee' : 'js'
        })
      );
    });

    // Then join it all together and make it available in our scope
    // for use in our targets.
    scope.attributes = compiledAttrDefs.join((scope.coffee) ? '\n' : ',\n');

    // Trigger callback with no error to proceed.
    return sb();

  }//</before>
Beispiel #16
0
/**
 * Constructor
 * @param {[type]} opts [description]
 */
function Upstream(opts) {
  var self = this;

  opts = opts || {};
  _.defaults(opts, {
    // highWaterMark: 0,
    objectMode: true,

    // The max # of ms this Upstream will wait without receiving a file
    // before getting frustrated and emitting an error.  (This will tell
    // any connected receivers (writestreams) that they ought to just give
    // up themselves.  This, in turn, triggers the callback for `req.file().upload()`
    // (no buffering is happening, so it's ok for this to be longer)
    // This needs to be long enough to allow any policies/middleware to run.
    // Should not need to exceed 500ms in most cases.
    maxTimeToWaitForFirstFile: 10000,

    // The max # of ms this Upstream will buffer bytes and wait to be plugged
    // into a receiver.  highWaterMark isn't quite enough, since we want to be
    // allow significant buffering in-memory (/utilize back-pressure whenever possible),
    // but we also want to timeout when the really silly sort of requests come in.
    maxTimeToBuffer: 4500
  });

  // Track fatal errors.
  this._fatalErrors = [];

  // Allow `noop` to be passed in to force this Upstream to immediately end.
  if (opts.noop) {
    this.isNoop = true;
  }

  // Keep track of file streams which we've emitted.
  this._files = [];

  // Keep track of timeout timers.
  this.timeouts = {};

  Readable.call(this, opts);

  // Enforce the `maxTimeToWaitForFirstFile` option.
  this.timeouts.untilFirstFileTimer = setTimeout(function() {
    debug('maxTimeToWaitForFirstFile timer fired- as of now there are %d file uploads pending %s', self._files.length, self._files.length === 0 ? '' : '(so it\'s fine)');
    if (self._files.length === 0) {
      var e = new Error();
      e.code = 'ETIMEOUT';
      e.message =
        e.code + ': ' +
        'An Upstream (`' + self.fieldName + '`) timed out waiting for file(s). ' +
        'No files were sent after waiting ' + opts.maxTimeToWaitForFirstFile + 'ms.';
      self.fatalIncomingError(e);
    }
  }, opts.maxTimeToWaitForFirstFile);
  debug('Set up "maxTimeToWaitForFirstFile" timer for %dms', opts.maxTimeToWaitForFirstFile);

  // Enforce the `maxTimeToBuffer` option.
  //
  // Note:
  // This consideration really ought to be taken care of by the normal highWaterMark
  // stuff.  As it is, you may not even want a `maxTimeToBuffer` in certain cases
  // since you may be perfectly happy waiting as long as necessary; provided back-pressure
  // is being properly applied in the receiver (we know with almost complete certainty that
  // it's being properly applied in the sending stream because it's a request- with the caveat
  // that it is possible to build your own fake request stream, e.g. the request interpreter in
  // Sails, or MockReq)
  this.timeouts.untilMaxBufferTimer = setTimeout(function() {
    debug('maxTimeToBuffer timer fired- upstream is %s As of now there are %d file uploads', self._connected?'connected to a receiver (so we\'re good).':'NOT CONNECTED TO A RECEIVER!!',self._files.length);
    if (!self._connected) {
      var e = new Error();
      e.code = 'EMAXBUFFER';
      e.message = e.code + ': An upstream (`' + self.fieldName + '`) timed out before it was plugged into a receiver. ' +
      'It was still unused after waiting ' + opts.maxTimeToBuffer + 'ms. ' +
      'You can configure this timeout by changing the `maxTimeToBuffer` option.\n\n'+
      'Note that this error might be occurring due to an earlier file upload that is ' +
      'finally timing out after an unrelated server error.';
      self.fatalIncomingError(e);
    }//fi
  }, opts.maxTimeToBuffer);//œ
}//ƒ < Upstream() >
Beispiel #17
0
  before: function (scope, done){


    var roughName;

    // Accept --name
    if (scope.name) {
      roughName = scope.name;
    }
    // Or otherwise the first serial arg is the "roughName" of the helper we want to create.
    else if (_.isArray(scope.args)) {

      // If more than one serial args were provided, they'll constitute the helper friendly name
      // and we'll combine them to form the base name.
      if (scope.args.length >= 2) {
        // However, note that we first check to be sure no dots or slashes were used
        // (if so, this would throw things off)
        if (scope.args[0].match(/[\/\.]/)) {
          return done(new Error(
            'Too many serial arguments: A "." or "/" was specified in the name for this new helper, but extra words were provided too.\n' +
            '(should be provided like either `db.lookups.get-search-results` or `Get search results`).'
          ));
        }
        scope.friendlyName = scope.args.join(' ');
        roughName = _.camelCase(scope.friendlyName);
      }
      // Otherwise, we'll infer an appropriate friendly name further on down. and just use the
      // first serial arg as our "roughName".
      else if (scope.args[0] && _.isString(scope.args[0])) {
        roughName = scope.args[0];
      }

    }//>-

    if (!roughName) {
      return done(new Error(
        'Missing argument: Please provide a name for the new helper.\n' +
        '(should be "verby" and use the imperative mood; e.g. `view-profile` or `sign-up`).'
      ));
    }

    // Now get our helperName.
    // Be kind -- transform slashes to dots when creating the helperName.
    scope.helperName = roughName.replace(/\/+/g, '.');
    // Then split on dots and make sure each segment is using camel-case before recombining.
    scope.helperName = _.map(scope.helperName.split('.'), function (segment){
      return _.camelCase(segment);
    }).join('.');

    // After transformation, the helperName must contain only letters, numbers, and dots--
    // and start with a lowercased letter.
    if (!scope.helperName.match(/^[a-z][a-zA-Z0-9.]*$/)) {
      return done( new Error('The name `' + scope.helperName + '` is invalid. '+
                           'Helper names must start with a lower-cased letter and contain only '+
                           'letters, numbers, and dots.'));
    }

    // Then get our `relPath` (filename / path).
    // When building the relPath, we convert any dots to slashes.
    scope.relPath = scope.helperName.replace(/\.+/g, '/');
    // Then split on slashes and make sure each segment is using kebab-case before recombining.
    scope.relPath = _.map(scope.relPath.split('/'), function (segment){
      // One exception: Don't kebab-case words like "v1" within this subdir name, if any.
      return _.map(_.words(segment), function(word){
        if (word.match(/[a-z]+[0-9]+/)) { return word; }
        return _.kebabCase(word);
      }).join('-');
    }).join('/');
    // (And of course, finally, we tack on `.js` (or `.coffee`) at the end-- we're not barbarians after all.)
    scope.relPath += scope.coffee ? '.coffee' : '.js';

    // Grab the filename for potential use in our template.
    scope.filename = path.basename(scope.relPath);

    // Identify the helper "stem" for use below.
    // (e.g. "get-stuff-and-things" from "db.lookups.get-stuff-and-things")
    var helperStem = _.last(scope.helperName.split('.'));

    // Attempt to invent a description, if there isn't one already.
    if (_.isUndefined(scope.description)) {
      scope.description = inventDescription(scope.helperName);
    }//>-

    _.defaults(scope, {
      friendlyName: scope.friendlyName || _.map(_.words(helperStem), function (word, i){ if (i===0) { return _.capitalize(word); } else { return word[0].toLowerCase() + word.slice(1); } }).join(' '),
      description: scope.description || '',
      inferredSuccessOutputFriendlyName: '',
      lang: scope.coffee ? 'coffee' : 'js',
      verbose: false,
      IS_CURRENT_NODE_VERSION_CAPABLE_OF_AWAIT: IS_CURRENT_NODE_VERSION_CAPABLE_OF_AWAIT,
    });

    // If helperStem begins with "get", then make an assumption about what to generate.
    // (set up the success exit with an inferred `outputFriendlyName`)
    if (helperStem.match(/^get/i)) {
      var inferredNounPhraseWords = _.map(_.words(helperStem).slice(1), function (word, i){ if (i===0) { return _.capitalize(word); } else { return word[0].toLowerCase() + word.slice(1); } });
      scope.inferredSuccessOutputFriendlyName = inferredNounPhraseWords.join(' ');
    }//>-

    // console.log('output friendly name:',scope.inferredSuccessOutputFriendlyName);
    // console.log('friendly name:',scope.friendlyName);

    return done();

  },
Beispiel #18
0
 /**
  * _cleanOptions()
  *
  * @param {Object} options
  * @type {Function}
  * @api private
  */
 function _cleanOptions (options) {
   var testDefaults = { log: {level: 'error'}, globals: false };
   options = _.isObject(options) ? options : {};
   return _.defaults(options, testDefaults);
 }
Beispiel #19
0
  res.view = function(/* specifiedPath, locals, optionalCb */) {

    var specifiedPath = arguments[0];
    var locals = arguments[1];
    var optionalCb = arguments[2];

    // sails.log.silly('Running res.view() with arguments:',arguments);

    // By default, generate a path to the view using what we know about the controller+action
    var relPathToView;

    // Ensure req.target is an object, then merge it into req.options
    // (this helps ensure backwards compatibility for users who were relying
    //  on `req.target` in previous versions of Sails)
    req.options = _.defaults(req.options, req.target || {});

    // Try to guess the view by looking at the controller/action
    if (!req.options.view && req.options.action) {
      relPathToView = req.options.action.replace(/\./g, '/');
    }
    // Use the new view config
    else {relPathToView = req.options.view;}

    // Now we have a reasonable guess in `relPathToView`

    // If the path to a view was explicitly specified, use that
    // Serve the view specified
    //
    // If first arg is not an obj or function, treat it like the path
    if (specifiedPath && !_.isObject(specifiedPath) && !_.isFunction(specifiedPath)) {
      relPathToView = specifiedPath;
    }

    // If the "locals" argument is actually the "specifiedPath"
    // give em the old switcheroo
    if (!relPathToView && _.isString(arguments[1])) {
      relPathToView = arguments[1] || relPathToView;
    }
    // If first arg ISSSSS AN object, treat it like locals
    if (_.isObject(arguments[0])) {
      locals = arguments[0];
    }
    // If the second argument is a function, treat it like the callback.
    if (_.isFunction(arguments[1])) {
      optionalCb = arguments[1];
      // In which case if the first argument is a string, it means no locals were specified,
      // so set `locals` to an empty dictionary and log a warning.
      if (_.isString(arguments[0])) {
        sails.log.warn('`res.view` called with (path, cb) signature (using path `' + specifiedPath + '`).  You should use `res.view(path, {}, cb)` to render a view without local variables.');
        locals = {};
      }
    }

    // if (_.isFunction(locals)) {
    //   optionalCb = locals;
    //   locals = {};
    // }
    // if (_.isFunction(specifiedPath)) {
    //   optionalCb = specifiedPath;
    // }

    // If a view path cannot be inferred, send back an error instead
    if (!relPathToView) {
      var err = new Error();
      err.name = 'Error in res.view()';
      err.code = 'E_VIEW_INFER';
      err.type = err.code;// <<TODO remove this
      err.message = 'No path specified, and no path could be inferred from the request context.';

      // Prevent endless recursion:
      if (req._errorInResView) { return res.sendStatus(500); }
      req._errorInResView = err;

      if (optionalCb) { return optionalCb(err); }
      else {return res.serverError(err);}
    }


    // Ensure specifiedPath is a string (important)
    relPathToView = '' + relPathToView + '';

    // Ensure `locals` is an object
    locals = _.isObject(locals) ? locals : {};

    // Mixin locals from req.options.
    // TODO -- replace this _.merge() with a call to the merge-dictionaries module?
    if (req.options.locals) {
      locals = _.merge(locals, req.options.locals);
    }

    // Merge with config views locals
    if (sails.config.views.locals) {
      // Formerly a deep merge: `_.merge(locals, sails.config.views.locals, _.defaults);`
      // Now shallow- see https://github.com/balderdashy/sails/issues/3500
      _.defaults(locals, sails.config.views.locals);
    }

    // If the path was specified, but invalid
    // else if (specifiedPath) {
    //   return res.serverError(new Error('Specified path for view (' + specifiedPath + ') is invalid!'));
    // }

    // Trim trailing slash
    if (relPathToView[(relPathToView.length - 1)] === '/') {
      relPathToView = relPathToView.slice(0, -1);
    }

    var pathToViews = sails.config.paths.views;
    var absPathToView = path.join(pathToViews, relPathToView);
    var absPathToLayout;
    var relPathToLayout;
    var layout = false;

    // Deal with layout options only if there is no custom rendering function in place --
    // that is, only if we're using the default EJS layouts.
    if (!sails.config.views.getRenderFn) {

      layout = locals.layout;

      // If local `layout` is set to true or unspecified
      // fall back to global config
      if (locals.layout === undefined || locals.layout === true) {
        layout = sails.config.views.layout;
      }

      // Allow `res.locals.layout` to override if it was set:
      if (typeof res.locals.layout !== 'undefined') {
        layout = res.locals.layout;
      }


      // At this point, layout should be either `false` or a string
      if (typeof layout !== 'string') {
        layout = false;
      }

      // Set layout file if enabled (using ejs-locals)
      if (layout) {

        // Solve relative path to layout from the view itself
        // (required by ejs-locals module)
        absPathToLayout = path.join(pathToViews, layout);
        relPathToLayout = path.relative(path.dirname(absPathToView), absPathToLayout);

        // If a layout was specified, set view local so it will be used
        res.locals._layoutFile = relPathToLayout;

        // sails.log.silly('Using layout at: ', absPathToLayout);
      }
    }

    // Locals passed in to `res.view()` override app and route locals.
    _.each(locals, function(local, key) {
      res.locals[key] = local;
    });


    // Provide access to view metadata in locals
    // (for convenience)
    if (_.isUndefined(res.locals.view)) {
      res.locals.view = {
        path: relPathToView,
        absPath: absPathToView,
        pathFromViews: relPathToView,
        pathFromApp: path.join(path.relative(sails.config.appPath, sails.config.paths.views), relPathToView),
        ext: sails.config.views.extension
      };
    }

    // Set up the `exposeLocalsToBrowser` view helper method
    // (unless there is already a local by the same name)
    //
    // Background:
    //  -> https://github.com/balderdashy/sails/pull/3522#issuecomment-174242822
    if (_.isUndefined(res.locals.exposeLocalsToBrowser)) {
      res.locals.exposeLocalsToBrowser = function (options){
        if (!_.isObject(options)) { options = {}; }

        // Note:
        // We get access to locals using a reference obtained via closure--
        // and since this view helper won't be used until AFTER the rest of
        // the code in THIS file has run, we know any relevant changes to
        // `locals` below will be available, since we're referring to the
        // same object.

        // Note that we include both explicit locals passed to res.view(),
        // and implicitly-set locals from `res.locals`.  But we exclude
        // non-relevant built-in properties like `sails` and `_`, as well
        // as experimental properties like `view`.
        //
        // Also note that we create a new dictionary to avoid tampering.
        var relevantLocals = {};

        _.each(_.union(_.keys(res.locals), _.keys(locals)), function(localName){

          // Explicitly exclude `_locals`, which appears even in explicit locals.
          // (FUTURE: longer term, could look into doing this _everywhere_ as an optimization-
          //  but need to investigate other view engines for potential differences)
          if (localName === '_locals') {}
          // Explicitly exclude `layout`, since it has special meaning,
          // even when it appears even in explicit locals.
          else if (localName === 'layout') {}
          // Otherwise, use explicit local, if available
          else if (locals[localName] !== undefined) {
            relevantLocals[localName] = locals[localName];
          }
          // Otherwise, use the one from res.locals... maybe.
          else {
            if (localName === '_csrf') {
              // Special case for CSRF token
              // > If the security hook is disabled, there won't be a CSRF token in the locals.
              // > If the hook is enabled but CSRF is disabled for this route, the token will
              // > be an empty string.  In either of those cases we can just skip it.
              if (res.locals._csrf !== undefined && res.locals._csrf !== '') {
                relevantLocals._csrf = res.locals._csrf;
              }
            }
            else if (_.contains(['_', 'sails', 'view', 'session', 'req', 'res', '__dirname', '_layoutFile'], localName)) {
              // Exclude any other auto-injected implicit locals
            }
            else if (_.isFunction(res.locals[localName])) {
              // Exclude any functions
            }
            else {
              // Otherwise include it!
              relevantLocals[localName] = res.locals[localName];
            }
          }
        });//∞

        // Return an HTML string which includes a special script tag.
        return htmlScriptify({
          data: relevantLocals,
          keys: options.keys,
          namespace: options.namespace,
          dontUnescapeOnClient: options.dontUnescapeOnClient
        });
      };//</defun :: res.locals.exposeLocalsToBrowser()>
    }//>-

    // Unless this is production, provide access to complete view path to view via `__dirname` local.
    if (process.env.NODE_ENV !== 'production') {
      res.locals.__dirname =
        res.locals.__dirname ||
        (absPathToView + '.' + sails.config.views.extension);
    }

    // If silly logging is enabled, display some diagnostic information about the res.view() call:
    if (specifiedPath) { sails.log.silly('View override argument passed to res.view(): ', specifiedPath); }
    sails.log.silly('Serving view at rel path: ', relPathToView);
    sails.log.silly('View root: ', sails.config.paths.views);

    // Render the view
    return res.render(relPathToView, locals, function viewFailedToRender(err, renderedViewStr) {


      // Prevent endless recursion:
      if (err && req._errorInResView) {
        return res.status(500).send(err);
      }


      if (err) {
        req._errorInResView = err;

        // Ensure that if res.serverError() likes to serve views,
        // it won't this time because we ran into a view error.
        req.wantsJSON = true;

        // Enhance the raw Express view error object
        // (this error appears when a view is missing)
        if (_.isObject(err) && err.view) {
          err = _.extend({
            message: util.format(
              'Could not render view "%s".  Tried locating view file @ "%s".%s',
              relPathToView,
              absPathToView,
              (layout ? util.format(' Layout configured as "%s", so tried using layout @ "%s")', layout, absPathToLayout) : '')
            ),
            code: 'E_VIEW_FAILED',
            status: 500
          }, err);
          err.inspect = function () {
            return err.message;
          };
        }
      }

      // If specified, trigger `res.view()` callback instead of proceeding
      if (typeof optionalCb === 'function') {
        // The provided optionalCb callback will receive the error (if there is one)
        // as the first argument, and the rendered HTML as the second argument.
        return optionalCb(err, renderedViewStr);
      }
      else {

        // if a template error occurred, don't rely on any of the Sails request/response methods
        // (since they may not exist yet at this point in the request lifecycle.)
        if (err) {

          //////////////////////////////////////////////////////////////////
          // TODO:
          // Consider removing this log and deferring to the logging that is
          // happening in res.serverError() instead.
          // sails.log.error('Error rendering view at ::', absPathToView);
          // sails.log.error('with layout located at ::', absPathToLayout);
          // sails.log.error(err && err.message);
          //
          //////////////////////////////////////////////////////////////////

          //////////////////////////////////////////////////////////////////
          // TODO:
          // Consider just calling some kind of default error handler fn here
          // in order to consolidate the "i dont know wtf i should do w/ this err" logic.
          // (keep in mind the `next` we have here is NOT THE SAME as the `next` from
          //  the point when this error occurred!  It is the next from when this
          //  method was initially attached to the request object in the views hook.)
          //
          if (res.serverError) {
            req.wantsJSON = true;
            return res.serverError(err);
          }
          else if (process.env.NODE_ENV !== 'production') {
            return res.status(500).send(err);
          }
          else {return res.sendStatus(500);}
          //
          //////////////////////////////////////////////////////////////////
        }

        // If verbose logging is enabled, write a log w/ the layout and view that was rendered.
        sails.log.verbose('Rendering view: "%s" (located @ "%s")', relPathToView,absPathToView);
        if (layout) {
          sails.log.verbose('• using configured layout:: %s (located @ "%s")', layout, absPathToLayout);
        }

        // Finally, send the compiled HTML from the view down to the client
        res.send(renderedViewStr);
      }

    });
  };//</defun :: res.view() >
Beispiel #20
0
module.exports = function(scope, cb) {

  //
  // scope.args are the raw command line arguments.
  //
  // e.g. if you run:
  // sails generate controlller user find create update
  // then:
  // scope.args = ['user', 'find', 'create', 'update']
  //


  // Look at arguments and set path to adapters (i.e. `:adapterPath`)
  if (scope.args) {
    if (scope.args[0]) {

      if (!scope.adapterType) {
        scope.adapterType = scope.args[0];
      }

      // Determine path to adapters
      var DEFAULT_ADAPTERS_PATH = scope.standalone ? '.' : 'api/adapters';
      scope.adapterPath = path.join(DEFAULT_ADAPTERS_PATH, scope.adapterType);
    }
  }

  // Use `adapterType` instead of `adapterName` if it's specified.
  scope.adapterName = scope.adapterType || scope.adapterName;
  scope.adapterType = scope.adapterType || scope.adapterName;
  scope.globalID = scope.adapterName+'Adapter';
  scope.identity = _.kebabCase(scope.adapterName);

  //
  // Validate custom scope variables which
  // are required by this generator.
  //

  if (!scope.adapterType) {
    return cb(new Error(
      'Missing argument: Please provide an `adapterType` for the new adapter.\n' +
      '(should refer to the type of database/webservice/thing it connects to; e.g. `mysql` or `irc`).'
    ));
  }

  // Determine default values based on the available scope.
  // (Taking multiple "passes" if necessary.)
  _.defaults(scope, {
    github: _.defaults(scope.github || {}, {
      // i.e.
      // Would you mind telling me your username on GitHub?
      // (or favorite pseudonym)
      username: process.env.USER || ''
    }),
    year: (new Date()).getFullYear(),

    // Waterline adapter tests (WAT) semver range
    waterlineAdapterTestsSVR: WATERLINE_ADAPTER_TESTS_SVR,

    // Waterline adapter spec API version
    adapterApiVersion: ADAPTER_SPEC_VERSION,

  });

  _.defaults(scope, {
    website: util.format('http://github.com/%s', scope.github.username),
    author: util.format('%s', scope.github.username) || 'a node.js/sails user',
    license: 'MIT',
    repository: _.defaults(scope.repository || {}, {
      type: 'git',
      url: util.format('git://github.com/%s/sails-%s.git', scope.github.username, scope.adapterType)
    })
  });


  // Determine an appropriate package name.
  // (Use a scoped package name if we have a github username)
  if (scope.github.username) {
    scope.packageName = ('@'+scope.github.username+'/' + 'sails-'+scope.adapterType.replace(/^sails-/,'')).toLowerCase();
  }
  else {
    scope.packageName = ('sails-'+scope.adapterType.replace(/^sails-/,'')).toLowerCase();
  }


  // Trigger callback with no error to proceed.
  return cb();

};