Full Code of leemalmac/frodo for AI

master 283c582d86df cached
24 files
36.2 KB
11.0k tokens
28 symbols
1 requests
Download .txt
Repository: leemalmac/frodo
Branch: master
Commit: 283c582d86df
Files: 24
Total size: 36.2 KB

Directory structure:
gitextract_bs2hj3vx/

├── .gitignore
├── README.md
├── bin/
│   └── frodo
├── lib/
│   ├── config.js
│   ├── files.js
│   ├── index.js
│   ├── pluralize.js
│   ├── routes.js
│   └── templates/
│       ├── .gitignore
│       ├── dynamic/
│       │   ├── controller
│       │   └── model
│       ├── index.js
│       └── static/
│           ├── app/
│           │   ├── assets/
│           │   │   └── stylesheets/
│           │   │       └── application.css
│           │   ├── controllers/
│           │   │   └── welcome_controller.js
│           │   └── views/
│           │       ├── layouts/
│           │       │   └── application.pug
│           │       └── welcome/
│           │           └── index.pug
│           ├── config/
│           │   ├── application.js
│           │   ├── database.js
│           │   ├── globals.js
│           │   └── routes.js
│           ├── db/
│           │   ├── connection.js
│           │   └── util.js
│           └── index.js
└── package.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
node_modules/

================================================
FILE: README.md
================================================
# Frodo

Frodo is a Rails-like app generator for Node and Express. It was created to simplify working
with projects based on the Express framework. Nowadays, many developers are familiar with the Ruby on Rails
framework, which helps you generate a structure for your app. An app generated with Frodo will help you in the same way.

**Motivation:**
  * I use Rails a lot and I love how Rails apps are organized
  * It can be difficult to start with a micro-framework like Express without a predefined structure.
    Frodo will help developers using Node/Express to create a new app as fast as possible.


## Documentation:

#### Installation

```shell
$ npm install -g frodojs
```

Done. Now you can easily invoke the Frodo command line tool.

#### Usage:
Frodo has a command line interface like Rails, but, for now, Frodo has support for a very limited set of actions. You are able to:
```
- create a new application
- generate controller/model or scaffold
```

To create a new application use 'new' followed by application name:
```shell
$ frodo new blog
```

This command will create a new project with a predefined folders structure.

If you want to build an API, and you don't need views or static files, use the `--skipViews` optional argument.
```shell
$ frodo new api --skipViews
```

**Generators**

Like in Rails, Frodo can create a controller, a model, or a combination of both with a scaffold.

Generating a controller
```shell
$ frodo generate controller user [methods]
```
This command creates an empty controller in the app/controllers folder, which
should look like this:
```javascript
// users_controller.js
var UsersController = (function () {
  return {
  }
}());

module.exports = UsersController;
```
and corresponding static files users.css and users.js in app/assests/stylesheets and app/assets/javascripts respectively.

You can also write a list of methods separated by whitespaces.
```shell
$ frodo generate controller user new show
```
this produces:
```javascript
// users_controller.js
var UsersController = (function () {
  return {
	new: function (req, res) {},
    show: function (req, res) {}
  }
}());
```
and creates corresponding the views.

Generating a model:
```shell
$ frodo generate model user name age:number
```
produces:
```javascript
var mongoose = require('mongoose'),
    ObjectId = mongoose.Schema.Types.ObjectId,
    Mixed = Schema.Types.Mixed;

var UserSchema = mongoose.Schema({
  name: {type: String, required: true},
  age: {type: Number, required: true},
  created_at: {type: Number, required: true, default: new Date().getTime()},
  updated_at: {type: Number, required: true, default: new Date().getTime()}
});

var User = mongoose.model('User', UserSchema);

module.exports = User;
```

Each property in a model generator may consist of 3 parts separated by a semicolon - 'name:type:required'
```shell
$ frodo generate model user email:string:true
```
where property name is email, type string, and it is required field. Type and required are not mandatory
parts and can be ommited.

Available types:
  * String
  * Number
  * Date
  * Buffer
  * Boolean
  * Mixed
  * Objectid
  * Array

At the moment, Frodo supports only mongoose for defining models.


================================================
FILE: bin/frodo
================================================
#! /usr/bin/env node

var frodo = require('../lib/index.js');

================================================
FILE: lib/config.js
================================================
module.exports = {
  appname: null,

  set: function (props) {
    for (var key in props) {
      this[key] = props[key];
    }
  }
};


================================================
FILE: lib/files.js
================================================
/**
 * [exports description]
 * @type {Object}
 */

var fs = require('fs'),
    os = {path: require('path')},
    config = require('./config');


/**
 *
 * @param {String} path [description]
 * @param {Object} locals
 *
 * @return {Boolean}
 */
function useTemplate(path, locals) {
  var fileName = path.split(config.appname).pop().replace('/', ''),
      template = os.path.join(__dirname, 'templates/static', fileName);

  if (fs.existsSync(template)) {
    fs.createReadStream(template).pipe(fs.createWriteStream(path));
    return true;
  }
};


module.exports = {
  createFile: function (path, content) {
    console.log('Creating file', path);

    if (useTemplate(path)) {
      return;
    }

    if (content) {
      fs.writeFileSync(path, content);
    } else {
      file = fs.openSync(path, 'w');
      fs.closeSync(file);
    }
  },

  /**
   *
   * @param  {[type]} files [description]
   * @param  {[type]} path  [description]
   */
  createFiles: function (files, path) {
    var file = null,
        self = this;

    for (var i = 0; i < files.length; i++) {
      file = files[i];
      self.createFile(os.path.join(path, file));
    }
  }
};


================================================
FILE: lib/index.js
================================================
/**
 * Frodo.js is a app generator for Express-based aplications.
 * Easily set structure in a json file and pass path for the file as command line argument
 */

String.prototype.capitalize = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
}

var fs = require('fs'),
    exec = require('child_process').execSync,
    /* path is actively used, so I made Python like name */
    os = {path: require('path')},
    pluralize = require('./pluralize'),
    generateRoutes = require('./routes'),
    config = require('./config'),
    files = require('./files'),
    templates = require('./templates');


