umbot
Version:
Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф
364 lines (335 loc) • 13.3 kB
JavaScript
;
const fs = require('node:fs');
const { exec } = require('node:child_process');
const utils = require(__dirname + '/../utils.js').utils;
/**
* Класс, создающий пустой проект, или шаблон для готового проекта.
*/
class CreateController {
/**
* Создает пустой проект
*/
static T_DEFAULT = 'Default';
/**
* Создает викторину
*/
static T_QUIZ = 'Quiz';
flags = [];
#path;
#name;
/**
* Параметры для создания приложения
*/
params;
/**
* Читает содержимое файла
* @param {string} file Путь к файлу
* @returns {string} Содержимое файла или пустая строка
* @private
*/
_getFileContent(file) {
let content = '';
if (file && utils.isFile(file)) {
content = utils.fread(file);
}
return content;
}
/**
* Формирует заголовок файла с информацией о создании
* @returns {string} Заголовок файла
* @private
*/
_getHeaderContent() {
let headerContent = '/*\n';
headerContent += '/* Created by umbot\n';
headerContent += ' * Date: {{date}}\n';
headerContent += ' * Time: {{time}}\n';
headerContent += ' */\n\n';
return headerContent;
}
/**
* Инициализирует параметры приложения
* @param defaultParams
* @returns {string}
* @private
*/
_initParams(defaultParams) {
let params;
if (this.params && this.params.params) {
params = { ...defaultParams, ...this.params.params };
} else {
params = defaultParams;
}
let content = this._getHeaderContent();
content += "import { IAppParam } from 'umbot';\n\n";
content += 'export default function(): IAppParam {\n';
content += '\treturn ';
content += JSON.stringify(params, null, '\t');
content += ';\n';
content += '}\n';
return content;
}
/**
* Инициализирует конфигурации приложения
* @param defaultConfig
* @returns {string}
* @private
*/
_initConfig(defaultConfig) {
let config;
if (this.params.config) {
config = { ...defaultConfig, ...this.params.config };
} else {
config = defaultConfig;
}
if (this.params.isEnv) {
config.env = `./.env`;
}
let content = this._getHeaderContent();
content += "import { IAppConfig } from 'umbot';\n\n";
content += 'export default function (): IAppConfig {\n';
content += '\treturn ';
content += JSON.stringify(config, null, '\t');
content += ';\n';
content += '}\n';
return content;
}
/**
* Заменяет все вхождения подстрок в строке
* @param {string|string[]} find Строка или массив строк для поиска
* @param {string|string[]} replace Строка или массив строк для замены
* @param {string} str Исходная строка
* @returns {string} Строка с произведенными заменами
* @private
*/
_replace(find, replace, str) {
if (typeof find === 'string') {
return str.replace(new RegExp(find, 'g'), replace);
} else {
let res = str;
const maxReplace = replace.length - 1;
find.forEach((f, i) => {
let r = replace[i];
if (r === undefined) {
r = replace[maxReplace];
}
res = res.replace(new RegExp(f, 'g'), r);
});
return res;
}
}
/**
* Генерирует файл из шаблона
* @param {string} templateContent Содержимое шаблона
* @param {string} fileName Имя файла для создания
* @returns {string} Путь к созданному файлу
* @private
*/
_generateFile(templateContent, fileName) {
const find = [
'{{date}}',
'{{time}}',
'{{name}}',
'{{className}}',
'__className__',
'{{}}',
'{{hostname}}',
'{{port}}',
];
const name = this.#name.substring(0, 1).toUpperCase() + this.#name.substring(1);
const date = `${new Date().getDate().toString().padStart(2, '0')}.${(new Date().getMonth() + 1).toString().padStart(2, '0')}.${new Date().getFullYear()}`;
const time = `${new Date().getHours().toString().padStart(2, '0')}:${new Date().getMinutes().toString().padStart(2, '0')}`;
const replace = [
date,
time,
this.#name,
name,
name,
'',
'"' + (this.params?.hostname || '0.0.0.0') + '"',
this.params?.port || 3000,
];
fileName = this._replace(find, replace, fileName);
const content = this._replace(find, replace, templateContent);
utils.fwrite(fileName, content);
return fileName;
}
/**
* Создает файл конфигурации проекта
* @param {string} path Путь к шаблонам
* @private
*/
_getConfigFile(path) {
console.log('Создается файл с конфигурацией приложения: ...');
const configFile = `${this.#path}/src/config/{{name}}Config.ts`;
let configContent;
if (utils.isFile(`${path}/config/defaultConfig.js`)) {
const config = require(`${path}/config/defaultConfig`);
configContent = this._initConfig(config.config);
} else {
configContent = '';
}
this._generateFile(configContent, configFile);
console.log('Файл с конфигурацией успешно создан');
}
/**
* Создает файл параметров проекта
* @param {string} path Путь к шаблонам
* @param {string} type Тип приложения
* @private
*/
_getParamsFile(path, type) {
console.log('Создается файл с параметрами приложения: ...');
const paramsFile = `${this.#path}/src/config/{{name}}Params.ts`;
let paramsContent;
if (utils.isFile(`${path}/config/${type}Params.js`)) {
const param = require(`${path}/config/${type}Params`);
paramsContent = this._initParams(param.params);
} else {
paramsContent = '';
}
this._generateFile(paramsContent, paramsFile);
console.log('Файл с параметрами успешно создан');
}
createDockerFile(path) {
const standardPath = __dirname + '/../template';
const dockerFile = `${path}/Dockerfile`;
const dockerContent = this._getFileContent(`${standardPath}/docker/Dockerfile.text`);
this._generateFile(dockerContent, dockerFile);
console.log('Dockerfile успешно создан');
}
createDeployFile(path) {
const standardPath = __dirname + '/../template';
const deployFile = `${path}/.github/workflows/deploy.yml`;
fs.mkdirSync(`${path}/.github`);
fs.mkdirSync(`${path}/.github/workflows`);
const deployContent = this._getFileContent(`${standardPath}/github/deploy.yml`);
this._generateFile(deployContent, deployFile);
console.log('deploy.yml успешно создан');
}
/**.
* Создает структуру проекта
* @param {string} type Тип проекта (Default или Quiz)
* @private
*/
_create(type = CreateController.T_DEFAULT) {
if (![CreateController.T_DEFAULT, CreateController.T_QUIZ].includes(type)) {
console.warn(
'Не удалось создать проект, так как не удалось определить тип создаваемого приложения',
);
} else {
const standardPath = __dirname + '/../template';
const srcPath = `${this.#path}/src`;
if (!utils.isDir(srcPath)) {
fs.mkdirSync(srcPath);
}
const configFile = `${srcPath}/config`;
if (!utils.isDir(configFile)) {
fs.mkdirSync(configFile);
}
const typeToLower = type.toLowerCase();
this._getConfigFile(standardPath);
this._getParamsFile(standardPath, typeToLower);
if (!(this.flags.includes('--minimal') && type === CreateController.T_DEFAULT)) {
let controllerFile = `${srcPath}/controller`;
if (!utils.isDir(controllerFile)) {
fs.mkdirSync(controllerFile);
}
console.log('Создается класс с логикой приложения: ...');
controllerFile += '/{{className}}Controller.ts';
const controllerContent = this._getFileContent(
`${standardPath}/controller/${type}Controller.ts.text`,
);
this._generateFile(controllerContent, controllerFile);
console.log('Класс с логикой приложения успешно создан');
}
console.log('Создается index файл: ...');
let path = 'index';
const mode = this.params?.mode;
if (mode === 'dev') {
path += 'Dev';
} else if (mode === 'dev-online') {
path += 'DevOnline';
} else if (mode === 'build') {
path += 'Build';
}
if (this.flags.includes('--minimal') && type === CreateController.T_DEFAULT) {
path += 'Min';
}
const indexFile = `${srcPath}/index.ts`;
const indexContent = this._getFileContent(`${standardPath}/${path}.ts.text`);
this._generateFile(indexContent, indexFile);
console.log('index.ts успешно создан');
const packageFile = `${this.#path}/package.json`;
const packageContent = this._getFileContent(`${standardPath}/package.json.text`);
this._generateFile(packageContent, packageFile);
console.log('package.json успешно создан');
const tsconfigFile = `${this.#path}/tsconfig.json`;
const tsconfigContent = this._getFileContent(`${standardPath}/tsconfig.json`);
this._generateFile(tsconfigContent, tsconfigFile);
console.log('tsconfig.json успешно создан');
if (this.flags.includes('--prod')) {
this.createDeployFile(this.#path);
this.createDockerFile(this.#path);
}
console.log(`Проект успешно создан, и находится в директории: ${this.#path}`);
}
}
/**
* Генерирует файл
* @param fileName
* @param content
*/
generateFile(fileName, content) {
utils.fwrite(`${this.#path}/${fileName}`, content);
console.log('.env файл успешно создан');
}
/**
* Форматирует проект через prettier
*/
format() {
exec(`prettier.cmd --write ${this.#path}`, (error) => {
if (error) {
console.error(`exec error: ${error}`);
}
});
}
/**
* Инициализация параметров проекта
* @param name Имя проекта
* @param type Тип проекта
* @public
*/
init(name = null, type = CreateController.T_DEFAULT) {
const correctName = name?.replace(/\W/g, '_');
if (correctName) {
this.#name = correctName;
this.#path = '';
if (this.params && this.params.path) {
this.#path = this.params.path;
const paths = this.#path.split('/');
let path = '';
paths.forEach((dir) => {
path += `${dir}/`;
if (dir !== './' && path !== '../') {
if (!utils.isDir(path)) {
fs.mkdirSync(path);
}
}
});
} else {
this.#path += correctName;
if (!utils.isDir(this.#path)) {
fs.mkdirSync(this.#path);
}
}
this._create(type);
} else {
console.error('Не указано имя проекта');
}
}
}
/**
* Контроллер для создания новых проектов и компонентов.
*/
exports.create = CreateController;