extract-mongo-schema
Version:
Extract (and visualize) schema from Mongo database (including foreign keys)
249 lines (218 loc) • 8.32 kB
JavaScript
const commandLineArgs = require('command-line-args');
const fs = require('fs');
const path = require('path');
const extractMongoSchema = require('./extract-mongo-schema');
const xlsx = require("xlsx");
const optionDefinitions = [
{ name: 'database', alias: 'd', type: String },
{ name: 'authSource', alias: 'u', type: String },
{ name: 'inputJson', alias: 'i', type: String },
{ name: 'output', alias: 'o', type: String },
{ name: 'format', alias: 'f', type: String },
{ name: 'collection', alias: 'c', type: String },
{ name: 'array', alias: 'a', type: String },
{
name: 'raw', alias: 'r', type: Boolean, defaultValue: false,
},
{
name: 'limit', alias: 'l', type: Number, defaultValue: 100,
},
{
name: 'dont-follow-fk', alias: 'n', multiple: true, type: String,
},
{
name: 'include-system', alias: 's', type: Boolean, defaultValue: false,
},
{
name: 'exclude-field', alias: 'e', type: String
},
];
const args = commandLineArgs(optionDefinitions);
if(args.output !== "-") {
console.log('');
console.log('Extract schema from Mongo database (including foreign keys)');
}
const printUsage = function () {
console.log('');
console.log('Usage:');
console.log('\textract-mongo-schema -d connection_string -o schema.json');
console.log('\t\t-u, --authSource string\tDatabase for authentication. Example: "admin".');
console.log('\t\t-d, --database string\tDatabase connection string. Example: "mongodb://localhost:3001/meteor".');
console.log('\t\t-o, --output string\tOutput file Use - to output to STDOUT');
console.log('\t\t-f, --format string\tOutput file format. Can be "json", "html-diagram" or "xlsx".');
console.log('\t\t-i, --inputJson string\tInput JSON file, to be used instead of --database. NOTE: this will ignore the remainder of input params and use a previously generated JSON file to generate the diagram.');
console.log('\t\t-c, --collection\tComma separated list of collections to analyze. Example: "collection1,collection2".');
console.log('\t\t-a, --array\tComma separated list of types of arrays to analyze. Example: "Uint8Array,ArrayBuffer,Array".');
console.log('\t\t-r, --raw\tShows the exact list of types with frequency instead of the most frequent type only.');
console.log('\t\t-l, --limit\tChanges the amount of items to parse from the collections. Default is 100.');
console.log('\t\t-n, --dont-follow-fk string\tDon\'t follow specified foreign key. Can be simply "fieldName" (all collections) or "collectionName:fieldName" (only for given collection).');
console.log('\t\t-s, --include-system string\tAnalyzes system collections as well.');
console.log('\t\t-e, --exclude-field string\tExcludes a field from being included in the output schema. Example: -e "_id".');
console.log('');
console.log('Enjoy! :)');
console.log('');
};
if (args.database && args.inputJson) {
console.log('');
console.log('Cannot provide both database connection string and input JSON path.');
printUsage();
process.exit(1);
}
if (!args.database && !args.inputJson) {
console.log('');
console.log('Database connection string or input JSON path is missing.');
printUsage();
process.exit(1);
}
if (!args.output) {
console.log('');
console.log('Output path is missing.');
printUsage();
process.exit(1);
}
if (fs.existsSync(args.output)) {
const outputStat = fs.lstatSync(args.output);
if (outputStat.isDirectory()) {
console.log(`Error: output "${args.output}" is not a file.`);
process.exit(1);
}
}
let collectionList = null;
if (args.collection) {
collectionList = args.collection.split(',');
}
let arrayList = null;
if (args.array) {
arrayList = args.array.split(',');
}
let fieldExclusionList = [];
if(args["exclude-field"]) {
fieldExclusionList = args["exclude-field"].split(',');
}
const outputFormat = args.format || 'json';
const dontFollowTMP = args['dont-follow-fk'] || [];
const dontFollowFK = {
__ANY__: {},
};
dontFollowTMP.map((df) => {
const dfArray = df.split(':');
let collection = '';
let field = '';
if (dfArray.length > 1) {
collection = dfArray[0];
field = dfArray[1];
} else {
collection = '__ANY__';
field = dfArray[0];
}
dontFollowFK[collection][field] = true;
});
if(args.output !== "-") {
console.log('');
console.log('Extracting...');
}
const opts = {
authSource: args.authSource,
collectionList,
arrayList,
raw: args.raw,
limit: args.limit,
dontFollowFK,
includeSystem: args['include-system'],
excludeFields: fieldExclusionList
};
(async () => {
try {
let schema;
if (args.inputJson) {
// read input json
const inputJsonPath = path.join(__dirname, args.inputJson)
try {
const inputJsonString = fs.readFileSync(inputJsonPath, 'utf8')
schema = JSON.parse(inputJsonString)
} catch (e) {
console.log(`Error: cannot read input json file "${inputJsonPath}". ${e.message}`);
process.exit(1);
}
}
else {
schema = await extractMongoSchema.extractMongoSchema(args.database, opts);
}
if (outputFormat === 'json') {
try {
if(args.output === "-")
console.log(JSON.stringify(schema, null, '\t'));
else
fs.writeFileSync(args.output, JSON.stringify(schema, null, '\t'), 'utf8');
} catch (e) {
console.log(`Error: cannot write output "${args.output}". ${e.message}`);
process.exit(1);
}
}
if (outputFormat === 'html-diagram') {
const templateFileName = path.join(__dirname, '/template-html-diagram.html');
// read input file
let templateHTML = '';
try {
templateHTML = fs.readFileSync(templateFileName, 'utf8');
} catch (e) {
console.log(`Error: cannot read template file "${templateFileName}". ${e.message}`);
process.exit(1);
}
templateHTML = templateHTML.replace('{/*DATA_HERE*/}', JSON.stringify(schema, null, '\t'));
try {
fs.writeFileSync(args.output, templateHTML, 'utf8');
} catch (e) {
console.log(`Error: cannot write output "${args.output}". ${e.message}`);
process.exit(1);
}
}
if(outputFormat == "xlsx"){
if(!args.output.endsWith(".xlsx")){
console.log("Wrong output format [xlsx]");
process.exit(1);
}
//get all collections
var collections = Object.keys(schema);
var wb = xlsx.utils.book_new();
//one worksheet per collection
collections.forEach(element => {
var wsName = element;
// console.log(element);
var wsData = [["Collection", "primaryKey", "type", "structure", "require"]];
var items = Object.keys(schema[element]);//items in collection
items.forEach( item => {
var props = Object.keys(schema[element][item]);
var itemProperties = {
primaryKey: schema[element][item]["primaryKey"] != "undefined" ? schema[element][item]["primaryKey"] == "undefined" : false,
type: schema[element][item]["type"] != "undefined" ? schema[element][item]["type"] : "undefined",
structure: schema[element][item]["structure"] != "undefined" ? schema[element][item]["structure"] : "undefined",
require: schema[element][item]["required"] != "undefined" ? schema[element][item]["required"] : "undefined"
};
if(itemProperties.type != "undefined" && itemProperties.type == "Object"){
itemProperties.structure = JSON.stringify(itemProperties.structure);
}
var data = [];
data.push(item);
data.push(itemProperties.primaryKey);
data.push(itemProperties.type);
data.push(itemProperties.structure);
data.push(itemProperties.require);
wsData.push(data);
});
// console.log(wsData);
var ws = xlsx.utils.aoa_to_sheet(wsData);
xlsx.utils.book_append_sheet(wb, ws, wsName);
});
xlsx.writeFile(wb, args.output);
}
if(args.output !== "-") {
console.log('Success.');
console.log('');
}
} catch (err) {
console.log(err);
process.exit(1);
}
})();