var $WORKING_DIR = process.cwd(),
    $SKIP_VIEWS = false,
    $SKIP_ASSETS = false,
    $SCAFFOLD = false,
    $APP = os.path.join($WORKING_DIR, 'app'),
    $ASSETS = os.path.join($APP, 'assets'),
    $CONTROLLERS = os.path.join($APP, 'controllers'),
    $MODELS = os.path.join($APP, 'models');
    $VIEWS = os.path.join($APP, 'views'),
    $VERSION = '0.6.0',
    $PREPROCESSORS = {
      views: 'pug',
      stylesheets: 'css',
      javascripts: 'js'
    },
    $SCHEMA_TYPES = ['String', 'Number', 'Date', 'Buffer', 'Boolean', 'Mixed', 'ObjectId', 'Array'];


var skipViewsItems = ['assets', 'views', 'vendor', 'public'];


/**
 * Project structure
 */
var skeleton = {
  files: ['index.js', '.gitignore'],
  app: {
    assets: {
      images: {files: ['.keep']},
      javascripts: {files: ['application.js']},
      stylesheets: {files: ['application.css']}
    },
    controllers: {files: ['welcome_controller.js']},
    models: {files: ['.keep']},
    views: {
      layouts: {files: ['application.pug']},
      welcome: {files: ['index.pug']}
    },
    helpers: {files: ['.keep']}
  },
  bin: {files: ['.keep']},
  config: {
    environments: {files: ['development.js', 'test.js', 'production.js']},
    files: ['application.js', 'environment.js', 'database.js', 'routes.js', 'globals.js']
  },
  db: {files: ['.keep', 'connection.js', 'util.js']},
  lib: {files: ['.keep']},
  log: {files: ['.keep']},
  middleware: {
    files: ['.keep']
  },
  public: {
    files: ['404.pug', '500.pug', 'favicon.ico', 'robots.txt']
  },
  test: {
    controllers: {files: ['.keep']},
    models: {files: ['.keep']},
    helpers: {files: ['.keep']}
  },
  tmp: {files: ['.keep']},
  vendor: {
    javascripts: {files: ['.keep']},
    stylesheets: {files: ['.keep']}
  }
};


/**
 * Checks if argument is object
 *
 * @method isObject
 * @param {Any} obj value to be checked
 * @return Boolean
 */
function isObject(obj) {
  return Object.prototype.toString.call(obj) === '[object Object]';
}


/**
 * Generates project folders structure
 */
function generateSkeleton(skeleton, path) {
  for (key in skeleton) {
    if (skeleton.hasOwnProperty(key)) {
      if ($SKIP_VIEWS && skipViewsItems.indexOf(key) >= 0) {
        continue;
      }

      if (isObject(skeleton[key])) {
        console.log('Creating folder', os.path.join(path, key));
        fs.mkdirSync(os.path.join(path, key));
        generateSkeleton(skeleton[key], os.path.join(path, key));
      } else if (key === 'files') {
        files.createFiles(skeleton.files, path);
      }
    }
  }
}


function generatePackageJson(projectPath, appName) {
  var packageJson = '{\n'+
                    '  "name": "'+appName+'",\n'+
                    '  "version": "0.0.1",\n'+
                    '  "description": "",\n'+
                    '  "main": "index.js",\n'+
                    '  "license": "ISC"\n'+
                    '}';

  files.createFile(os.path.join(projectPath, 'package.json'), packageJson)
}


function npmInit(projectPath) {
  console.log('Installing dependencies');
  process.chdir(projectPath);
  console.log('Running: npm install express --save');
  exec('npm install express --save');
  console.log('Running: npm install body-parser --save');
  exec('npm install body-parser --save');
  console.log('Running: npm install mongoose --save');
  exec('npm install mongoose --save');
  console.log('Running: npm install async --save');
  exec('npm install async --save');

  if (!$SKIP_VIEWS) {
    console.log('Running: npm install pug --save');
    exec('npm install pug --save');
  }
}


/**
 *
 */
function getControllerContents (name, methods) {
  return templates.render('dynamic/controller', {methods: methods});
}


/**
 * Generates a new controller. If $SKIP_VIEWS is false, generateViews will be executed.
 * Views folder name is first argument of the generateController function, views names the same as
 * methods.
 *
 * ! For now routes would not be generated automatically. This feature is on the road map
 *
 * @method generateController
 * @param {String} name controller name
 * @param {Array} methods a list of methods to be added to the controller
 */
function generateController(name, methods) {
  var pluralName = pluralize(name),
      fullName = pluralName+'_controller.js',
      contents = getControllerContents(pluralName, methods);

  files.createFile(os.path.join($CONTROLLERS, fullName), contents);

  if (!$SKIP_VIEWS && methods) {
    generateViews(pluralName, methods);
  }

  if (!$SKIP_ASSETS) {
    generateAssets(pluralName);
  }

  generateRoutes(pluralName, fullName, methods, $SCAFFOLD);
}


/**
 * Generates javascript and stylesheet files.
 *
 * @method generateAssets
 *
 * @param {String} name name of file as is.
 */
function generateAssets(name) {
  var js = os.path.join($ASSETS, 'javascripts', name+'.'+$PREPROCESSORS.javascripts),
      css = os.path.join($ASSETS, 'stylesheets', name+'.'+$PREPROCESSORS.stylesheets);

  files.createFile(js);
  files.createFile(css);
}


function getDefaultViewContent (viewPath) {
  return "extends ../layouts/application.pug\n\n" +
         "block content\n"+
         "  p You can find this view template at "+viewPath;
}


/**
 * Generates new views
 *
 * @method generateViews
 *
 * @param {String} dirName a directory for new views
 * @param {Array} methods a list of views to be generated
 */
