Created
September 13, 2020 14:15
-
-
Save rhalff/0c82b3f52b641acf9d209f35fba7c2b5 to your computer and use it in GitHub Desktop.
Generate Type Definitions for Sails Models
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
// Usage: node ./typeSails.js --models ./api/models -t ./typings -f | |
// This generates an index.d.ts in ./typings which enables Intellisense for your Sails models. | |
// Based on: https://github.com/jacobhenke/waterline2ts | |
const program = require("commander"); | |
const chalk = require("chalk"); | |
const includeAll = require("include-all"); | |
const fs = require("fs"); | |
const path = require("path"); | |
program | |
.version("0.1.0") | |
.option( | |
"-m, --models <models>", | |
"the directory of models to convert", | |
process.cwd() | |
) | |
.option( | |
"-t, --ts-dir <tsDir>", | |
"The directory to output typescript interfaces", | |
"./ts" | |
) | |
.option("-f, --force-overwrite", "overwrite existing interfaces") | |
.parse(process.argv); | |
// don't overwrite existing files unless -f | |
let fileMode = "wx"; | |
if (program.forceOverwrite) { | |
fileMode = "w"; | |
} | |
let writePrefix = fs.realpathSync(program.tsDir); | |
let excludeDirs = new RegExp( | |
"node_modules|.git|" + writePrefix.split("/").pop() | |
); | |
writePrefix += "/"; | |
// if the output dir doesn't exist, create it | |
if (!fs.existsSync(writePrefix)) { | |
fs.mkdirSync(writePrefix); | |
} | |
console.log(chalk.bold("modelsDir: ") + program.models + "/"); | |
console.log(chalk.bold(" tsDir: ") + writePrefix); | |
const models = includeAll({ | |
dirname: path.join(__dirname, program.models), | |
filter: /^([^.]+)\.(js|coffee|litcoffee)$/, | |
excludeDirs: excludeDirs | |
}); | |
const nameMap = []; | |
for (let name in models) { | |
nameMap[name.toLowerCase()] = name; | |
} | |
let interfaceString = "declare namespace Models {"; | |
// loop through models | |
for (let name in models) { | |
// generate interfaces | |
interfaceString += `\n interface ${name} extends Model {`; | |
// console.log(models[name]); | |
for (let column in models[name].attributes) { | |
let colOpts = models[name].attributes[column]; | |
if (typeof colOpts == "function") { | |
continue; // doesn't seem like there should be functions in attributes, but idk... | |
} | |
interfaceString += `\n ${column}`; | |
if (!colOpts.required) { | |
interfaceString += "?"; | |
} | |
if (colOpts.type) { | |
let type = translateType(colOpts.type); | |
interfaceString += `: ${type};`; | |
if ( | |
colOpts.type === "date" || | |
colOpts.type === "time" || | |
colOpts.type === "datetime" | |
) { | |
interfaceString += " // " + colOpts.type; | |
} else if (colOpts.enum) { | |
interfaceString += " // enum: " + JSON.stringify(colOpts.enum); | |
} | |
} else if (colOpts.model) { | |
let model = colOpts.model; | |
if (nameMap[colOpts.model.toLowerCase()]) { | |
model = nameMap[colOpts.model.toLowerCase()]; | |
} else { | |
console.log( | |
"� " + | |
chalk.red( | |
"Model " + chalk.bold(model.toLowerCase()) + " was not found" | |
) | |
); | |
console.dir(nameMap[colOpts.model.toLowerCase()]); | |
} | |
interfaceString += `: ${model}; // Association... See ${model}.ts`; | |
} else if (colOpts.collection) { | |
let model = colOpts.collection; | |
if (nameMap[model.toLowerCase()]) { | |
model = nameMap[model.toLowerCase()]; | |
} else { | |
console.log( | |
"� " + | |
chalk.red( | |
"Model " + chalk.bold(model.toLowerCase()) + " was not found" | |
) | |
); | |
console.dir(nameMap[colOpts.model.toLowerCase()]); | |
} | |
interfaceString += `: ${model}[]; // Association via ${colOpts.via}... See ${model}.ts`; | |
} | |
} | |
interfaceString += "\n }\n"; | |
} | |
interfaceString += "}\n\n"; | |
let importsString = "import {Model} from 'waterline'\n\n"; | |
let varDeclarations = ""; | |
for (let name in models) { | |
varDeclarations += `declare var ${name}: Models.${name};\n`; | |
} | |
interfaceString = | |
"// Autogenerated via waterline2ts\n\n" + | |
importsString + | |
interfaceString + | |
varDeclarations; | |
// write to file | |
fs.open(writePrefix + "/index.d.ts", fileMode, (err, fd) => { | |
if (err) { | |
if (err.code === "EEXIST") { | |
console.log( | |
"❌ " + | |
chalk.red( | |
chalk.bold(writePrefix + "/index.d.ts") + " already exists..." | |
) | |
); | |
return; | |
} | |
return console.log(chalk.red.bold(err)); | |
} | |
fs.write(fd, interfaceString, err => { | |
if (err) { | |
return console.log(chalk.red.bold(err)); | |
} | |
}); | |
}); | |
function translateType(type) { | |
switch (type) { | |
case "integer": | |
case "float": | |
return "number"; | |
case "date": | |
case "time": | |
case "datetime": | |
case "binary": | |
case "text": | |
return "string"; | |
case "json": | |
case "array": | |
return "Array<any>"; | |
default: | |
return type; | |
} | |