sequelize/sequelize-auto

View on GitHub
bin/sequelize-auto

Summary

Maintainability
Test Coverage
#!/usr/bin/env node
const SequelizeAuto = require('../');
const path = require('path');
const readline = require('readline');
const _ = require('lodash');

const argv = require('yargs')
  .parserConfiguration({
    "parse-numbers": false // disabled because of password field, other option can still be explicitly defined as number type
  })
  .usage(
    'Usage: sequelize-auto -h <host> -d <database> -u <user> -x [password] -p [port]  --dialect [dialect] -c [/path/to/config] -o [/path/to/models] -t [tableName]'
  )
  .option('host', {
    description: 'IP/Hostname for the database.',
    type: 'string',
    alias: 'h'
  })
  .option('database', {
    description: 'Database name.',
    type: 'string',
    alias: 'd'
  })
  .option('user', {
    description: 'Username for database.',
    type: 'string',
    alias: 'u'
  })
  .option('pass', {
    description: 'Password for database. If specified without providing a password, it will be requested interactively from the terminal.',
    alias: 'x'
  })
  .option('port', {
    description: 'Port number for database (not for sqlite). Ex: MySQL/MariaDB: 3306, Postgres: 5432, MSSQL: 1433',
    type: 'number',
    alias: 'p'
  })
  .option('config', {
    description: 'Path to JSON file for Sequelize-Auto options and Sequelize\'s constructor "options" flag object as defined here: https://sequelize.org/api/v6/class/src/sequelize.js~sequelize#instance-constructor-constructor',
    type: 'string',
    alias: 'c'
  })
  .option('output', {
    description: 'What directory to place the models.',
    type: 'string',
    alias: 'o'
  })
  .option('dialect', {
    description: "The dialect/engine that you're using: postgres, mysql, sqlite, mssql",
    type: 'string',
    alias: 'e'
  })
  .option('additional', {
    description: "Path to JSON file containing model options (for all tables). See the options: https://sequelize.org/api/v6/class/src/model.js~model#static-method-init",
    type: 'string',
    alias: 'a'
  })
  .option('indentation', {
    description: 'Number of spaces to indent',
    type: 'number'
  })
  .option('tables', {
    description: 'Space-separated names of tables to import',
    array: true,
    type: 'string',
    alias: 't'
  })
  .option('skipTables', {
    description: 'Space-separated names of tables to skip',
    array: true,
    type: 'string',
    alias: 'T'
  })
  .option('skipFields', {
    description: 'Space-separated names of fields to skip',
    array: true,
    type: 'string',
    alias: 'F'
  })
  .option('pkSuffixes', {
    description: 'Space-separated names of primary key suffixes to trim (default is "id")',
    array: true,
    type: 'string'
  })
  .option('caseModel', {
    description: 'Set case of model names: c|l|o|p|u \n c = camelCase \n l = lower_case \n o = original (default) \n p = PascalCase \n u = UPPER_CASE',
    alias: 'cm'
  })
  .option('caseProp', {
    description: 'Set case of property names: c|l|o|p|u',
    alias: 'cp'
  })
  .option('caseFile', {
    description: 'Set case of file names: c|l|o|p|u|k \n k = kebab-case',
    alias: 'cf'
  })
  .option('noAlias', {
    description: 'Avoid creating alias `as` property in relations',
    type: 'boolean'
  })
  .option('noIndexes', {
    description: 'Prevent writing index information in the models',
    type: 'boolean'
  })
  .option('noInitModels', {
    description: 'Prevent writing the init-models file',
    type: 'boolean'
  })
  .option('noWrite', {
    description: 'Prevent writing the models to disk',
    type: 'boolean',
    alias: 'n'
  })
  .option('schema', {
    description: 'Database schema from which to retrieve tables',
    type: 'string',
    alias: 's'
  })
  .option('views', {
    description: 'Include database views in generated models',
    type: 'boolean',
    alias: 'v'
  })
  .option('lang', {
    description: 'Language for Model output: es5|es6|esm|ts \n es5 = ES5 CJS modules (default) \n es6 = ES6 CJS modules \n esm = ES6 ESM modules \n ts = TypeScript ',
    type: 'string',
    alias: 'l'
  })
  .option('useDefine', {
    description: 'Use `sequelize.define` instead of `init` for es6|esm|ts',
    type: 'boolean'
  })
  .option('singularize', {
    description: 'Singularize model and file names from plural table names',
    type: 'boolean',
    alias: 'sg'
  })
  .check(argv => Boolean((argv.database && (argv.host || argv.dialect === 'sqlite')) || argv.config))
  .argv;