function generateViews(dirName, views) {
  var viewsPath = os.path.join($VIEWS, dirName),
      view = null;

  /* If views directory does not exist - skip this step */
  if (!fs.existsSync($VIEWS)) {
    return;
  }

  if (!fs.existsSync(os.path.join($VIEWS, dirName))) {
    fs.mkdirSync(os.path.join($VIEWS, dirName));
  }

  for (var i = 0; i < views.length; i++) {
    /* Don't create views in the array if scaffold has been called */
    if ($SCAFFOLD && ['create', 'update', 'delete'].indexOf(views[i]) >= 0 ) {
      continue;
    }

    view = views[i]+'.'+$PREPROCESSORS.views;

    var content = getDefaultViewContent(os.path.join(viewsPath, view));
    files.createFile(os.path.join(viewsPath, view), content);
  }
}


/**
 * Generates mongoose compatible schema object.
 *
 * @method createMongooseSchema
 *
 * @param {Array} props - an array of model properties. Each item is a string 'name:type:required'
 *                        where only 'name' is required part. If type is omitted, then the default
 *                        value (String) will be used.
 */
function createMongooseSchema(options) {
  var modelName = options.name,
      nameCap = modelName.capitalize(),
      schemaName = nameCap+"Schema",
      propsRaw = options.props,
      props = [],
      splitted = null;

  propsRaw.forEach(function (val, i) {
    splitted = val.split(':');
    var name = splitted[0],
        type = splitted[1],
        required = splitted[2];

    if (splitted.length === 2 && type === 'true') {
      type = null;
      required = 'true';
    }

    if (!type) {
      type = 'String';
    }

    if ($SCHEMA_TYPES.indexOf(type.capitalize()) < 0) {
      console.error('Unknown type', type, 'for', name);
      process.exit(1);
    }

    props.push({name: name, type: type.capitalize(), required: required});
  });

  return templates.render('dynamic/model', {name: modelName, nameCap: nameCap,
                                            schemaName: schemaName, props: props});
}


/**
 * Generates a new model
 *
 * @method generateModel
 *
 * @param {String} name model name
 * @prop {Array} props an array of properties. A property of collon seperated strings.
 * propName:propType:required:defaultValue. Example: login:string:required,
 * created_at:date:required:now
 *
 */
function generateModel(name, props) {
  var schema = createMongooseSchema({name: name, props: props});

  /* If models directory does not exist - skip this step */
  /* TODO: add logging here */
  if (!fs.existsSync($MODELS)) {
    return;
  }

  files.createFile(os.path.join($MODELS, name+'.js'), schema);
}


/**
 * ------ THIS FEATURE NOT AVAILABLE --------
 * Use your favorite preprocessors with frodo generators. In order to configure preprocessors
 * add module.exports.preprocessors into config/application. If preprocessors are not set frodo
 * will use default values: jade for views, css for stylesheets, js for scripts. If any value is
 * missed, frodo will use default for this as well.
 *
 * /!\ These settigns do not set your assets pipeline. Such a feature is on the roadmap. Don't forget
 * to set preprocessing of your assets.
 *
 * Example ('config/application.js'):
 *  module.exports.preprocessors = {
 *    views: 'jade',
 *    stylesheets: 'scss',
 *    javascripts: 'coffee'
 *  }
 */
function setPreprocessors() {
  var preprocessors = null;

  try {
    preprocessors = require(os.path.join($WORKING_DIR, 'config/application')).preprocessors;
  } catch (e) {
    // console.log("Can't find config/application.js");
  }

  if (!preprocessors) {
    return;
  } else {
    if (preprocessors.views) {
      $PREPROCESSORS.views = preprocessors.views;
    }

    if (preprocessors.stylesheets) {
      $PREPROCESSORS.stylesheets = preprocessors.stylesheets;
    }

    if (preprocessors.javascripts) {
      $PREPROCESSORS.javascripts = preprocessors.javascripts;
    }
  }
}


/**
 * Parses command line arguments and if they are correct, call required action.
 *
 * @method main
 */
function main () {
  var argv = process.argv;

  if (!fs.existsSync($ASSETS)) {
    $SKIP_ASSETS = true;
  }

  setPreprocessors();

  if (argv.length === 2) {
    console.log('too few arguments');
    return;
  } else if (argv[2] === 'new') {
    if (argv.length === 3) {
      console.log('Usage: frodo new project_name. Example: frodo new blog');
    } else {
      config.set({appname: argv[3]});
      var projectPath = os.path.join($WORKING_DIR, argv[3]);
      fs.mkdirSync(projectPath);

      if (argv.indexOf('--skipViews') >= 0) {
        $SKIP_VIEWS = true;
      }

      generateSkeleton(skeleton, projectPath);
      generatePackageJson(projectPath, argv[3]);
      npmInit(projectPath);
    }
  } else if (argv[2] === 'generate') {
    config.set({appname: process.cwd().split('/').pop()});

    if  (argv.length < 5) {
      console.log('Usage: frodo generate controller controller_name.'+
                  'Example: frodo generate controller users');
      return;
    }
    if (argv[3] === 'controller') {
      generateController(argv[4], argv.slice(5));
    } else if (argv[3] === 'model') {
      generateModel(argv[4], argv.slice(5));
    } else if (argv[3] === 'scaffold') {
      $SCAFFOLD = true;
      generateController(argv[4], ['index', 'show', 'new', 'edit', 'create', 'update', 'delete']);
      generateModel(argv[4], argv.slice(5));
    }
  } else if (argv[2] === 'server') {
    try {
      exec('node index.js', {stdio: 'inherit'});
    } catch (e) {
      /* TODO: add logging */
      return;
    }
  } else {
    console.log('=> Unknown command "' + argv[2] + '"');
  }
}

main();


================================================
FILE: lib/pluralize.js
================================================
/**
 * Return a pluralized or singularized word based on the input string.
 * Author: Blake Embrey
 * Repository: https://github.com/blakeembrey/pluralize
 */

/* global define */

