UNPKG

autoforce

Version:

Developer Automation tool for Github / Gitlab and Salesforce projects.

320 lines (319 loc) 13.1 kB
import fs from "fs"; import { fileURLToPath } from 'url'; import prompts from "prompts"; import context, { ProjectServices, GitServices } from "./context.js"; import { logInfo, logWarning } from "./color.js"; const MODELS_FOLDER = searchInFolderHierarchy('models', fileURLToPath(import.meta.url)) + '/models'; export const WORKING_FOLDER = process.env.INIT_CWD || "."; export const PROJECT_FOLDER = searchInFolderHierarchy('package.json', WORKING_FOLDER); export const CONFIG_FILE = PROJECT_FOLDER + '/.autoforce.json'; export const DICTIONARY_FOLDER = process.cwd() + "/docs"; // context.dictionaryFolder; export const filterJson = (fullPath) => fullPath.endsWith(".json"); export const filterDirectory = (fullPath) => fs.lstatSync(fullPath).isDirectory(); export const filterFiles = (fullPath) => !fs.lstatSync(fullPath).isDirectory(); export const filterBash = (fullPath) => fullPath.endsWith(".bash"); export const camelToText = (s) => s.replace(/[A-Z]/g, x => ' ' + x); export const kebabToText = (s) => s.replace(/-./g, x => ' ' + x[1].toUpperCase()); export const snakeToText = (s) => s.replace(/_./g, x => ' ' + x[1].toUpperCase()); export function valuesToChoices(list, valueToTitle = (value) => value) { return list.map(value => { return { value, title: valueToTitle(value) }; }); } export function titlesToChoices(list, titleToValue = (title) => title) { return list.map(title => { return { title, value: titleToValue(title) }; }); } export function findChoicesPosition(choices, value) { const index = choices.findIndex(choice => choice.value === value); return index === -1 ? 0 : index; } export function getFilesInFolders(folders, filter, recursive = false, ignoreList = []) { const files = new Set(); for (const folder of folders) { getFiles(folder, filter, recursive, ignoreList) .forEach(file => files.add(file)); } return Array.from(files); } export function getModelFolders(subfolder) { const folders = [ `${MODELS_FOLDER}/dev/${context.devModel}/${subfolder}`, `${MODELS_FOLDER}/git/${context.gitModel}/${subfolder}`, `${MODELS_FOLDER}/doc/${context.docModel}/${subfolder}`, `${MODELS_FOLDER}/project/${context.projectModel}/${subfolder}`, ]; // Filter only folders that exists return folders.filter(folder => fs.existsSync(folder)); } function getTemplates(filter) { return getFilesInFolders(getModelFolders('templates'), filter); } async function getTaskConfig(config) { // TODO: Ver si esto se mueve a un config list // List Command settings const filters = context.listFilters(); const listFilter = await prompts([ { message: 'Elija un filtro, o bien lo puede dejar fijo en autoforce como listFilter', name: 'filter', type: 'select', initial: findChoicesPosition(filters, config.listFilter), choices: filters } ]); if (listFilter.filter === undefined) return; config.listFilter = listFilter.filter; const files = getTemplates(filterBash).map(filename => filename.split(".")[0]); if (files.length > 0) { const templates = valuesToChoices(files); const template = await prompts([ { message: 'Elija un template, o bien lo puede dejar en autoforce como listTemplate', name: 'template', type: 'select', initial: findChoicesPosition(templates, config.listTemplate), choices: templates } ]); if (template.template === undefined) return; config.listTemplate = template.template; } return config; } async function getBaseConfig(config) { // Todo: Chequear el repoOwner y repo const gitChoices = [{ title: 'Github', value: GitServices.GitHub }, { title: 'Gitlab', value: GitServices.GitLab }]; // Preguntar por GitHub o GitLab const gitServices = await prompts([{ type: "select", name: "git", message: "Elija un servicio de Git", initial: findChoicesPosition(gitChoices, config.gitServices), choices: gitChoices }]); if (gitServices.git === undefined) process.exit(0); config.gitServices = gitServices.git; // Chequear las variables de entorno if (gitServices.git === GitServices.GitHub && !process.env.GITHUB_TOKEN) { logWarning('A fin de que la herramienta funcione debe configurar una variable de entorno GITHUB_TOKEN'); } if (gitServices.git === GitServices.GitLab && !process.env.GITLAB_TOKEN) { logWarning('A fin de que la herramienta funcione debe configurar una variable de entorno GITLAB_TOKEN'); } // Selecciona los modelos de automatizacion for (const prefix of ['dev', 'git', 'doc', 'project']) { const contextProperty = prefix + 'Model'; const models = readJsonSync(`${MODELS_FOLDER}/${prefix}/models.json`); const automationModel = await prompts([{ type: "select", name: "model", message: `Elija un modelo de automatizacion para ${prefix}`, initial: findChoicesPosition(models, config[contextProperty]), choices: models }]); if (automationModel.model === undefined) return; config[contextProperty] = automationModel.model; } // Gestion del Proyecto const projectChoices = [{ title: 'Github Projects', value: ProjectServices.GitHub }, { title: 'GitLab Projects', value: ProjectServices.GitLab }, { title: 'Jira', value: ProjectServices.Jira }, { title: 'None', value: ProjectServices.None }]; const projectServices = await prompts([{ type: "select", name: "project", message: "Gestion de proyecto", initial: findChoicesPosition(projectChoices, config.projectServices), choices: projectChoices }]); if (projectServices.project === undefined) return; config.projectServices = projectServices.project; if (projectServices.project === ProjectServices.GitHub || projectServices.project === ProjectServices.GitLab) { // Gestion del Proyecto const backlogColumn = await prompts([{ type: "text", name: "backlogColumn", initial: config.backlogColumn, message: "Nombre de la columna donde se crean nuevos issues" }]); if (backlogColumn.backlogColumn === undefined) return; config.backlogColumn = backlogColumn.backlogColumn; logInfo(`Por omision ser utilizan proyectos dentro de ${context.repositoryOwner} y ${context.repositoryRepo} `); } // dictionaryFolder const dictionaryFolder = await prompts([{ type: "text", name: "dictionaryFolder", initial: config.dictionaryFolder, message: "Ruta a los modulos de la documentacion (desde el root del proyecto)" }]); if (dictionaryFolder.dictionaryFolder === undefined) return; config.dictionaryFolder = dictionaryFolder.dictionaryFolder; // Id de Projecto const projectId = await prompts([{ type: "text", name: "projectId", initial: config.projectId, message: "Id del proyecto" }]); if (projectId.projectId === undefined) return; config.projectId = projectId.projectId; return config; } export async function createConfigurationFile(taskName, options) { if (options?.noprompt) { delete options.noprompt; storeConfig(options); return true; } const baseConfig = { backlogColumn: options?.backlogColumn || context.backlogColumn, devModel: options?.devModel || context.devModel, docModel: options?.docModel || context.docModel, projectModel: options?.projectModel || context.projectModel, gitModel: options?.gitModel || context.gitModel, gitServices: options?.gitServices || context.gitServices, projectServices: options?.projectServices || context.projectServices, projectId: options?.projectId || context.projectId, listFilter: options?.listFilter || context.listFilter, listTemplate: options?.listTemplate || context.listTemplate }; const config = taskName ? await getTaskConfig(baseConfig) : await getBaseConfig(baseConfig); if (!config) return false; storeConfig(config); return true; } export function getConfigFile(file, variable, defaultValue) { if (fs.existsSync(file)) { const content = fs.readFileSync(file, "utf8"); try { const config = JSON.parse(content); if (config[variable]) { return config[variable]; } } catch { return defaultValue; } } return defaultValue; } export function getConfig(variable, defaultValue) { return getConfigFile(CONFIG_FILE, variable, defaultValue); } export function storeConfig(record) { let config = {}; if (fs.existsSync(CONFIG_FILE)) { const content = fs.readFileSync(CONFIG_FILE, "utf8"); try { config = JSON.parse(content); } catch { throw new Error(`Verifique que el ${CONFIG_FILE} sea json valido`); } } for (const [variable, value] of Object.entries(record)) { config[variable] = value; } try { fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)); } catch { throw new Error(`No se pudo guardar la configuracion en ${CONFIG_FILE}`); } } export function sortByName(objA, objB) { return objA.Name > objB.Name ? 1 : objA.Name < objB.Name ? -1 : 0; } export function sortByLabel(objA, objB) { return (objA.label && objB.label && objA.label > objB.label || !objB.label) ? 1 : (objA.label && objB.label && objA.label < objB.label || !objA.label) ? -1 : 0; } export function verFecha() { try { const fecha = new Date(this); return fecha.toLocaleString("es", { day: "numeric", year: "2-digit", month: "long" }); } catch (e) { console.error(e); return this; } } // Devuelve la lista de nombres de archivos de una extension de una carpeta, sin la extension. export function getNamesByExtension(folder, extension) { const allFiles = fs.readdirSync(folder); const filterFiles = []; for (const fullname in allFiles) { if (fullname.endsWith(extension)) { filterFiles.push(fullname.replace("." + extension, "")); } } return filterFiles; } export function splitFilename(fullname, defaultFolder = '') { let filename = fullname; let folder = defaultFolder; const separatorIndex = fullname.lastIndexOf("/"); if (separatorIndex !== -1) { folder = fullname.substring(0, separatorIndex); filename = fullname.substring(separatorIndex + 1); } return { filename, folder }; } /** * Agrega los elementos de newArray a baseArray, si no existen previamente en baseArray. * @param {string[]} baseArray El array donde se agregan los elementos * @param {string[]} newArray El array de elementos a agregar */ export function addNewItems(baseArray, newArray) { for (const item of newArray) { if (!baseArray.includes(item)) { baseArray.push(item); } } } export function searchInFolderHierarchy(element, parentFolder) { if (fs.existsSync(`${parentFolder}/${element}`)) { return parentFolder; } else { const lastIndex = parentFolder.lastIndexOf('/'); if (lastIndex !== -1) { const newParentFolder = parentFolder.substring(0, lastIndex); if (newParentFolder !== '') { return searchInFolderHierarchy(element, newParentFolder); } } } return ''; } export function getFiles(source, filter = (file) => file !== undefined, recursive = false, ignoreList = []) { const files = []; for (const file of fs.readdirSync(source)) { const fullPath = source + "/" + file; const filtered = filter(fullPath); if (!ignoreList.includes(file)) { if (filtered) { files.push(file); } if (fs.lstatSync(fullPath).isDirectory() && recursive) { getFiles(fullPath, filter, recursive, ignoreList).forEach((x) => files.push(file + "/" + x)); } } } return files; } export function convertNameToKey(name) { return name.toLowerCase().replaceAll(/[ /]/g, '-'); } export function convertKeyToName(key) { return key.replaceAll('-', ' ') .split(' ') .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } export function readJsonSync(filename) { const content = fs.readFileSync(filename, "utf8"); try { return JSON.parse(content); } catch { throw new Error(`Verifique que el ${filename} sea json valido`); } }