zova-cli
Version:
zova cli
308 lines • 11.2 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalTemplate = void 0;
const fs_1 = __importDefault(require("fs"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const egg_born_utils_1 = __importDefault(require("egg-born-utils"));
const istextorbinary_1 = __importDefault(require("istextorbinary"));
const ejs_1 = __importDefault(require("@zhennann/ejs"));
const gogocode_1 = __importDefault(require("gogocode"));
const config_js_1 = require("../config.js");
class LocalTemplate {
constructor(cli) {
Object.defineProperty(this, "cli", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.cli = cli;
}
get options() {
return this.cli.options;
}
get context() {
return this.cli.options.context;
}
get console() {
return this.cli.console;
}
get helper() {
return this.cli.helper;
}
get moduleConfig() {
return config_js_1.commandsConfig;
}
get fileMapping() {
return this.moduleConfig.template.render.fileMapping;
}
get filesIgnore() {
return this.moduleConfig.template.render.ignore;
}
resolveTemplatePath({ setName, path: _path }) {
const sets = this.moduleConfig.sets;
const modulePath = require.resolve(`${sets[setName]}/package.json`);
return path_1.default.join(path_1.default.dirname(modulePath), 'cli/templates', _path);
}
async renderBoilerplateAndSnippets({ targetDir, setName, snippetsPath, boilerplatePath }) {
// first
if (snippetsPath) {
const snippetsDir = this.resolveTemplatePath({
setName,
path: snippetsPath,
});
await this.applySnippets({ targetDir, snippetsDir });
}
// then
if (boilerplatePath) {
const templateDir = this.resolveTemplatePath({
setName,
path: boilerplatePath,
});
await this.renderDir({ targetDir, templateDir });
}
}
async renderDir({ targetDir, templateDir }) {
const { argv } = this.context;
// files
const files = egg_born_utils_1.default.tools.globbySync('**/*', {
cwd: templateDir,
dot: true,
onlyFiles: false,
followSymlinkedDirectories: false,
});
// loop
for (const file of files) {
const { dir: dirname, base: basename } = path_1.default.parse(file);
if (this.filesIgnore.includes(basename))
continue;
const templateFile = path_1.default.join(templateDir, file);
const fileName = this.parseFileBaseName(basename);
const parentPath = path_1.default.join(targetDir, dirname);
const targetFile = path_1.default.join(parentPath, this.replaceTemplate(fileName, argv));
await this.renderFile({ targetFile, templateFile });
if (fileName !== '.gitkeep') {
const gitkeep = path_1.default.join(parentPath, '.gitkeep');
if (fs_1.default.existsSync(gitkeep)) {
fs_1.default.unlinkSync(gitkeep);
}
}
}
return files;
}
replaceTemplate(content, scope) {
if (!content)
return null;
return content.toString().replace(/(\\)?{{ *([\w\.]+) *}}/g, (block, skip, key) => {
if (skip) {
return block.substring(skip.length);
}
const value = this.getProperty(scope, key);
return value !== undefined ? value : '';
});
}
getProperty(obj, name, sep) {
return this._getProperty(obj, name, sep, false);
}
_getProperty(obj, name, sep, forceObject) {
if (!obj)
return undefined;
const names = name.split(sep || '.');
// loop
for (const name of names) {
if (obj[name] === undefined || obj[name] === null) {
if (forceObject) {
obj[name] = {};
}
else {
obj = obj[name];
break;
}
}
obj = obj[name];
}
return obj;
}
parseFileBaseName(basename) {
let fileName = this.fileMapping[basename] || basename;
if (fileName.lastIndexOf('_') === fileName.length - 1) {
fileName = fileName.substring(0, fileName.length - 1);
}
return fileName;
}
async renderFile({ targetFile, templateFile }) {
const stats = fs_1.default.lstatSync(templateFile);
if (stats.isSymbolicLink()) {
const target = fs_1.default.readlinkSync(templateFile);
fs_1.default.symlinkSync(target, targetFile);
await this.console.log(`${targetFile} link to ${target}`);
}
else if (stats.isDirectory()) {
await this.helper.ensureDir(targetFile);
}
else if (stats.isFile()) {
const content = fs_1.default.readFileSync(templateFile);
await this.console.log(`write to ${targetFile}`);
// check if content is a text file
let result;
let changed;
if (!istextorbinary_1.default.isTextSync(templateFile, content)) {
result = content;
}
else {
const _content = content.toString('utf8');
result = await this.renderContent({ content: _content });
changed = _content !== result;
}
// save
fs_1.default.writeFileSync(targetFile, result);
// format
if (changed) {
await this.helper.formatFile({ fileName: targetFile, logPrefix: 'format: ' });
}
}
else {
await this.console.log(`ignore ${templateFile}, only support file, dir, symlink`);
}
}
async renderContent({ content }) {
if (!content)
return content;
const data = this.getEjsData();
const options = this.getEjsOptions();
return await ejs_1.default.render(content, data, options);
}
getEjsOptions() {
return {
async: true,
cache: false,
compileDebug: true,
outputFunctionName: 'echo',
rmWhitespace: false,
};
}
getEjsData() {
return {
cli: this.cli,
...this.context,
};
}
getAstData(ast, snippet) {
return {
cli: this.cli,
ast,
snippet,
...this.context,
};
}
async applySnippets({ targetDir, snippetsDir }) {
// snippets
let files = egg_born_utils_1.default.tools.globbySync('*.cjs', {
cwd: snippetsDir,
onlyFiles: true,
});
// snippets sort
files = files
.filter(item => item[0] !== '-')
.sort((a, b) => this._parseSnippetFilePrefix(a) - this._parseSnippetFilePrefix(b));
// for
for (const file of files) {
const snippetTemplatePath = path_1.default.join(snippetsDir, file);
const snippet = this.requireDynamic(snippetTemplatePath);
if (!snippet.file) {
throw new Error(`should provider file path for: ${file}`);
}
let fileName;
if (typeof snippet.file === 'function') {
fileName = snippet.file(this.getEjsData());
}
else {
fileName = await this.renderContent({ content: snippet.file });
}
if (!fileName) {
// means ignore, so do nothing
}
else {
const targetFile = path_1.default.join(targetDir, fileName);
await this.applySnippet({ targetFile, snippet });
}
}
}
async applySnippet({ targetFile, snippet }) {
await this.console.log(`apply changes to ${targetFile}`);
// source code
let sourceCode;
if (fs_1.default.existsSync(targetFile)) {
sourceCode = fs_1.default.readFileSync(targetFile);
sourceCode = sourceCode.toString('utf8');
}
else {
if (!snippet.init) {
throw new Error(`should provider init content for: ${targetFile}`);
}
sourceCode = await this.renderContent({ content: snippet.init });
}
// language
const language = snippet.parseOptions && snippet.parseOptions.language;
// transform
let outputCode;
if (language === 'plain') {
const ast = sourceCode;
const outAst = await snippet.transform(this.getAstData(ast, snippet));
outputCode = outAst;
}
else if (language === 'json') {
const ast = JSON.parse(sourceCode);
const outAst = await snippet.transform(this.getAstData(ast, snippet));
outputCode = outAst === undefined ? outAst : JSON.stringify(outAst, null, 2);
}
else {
const ast = (0, gogocode_1.default)(sourceCode, { parseOptions: snippet.parseOptions });
const outAst = await snippet.transform(this.getAstData(ast, snippet));
outputCode = outAst === undefined ? outAst : outAst.root().generate();
}
if (outputCode !== undefined) {
// save
fs_1.default.writeFileSync(targetFile, outputCode);
// format
await this.helper.formatFile({ fileName: targetFile, logPrefix: 'format: ' });
}
}
_parseSnippetFilePrefix(fileName) {
const num = fileName.split('-')[0];
if (!num || isNaN(num))
return 10000;
return parseInt(num);
}
requireDynamic(file) {
if (!file)
throw new Error('file should not empty');
let instance = require(file);
const mtime = this._requireDynamic_getFileTime(file);
if (instance.__requireDynamic_mtime === undefined) {
instance.__requireDynamic_mtime = mtime;
}
else if (instance.__requireDynamic_mtime !== mtime) {
delete require.cache[require.resolve(file)];
instance = require(file);
instance.__requireDynamic_mtime = mtime;
}
return instance;
}
_requireDynamic_getFileTime(file) {
if (!path_1.default.isAbsolute(file))
return null;
const exists = fs_extra_1.default.pathExistsSync(file);
if (!exists)
return null;
// stat
const stat = fs_extra_1.default.statSync(file);
return stat.mtime.valueOf();
}
}
exports.LocalTemplate = LocalTemplate;
//# sourceMappingURL=local.template.js.map