terramake
Version:
Type-safe Infrastructure-as-Code
168 lines (136 loc) • 5.74 kB
JavaScript
const ELM_VER = '0.18.0';
const fs = require('fs');
const path = require('path');
const compiler = require('node-elm-compiler')
const elmiParser = require('node-elm-repl/src/parser.js');
const meow = require('meow');
const cli = meow(`
Usage
$ terramake [OPTIONS]
Options
--help Print this page
--version Print Terramake version
--outputFolder, -o Path of the generated tfvars files. Default is '.'
--singleFilePerFolder, -s Generate each tfvars file in its own folder. Default is false
Examples
$ terramake -o iac -s
`, {
flags: {
outputFolder: {
type: 'string',
alias: 'o',
default: '.'
},
singleFilePerFolder: {
type: 'boolean',
alias: 's',
default: false
}
}
});
const iacOutput = cli.flags.outputFolder;
if (!fs.existsSync('./elm-package.json')) {
fail('This command needs to be executed from the root of the elm project.')
}
var elmPackageJson = JSON.parse(fs.readFileSync('./elm-package.json', 'utf8'));
const repoRegExp = /https?:\/\/github\.com\/([-\w]*)\/([-\w]*)\.git/;
var repoMatches = repoRegExp.exec(elmPackageJson.repository);
if (repoMatches == null) {
fail('The elm-package.json contains an invalid value for the repository field.')
}
elmPackageJson.user = repoMatches[1];
elmPackageJson.package = repoMatches[2];
const elmFilePaths = elmPackageJson['source-directories'].reduce((acc, srcDir) => acc.concat(walkSync(srcDir)), []);
var targetPath = path.join(process.cwd(), iacOutput, elmPackageJson.package + '.js');
var compileOptions = { output: targetPath, yes: true, verbose: true, warn: true, processOpts: { stdio: 'pipe' } };
compiler.compile(elmFilePaths.map(pathParts => pathParts.join(path.sep)), compileOptions)
.on('close', function(exitCode) {
if (exitCode != 0) {
console.log();
fail(" Ooops, something went wrong. Please be sure before using Terramake to fetch the dependencies by using elm-github-install");
} else {
console.log();
success("Successfully compiled.");
console.log();
var Elm = require(targetPath);
var foundMain = false;
elmFilePaths.forEach(pathParts => {
const elmPathParts = pathParts.slice(1);
const moduleElmiPath = getModuleElmiPath(elmPackageJson, elmPathParts);
const buffer = fs.readFileSync(moduleElmiPath);
var parsedModule = elmiParser.parse(buffer);
if (isTerramakeMainModule(parsedModule)) {
foundMain = true;
var elmModules = elmPathParts.map(x => x.replace(/.elm/, ''));
var iacPath = [iacOutput].concat(elmModules).concat(cli.flags.singleFilePerFolder ? ["terraform"] : []);
var iacDirs = iacPath.slice(0, iacPath.length - 1); //cut the last one, which is the file
iacDirs.reduce((currentPath, folder) => {
var newPath = path.join(currentPath, folder);
if (!fs.existsSync(newPath)){
fs.mkdirSync(newPath);
}
return newPath;
}, '');
var elmModule = elmModules.reduce((acc, cur) => acc[cur], Elm);
var filePath = iacPath.join(path.sep);
elmModule.worker([{ "filePath" : filePath}, {}]);
success("Generated " + green(filePath + ".tfvars"));
}
});
if (!foundMain) {
warning("No Terramake compatible main function found in any modules.");
}
fs.unlinkSync(targetPath);
}
});
function isTerramakeMainModule(parsedModule) {
return parsedModule.types.some(f =>
f.name == 'main'
&& f.value.type =='app'
&& f.value.subject.type == 'type'
&& f.value.subject.def.name == 'Program'
&& f.value.subject.def.user == 'elm-lang'
&& f.value.subject.def.package == 'core'
&& f.value.subject.def.path[0] == 'Platform'
&& f.value.object.length == 3
&& f.value.object[0].type == 'app'
&& f.value.object[0].subject.type == 'type'
&& f.value.object[0].subject.def.name == '_Tuple2'
&& f.value.object[0].object.length == 2
&& f.value.object[0].object[0].type == 'aliased'
&& f.value.object[0].object[0].def.name == 'Flags'
&& f.value.object[0].object[0].def.user == 'karandit'
&& f.value.object[0].object[0].def.package == 'elm-terramake'
&& f.value.object[0].object[0].def.path[0] == 'Terramake'
&& f.value.object[1].type == 'type'
&& f.value.object[1].def.name == '_Tuple0'
&& f.value.object[2].type == 'type'
&& f.value.object[2].def.name == '_Tuple0');
}
function getModuleElmiPath(options, moduleNameParts) {
return path.join('elm-stuff', 'build-artifacts', ELM_VER,
options.user, options.package, options.version, moduleNameParts.join('-') + 'i');
}
function walkSync(dir) {
return _walkSync([dir], []);
}
function _walkSync(dirArr, result) {
const dir = dirArr.join(path.sep);
fs.readdirSync(dir).forEach(file => {
const dirFileArr = dirArr.concat([file]);
result = fs.statSync(path.join(dir, file)).isDirectory()
? _walkSync(dirFileArr, result)
: (file.endsWith('.elm') ? result.concat([dirFileArr]) : result);
});
return result;
}
function red(msg) { return "\x1b[31m" + msg + "\x1b[0m"; }
function green(msg) { return "\x1b[32m" + msg + "\x1b[0m"; }
function yellow(msg) { return "\x1b[33m" + msg + "\x1b[0m"; }
function fail(msg) {
process.stderr.write(" " + red("❌") + "Error: " + msg)
process.exit(1)
}
function warning(msg) { console.log(" " + yellow("❗") + " Warning: " + msg); }
function success(msg) { console.log(" " + green("✔︎") + " " + msg); }