function getDefaultPort(dialect) {
  switch (dialect.toLowerCase()) {
    case 'mssql':
      return 1433;
    case 'postgres':
      return 5432;
    default:
      return 3306;
  }
}

async function readPassword() {
  let rl = readline.createInterface({
    input: process.stdin,
    terminal: true
  });

  process.stdout.write('Password: ');
  let pwd = await new Promise(resolve => rl.question('', pwd => resolve(pwd)));
  rl.close();
  process.stdout.write('\n');

  return pwd;
}

/* eslint-disable complexity, max-statements */
(async function() {

  let password;
  if (typeof argv.pass === 'boolean' && argv.pass) {
    password = await readPassword();
  } else if (typeof argv.pass === 'string') {
      console.warn('Warning: using a password on the command line interface can be insecure.');
      password = argv.pass;
  }

  const dir = !argv.noWrite && (argv.output || path.resolve(process.cwd() + '/models'));

  /** @type {import('../types').AutoOptions}  */
  let configFile = {
    spaces: true,
    indentation: 2
  };

  if (argv.config) {
    configFile = require(path.resolve(argv.config));
  }

  configFile.directory = configFile.directory || dir;

  let additional = {};
  if (argv.additional) {
    additional = require(path.resolve(argv.additional));
  } else if (configFile.additional) {
    additional = configFile.additional;
  }

  configFile.additional = additional;
  configFile.dialect = argv.dialect || configFile.dialect || 'mysql';
  configFile.port = argv.port || configFile.port || getDefaultPort(configFile.dialect);
  configFile.host = argv.host || configFile.host || 'localhost';
  configFile.database = argv.database || configFile.database;
  configFile.storage = configFile.storage || configFile.database;
  configFile.tables = argv.tables || configFile.tables || null;
  configFile.skipTables = argv.skipTables || configFile.skipTables || null;
  configFile.skipFields = argv.skipFields || configFile.skipFields || null;
  configFile.pkSuffixes = argv.pkSuffixes || configFile.pkSuffixes || null;
  configFile.schema = argv.schema || configFile.schema;
  configFile.lang = argv.lang || configFile.lang || 'es5';
  configFile.caseModel = argv.caseModel || configFile.caseModel || 'o';
  configFile.caseFile = argv.caseFile || configFile.caseFile || 'o';
  configFile.caseProp = argv.caseProp || configFile.caseProp || 'o';
  configFile.noAlias = argv.noAlias || configFile.noAlias || false;
  configFile.noInitModels = argv.noInitModels || configFile.noInitModels || false;
  configFile.noWrite = argv.noWrite || configFile.noWrite || false;
  configFile.views = argv.views || configFile.views || false;
  configFile.singularize = argv.singularize || configFile.singularize || false;
  configFile.password = password || configFile.password || null;
  configFile.username = argv.user || configFile.username;
  configFile.useDefine = argv.useDefine || configFile.useDefine || false;
  configFile.indentation = argv.indentation || configFile.indentation || 2;
  configFile.noIndexes = argv.noIndexes || configFile.noIndexes || false;

  console.log(_.omit(configFile, 'password'));

  /** @type {import('../types').SequelizeAuto}  */
  const auto = new SequelizeAuto(configFile.database, configFile.username, configFile.password, configFile);

  await auto.run();
  console.log("Done!");

}()).catch(err => {
  if (err.stack) {
    console.error(err.stack);
  } else if (err.message) {
    console.error(err.message);
  } else {
    console.error(err);
  }
  process.exitCode = 1;
});