Search code examples
node.jsember.jsember-cli

Ember blueprint that injects environment config?


I'm relatively new to Ember and was wondering if there is a way to create a blueprint/generator that would inject a new value into the environment config while maintaining all existing configuration. Is there some Ember magic that allows an existing file to act as the blueprint template? My ideal implementation would look something like this:

ember g platform foo

// config/environment.js
module.exports = function(environment) {
var ENV = {
  // Existing config values here...
  APP: {
    platforms: {
      foo: 'abc123'  // Generator injects the 'foo' platform and a GUID
    }
  };
  // Existing environment-specific settings here...
  return ENV;
};

Is this something that would be more easily accomplished using Node's fs.readFile() and fs.writeFile()? If so, how could I parse environment.js?


Solution

  • No there's no existing magic in Ember to my knowledge sorry. When you generate a route, something very similar to what you are talking about happens but the code is rather complex. The ember generate route new_route function has a call to this function

    function addRouteToRouter(name, options) {
      var routerPath = path.join(options.root, 'app', 'router.js');
      var source = fs.readFileSync(routerPath, 'utf-8');
    
      var routes = new EmberRouterGenerator(source);
      var newRoutes = routes.add(name, options);
    
      fs.writeFileSync(routerPath, newRoutes.code());
    }
    

    which then exectutes interpreter level like code to add the router and revert it back to code:

    module.exports = EmberRouterGenerator;
    
    var recast    = require('recast');
    var traverse  = require('es-simpler-traverser');
    
    var Scope     = require('./scope');
    var DefineCallExpression = require('./visitors/define-call-expression.js');
    
    var findFunctionExpression = require('./helpers/find-function-expression');
    var hasRoute               = require('./helpers/has-route');
    var newFunctionExpression  = require('./helpers/new-function-expression');
    var resourceNode           = require('./helpers/resource-node');
    var routeNode              = require('./helpers/route-node');
    
    function EmberRouterGenerator(source, ast) {
      this.source = source;
      this.ast = ast;
      this.mapNode = null;
      this.scope = new Scope();
    
      this.visitors = {
        CallExpression: new DefineCallExpression(this.scope, this)
      };
    
      this._ast();
      this._walk();
    }
    
    EmberRouterGenerator.prototype.clone = function() {
      var route = new EmberRouterGenerator(this.source);
    
      return route;
    };
    
    EmberRouterGenerator.prototype._ast  = function() {
      this.ast = this.ast || recast.parse(this.source);
    };
    
    EmberRouterGenerator.prototype._walk  = function() {
      var scope = this.scope;
      var visitors = this.visitors;
    
      traverse(this.ast, {
        exit: function(node) {
          var visitor = visitors[node.type];
    
          if (visitor && typeof visitor.exit === 'function') {
            visitor.exit(node);
          }
        },
    
        enter: function(node) {
          var visitor = visitors[node.type];
    
          if (visitor && typeof visitor.enter === 'function') {
            visitor.enter(node);
          }
        }
      });
    };
    
    EmberRouterGenerator.prototype.add = function(routeName, options) {
      if (typeof this.mapNode === 'undefined') {
        throw new Error('Source doesn\'t include Ember.map');
      }
    
      var route = this.clone();
      var routes  = route.mapNode.arguments[0].body.body;
    
      route._add.call(
        route,
        routeName.split('/'),
        routes,
        options
      );
    
      return route;
    };
    
    
    
    EmberRouterGenerator.prototype._add = function(nameParts, routes, options) {
      options = options || {};
      var parent   =  nameParts[0];
      var name     = parent;
      var children = nameParts.slice(1);
      var route    = hasRoute(parent, routes);
    
      if (!route) {
        if (options.type === 'resource') {
          route = resourceNode(name, options);
          routes.push(route);
        } else {
          route = routeNode(name, options);
          routes.push(route);
        }
      }
    
      if (children.length > 0) {
        var routesFunction = findFunctionExpression(route.expression.arguments);
    
        if (!routesFunction) {
          routesFunction = newFunctionExpression();
          route.expression.arguments.push(routesFunction);
        }
    
        this._add(children, routesFunction.body.body, options);
      }
    };
    
    EmberRouterGenerator.prototype.remove = function(routeName) {
      if (typeof this.mapNode === 'undefined') {
        throw new Error('Source doesn\'t include Ember.map');
      }
    
      var route = this.clone();
      var routes  = route.mapNode.arguments[0].body.body;
    
      var newRoutes = route._remove.call(
        route,
        routeName.split('/'),
        routes
      );
    
      if (newRoutes) {
        route.mapNode.arguments[0].body.body = newRoutes;
      }
    
      return route;
    };
    
    
    
    EmberRouterGenerator.prototype._remove = function(nameParts, routes) {
      var parent   =  nameParts[0];
      var name     = parent;
      var children = nameParts.slice(1);
      var route    = hasRoute(parent, routes);
      var newRoutes;
    
      if (children.length > 0) {
        var routesFunction = route.expression && findFunctionExpression(route.expression.arguments);
    
        if (routesFunction) {
          newRoutes = this._remove(children, routesFunction.body.body);
    
          if (newRoutes) {
            routesFunction.body.body = newRoutes;
          }
    
          return routes;
        }
      } else {
        if (route) {
          routes = routes.filter(function(node) {
            return node !== route;
          });
    
          return routes;
        } else {
          return false;
        }
      }
    };
    
    EmberRouterGenerator.prototype.code = function(options) {
      options = options || { tabWidth: 2, quote: 'single' };
    
      return recast.print(this.ast, options).code;
    };
    

    So then there's the alternative, which involves reading the file, adding in your new environment in the correct place after parsing the file correctly, and then writing the stream back. The complexity of what you are wanting to do probably outweighs the time it would take to do this manually IMO. If this is something you are doing often, maybe consider writing a script in another language that's better(read as more people use it for this) at textual file manipulation