(function (root, pluralize) {
  /* istanbul ignore else */
  if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
    // Node.
    module.exports = pluralize()
  } else if (typeof define === 'function' && define.amd) {
    // AMD, registers as an anonymous module.
    define(function () {
      return pluralize()
    })
  } else {
    // Browser global.
    root.pluralize = pluralize()
  }
})(this, function () {
  // Rule storage - pluralize and singularize need to be run sequentially,
  // while other rules can be optimized using an object for instant lookups.
  var pluralRules = []
  var singularRules = []
  var uncountables = {}
  var irregularPlurals = {}
  var irregularSingles = {}

  /**
   * Title case a string.
   *
   * @param  {string} str
   * @return {string}
   */
  function toTitleCase (str) {
    return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase()
  }

  /**
   * Sanitize a pluralization rule to a usable regular expression.
   *
   * @param  {(RegExp|string)} rule
   * @return {RegExp}
   */
  function sanitizeRule (rule) {
    if (typeof rule === 'string') {
      return new RegExp('^' + rule + '$', 'i')
    }

    return rule
  }

  /**
   * Pass in a word token to produce a function that can replicate the case on
   * another word.
   *
   * @param  {string}   word
   * @param  {string}   token
   * @return {Function}
   */
  function restoreCase (word, token) {
    // Upper cased words. E.g. "HELLO".
    if (word === word.toUpperCase()) {
      return token.toUpperCase()
    }

    // Title cased words. E.g. "Title".
    if (word[0] === word[0].toUpperCase()) {
      return toTitleCase(token)
    }

    // Lower cased words. E.g. "test".
    return token.toLowerCase()
  }

  /**
   * Interpolate a regexp string.
   *
   * @param  {string} str
   * @param  {Array}  args
   * @return {string}
   */
  function interpolate (str, args) {
    return str.replace(/\$(\d{1,2})/g, function (match, index) {
      return args[index] || ''
    })
  }

  /**
   * Sanitize a word by passing in the word and sanitization rules.
   *
   * @param  {String}   word
   * @param  {Array}    collection
   * @return {String}
   */
  function sanitizeWord (word, collection) {
    // Empty string or doesn't need fixing.
    if (!word.length || uncountables.hasOwnProperty(word)) {
      return word
    }

    var len = collection.length

    // Iterate over the sanitization rules and use the first one to match.
    while (len--) {
      var rule = collection[len]

      // If the rule passes, return the replacement.
      if (rule[0].test(word)) {
        return word.replace(rule[0], function (match, index, word) {
          var result = interpolate(rule[1], arguments)

          if (match === '') {
            return restoreCase(word[index - 1], result)
          }

          return restoreCase(match, result)
        })
      }
    }

    return word
  }

  /**
   * Replace a word with the updated word.
   *
   * @param  {Object}   replaceMap
   * @param  {Object}   keepMap
   * @param  {Array}    rules
   * @return {Function}
   */
  function replaceWord (replaceMap, keepMap, rules) {
    return function (word) {
      // Get the correct token and case restoration functions.
      var token = word.toLowerCase()

      // Check against the keep object map.
      if (keepMap.hasOwnProperty(token)) {
        return restoreCase(word, token)
      }

      // Check against the replacement map for a direct word replacement.
      if (replaceMap.hasOwnProperty(token)) {
        return restoreCase(word, replaceMap[token])
      }

      // Run all the rules against the word.
      return sanitizeWord(word, rules)
    }
  }

  /**
   * Pluralize or singularize a word based on the passed in count.
   *
   * @param  {String}  word
   * @param  {Number}  count
   * @param  {Boolean} inclusive
   * @return {String}
   */
  function pluralize (word, count, inclusive) {
    var pluralized = count === 1 ?
      pluralize.singular(word) : pluralize.plural(word)

    return (inclusive ? count + ' ' : '') + pluralized
  }

  /**
   * Pluralize a word.
   *
   * @type {Function}
   */
  pluralize.plural = replaceWord(
    irregularSingles, irregularPlurals, pluralRules
  )

  /**
   * Singularize a word.
   *
   * @type {Function}
   */
  pluralize.singular = replaceWord(
    irregularPlurals, irregularSingles, singularRules
  )

  /**
   * Add a pluralization rule to the collection.
   *
   * @param {(string|RegExp)} rule
   * @param {string}          replacement
   */
  pluralize.addPluralRule = function (rule, replacement) {
    pluralRules.push([sanitizeRule(rule), replacement])
  }

  /**
   * Add a singularization rule to the collection.
   *
   * @param {(string|RegExp)} rule
   * @param {string}          replacement
   */
  pluralize.addSingularRule = function (rule, replacement) {
    singularRules.push([sanitizeRule(rule), replacement])
  }

  /**
   * Add an uncountable word rule.
   *
   * @param {(string|RegExp)} word
   */
  pluralize.addUncountableRule = function (word) {
    if (typeof word === 'string') {
      uncountables[word.toLowerCase()] = true
      return
    }

    // Set singular and plural references for the word.
    pluralize.addPluralRule(word, '$0')
    pluralize.addSingularRule(word, '$0')
  }

  /**
   * Add an irregular word definition.
   *
   * @param {String} single
   * @param {String} plural
   */
  pluralize.addIrregularRule = function (single, plural) {
    plural = plural.toLowerCase()
    single = single.toLowerCase()

    irregularSingles[single] = plural
    irregularPlurals[plural] = single
  }

  /**
   * Irregular rules.
   */
  ;[
    // Pronouns.
    ['I', 'we'],
    ['me', 'us'],
    ['he', 'they'],
    ['she', 'they'],
    ['them', 'them'],
    ['myself', 'ourselves'],
    ['yourself', 'yourselves'],
    ['itself', 'themselves'],
    ['herself', 'themselves'],
    ['himself', 'themselves'],
    ['themself', 'themselves'],
    ['this', 'these'],
    ['that', 'those'],
    // Words ending in with a consonant and `o`.
    ['echo', 'echoes'],
    ['dingo', 'dingoes'],
    ['volcano', 'volcanoes'],
    ['tornado', 'tornadoes'],
    ['torpedo', 'torpedoes'],
    // Ends with `us`.
    ['genus', 'genera'],
    ['viscus', 'viscera'],
    // Ends with `ma`.
    ['stigma', 'stigmata'],
    ['stoma', 'stomata'],
    ['dogma', 'dogmata'],
    ['lemma', 'lemmata'],
    ['schema', 'schemata'],
    ['anathema', 'anathemata'],
    // Other irregular rules.
    ['ox', 'oxen'],
    ['axe', 'axes'],
    ['die', 'dice'],
    ['yes', 'yeses'],
    ['foot', 'feet'],
    ['eave', 'eaves'],
    ['goose', 'geese'],
    ['tooth', 'teeth'],
    ['quiz', 'quizzes'],
    ['human', 'humans'],
    ['proof', 'proofs'],
    ['carve', 'carves'],
    ['valve', 'valves'],
    ['thief', 'thieves'],
    ['genie', 'genies'],
    ['groove', 'grooves'],
    ['pickaxe', 'pickaxes'],
    ['whiskey', 'whiskies']
  ].forEach(function (rule) {
    return pluralize.addIrregularRule(rule[0], rule[1])
  })

  /**
   * Pluralization rules.
   */
  ;[
    [/s?$/i, 's'],
    [/([^aeiou]ese)$/i, '$1'],
    [/(ax|test)is$/i, '$1es'],
    [/(alias|[^aou]us|tlas|gas|ris)$/i, '$1es'],
    [/(e[mn]u)s?$/i, '$1s'],
    [/([^l]ias|[aeiou]las|[emjzr]as|[iu]am)$/i, '$1'],
    [/(alumn|syllab|octop|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1i'],
    [/(alumn|alg|vertebr)(?:a|ae)$/i, '$1ae'],
    [/(seraph|cherub)(?:im)?$/i, '$1im'],
    [/(her|at|gr)o$/i, '$1oes'],
    [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, '$1a'],
    [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, '$1a'],
    [/sis$/i, 'ses'],
    [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, '$1$2ves'],
    [/([^aeiouy]|qu)y$/i, '$1ies'],
    [/([^ch][ieo][ln])ey$/i, '$1ies'],
    [/(x|ch|ss|sh|zz)$/i, '$1es'],
    [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, '$1ices'],
    [/(m|l)(?:ice|ouse)$/i, '$1ice'],
    [/(pe)(?:rson|ople)$/i, '$1ople'],
    [/(child)(?:ren)?$/i, '$1ren'],
    [/eaux$/i, '$0'],
    [/m[ae]n$/i, 'men'],
    ['thou', 'you']
  ].forEach(function (rule) {
    return pluralize.addPluralRule(rule[0], rule[1])
  })

  /**
   * Singularization rules.
   */
  ;[
    [/s$/i, ''],
    [/(ss)$/i, '$1'],
    [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(?:sis|ses)$/i, '$1sis'],
    [/(^analy)(?:sis|ses)$/i, '$1sis'],
    [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, '$1fe'],
    [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, '$1f'],
    [/([^aeiouy]|qu)ies$/i, '$1y'],
    [/(^[pl]|zomb|^(?:neck)?t|[aeo][lt]|cut)ies$/i, '$1ie'],
    [/([^c][eor]n|smil)ies$/i, '$1ey'],
    [/(m|l)ice$/i, '$1ouse'],
    [/(seraph|cherub)im$/i, '$1'],
    [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|tlas|gas|(?:her|at|gr)o|ris)(?:es)?$/i, '$1'],
    [/(e[mn]u)s?$/i, '$1'],
    [/(movie|twelve)s$/i, '$1'],
    [/(cris|test|diagnos)(?:is|es)$/i, '$1is'],
    [/(alumn|syllab|octop|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1us'],
    [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, '$1um'],
    [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, '$1on'],
    [/(alumn|alg|vertebr)ae$/i, '$1a'],
    [/(cod|mur|sil|vert|ind)ices$/i, '$1ex'],
    [/(matr|append)ices$/i, '$1ix'],
    [/(pe)(rson|ople)$/i, '$1rson'],
    [/(child)ren$/i, '$1'],
    [/(eau)x?$/i, '$1'],
    [/men$/i, 'man']
  ].forEach(function (rule) {
    return pluralize.addSingularRule(rule[0], rule[1])
  })

  /**
   * Uncountable rules.
   */
  ;[
    // Singular words with no plurals.
    'advice',
    'agenda',
    'bison',
    'bream',
    'buffalo',
    'carp',
    'chassis',
    'cod',
    'cooperation',
    'corps',
    'digestion',
    'debris',
    'diabetes',
    'energy',
    'equipment',
    'elk',
    'excretion',
    'expertise',
    'flounder',
    'gallows',
    'garbage',
    'graffiti',
    'headquarters',
    'health',
    'herpes',
    'highjinks',
    'homework',
    'information',
    'jeans',
    'justice',
    'kudos',
    'labour',
    'machinery',
    'mackerel',
    'media',
    'mews',
    'moose',
    'news',
    'pike',
    'plankton',
    'pliers',
    'pollution',
    'premises',
    'rain',
    'rice',
    'salmon',
    'scissors',
    'series',
    'sewage',
    'shambles',
    'shrimp',
    'species',
    'staff',
    'swine',
    'trout',
    'tuna',
    'whiting',
    'wildebeest',
    'wildlife',
    'you',
    'hello',
    'auth',
    // Regexes.
    /pox$/i, // "chickpox", "smallpox"
    /ois$/i,
    /deer$/i, // "deer", "reindeer"
    /fish$/i, // "fish", "blowfish", "angelfish"
    /sheep$/i,
    /measles$/i,
    /[^aeiou]ese$/i // "chinese", "japanese"
  ].forEach(pluralize.addUncountableRule)

  return pluralize
})


================================================
FILE: lib/routes.js
================================================
/**
 * config/routes.js manipulations
 */

var fs = require('fs'),
    path = require('path');

var $ROUTES_FILE = path.join(process.cwd(), 'config/routes.js');


function findPlaceForRequire (lines, linesNum) {
  var lastRequireLineNum = 0,
      line = null;

  for (var i = 0; i < linesNum; i++) {
    line = lines[i];

    if (line.indexOf('controller') >= 0) {
      lastRequireLineNum = i;
    }
  }

  return lastRequireLineNum;
}


function addNewRequire (lines, lastRequireLineNum, name, fullName) {
  var newLine = "    "+name+" = require('../app/controllers/"+ fullName +"');"
  lines[lastRequireLineNum] = lines[lastRequireLineNum].replace(";", ",");
  lines.splice(lastRequireLineNum+1, 0, newLine);
}


function addRoutes (lines, controller, methods) {
  var method = null;

  for (var i = 0; i < methods.length; i++) {
    method = methods[i];
    lines.push("app.get('/"+controller+"/"+method+"', "+controller+"."+method+");");
  }
}


function addScaffoldRoutes (lines, controller) {
  lines.push("app.get('/"+controller+"', "+controller+".index);");
  lines.push("app.get('/"+controller+"/new', "+controller+".new);");
  lines.push("app.post('/"+controller+"/create', "+controller+".create);");
  lines.push("app.get('/"+controller+"/:id/edit', "+controller+".edit);");
  lines.push("app.get('/"+controller+"/:id', "+controller+".show);");
  lines.push("app.put('/"+controller+"/:id', "+controller+".update);");
  lines.push("app.delete('/"+controller+"/:id', "+controller+".delete);");
}


module.exports = function (name, fullName, methods, scaffold) {
  var lines = fs.readFileSync($ROUTES_FILE, {encoding: 'utf-8'}).split('\n'),
      linesNum = lines.length;

  var lastRequireLineNum = findPlaceForRequire(lines, linesNum);

  /* Prevents addition of routes in any routes for a controller exists */
  for (var i = 0; i < lines.length; i++) {
    if (lines[i].indexOf(fullName) >= 0) {
      return;
    }
  }

  addNewRequire(lines, lastRequireLineNum, name, fullName);

  if (scaffold) {
    addScaffoldRoutes(lines, name);
  } else if (methods) {
    addRoutes(lines, name, methods);
  }

  fs.writeFileSync($ROUTES_FILE, lines.join('\n'), {encoding: 'utf-8'});
}


================================================
FILE: lib/templates/.gitignore
================================================
# Ignore all logfiles and tempfiles.
node_modules/
/log/*
!/log/.keep
/tmp
!/tmp/.keep


================================================
FILE: lib/templates/dynamic/controller
================================================

module.exports = {
  {{#methods}}
  {{.}}: (req, res) => {
    // your code goes here
  },
  {{/methods}}
};


================================================
FILE: lib/templates/dynamic/model
================================================
var mongoose = require('mongoose'),
    ObjectId = mongoose.Schema.Types.ObjectId,
    Mixed = mongoose.Schema.Types.Mixed;

var {{schemaName}} = mongoose.Schema({
  {{#props}}
  {{name}}: {type: {{type}}},
  {{/props}}
  created_at: {type: Number, required: true, default: new Date().getTime()},
  updated_at: {type: Number, required: true, default: new Date().getTime()}
});

var {{nameCap}} = mongoose.model('{{nameCap}}', {{schemaName}});

module.exports = {{nameCap}};


================================================
FILE: lib/templates/index.js
================================================
var fs = require('fs'),
    path = require('path'),
    Mustache = require('mustache');


/**
 *
 * @param  {Object} options [description]
 * @return {String}         [description]
 */
function renderController (options) {
  var _path = path.join(__dirname, 'dynamic/controller'),
      text = fs.readFileSync(_path, {encoding: 'utf8'});

  return Mustache.render(text, {methods: options.methods});
}


/**
 *
 * @param  {Object} options [description]
 * @return {String}         [description]
 */
function renderModel (options) {
  var _path = path.join(__dirname, 'dynamic/model'),
      text = fs.readFileSync(_path, {encoding: 'utf8'});

  return Mustache.render(text, options);
}



module.exports = {
  /**
   * [render description]
   * @param  {String} template [description]
   * @param  {Object} options  [description]
   * @return {String}          [description]
   */
  render(template, options) {
    switch  (template) {
      case 'dynamic/controller':
        return renderController(options);
        break;
      case 'dynamic/model':
        return renderModel(options);
        break;
      default:
        console.log('Unknown template ' + template);
        return;
    }
  },
};



================================================
FILE: lib/templates/static/app/assets/stylesheets/application.css
================================================
div#content {font-size: 16px;}

section {margin-bottom: 20px;}
section:last-child {margin-bottom: 0;}


================================================
FILE: lib/templates/static/app/controllers/welcome_controller.js
================================================

module.exports = {
  index: function (req, res) {
    res.render('welcome/index');
  }
};



================================================
FILE: lib/templates/static/app/views/layouts/application.pug
================================================
doctype html
html(lang="en")
  head
    title Welcome!

    link(href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" type="text/css" rel="stylesheet")
    link(href="/assets/stylesheets/application.css" type="text/css" rel="stylesheet")
    script(src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js")
    script(src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js")
    script(src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js")
    script(src="/assets/javascripts/application.js")
  body
    div#header

    div#content
      block content

    div#footer


================================================
FILE: lib/templates/static/app/views/welcome/index.pug
================================================
extends ../layouts/application.pug

block content
  div.fluid_container
    div.row
      div.col-md-8.col-md-offset-2
        h1.text-center Welcome to the NodeJS world!
        section#intro
          | This page has been automaticaly created by
          a(href="https://www.npmjs.com/package/frodojs") frodojs
          |. Frodo is a Rails-like app generator for
          a(href="https://nodejs.org") Node
          |  and
          a(href="http://expressjs.com/") Express
          | . It was created to simplify working with projects based on the Express framework.

        section#docs
          | First thing first - frodojs is not a framework, it is a tool to help you organize your
          | Node apps similar to Rails (if you want it:)). Frodo uses Express as the framework,
          | moreover it is pure Express withou any modifications, so you will be able to upgrage
          | instantly. See available documentation on the
          a(href="https://github.com/leemalmac/frodo") Github Page


================================================
FILE: lib/templates/static/config/application.js
================================================
var path = require('path'),
    express = require('express'),
    app = express();


global.ENV = process.env.NODE_ENV || 'development';
console.log('=> NODE_ENV:', ENV);


app.set('view engine', 'pug');
app.set('views', path.join(ROOT, 'app/views'));
app.use('/assets', express.static(path.join(ROOT, 'app/assets')));

/**
 * Add middleware here
 *
 * Example:
 *    app.use(bodyParser.json());
 *    app.use('/public', express.static('public'));
 */

module.exports = app;


================================================
FILE: lib/templates/static/config/database.js
================================================
/* Database configuration file */

module.exports = {
  development: {
    servers: [['127.0.0.1', 27017]],
    database: '',
    user: '',
    password: '',
    replicaSet: null,
  }
}

================================================
FILE: lib/templates/static/config/globals.js
================================================
/**
 * Define useful global veriables here
 */

global.ROOT = process.cwd();


================================================
FILE: lib/templates/static/config/routes.js
================================================
/**
 * Require controllers and bound it to routes.
 *
 * Example:
 *    var app = require('./application'),
 *        users = require('../app/controllers/users_controller');
 *
 *    app.get('/users', users.index);
 *    app.post('/users', users.create);
 *    app.put('/users/:id', users.update);
 *    app.delete('/users/:id, users.delete');
 *
 */

var app = require('./application'),
    welcome = require('../app/controllers/welcome_controller');

app.get('/', welcome.index);


================================================
FILE: lib/templates/static/db/connection.js
================================================
var mongoose = require('mongoose');

/**
 * Require databse configuration depending on environment
 */
var conf = require('../config/database.js')[ENV],
    util = require('./util'),
    options = {useMongoClient: true};


mongoose.Promise = Promise;


var connectionString = util.createConnectionString(conf);


if (conf.replicaSet) {
  options.replset = conf.replicaSet;
}


mongoose.connect(connectionString, options);


================================================
FILE: lib/templates/static/db/util.js
================================================
/**
 * Creates mongodb's connection string
 */
var createConnectionString = function (conf) {
  'mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]';
  var str = 'mongodb://';

  if (conf.user && conf.password)
    str = str+conf.user + ':' + conf.password + '@';

  conf.servers.forEach(function (v, i, a) {
    var host = v[0],
        port = v[1];

    str = str+host+':'+port;

    if (i < conf.servers.length-1) {
      str=str+',';
    }
  });

  if (!conf.database || typeof conf.database !== "string" || conf.database.length === 0) {
    console.error('=> Database name should be a nonempty string');
    process.exit(1);
  }

  str = str + '/' + conf.database;

  return str;
};


module.exports = {
  createConnectionString: createConnectionString
}


================================================
FILE: lib/templates/static/index.js
================================================
require('./config/globals');
var app = require('./config/application');
require('./db/connection');
require('./config/routes');


var server = app.listen(7000, function () {
  var host = server.address().address;
  var port = server.address().port;
  console.log('=> Express application starting on http://%s:%s', host, port);
  console.log('=> Ctrl-C to shutdown server');
});


================================================
FILE: package.json
================================================
{
  "name": "frodojs",
  "version": "0.6.1",
  "description": "Frodo is a Rails-like app generator for Node and Express. It was created to simplify work with projects based on Express framework. Nowadays, many developers are familiar with Ruby on Rails framework, so app generated with Frodo will help you to kickstart faster.",
  "main": "frodo",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/leemalmac/frodo.git"
  },
  "keywords": [
    "node",
    "mean",
    "javascript",
    "rails"
  ],
  "author": "Nodari Lipartiya <nodari.lipartiya@gmail.com>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/leemalmac/frodo/issues"
  },
  "homepage": "https://github.com/leemalmac/frodo#readme",
  "dependencies": {
    "jade": "^1.11.0",
    "mustache": "^2.3.0"
  },
  "bin": {
    "frodo": "./bin/frodo"
  }
}
Download .txt
gitextract_bs2hj3vx/

├── .gitignore
├── README.md
├── bin/
│   └── frodo
├── lib/
│   ├── config.js
│   ├── files.js
│   ├── index.js
│   ├── pluralize.js
│   ├── routes.js
│   └── templates/
│       ├── .gitignore
│       ├── dynamic/
│       │   ├── controller
│       │   └── model
│       ├── index.js
│       └── static/
│           ├── app/
│           │   ├── assets/
│           │   │   └── stylesheets/
│           │   │       └── application.css
│           │   ├── controllers/
│           │   │   └── welcome_controller.js
│           │   └── views/
│           │       ├── layouts/
│           │       │   └── application.pug
│           │       └── welcome/
│           │           └── index.pug
│           ├── config/
│           │   ├── application.js
│           │   ├── database.js
│           │   ├── globals.js
│           │   └── routes.js
│           ├── db/
│           │   ├── connection.js
│           │   └── util.js
│           └── index.js
└── package.json
Download .txt
SYMBOL INDEX (28 symbols across 5 files)

FILE: lib/files.js
  function useTemplate (line 18) | function useTemplate(path, locals) {

FILE: lib/index.js
  function isObject (line 95) | function isObject(obj) {
  function generateSkeleton (line 103) | function generateSkeleton(skeleton, path) {
  function generatePackageJson (line 122) | function generatePackageJson(projectPath, appName) {
  function npmInit (line 135) | function npmInit(projectPath) {
  function getControllerContents (line 157) | function getControllerContents (name, methods) {
  function generateController (line 173) | function generateController(name, methods) {
  function generateAssets (line 199) | function generateAssets(name) {
  function getDefaultViewContent (line 208) | function getDefaultViewContent (viewPath) {
  function generateViews (line 223) | function generateViews(dirName, views) {
  function createMongooseSchema (line 259) | function createMongooseSchema(options) {
  function generateModel (line 306) | function generateModel(name, props) {
  function setPreprocessors (line 336) | function setPreprocessors() {
  function main (line 368) | function main () {

FILE: lib/pluralize.js
  function toTitleCase (line 38) | function toTitleCase (str) {
  function sanitizeRule (line 48) | function sanitizeRule (rule) {
  function restoreCase (line 64) | function restoreCase (word, token) {
  function interpolate (line 86) | function interpolate (str, args) {
  function sanitizeWord (line 99) | function sanitizeWord (word, collection) {
  function replaceWord (line 136) | function replaceWord (replaceMap, keepMap, rules) {
  function pluralize (line 164) | function pluralize (word, count, inclusive) {

FILE: lib/routes.js
  function findPlaceForRequire (line 11) | function findPlaceForRequire (lines, linesNum) {
  function addNewRequire (line 27) | function addNewRequire (lines, lastRequireLineNum, name, fullName) {
  function addRoutes (line 34) | function addRoutes (lines, controller, methods) {
  function addScaffoldRoutes (line 44) | function addScaffoldRoutes (lines, controller) {

FILE: lib/templates/index.js
  function renderController (line 11) | function renderController (options) {
  function renderModel (line 24) | function renderModel (options) {
  method render (line 40) | render(template, options) {
Condensed preview — 24 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (40K chars).
[
  {
    "path": ".gitignore",
    "chars": 13,
    "preview": "node_modules/"
  },
  {
    "path": "README.md",
    "chars": 3209,
    "preview": "# Frodo\n\nFrodo is a Rails-like app generator for Node and Express. It was created to simplify working\nwith projects base"
  },
  {
    "path": "bin/frodo",
    "chars": 61,
    "preview": "#! /usr/bin/env node\n\nvar frodo = require('../lib/index.js');"
  },
  {
    "path": "lib/config.js",
    "chars": 135,
    "preview": "module.exports = {\n  appname: null,\n\n  set: function (props) {\n    for (var key in props) {\n      this[key] = props[key]"
  },
  {
    "path": "lib/files.js",
    "chars": 1161,
    "preview": "/**\n * [exports description]\n * @type {Object}\n */\n\nvar fs = require('fs'),\n    os = {path: require('path')},\n    config"
  },
  {
    "path": "lib/index.js",
    "chars": 11613,
    "preview": "/**\n * Frodo.js is a app generator for Express-based aplications.\n * Easily set structure in a json file and pass path f"
  },
  {
    "path": "lib/pluralize.js",
    "chars": 11211,
    "preview": "/**\n * Return a pluralized or singularized word based on the input string.\n * Author: Blake Embrey\n * Repository: https:"
  },
  {
    "path": "lib/routes.js",
    "chars": 2190,
    "preview": "/**\n * config/routes.js manipulations\n */\n\nvar fs = require('fs'),\n    path = require('path');\n\nvar $ROUTES_FILE = path."
  },
  {
    "path": "lib/templates/.gitignore",
    "chars": 87,
    "preview": "# Ignore all logfiles and tempfiles.\nnode_modules/\n/log/*\n!/log/.keep\n/tmp\n!/tmp/.keep\n"
  },
  {
    "path": "lib/templates/dynamic/controller",
    "chars": 110,
    "preview": "\nmodule.exports = {\n  {{#methods}}\n  {{.}}: (req, res) => {\n    // your code goes here\n  },\n  {{/methods}}\n};\n"
  },
  {
    "path": "lib/templates/dynamic/model",
    "chars": 474,
    "preview": "var mongoose = require('mongoose'),\n    ObjectId = mongoose.Schema.Types.ObjectId,\n    Mixed = mongoose.Schema.Types.Mix"
  },
  {
    "path": "lib/templates/index.js",
    "chars": 1204,
    "preview": "var fs = require('fs'),\n    path = require('path'),\n    Mustache = require('mustache');\n\n\n/**\n *\n * @param  {Object} opt"
  },
  {
    "path": "lib/templates/static/app/assets/stylesheets/application.css",
    "chars": 102,
    "preview": "div#content {font-size: 16px;}\n\nsection {margin-bottom: 20px;}\nsection:last-child {margin-bottom: 0;}\n"
  },
  {
    "path": "lib/templates/static/app/controllers/welcome_controller.js",
    "chars": 92,
    "preview": "\nmodule.exports = {\n  index: function (req, res) {\n    res.render('welcome/index');\n  }\n};\n\n"
  },
  {
    "path": "lib/templates/static/app/views/layouts/application.pug",
    "chars": 647,
    "preview": "doctype html\nhtml(lang=\"en\")\n  head\n    title Welcome!\n\n    link(href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/c"
  },
  {
    "path": "lib/templates/static/app/views/welcome/index.pug",
    "chars": 1012,
    "preview": "extends ../layouts/application.pug\n\nblock content\n  div.fluid_container\n    div.row\n      div.col-md-8.col-md-offset-2\n "
  },
  {
    "path": "lib/templates/static/config/application.js",
    "chars": 475,
    "preview": "var path = require('path'),\n    express = require('express'),\n    app = express();\n\n\nglobal.ENV = process.env.NODE_ENV |"
  },
  {
    "path": "lib/templates/static/config/database.js",
    "chars": 185,
    "preview": "/* Database configuration file */\n\nmodule.exports = {\n  development: {\n    servers: [['127.0.0.1', 27017]],\n    database"
  },
  {
    "path": "lib/templates/static/config/globals.js",
    "chars": 77,
    "preview": "/**\n * Define useful global veriables here\n */\n\nglobal.ROOT = process.cwd();\n"
  },
  {
    "path": "lib/templates/static/config/routes.js",
    "chars": 482,
    "preview": "/**\n * Require controllers and bound it to routes.\n *\n * Example:\n *    var app = require('./application'),\n *        us"
  },
  {
    "path": "lib/templates/static/db/connection.js",
    "chars": 422,
    "preview": "var mongoose = require('mongoose');\n\n/**\n * Require databse configuration depending on environment\n */\nvar conf = requir"
  },
  {
    "path": "lib/templates/static/db/util.js",
    "chars": 812,
    "preview": "/**\n * Creates mongodb's connection string\n */\nvar createConnectionString = function (conf) {\n  'mongodb://[username:pas"
  },
  {
    "path": "lib/templates/static/index.js",
    "chars": 378,
    "preview": "require('./config/globals');\nvar app = require('./config/application');\nrequire('./db/connection');\nrequire('./config/ro"
  },
  {
    "path": "package.json",
    "chars": 928,
    "preview": "{\n  \"name\": \"frodojs\",\n  \"version\": \"0.6.1\",\n  \"description\": \"Frodo is a Rails-like app generator for Node and Express."
  }
]

About this extraction

This page contains the full source code of the leemalmac/frodo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 24 files (36.2 KB), approximately 11.0k tokens, and a symbol index with 28 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!