From 10cfe2376311d6498b8588d19c8a42b4208fbdd1 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 23 Feb 2025 21:29:45 -0600 Subject: [PATCH 1/2] feat: enhance CLI commands with improved flags and error handling This commit introduces several improvements across multiple CLI commands: - Added new flags for more flexible command usage - Implemented interactive mode for commands when flags are not provided - Improved error handling and messaging - Added support for environment variable authentication - Standardized confirmation prompts and error messages - Enhanced type safety with TypeScript improvements --- src/commands/app/create.ts | 131 +++++++---- src/commands/app/delete.ts | 81 ++++--- src/commands/app/deploy.ts | 155 ++++++++----- src/commands/app/stop.ts | 148 +++++++----- src/commands/database/mariadb/create.ts | 245 ++++++++++++++------ src/commands/database/mariadb/delete.ts | 108 ++++----- src/commands/database/mariadb/deploy.ts | 149 +++++++----- src/commands/database/mariadb/stop.ts | 149 +++++++----- src/commands/database/mongo/create.ts | 249 +++++++++++++------- src/commands/database/mongo/delete.ts | 110 ++++----- src/commands/database/mongo/deploy.ts | 149 +++++++----- src/commands/database/mongo/stop.ts | 149 +++++++----- src/commands/database/mysql/create.ts | 279 +++++++++++++++-------- src/commands/database/mysql/delete.ts | 111 ++++----- src/commands/database/mysql/deploy.ts | 129 ++++++----- src/commands/database/mysql/stop.ts | 129 ++++++----- src/commands/database/postgres/create.ts | 260 ++++++++++++++------- src/commands/database/postgres/delete.ts | 110 ++++----- src/commands/database/postgres/deploy.ts | 153 ++++++++----- src/commands/database/postgres/stop.ts | 157 ++++++++----- src/commands/database/redis/create.ts | 221 +++++++++++------- src/commands/database/redis/delete.ts | 121 +++++----- src/commands/database/redis/deploy.ts | 153 ++++++++----- src/commands/database/redis/stop.ts | 157 ++++++++----- src/commands/project/create.ts | 102 +++++---- src/utils/utils.ts | 13 +- 26 files changed, 2390 insertions(+), 1528 deletions(-) diff --git a/src/commands/app/create.ts b/src/commands/app/create.ts index a9be784..70092e3 100644 --- a/src/commands/app/create.ts +++ b/src/commands/app/create.ts @@ -22,65 +22,112 @@ export default class AppCreate extends Command { description: "ID of the project", required: false, }), + name: Flags.string({ + char: "n", + description: "Application name", + required: false, + }), + description: Flags.string({ + char: "d", + description: "Application description", + required: false, + }), + appName: Flags.string({ + description: "Docker app name", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), }; public async run(): Promise { const auth = await readAuthConfig(this); - const { flags } = await this.parse(AppCreate); + let { projectId, name, description, appName } = flags; - let { projectId } = flags; - - if (!projectId) { + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !name || !appName) { console.log(chalk.blue.bold("\n Listing all Projects \n")); - const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to create the application in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to create the application in:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - projectId = project.projectId; + if (!name || !appName) { + const appDetails = await inquirer.prompt([ + { + message: "Enter the application name:", + name: "name", + type: "input", + validate: (input) => (input ? true : "Application name is required"), + default: name, + }, + { + message: "Enter the application description (optional):", + name: "appDescription", + type: "input", + default: description, + }, + ]); + + name = appDetails.name; + description = appDetails.appDescription; + + const appNamePrompt = await inquirer.prompt([ + { + default: appName || `${slugify(name)}`, + message: "Enter the App name:", + name: "appName", + type: "input", + validate: (input) => (input ? true : "App name is required"), + }, + ]); - const appDetails = await inquirer.prompt([ - { - message: "Enter the application name:", - name: "name", - type: "input", - validate: (input) => (input ? true : "Application name is required"), - }, - { - message: "Enter the application description (optional):", - name: "appDescription", - type: "input", - }, - ]); + appName = appNamePrompt.appName; + } + } - const appName = await inquirer.prompt([ + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirm = await inquirer.prompt([ { - default: `${slugify(project.name)}-${appDetails.name}`, - message: "Enter the App name: (optional):", - name: "appName", - type: "input", - validate: (input) => (input ? true : "App name is required"), + type: 'confirm', + name: 'proceed', + message: 'Do you want to create this application?', + default: false, }, ]); + if (!confirm.proceed) { + this.error(chalk.yellow("Application creation cancelled.")); + return; + } + } + + try { const response = await axios.post( `${auth.url}/api/trpc/application.create`, { json: { - ...appDetails, - appName: appName.appName, - projectId: project.projectId, + name, + appDescription: description, + appName, + projectId, }, }, { @@ -95,9 +142,9 @@ export default class AppCreate extends Command { this.error(chalk.red("Error creating application")); } - this.log( - chalk.green(`Application '${appDetails.name}' created successfully.`), - ); + this.log(chalk.green(`Application '${name}' created successfully.`)); + } catch (error: any) { + this.error(chalk.red(`Error creating application: ${error.message}`)); } } } diff --git a/src/commands/app/delete.ts b/src/commands/app/delete.ts index b8ca78b..b5705f7 100644 --- a/src/commands/app/delete.ts +++ b/src/commands/app/delete.ts @@ -21,55 +21,68 @@ export default class AppDelete extends Command { description: "ID of the project", required: false, }), + applicationId: Flags.string({ + char: 'a', + description: 'ID of the application to delete', + required: false, + }), + skipConfirm: Flags.boolean({ + char: 'y', + description: 'Skip confirmation prompt', + default: false, + }) }; public async run(): Promise { const auth = await readAuthConfig(this); - const { flags } = await this.parse(AppDelete); + let { projectId, applicationId } = flags; - let { projectId } = flags; - - if (!projectId) { + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !applicationId) { console.log(chalk.blue.bold("\n Listing all Projects \n")); - const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to create the application in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to delete the application from:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - projectId = project.projectId; const projectSelected = await getProject(projectId, auth, this); if (projectSelected.applications.length === 0) { this.error(chalk.yellow("No applications found in this project.")); } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.applications.map((app) => ({ - name: app.name, - value: app.applicationId, - })), - message: "Select the application to delete:", - name: "selectedApp", - type: "list", - }, - ]); - - const applicationId = appAnswers.selectedApp; + if (!applicationId) { + const appAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.applications.map((app) => ({ + name: app.name, + value: app.applicationId, + })), + message: "Select the application to delete:", + name: "selectedApp", + type: "list", + }, + ]); + applicationId = appAnswers.selectedApp; + } + } - // // Confirmar eliminación + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { const confirmAnswers = await inquirer.prompt([ { default: false, @@ -82,7 +95,9 @@ export default class AppDelete extends Command { if (!confirmAnswers.confirmDelete) { this.error(chalk.yellow("Application deletion cancelled.")); } + } + try { const deleteResponse = await axios.post( `${auth.url}/api/trpc/application.delete`, { @@ -103,6 +118,8 @@ export default class AppDelete extends Command { } this.log(chalk.green("Application deleted successfully.")); + } catch (error: any) { + this.error(chalk.red(`Failed to delete application: ${error.message}`)); } } } diff --git a/src/commands/app/deploy.ts b/src/commands/app/deploy.ts index 4ecf97a..6d756be 100644 --- a/src/commands/app/deploy.ts +++ b/src/commands/app/deploy.ts @@ -1,4 +1,4 @@ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; import { readAuthConfig } from "../../utils/utils.js"; import chalk from "chalk"; import { getProject, getProjects } from "../../utils/shared.js"; @@ -9,81 +9,116 @@ import axios from "axios"; export default class AppDeploy extends Command { static description = "Deploy an application to a project."; - static examples = ["$ <%= config.bin %> app deploy"]; + static examples = [ + "$ <%= config.bin %> app deploy", + "$ <%= config.bin %> app deploy --applicationId myAppId", + "$ DOKPLOY_URL=xxx DOKPLOY_AUTH_TOKEN=xxx <%= config.bin %> app deploy --applicationId myAppId" + ]; + + static flags = { + applicationId: Flags.string({ + char: 'a', + description: 'ID of the application to deploy', + required: false, + }), + projectId: Flags.string({ + char: 'p', + description: 'ID of the project', + required: false, + }), + skipConfirm: Flags.boolean({ + char: 'y', + description: 'Skip confirmation prompt', + default: false, + }) + }; public async run(): Promise { const auth = await readAuthConfig(this); + const { flags } = await this.parse(AppDeploy); + let { projectId, applicationId } = flags; - console.log(chalk.blue.bold("\n Listing all Projects \n")); - - const projects = await getProjects(auth, this); + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !applicationId) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to deploy the application in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to deploy the application from:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - const projectId = project.projectId; + const projectSelected = await getProject(projectId, auth, this); - const projectSelected = await getProject(projectId, auth, this); + if (projectSelected.applications.length === 0) { + this.error(chalk.yellow("No applications found in this project.")); + } - if (projectSelected.applications.length === 0) { - this.error(chalk.yellow("No applications found in this project.")); + if (!applicationId) { + const appAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.applications.map((app) => ({ + name: app.name, + value: app.applicationId, + })), + message: "Select the application to deploy:", + name: "selectedApp", + type: "list", + }, + ]); + applicationId = appAnswers.selectedApp; + } } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.applications.map((app) => ({ - name: app.name, - value: app.applicationId, - })), - message: "Select the application to deploy:", - name: "selectedApp", - type: "list", - }, - ]); - - const applicationId = appAnswers.selectedApp; - - const confirmAnswers = await inquirer.prompt([ - { - default: false, - message: "Are you sure you want to deploy this application?", - name: "confirmDelete", - type: "confirm", - }, - ]); + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirmAnswers = await inquirer.prompt([ + { + default: false, + message: "Are you sure you want to deploy this application?", + name: "confirmDelete", + type: "confirm", + }, + ]); - if (!confirmAnswers.confirmDelete) { - this.error(chalk.yellow("Application deployment cancelled.")); + if (!confirmAnswers.confirmDelete) { + this.error(chalk.yellow("Application deployment cancelled.")); + } } - const response = await axios.post( - `${auth.url}/api/trpc/application.deploy`, - { - json: { - applicationId, + try { + const response = await axios.post( + `${auth.url}/api/trpc/application.deploy`, + { + json: { + applicationId, + }, }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - }, - ); + ); - if (response.status !== 200) { - this.error(chalk.red("Error deploying application")); + if (response.status !== 200) { + this.error(chalk.red("Error deploying application")); + } + this.log(chalk.green("Application deploy successful.")); + } catch (error: any) { + this.error(chalk.red(`Error deploying application: ${error.message}`)); } - this.log(chalk.green("Application deploy successful.")); } } diff --git a/src/commands/app/stop.ts b/src/commands/app/stop.ts index b04f690..9aab70e 100644 --- a/src/commands/app/stop.ts +++ b/src/commands/app/stop.ts @@ -1,4 +1,4 @@ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; import { readAuthConfig } from "../../utils/utils.js"; import chalk from "chalk"; import inquirer from "inquirer"; @@ -11,79 +11,109 @@ export default class AppStop extends Command { static examples = ["$ <%= config.bin %> app stop"]; + static flags = { + projectId: Flags.string({ + char: 'p', + description: 'ID of the project', + required: false, + }), + applicationId: Flags.string({ + char: 'a', + description: 'ID of the application to stop', + required: false, + }), + skipConfirm: Flags.boolean({ + char: 'y', + description: 'Skip confirmation prompt', + default: false, + }) + }; + public async run(): Promise { const auth = await readAuthConfig(this); + const { flags } = await this.parse(AppStop); + let { projectId, applicationId } = flags; - console.log(chalk.blue.bold("\n Listing all Projects \n")); - - const projects = await getProjects(auth, this); + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !applicationId) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to stop the application in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to stop the application from:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - const projectId = project.projectId; + const projectSelected = await getProject(projectId, auth, this); - const projectSelected = await getProject(projectId, auth, this); + if (projectSelected.applications.length === 0) { + this.error(chalk.yellow("No applications found in this project.")); + } - if (projectSelected.applications.length === 0) { - this.error(chalk.yellow("No applications found in this project.")); + if (!applicationId) { + const appAnswers = await inquirer.prompt([ + { + choices: projectSelected.applications.map((app: { name: string; applicationId: string }) => ({ + name: app.name, + value: app.applicationId, + })), + message: "Select the application to stop:", + name: "selectedApp", + type: "list", + }, + ]); + applicationId = appAnswers.selectedApp; + } } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.applications.map((app) => ({ - name: app.name, - value: app.applicationId, - })), - message: "Select the application to stop:", - name: "selectedApp", - type: "list", - }, - ]); - - const applicationId = appAnswers.selectedApp; - - const confirmAnswers = await inquirer.prompt([ - { - default: false, - message: "Are you sure you want to stop this application?", - name: "confirmDelete", - type: "confirm", - }, - ]); + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirmAnswers = await inquirer.prompt([ + { + default: false, + message: "Are you sure you want to stop this application?", + name: "confirmDelete", + type: "confirm", + }, + ]); - if (!confirmAnswers.confirmDelete) { - this.error(chalk.yellow("Application stop cancelled.")); + if (!confirmAnswers.confirmDelete) { + this.error(chalk.yellow("Application stop cancelled.")); + } } - const response = await axios.post( - `${auth.url}/api/trpc/application.stop`, - { - json: { - applicationId, + try { + const response = await axios.post( + `${auth.url}/api/trpc/application.stop`, + { + json: { + applicationId, + }, }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - }, - ); + ); - if (response.status !== 200) { - this.error(chalk.red("Error stopping application")); + if (response.status !== 200) { + this.error(chalk.red("Error stopping application")); + } + this.log(chalk.green("Application stop successful.")); + } catch (error: any) { + this.error(chalk.red(`Error stopping application: ${error.message}`)); } - this.log(chalk.green("Application stop successful.")); } } diff --git a/src/commands/database/mariadb/create.ts b/src/commands/database/mariadb/create.ts index 71a32df..7777b11 100644 --- a/src/commands/database/mariadb/create.ts +++ b/src/commands/database/mariadb/create.ts @@ -18,93 +18,194 @@ export default class DatabaseMariadbCreate extends Command { description: "ID of the project", required: false, }), + name: Flags.string({ + char: "n", + description: "Database name", + required: false, + }), + databaseName: Flags.string({ + description: "MariaDB database name", + required: false, + }), + description: Flags.string({ + char: "d", + description: "Database description", + required: false, + }), + databaseRootPassword: Flags.string({ + description: "Database root password", + required: false, + }), + databasePassword: Flags.string({ + description: "Database password", + required: false, + }), + databaseUser: Flags.string({ + description: "Database user", + default: "mariadb", + }), + dockerImage: Flags.string({ + description: "Docker image", + default: "mariadb:11", + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + appName: Flags.string({ + description: "App name", + required: false, + }), }; public async run(): Promise { const auth = await readAuthConfig(this); - const { flags } = await this.parse(DatabaseMariadbCreate); + let { + projectId, + name, + databaseName, + description, + databaseRootPassword, + databasePassword, + databaseUser, + dockerImage, + appName + } = flags; - let { projectId } = flags; - - if (!projectId) { + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !name || !databaseName || !appName) { console.log(chalk.blue.bold("\n Listing all Projects \n")); - const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to create the MariaDB database in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to create the MariaDB instance in:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } + + if (!name || !databaseName || !appName) { + const dbDetails = await inquirer.prompt([ + { + message: "Enter the name:", + name: "name", + type: "input", + validate: (input) => (input ? true : "Database name is required"), + default: name, + }, + { + message: "Database name:", + name: "databaseName", + type: "input", + validate: (input) => (input ? true : "Database name is required"), + default: databaseName, + }, + { + message: "Enter the database description (optional):", + name: "description", + type: "input", + default: description, + }, + { + message: "Database Root Password (optional):", + name: "databaseRootPassword", + type: "password", + default: databaseRootPassword, + }, + { + message: "Database password (optional):", + name: "databasePassword", + type: "password", + default: databasePassword, + }, + { + default: dockerImage || "mariadb:11", + message: "Docker Image (default: mariadb:11):", + name: "dockerImage", + type: "input", + }, + { + default: databaseUser || "mariadb", + message: "Database User: (default: mariadb):", + name: "databaseUser", + type: "input", + }, + ]); - projectId = project.projectId; + name = dbDetails.name; + databaseName = dbDetails.databaseName; + description = dbDetails.description; + databaseRootPassword = dbDetails.databaseRootPassword; + databasePassword = dbDetails.databasePassword; + dockerImage = dbDetails.dockerImage; + databaseUser = dbDetails.databaseUser; - const dbDetails = await inquirer.prompt([ - { - message: "Enter the name:", - name: "name", - type: "input", - validate: (input) => (input ? true : "Database name is required"), - }, - { - message: "Database name:", - name: "databaseName", - type: "input", - validate: (input) => (input ? true : "Database name is required"), - }, - { - message: "Enter the database description (optional):", - name: "description", - type: "input", - }, - { - message: "Database Root Password (optional):", - name: "databaseRootPassword", - type: "password", - }, - { - message: "Database password (optional):", - name: "databasePassword", - type: "password", - }, - { - default: "mariadb:11", - message: "Docker Image (default: mariadb:11):", - name: "dockerImage", - type: "input", - }, - { - default: "mariadb", - message: "Database User: (default: mariadb):", - name: "databaseUser", - type: "input", - }, - ]); + const appNamePrompt = await inquirer.prompt([ + { + default: appName || `${slugify(name)}`, + message: "Enter the App name:", + name: "appName", + type: "input", + validate: (input) => (input ? true : "App name is required"), + }, + ]); - const appName = await inquirer.prompt([ + appName = appNamePrompt.appName; + } + } + + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirm = await inquirer.prompt([ { - default: `${slugify(project.name)}-${dbDetails.name}`, - message: "Enter the App name:", - name: "appName", - type: "input", - validate: (input) => (input ? true : "App name is required"), + type: 'confirm', + name: 'proceed', + message: 'Do you want to create this MariaDB instance?', + default: false, }, ]); + if (!confirm.proceed) { + this.error(chalk.yellow("MariaDB creation cancelled.")); + return; + } + } + + try { + console.log(JSON.stringify({ + name, + databaseName, + description, + databaseRootPassword, + databasePassword, + databaseUser, + dockerImage, + appName, + projectId, + }, null, 2)); const response = await axios.post( `${auth.url}/api/trpc/mariadb.create`, { json: { - ...dbDetails, - appName: appName.appName, - projectId: project.projectId, + name, + databaseName, + description, + databaseRootPassword, + databasePassword, + databaseUser, + dockerImage, + appName, + projectId, }, }, { @@ -116,14 +217,12 @@ export default class DatabaseMariadbCreate extends Command { ); if (!response.data.result.data.json) { - this.error(chalk.red("Error creating MariaDB database")); + this.error(chalk.red("Error creating MariaDB instance", response.data.result.data.json)); } - this.log( - chalk.green( - `MariaDB database '${dbDetails.name}' created successfully.`, - ), - ); + this.log(chalk.green(`MariaDB instance '${name}' created successfully.`)); + } catch (error: any) { + this.error(chalk.red(`Error creating MariaDB instance: ${error.message}`)); } } } diff --git a/src/commands/database/mariadb/delete.ts b/src/commands/database/mariadb/delete.ts index bc8dd4c..ba408a9 100644 --- a/src/commands/database/mariadb/delete.ts +++ b/src/commands/database/mariadb/delete.ts @@ -18,73 +18,82 @@ export default class DatabaseMariadbDelete extends Command { description: "ID of the project", required: false, }), + mariadbId: Flags.string({ + char: "m", + description: "ID of the MariaDB instance to delete", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), }; public async run(): Promise { const auth = await readAuthConfig(this); const { flags } = await this.parse(DatabaseMariadbDelete); - let { projectId } = flags; + let { projectId, mariadbId } = flags; - if (!projectId) { + if (!projectId || !mariadbId) { console.log(chalk.blue.bold("\n Listing all Projects \n")); const projects = await getProjects(auth, this); - if (projects.length === 0) { - this.log(chalk.yellow("No projects found.")); - return; + if (!projectId) { + const answers = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project.projectId, + })), + message: "Select a project to delete the MariaDB instance from:", + name: "selectedProject", + type: "list", + }, + ]); + projectId = answers.selectedProject; } - const answers = await inquirer.prompt([ - { - type: "list", - name: "selectedProject", - message: "Select a project to delete the MariaDB database from:", - choices: projects.map((project: any) => ({ - name: project.name, - value: project.projectId, - })), - }, - ]); - projectId = answers.selectedProject; - } - - try { - const project = await getProject(projectId, auth, this); + const projectSelected = await getProject(projectId, auth, this); - if (!project.mariadb || project.mariadb.length === 0) { - this.log(chalk.yellow("No MariaDB databases found in this project.")); - return; + if (!projectSelected.mariadb || projectSelected.mariadb.length === 0) { + this.error(chalk.yellow("No MariaDB instances found in this project.")); } - const appAnswers = await inquirer.prompt([ - { - type: "list", - name: "selectedDb", - message: "Select the MariaDB database to delete:", - choices: project.mariadb.map((db: any) => ({ - name: db.name, - value: db.mariadbId, - })), - }, - ]); - - const mariadbId = appAnswers.selectedDb; + if (!mariadbId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.mariadb.map((db) => ({ + name: db.name, + value: db.mariadbId, + })), + message: "Select the MariaDB instance to delete:", + name: "selectedDb", + type: "list", + }, + ]); + mariadbId = dbAnswers.selectedDb; + } + } + if (!flags.skipConfirm) { const confirmAnswers = await inquirer.prompt([ { - type: "confirm", - name: "confirmDelete", - message: "Are you sure you want to delete this MariaDB database?", default: false, + message: "Are you sure you want to delete this MariaDB instance?", + name: "confirmDelete", + type: "confirm", }, ]); if (!confirmAnswers.confirmDelete) { - this.log(chalk.yellow("Database deletion cancelled.")); - return; + this.error(chalk.yellow("MariaDB deletion cancelled.")); } + } - const deleteResponse = await axios.post( + try { + const response = await axios.post( `${auth.url}/api/trpc/mariadb.remove`, { json: { @@ -99,15 +108,12 @@ export default class DatabaseMariadbDelete extends Command { }, ); - if (!deleteResponse.data.result.data.json) { - this.error(chalk.red("Error deleting mariadb database")); + if (!response.data.result.data.json) { + this.error(chalk.red("Error deleting MariaDB instance")); } - this.log(chalk.green("MariaDB database deleted successfully.")); - } catch (error) { - this.error( - // @ts-ignore - chalk.red(`Failed to delete MariaDB database: ${error.message}`), - ); + this.log(chalk.green("MariaDB instance deleted successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error deleting MariaDB instance: ${error.message}`)); } } } diff --git a/src/commands/database/mariadb/deploy.ts b/src/commands/database/mariadb/deploy.ts index 0874a60..5b9ab18 100644 --- a/src/commands/database/mariadb/deploy.ts +++ b/src/commands/database/mariadb/deploy.ts @@ -1,4 +1,4 @@ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; import { readAuthConfig } from "../../../utils/utils.js"; import chalk from "chalk"; import { getProject, getProjects } from "../../../utils/shared.js"; @@ -11,79 +11,110 @@ export default class DatabaseMariadbDeploy extends Command { static examples = ["$ <%= config.bin %> app deploy"]; + static flags = { + projectId: Flags.string({ + char: "p", + description: "ID of the project", + required: false, + }), + mariadbId: Flags.string({ + char: "m", + description: "ID of the MariaDB instance to deploy", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + }; + public async run(): Promise { const auth = await readAuthConfig(this); + const { flags } = await this.parse(DatabaseMariadbDeploy); + let { projectId, mariadbId } = flags; - console.log(chalk.blue.bold("\n Listing all Projects \n")); - - const projects = await getProjects(auth, this); + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !mariadbId) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to deploy the mariadb in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to deploy the MariaDB in:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - const projectId = project.projectId; + const projectSelected = await getProject(projectId, auth, this); - const projectSelected = await getProject(projectId, auth, this); + if (projectSelected.mariadb.length === 0) { + this.error(chalk.yellow("No MariaDB instances found in this project.")); + } - if (projectSelected.mariadb.length === 0) { - this.error(chalk.yellow("No mariadb found in this project.")); + if (!mariadbId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.mariadb.map((db) => ({ + name: db.name, + value: db.mariadbId, + })), + message: "Select the MariaDB instance to deploy:", + name: "selectedDb", + type: "list", + }, + ]); + mariadbId = dbAnswers.selectedDb; + } } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.mariadb.map((app) => ({ - name: app.name, - value: app.mariadbId, - })), - message: "Select the mariadb to deploy:", - name: "selectedApp", - type: "list", - }, - ]); - - const mariadbId = appAnswers.selectedApp; - - const confirmAnswers = await inquirer.prompt([ - { - default: false, - message: "Are you sure you want to deploy this mariadb?", - name: "confirmDelete", - type: "confirm", - }, - ]); + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirmAnswers = await inquirer.prompt([ + { + default: false, + message: "Are you sure you want to deploy this MariaDB instance?", + name: "confirmDeploy", + type: "confirm", + }, + ]); - if (!confirmAnswers.confirmDelete) { - this.error(chalk.yellow("mariadb deployment cancelled.")); + if (!confirmAnswers.confirmDeploy) { + this.error(chalk.yellow("MariaDB deployment cancelled.")); + } } - const response = await axios.post( - `${auth.url}/api/trpc/mariadb.deploy`, - { - json: { - mariadbId, + try { + const response = await axios.post( + `${auth.url}/api/trpc/mariadb.deploy`, + { + json: { + mariadbId, + }, }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - }, - ); + ); - if (response.status !== 200) { - this.error(chalk.red("Error deploying mariadb")); + if (response.status !== 200) { + this.error(chalk.red("Error deploying MariaDB instance")); + } + this.log(chalk.green("MariaDB instance deployed successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error deploying MariaDB instance: ${error.message}`)); } - this.log(chalk.green("Mariadb deploy successful.")); } } diff --git a/src/commands/database/mariadb/stop.ts b/src/commands/database/mariadb/stop.ts index a39bba6..f184705 100644 --- a/src/commands/database/mariadb/stop.ts +++ b/src/commands/database/mariadb/stop.ts @@ -1,4 +1,4 @@ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; import chalk from "chalk"; import inquirer from "inquirer"; import axios from "axios"; @@ -11,79 +11,110 @@ export default class DatabaseMariadbStop extends Command { static examples = ["$ <%= config.bin %> mariadb stop"]; + static flags = { + projectId: Flags.string({ + char: "p", + description: "ID of the project", + required: false, + }), + mariadbId: Flags.string({ + char: "m", + description: "ID of the MariaDB instance to stop", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + }; + public async run(): Promise { const auth = await readAuthConfig(this); + const { flags } = await this.parse(DatabaseMariadbStop); + let { projectId, mariadbId } = flags; - console.log(chalk.blue.bold("\n Listing all Projects \n")); - - const projects = await getProjects(auth, this); + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !mariadbId) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to stop the mariadb in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to stop the MariaDB instance from:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - const projectId = project.projectId; + const projectSelected = await getProject(projectId, auth, this); - const projectSelected = await getProject(projectId, auth, this); + if (projectSelected.mariadb.length === 0) { + this.error(chalk.yellow("No MariaDB instances found in this project.")); + } - if (projectSelected.mariadb.length === 0) { - this.error(chalk.yellow("No mariadb found in this project.")); + if (!mariadbId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.mariadb.map((db) => ({ + name: db.name, + value: db.mariadbId, + })), + message: "Select the MariaDB instance to stop:", + name: "selectedDb", + type: "list", + }, + ]); + mariadbId = dbAnswers.selectedDb; + } } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.mariadb.map((app) => ({ - name: app.name, - value: app.mariadbId, - })), - message: "Select the mariadb to stop:", - name: "selectedApp", - type: "list", - }, - ]); - - const mariadbId = appAnswers.selectedApp; - - const confirmAnswers = await inquirer.prompt([ - { - default: false, - message: "Are you sure you want to stop this mariadb?", - name: "confirmDelete", - type: "confirm", - }, - ]); + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirmAnswers = await inquirer.prompt([ + { + default: false, + message: "Are you sure you want to stop this MariaDB instance?", + name: "confirmStop", + type: "confirm", + }, + ]); - if (!confirmAnswers.confirmDelete) { - this.error(chalk.yellow("Mariadb stop cancelled.")); + if (!confirmAnswers.confirmStop) { + this.error(chalk.yellow("MariaDB stop cancelled.")); + } } - const response = await axios.post( - `${auth.url}/api/trpc/mariadb.stop`, - { - json: { - mariadbId, + try { + const response = await axios.post( + `${auth.url}/api/trpc/mariadb.stop`, + { + json: { + mariadbId, + }, }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - }, - ); + ); - if (response.status !== 200) { - this.error(chalk.red("Error stopping mariadb")); + if (response.status !== 200) { + this.error(chalk.red("Error stopping MariaDB instance")); + } + this.log(chalk.green("MariaDB instance stopped successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error stopping MariaDB instance: ${error.message}`)); } - this.log(chalk.green("Mariadb stop successful.")); } } diff --git a/src/commands/database/mongo/create.ts b/src/commands/database/mongo/create.ts index ecb486a..4341201 100644 --- a/src/commands/database/mongo/create.ts +++ b/src/commands/database/mongo/create.ts @@ -18,114 +18,187 @@ export default class DatabaseMongoCreate extends Command { description: "ID of the project", required: false, }), + name: Flags.string({ + char: "n", + description: "Database name", + required: false, + }), + databaseName: Flags.string({ + description: "MongoDB database name", + required: false, + }), + description: Flags.string({ + char: "d", + description: "Database description", + required: false, + }), + databasePassword: Flags.string({ + description: "Database password", + required: false, + }), + databaseUser: Flags.string({ + description: "Database user", + default: "mongo", + }), + dockerImage: Flags.string({ + description: "Docker image", + default: "mongo:6", + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + appName: Flags.string({ + description: "App name", + required: false, + }), }; public async run(): Promise { const auth = await readAuthConfig(this); - const { flags } = await this.parse(DatabaseMongoCreate); + let { + projectId, + name, + databaseName, + description, + databasePassword, + databaseUser, + dockerImage, + appName + } = flags; - let { projectId } = flags; - - if (!projectId) { + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !name || !databaseName || !appName || !databasePassword) { console.log(chalk.blue.bold("\n Listing all Projects \n")); - const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to create the MongoDB database in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to create the MongoDB instance in:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } + + if (!name || !databaseName || !appName || !databasePassword) { + const dbDetails = await inquirer.prompt([ + { + message: "Enter the name:", + name: "name", + type: "input", + validate: (input) => (input ? true : "Database name is required"), + default: name, + }, + { + message: "Database name:", + name: "databaseName", + type: "input", + validate: (input) => (input ? true : "Database name is required"), + default: databaseName, + }, + { + message: "Enter the database description (optional):", + name: "description", + type: "input", + default: description, + }, + { + message: "Database password (optional):", + name: "databasePassword", + type: "password", + default: databasePassword, + }, + { + default: dockerImage || "mongo:6", + message: "Docker Image (default: mongo:6):", + name: "dockerImage", + type: "input", + }, + { + default: databaseUser || "mongo", + message: "Database User: (default: mongo):", + name: "databaseUser", + type: "input", + }, + ]); - projectId = project.projectId; + name = dbDetails.name; + databaseName = dbDetails.databaseName; + description = dbDetails.description; + databasePassword = dbDetails.databasePassword; + dockerImage = dbDetails.dockerImage; + databaseUser = dbDetails.databaseUser; - const dbDetails = await inquirer.prompt([ - { - message: "Enter the name:", - name: "name", - type: "input", - validate: (input) => (input ? true : "Database name is required"), - }, - { - message: "Database name:", - name: "databaseName", - type: "input", - validate: (input) => (input ? true : "Database name is required"), - }, - { - message: "Enter the database description (optional):", - name: "description", - type: "input", - }, - { - message: "Database password (optional):", - name: "databasePassword", - type: "password", - }, - { - default: "mongo:6", - message: "Docker Image (default: mongo:6):", - name: "dockerImage", - type: "input", - }, - { - default: "mongo", - message: "Database User: (default: mongo):", - name: "databaseUser", - type: "input", - }, - ]); + const appNamePrompt = await inquirer.prompt([ + { + default: appName || `${slugify(name)}`, + message: "Enter the App name:", + name: "appName", + type: "input", + validate: (input) => (input ? true : "App name is required"), + }, + ]); + + appName = appNamePrompt.appName; + } + } - const appName = await inquirer.prompt([ + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirm = await inquirer.prompt([ { - default: `${slugify(project.name)}-${dbDetails.name}`, - message: "Enter the App name:", - name: "appName", - type: "input", - validate: (input) => (input ? true : "App name is required"), + type: 'confirm', + name: 'proceed', + message: 'Do you want to create this MongoDB instance?', + default: false, }, ]); - try { - const response = await axios.post( - `${auth.url}/api/trpc/mongo.create`, - { - json: { - ...dbDetails, - appName: appName.appName, - projectId: project.projectId, - }, + if (!confirm.proceed) { + this.error(chalk.yellow("MongoDB creation cancelled.")); + return; + } + } + + try { + const response = await axios.post( + `${auth.url}/api/trpc/mongo.create`, + { + json: { + name, + databaseName, + description, + databasePassword, + databaseUser, + dockerImage, + appName, + projectId, }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", - }, + }, + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", }, - ); - - if (!response.data.result.data.json) { - this.error(chalk.red("Error creating MongoDB database")); - } + }, + ); - this.log( - chalk.green( - `MongoDB database '${dbDetails.name}' created successfully.`, - ), - ); - } catch (error) { - this.error( - // @ts-ignore - chalk.red(`Failed to create MongoDB database: ${error.message}`), - ); + if (!response.data.result.data.json) { + this.error(chalk.red("Error creating MongoDB instance")); } + + this.log(chalk.green(`MongoDB instance '${name}' created successfully.`)); + } catch (error: any) { + this.error(chalk.red(`Error creating MongoDB instance: ${error.message}`)); } } } diff --git a/src/commands/database/mongo/delete.ts b/src/commands/database/mongo/delete.ts index 6bdabbc..b9ffbd9 100644 --- a/src/commands/database/mongo/delete.ts +++ b/src/commands/database/mongo/delete.ts @@ -20,76 +20,84 @@ export default class DatabaseMongoDelete extends Command { description: "ID of the project", required: false, }), + mongoId: Flags.string({ + char: "m", + description: "ID of the MongoDB instance to delete", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), }; public async run(): Promise { const auth = await readAuthConfig(this); - const { flags } = await this.parse(DatabaseMongoDelete); - let { projectId } = flags; + let { projectId, mongoId } = flags; - if (!projectId) { + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !mongoId) { console.log(chalk.blue.bold("\n Listing all Projects \n")); - const projects = await getProjects(auth, this); - if (projects.length === 0) { - this.log(chalk.yellow("No projects found.")); - return; + if (!projectId) { + const answers = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project.projectId, + })), + message: "Select a project to delete the MongoDB instance from:", + name: "selectedProject", + type: "list", + }, + ]); + projectId = answers.selectedProject; } - const answers = await inquirer.prompt([ - { - choices: projects.map((project: any) => ({ - name: project.name, - value: project.projectId, - })), - message: "Select a project to delete the MongoDB database from:", - name: "selectedProject", - type: "list", - }, - ]); - - projectId = answers.selectedProject; - } - - try { - const project = await getProject(projectId, auth, this); + const projectSelected = await getProject(projectId, auth, this); - if (!project.mongo || project.mongo.length === 0) { - this.log(chalk.yellow("No MongoDB databases found in this project.")); - return; + if (!projectSelected.mongo || projectSelected.mongo.length === 0) { + this.error(chalk.yellow("No MongoDB instances found in this project.")); } - const appAnswers = await inquirer.prompt([ - { - choices: project.mongo.map((db: any) => ({ - name: db.name, - value: db.mongoId, - })), - message: "Select the MongoDB database to delete:", - name: "selectedApp", - type: "list", - }, - ]); - - const mongoId = appAnswers.selectedApp; + if (!mongoId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.mongo.map((db) => ({ + name: db.name, + value: db.mongoId, + })), + message: "Select the MongoDB instance to delete:", + name: "selectedDb", + type: "list", + }, + ]); + mongoId = dbAnswers.selectedDb; + } + } + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { const confirmAnswers = await inquirer.prompt([ { default: false, - message: "Are you sure you want to delete this MongoDB database?", + message: "Are you sure you want to delete this MongoDB instance?", name: "confirmDelete", type: "confirm", }, ]); if (!confirmAnswers.confirmDelete) { - this.log(chalk.yellow("Database deletion cancelled.")); - return; + this.error(chalk.yellow("MongoDB deletion cancelled.")); } + } - const deleteResponse = await axios.post( + try { + const response = await axios.post( `${auth.url}/api/trpc/mongo.remove`, { json: { @@ -104,16 +112,12 @@ export default class DatabaseMongoDelete extends Command { }, ); - if (!deleteResponse.data.result.data.json) { - this.error(chalk.red("Error deleting MongoDB database")); + if (!response.data.result.data.json) { + this.error(chalk.red("Error deleting MongoDB instance")); } - - this.log(chalk.green("MongoDB database deleted successfully.")); - } catch (error) { - this.error( - // @ts-ignore - chalk.red(`Failed to delete MongoDB database: ${error.message}`), - ); + this.log(chalk.green("MongoDB instance deleted successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error deleting MongoDB instance: ${error.message}`)); } } } diff --git a/src/commands/database/mongo/deploy.ts b/src/commands/database/mongo/deploy.ts index cbee40e..1cb5b66 100644 --- a/src/commands/database/mongo/deploy.ts +++ b/src/commands/database/mongo/deploy.ts @@ -1,4 +1,4 @@ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; import { readAuthConfig } from "../../../utils/utils.js"; import chalk from "chalk"; import { getProject, getProjects } from "../../../utils/shared.js"; @@ -11,79 +11,110 @@ export default class DatabaseMongoDeploy extends Command { static examples = ["$ <%= config.bin %> app deploy"]; + static flags = { + projectId: Flags.string({ + char: "p", + description: "ID of the project", + required: false, + }), + mongoId: Flags.string({ + char: "m", + description: "ID of the MongoDB instance to deploy", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + }; + public async run(): Promise { const auth = await readAuthConfig(this); + const { flags } = await this.parse(DatabaseMongoDeploy); + let { projectId, mongoId } = flags; - console.log(chalk.blue.bold("\n Listing all Projects \n")); - - const projects = await getProjects(auth, this); + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !mongoId) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to deploy the mongo in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to deploy the MongoDB instance from:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - const projectId = project.projectId; + const projectSelected = await getProject(projectId, auth, this); - const projectSelected = await getProject(projectId, auth, this); + if (projectSelected.mongo.length === 0) { + this.error(chalk.yellow("No MongoDB instances found in this project.")); + } - if (projectSelected.mongo.length === 0) { - this.error(chalk.yellow("No mongo found in this project.")); + if (!mongoId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.mongo.map((db) => ({ + name: db.name, + value: db.mongoId, + })), + message: "Select the MongoDB instance to deploy:", + name: "selectedDb", + type: "list", + }, + ]); + mongoId = dbAnswers.selectedDb; + } } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.mongo.map((app) => ({ - name: app.name, - value: app.mongoId, - })), - message: "Select the mongo to deploy:", - name: "selectedApp", - type: "list", - }, - ]); - - const mongoId = appAnswers.selectedApp; - - const confirmAnswers = await inquirer.prompt([ - { - default: false, - message: "Are you sure you want to deploy this mongo?", - name: "confirmDelete", - type: "confirm", - }, - ]); + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirmAnswers = await inquirer.prompt([ + { + default: false, + message: "Are you sure you want to deploy this MongoDB instance?", + name: "confirmDeploy", + type: "confirm", + }, + ]); - if (!confirmAnswers.confirmDelete) { - this.error(chalk.yellow("mongo deployment cancelled.")); + if (!confirmAnswers.confirmDeploy) { + this.error(chalk.yellow("MongoDB deployment cancelled.")); + } } - const response = await axios.post( - `${auth.url}/api/trpc/mongo.deploy`, - { - json: { - mongoId, + try { + const response = await axios.post( + `${auth.url}/api/trpc/mongo.deploy`, + { + json: { + mongoId, + }, }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - }, - ); + ); - if (response.status !== 200) { - this.error(chalk.red("Error deploying mongo")); + if (response.status !== 200) { + this.error(chalk.red("Error deploying MongoDB instance")); + } + this.log(chalk.green("MongoDB instance deployed successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error deploying MongoDB instance: ${error.message}`)); } - this.log(chalk.green("Mongo deploy successful.")); } } diff --git a/src/commands/database/mongo/stop.ts b/src/commands/database/mongo/stop.ts index 96b3975..dabe51c 100644 --- a/src/commands/database/mongo/stop.ts +++ b/src/commands/database/mongo/stop.ts @@ -1,4 +1,4 @@ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; import chalk from "chalk"; import inquirer from "inquirer"; import axios from "axios"; @@ -11,79 +11,110 @@ export default class DatabaseMongoStop extends Command { static examples = ["$ <%= config.bin %> mongo stop"]; + static flags = { + projectId: Flags.string({ + char: "p", + description: "ID of the project", + required: false, + }), + mongoId: Flags.string({ + char: "m", + description: "ID of the MongoDB instance to stop", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + }; + public async run(): Promise { const auth = await readAuthConfig(this); + const { flags } = await this.parse(DatabaseMongoStop); + let { projectId, mongoId } = flags; - console.log(chalk.blue.bold("\n Listing all Projects \n")); - - const projects = await getProjects(auth, this); + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !mongoId) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to stop the mongo in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to stop the MongoDB instance from:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - const projectId = project.projectId; + const projectSelected = await getProject(projectId, auth, this); - const projectSelected = await getProject(projectId, auth, this); + if (projectSelected.mongo.length === 0) { + this.error(chalk.yellow("No MongoDB instances found in this project.")); + } - if (projectSelected.mongo.length === 0) { - this.error(chalk.yellow("No mongo found in this project.")); + if (!mongoId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.mongo.map((db) => ({ + name: db.name, + value: db.mongoId, + })), + message: "Select the MongoDB instance to stop:", + name: "selectedDb", + type: "list", + }, + ]); + mongoId = dbAnswers.selectedDb; + } } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.mongo.map((app) => ({ - name: app.name, - value: app.mongoId, - })), - message: "Select the mongo to stop:", - name: "selectedApp", - type: "list", - }, - ]); - - const mongoId = appAnswers.selectedApp; - - const confirmAnswers = await inquirer.prompt([ - { - default: false, - message: "Are you sure you want to stop this mongo?", - name: "confirmDelete", - type: "confirm", - }, - ]); + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirmAnswers = await inquirer.prompt([ + { + default: false, + message: "Are you sure you want to stop this MongoDB instance?", + name: "confirmStop", + type: "confirm", + }, + ]); - if (!confirmAnswers.confirmDelete) { - this.error(chalk.yellow("mongo stop cancelled.")); + if (!confirmAnswers.confirmStop) { + this.error(chalk.yellow("MongoDB stop cancelled.")); + } } - const response = await axios.post( - `${auth.url}/api/trpc/mongo.stop`, - { - json: { - mongoId, + try { + const response = await axios.post( + `${auth.url}/api/trpc/mongo.stop`, + { + json: { + mongoId, + }, }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - }, - ); + ); - if (response.status !== 200) { - this.error(chalk.red("Error stopping mongo")); + if (response.status !== 200) { + this.error(chalk.red("Error stopping MongoDB instance")); + } + this.log(chalk.green("MongoDB instance stopped successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error stopping MongoDB instance: ${error.message}`)); } - this.log(chalk.green("Mongo stop successful.")); } } diff --git a/src/commands/database/mysql/create.ts b/src/commands/database/mysql/create.ts index 8f654c5..feb78f9 100644 --- a/src/commands/database/mysql/create.ts +++ b/src/commands/database/mysql/create.ts @@ -19,119 +19,212 @@ export default class DatabaseMysqlCreate extends Command { description: "ID of the project", required: false, }), + name: Flags.string({ + char: "n", + description: "Database name", + required: false, + }), + databaseName: Flags.string({ + description: "MySQL database name", + required: false, + }), + description: Flags.string({ + char: "d", + description: "Database description", + required: false, + }), + databaseRootPassword: Flags.string({ + description: "Database root password", + required: false, + }), + databasePassword: Flags.string({ + description: "Database password", + required: false, + }), + databaseUser: Flags.string({ + description: "Database user", + default: "mysql", + }), + dockerImage: Flags.string({ + description: "Docker image", + default: "mysql:8", + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + appName: Flags.string({ + description: "App name", + required: false, + }), }; public async run(): Promise { const auth = await readAuthConfig(this); - const { flags } = await this.parse(DatabaseMysqlCreate); + let { + projectId, + name, + databaseName, + description, + databaseRootPassword, + databasePassword, + databaseUser, + dockerImage, + appName + } = flags; + + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !name || !databaseName || !appName || !databasePassword || !databaseRootPassword) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - let { projectId } = flags; + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to create the MySQL instance in:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - if (!projectId) { - console.log(chalk.blue.bold("\n Listing all Projects \n")); + if (!name || !databaseName || !appName || !databasePassword || !databaseRootPassword) { + const dbDetails = await inquirer.prompt([ + { + message: "Enter the name:", + name: "name", + type: "input", + validate: (input) => (input ? true : "Database name is required"), + default: name, + }, + { + message: "Database name:", + name: "databaseName", + type: "input", + validate: (input) => (input ? true : "Database name is required"), + default: databaseName, + }, + { + message: "Enter the database description (optional):", + name: "description", + type: "input", + default: description, + }, + { + message: "Database Root Password:", + name: "databaseRootPassword", + type: "password", + default: databaseRootPassword, + }, + { + message: "Database password:", + name: "databasePassword", + type: "password", + default: databasePassword, + }, + { + default: dockerImage || "mysql:8", + message: "Docker Image (default: mysql:8):", + name: "dockerImage", + type: "input", + }, + { + default: databaseUser || "mysql", + message: "Database User: (default: mysql):", + name: "databaseUser", + type: "input", + }, + ]); - const projects = await getProjects(auth, this); + name = dbDetails.name; + databaseName = dbDetails.databaseName; + description = dbDetails.description; + databaseRootPassword = dbDetails.databaseRootPassword; + databasePassword = dbDetails.databasePassword; + dockerImage = dbDetails.dockerImage; + databaseUser = dbDetails.databaseUser; + + const appNamePrompt = await inquirer.prompt([ + { + default: appName || `${slugify(name)}`, + message: "Enter the App name:", + name: "appName", + type: "input", + validate: (input) => (input ? true : "App name is required"), + }, + ]); + + appName = appNamePrompt.appName; + } + } - const { project } = await inquirer.prompt([ + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirm = await inquirer.prompt([ { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to create the MySQL database in:", - name: "project", - type: "list", + type: 'confirm', + name: 'proceed', + message: 'Do you want to create this MySQL instance?', + default: false, }, ]); - projectId = project.projectId; + if (!confirm.proceed) { + this.error(chalk.yellow("MySQL creation cancelled.")); + return; + } + } - const dbDetails = await inquirer.prompt([ - { - message: "Enter the name:", - name: "name", - type: "input", - validate: (input) => (input ? true : "Database name is required"), - }, - { - message: "Database name:", - name: "databaseName", - type: "input", - validate: (input) => (input ? true : "Database name is required"), - }, - { - message: "Enter the database description (optional):", - name: "description", - type: "input", - }, + try { + console.log(JSON.stringify({ + name, + databaseName, + description, + databaseRootPassword, + databasePassword, + databaseUser, + dockerImage, + appName, + projectId, + }, null, 2)); + + const response = await axios.post( + `${auth.url}/api/trpc/mysql.create`, { - message: "Database Root Password (optional):", - name: "databaseRootPassword", - type: "password", - }, - { - message: "Database password (optional):", - name: "databasePassword", - type: "password", - }, - { - default: "mysql:8", - message: "Docker Image (default: mysql:8):", - name: "dockerImage", - type: "input", - }, - { - default: "mysql", - message: "Database User: (default: mysql):", - name: "databaseUser", - type: "input", + json: { + name, + databaseName, + description, + databaseRootPassword, + databasePassword, + databaseUser, + dockerImage, + appName, + projectId, + }, }, - ]); - - const appName = await inquirer.prompt([ { - default: `${slugify(project.name)}-${dbDetails.name}`, - message: "Enter the App name:", - name: "appName", - type: "input", - validate: (input) => (input ? true : "App name is required"), + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - ]); + ); - try { - const response = await axios.post( - `${auth.url}/api/trpc/mysql.create`, - { - json: { - ...dbDetails, - appName: appName.appName, - projectId: project.projectId, - }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", - }, - }, - ); - - if (!response.data.result.data.json) { - this.error(chalk.red("Error creating MySQL database")); - } - - this.log( - chalk.green( - `MySQL database '${dbDetails.name}' created successfully.`, - ), - ); - } catch (error) { - this.error( - // @ts-ignore - chalk.red(`Failed to create MySQL database: ${error.message}`), - ); + if (!response.data.result.data.json) { + this.error(chalk.red("Error creating MySQL instance", response.data.result.data.json)); } + + this.log(chalk.green(`MySQL instance '${name}' created successfully.`)); + } catch (error: any) { + this.error(chalk.red(`Error creating MySQL instance: ${error.message}`)); } } } diff --git a/src/commands/database/mysql/delete.ts b/src/commands/database/mysql/delete.ts index 8ea0c01..9fd016d 100644 --- a/src/commands/database/mysql/delete.ts +++ b/src/commands/database/mysql/delete.ts @@ -20,79 +20,84 @@ export default class DatabaseMysqlDelete extends Command { description: "ID of the project", required: false, }), + mysqlId: Flags.string({ + char: "i", + description: "ID of the MySQL database", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation", + required: false, + }), }; public async run(): Promise { const auth = await readAuthConfig(this); - const { flags } = await this.parse(DatabaseMysqlDelete); - let { projectId } = flags; + let { projectId, mysqlId } = flags; - if (!projectId) { + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !mysqlId) { console.log(chalk.blue.bold("\n Listing all Projects \n")); - const projects = await getProjects(auth, this); - if (projects.length === 0) { - this.log(chalk.yellow("No projects found.")); - return; + if (!projectId) { + const answers = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project.projectId, + })), + message: "Select a project to delete the MySQL instance from:", + name: "selectedProject", + type: "list", + }, + ]); + projectId = answers.selectedProject; } - const answers = await inquirer.prompt([ - { - choices: projects.map((project: any) => ({ - name: project.name, - value: project.projectId, - })), - message: "Select a project to delete the MySQL database from:", - name: "selectedProject", - type: "list", - }, - ]); - - projectId = answers.selectedProject; - } - - try { - const project = await getProject(projectId, auth, this); + const projectSelected = await getProject(projectId, auth, this); - if (!project.mysql || project.mysql.length === 0) { - this.log(chalk.yellow("No MySQL databases found in this project.")); - return; + if (!projectSelected.mysql || projectSelected.mysql.length === 0) { + this.error(chalk.yellow("No MySQL instances found in this project.")); } - // Permitir al usuario seleccionar una aplicación - const appAnswers = await inquirer.prompt([ - { - choices: project.mysql.map((app: any) => ({ - name: app.name, - value: app.mysqlId, - })), - message: "Select the MySQL database to delete:", - name: "selectedApp", - type: "list", - }, - ]); - - const mysqlId = appAnswers.selectedApp; + if (!mysqlId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.mysql.map((db) => ({ + name: db.name, + value: db.mysqlId, + })), + message: "Select the MySQL instance to delete:", + name: "selectedDb", + type: "list", + }, + ]); + mysqlId = dbAnswers.selectedDb; + } + } - // Confirmar eliminación + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { const confirmAnswers = await inquirer.prompt([ { default: false, - message: "Are you sure you want to delete this mysql database?", + message: "Are you sure you want to delete this MySQL instance?", name: "confirmDelete", type: "confirm", }, ]); if (!confirmAnswers.confirmDelete) { - this.log(chalk.yellow("Application deletion cancelled.")); - return; + this.error(chalk.yellow("MySQL deletion cancelled.")); } + } - // Eliminar la aplicación seleccionada - const deleteResponse = await axios.post( + try { + const response = await axios.post( `${auth.url}/api/trpc/mysql.remove`, { json: { @@ -107,14 +112,12 @@ export default class DatabaseMysqlDelete extends Command { }, ); - if (!deleteResponse.data.result.data.json) { - this.error(chalk.red("Error deleting application")); + if (!response.data.result.data.json) { + this.error(chalk.red("Error deleting MySQL instance")); } - - this.log(chalk.green("Application deleted successfully.")); - } catch (error) { - // @ts-expect-error - TS2339: Property 'data' does not exist on type 'AxiosError'. - this.error(chalk.red(`Failed to delete application: ${error.message}`)); + this.log(chalk.green("MySQL instance deleted successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error deleting MySQL instance: ${error.message}`)); } } } diff --git a/src/commands/database/mysql/deploy.ts b/src/commands/database/mysql/deploy.ts index b1cb76b..881e547 100644 --- a/src/commands/database/mysql/deploy.ts +++ b/src/commands/database/mysql/deploy.ts @@ -13,77 +13,90 @@ export default class DatabaseMysqlDeploy extends Command { public async run(): Promise { const auth = await readAuthConfig(this); + const { flags } = await this.parse(DatabaseMysqlDeploy); + let { projectId, mysqlId } = flags; - console.log(chalk.blue.bold("\n Listing all Projects \n")); + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !mysqlId) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - const projects = await getProjects(auth, this); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to deploy the MySQL instance from:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to deploy the mysql in:", - name: "project", - type: "list", - }, - ]); + const projectSelected = await getProject(projectId, auth, this); - const projectId = project.projectId; + if (projectSelected.mysql.length === 0) { + this.error(chalk.yellow("No MySQL instances found in this project.")); + } - const projectSelected = await getProject(projectId, auth, this); - - if (projectSelected.mysql.length === 0) { - this.error(chalk.yellow("No mysql found in this project.")); + if (!mysqlId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.mysql.map((db) => ({ + name: db.name, + value: db.mysqlId, + })), + message: "Select the MySQL instance to deploy:", + name: "selectedDb", + type: "list", + }, + ]); + mysqlId = dbAnswers.selectedDb; + } } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.mysql.map((app) => ({ - name: app.name, - value: app.mysqlId, - })), - message: "Select the mysql to deploy:", - name: "selectedApp", - type: "list", - }, - ]); - - const mysqlId = appAnswers.selectedApp; - - const confirmAnswers = await inquirer.prompt([ - { - default: false, - message: "Are you sure you want to deploy this mysql?", - name: "confirmDelete", - type: "confirm", - }, - ]); + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirmAnswers = await inquirer.prompt([ + { + default: false, + message: "Are you sure you want to deploy this MySQL instance?", + name: "confirmDeploy", + type: "confirm", + }, + ]); - if (!confirmAnswers.confirmDelete) { - this.error(chalk.yellow("mysql deployment cancelled.")); + if (!confirmAnswers.confirmDeploy) { + this.error(chalk.yellow("MySQL deployment cancelled.")); + } } - const response = await axios.post( - `${auth.url}/api/trpc/mysql.deploy`, - { - json: { - mysqlId, + try { + const response = await axios.post( + `${auth.url}/api/trpc/mysql.deploy`, + { + json: { + mysqlId, + }, }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - }, - ); + ); - if (response.status !== 200) { - this.error(chalk.red("Error deploying mysql")); + if (response.status !== 200) { + this.error(chalk.red("Error deploying MySQL instance")); + } + this.log(chalk.green("MySQL instance deployed successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error deploying MySQL instance: ${error.message}`)); } - this.log(chalk.green("Mysql deployed successful.")); } } diff --git a/src/commands/database/mysql/stop.ts b/src/commands/database/mysql/stop.ts index e13c51a..83bae2d 100644 --- a/src/commands/database/mysql/stop.ts +++ b/src/commands/database/mysql/stop.ts @@ -13,77 +13,90 @@ export default class DatabaseMysqlStop extends Command { public async run(): Promise { const auth = await readAuthConfig(this); + const { flags } = await this.parse(DatabaseMysqlStop); + let { projectId, mysqlId } = flags; - console.log(chalk.blue.bold("\n Listing all Projects \n")); + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !mysqlId) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - const projects = await getProjects(auth, this); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to stop the MySQL instance from:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to stop the mysql in:", - name: "project", - type: "list", - }, - ]); + const projectSelected = await getProject(projectId, auth, this); - const projectId = project.projectId; + if (projectSelected.mysql.length === 0) { + this.error(chalk.yellow("No MySQL instances found in this project.")); + } - const projectSelected = await getProject(projectId, auth, this); - - if (projectSelected.mysql.length === 0) { - this.error(chalk.yellow("No mysql found in this project.")); + if (!mysqlId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.mysql.map((db) => ({ + name: db.name, + value: db.mysqlId, + })), + message: "Select the MySQL instance to stop:", + name: "selectedDb", + type: "list", + }, + ]); + mysqlId = dbAnswers.selectedDb; + } } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.mysql.map((app) => ({ - name: app.name, - value: app.mysqlId, - })), - message: "Select the mysql to stop:", - name: "selectedApp", - type: "list", - }, - ]); - - const mysqlId = appAnswers.selectedApp; - - const confirmAnswers = await inquirer.prompt([ - { - default: false, - message: "Are you sure you want to stop this mysql?", - name: "confirmDelete", - type: "confirm", - }, - ]); + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirmAnswers = await inquirer.prompt([ + { + default: false, + message: "Are you sure you want to stop this MySQL instance?", + name: "confirmStop", + type: "confirm", + }, + ]); - if (!confirmAnswers.confirmDelete) { - this.error(chalk.yellow("mysql stop cancelled.")); + if (!confirmAnswers.confirmStop) { + this.error(chalk.yellow("MySQL stop cancelled.")); + } } - const response = await axios.post( - `${auth.url}/api/trpc/mysql.stop`, - { - json: { - mysqlId, + try { + const response = await axios.post( + `${auth.url}/api/trpc/mysql.stop`, + { + json: { + mysqlId, + }, }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - }, - ); + ); - if (response.status !== 200) { - this.error(chalk.red("Error stopping mysql")); + if (response.status !== 200) { + this.error(chalk.red("Error stopping MySQL instance")); + } + this.log(chalk.green("MySQL instance stopped successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error stopping MySQL instance: ${error.message}`)); } - this.log(chalk.green("Mysql stop successful.")); } } diff --git a/src/commands/database/postgres/create.ts b/src/commands/database/postgres/create.ts index 6b8514f..e692afa 100644 --- a/src/commands/database/postgres/create.ts +++ b/src/commands/database/postgres/create.ts @@ -17,114 +17,198 @@ export default class DatabasePostgresCreate extends Command { description: "ID of the project", required: false, }), + name: Flags.string({ + char: "n", + description: "Database name", + required: false, + }), + databaseName: Flags.string({ + description: "PostgreSQL database name", + required: false, + }), + description: Flags.string({ + char: "d", + description: "Database description", + required: false, + }), + databasePassword: Flags.string({ + description: "Database password", + required: false, + }), + databaseUser: Flags.string({ + description: "Database user", + default: "postgres", + }), + dockerImage: Flags.string({ + description: "Docker image", + default: "postgres:15", + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + appName: Flags.string({ + description: "App name", + required: false, + }), }; public async run(): Promise { const auth = await readAuthConfig(this); - const { flags } = await this.parse(DatabasePostgresCreate); + let { + projectId, + name, + databaseName, + description, + databasePassword, + databaseUser, + dockerImage, + appName + } = flags; - let { projectId } = flags; - - if (!projectId) { + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !name || !databaseName || !appName || !databasePassword) { console.log(chalk.blue.bold("\n Listing all Projects \n")); - const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to create the PostgreSQL database in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to create the PostgreSQL instance in:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - projectId = project.projectId; + if (!name || !databaseName || !appName || !databasePassword) { + const dbDetails = await inquirer.prompt([ + { + message: "Enter the name:", + name: "name", + type: "input", + validate: (input) => (input ? true : "Database name is required"), + default: name, + }, + { + message: "Database name:", + name: "databaseName", + type: "input", + validate: (input) => (input ? true : "Database name is required"), + default: databaseName, + }, + { + message: "Enter the database description (optional):", + name: "description", + type: "input", + default: description, + }, + { + message: "Database password:", + name: "databasePassword", + type: "password", + default: databasePassword, + }, + { + default: dockerImage || "postgres:15", + message: "Docker Image (default: postgres:15):", + name: "dockerImage", + type: "input", + }, + { + default: databaseUser || "postgres", + message: "Database User: (default: postgres):", + name: "databaseUser", + type: "input", + }, + ]); - const dbDetails = await inquirer.prompt([ - { - message: "Enter the name:", - name: "name", - type: "input", - validate: (input) => (input ? true : "Database name is required"), - }, - { - message: "Database name:", - name: "databaseName", - type: "input", - validate: (input) => (input ? true : "Database name is required"), - }, - { - message: "Enter the database description (optional):", - name: "description", - type: "input", - }, - { - message: "Database password (optional):", - name: "databasePassword", - type: "password", - }, - { - default: "postgres:15", - message: "Docker Image (default: postgres:15):", - name: "dockerImage", - type: "input", - }, - { - default: "postgres", - message: "Database User: (default: postgres):", - name: "databaseUser", - type: "input", - }, - ]); + name = dbDetails.name; + databaseName = dbDetails.databaseName; + description = dbDetails.description; + databasePassword = dbDetails.databasePassword; + dockerImage = dbDetails.dockerImage; + databaseUser = dbDetails.databaseUser; + + const appNamePrompt = await inquirer.prompt([ + { + default: appName || `${slugify(name)}`, + message: "Enter the App name:", + name: "appName", + type: "input", + validate: (input) => (input ? true : "App name is required"), + }, + ]); + + appName = appNamePrompt.appName; + } + } - const appName = await inquirer.prompt([ + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirm = await inquirer.prompt([ { - default: `${slugify(project.name)}-${dbDetails.name}`, - message: "Enter the App name:", - name: "appName", - type: "input", - validate: (input) => (input ? true : "App name is required"), + type: 'confirm', + name: 'proceed', + message: 'Do you want to create this PostgreSQL instance?', + default: false, }, ]); - try { - const response = await axios.post( - `${auth.url}/api/trpc/postgres.create`, - { - json: { - ...dbDetails, - appName: appName.appName, - projectId: project.projectId, - }, + if (!confirm.proceed) { + this.error(chalk.yellow("PostgreSQL creation cancelled.")); + return; + } + } + + try { + console.log(JSON.stringify({ + name, + databaseName, + description, + databasePassword, + databaseUser, + dockerImage, + appName, + projectId, + }, null, 2)); + + const response = await axios.post( + `${auth.url}/api/trpc/postgres.create`, + { + json: { + name, + databaseName, + description, + databasePassword, + databaseUser, + dockerImage, + appName, + projectId, }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", - }, + }, + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", }, - ); - - if (!response.data.result.data.json) { - this.error(chalk.red("Error creating PostgreSQL database")); - } + }, + ); - this.log( - chalk.green( - `PostgreSQL database '${dbDetails.name}' created successfully.`, - ), - ); - } catch (error) { - this.error( - // @ts-ignore - chalk.red(`Failed to create PostgreSQL database: ${error.message}`), - ); + if (!response.data.result.data.json) { + this.error(chalk.red("Error creating PostgreSQL instance", response.data.result.data.json)); } + + this.log(chalk.green(`PostgreSQL instance '${name}' created successfully.`)); + } catch (error: any) { + this.error(chalk.red(`Error creating PostgreSQL instance: ${error.message}`)); } } } diff --git a/src/commands/database/postgres/delete.ts b/src/commands/database/postgres/delete.ts index e91298a..b26dace 100644 --- a/src/commands/database/postgres/delete.ts +++ b/src/commands/database/postgres/delete.ts @@ -20,78 +20,84 @@ export default class DatabasePostgresDelete extends Command { description: "ID of the project", required: false, }), + postgresId: Flags.string({ + char: "d", + description: "ID of the PostgreSQL database", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation", + required: false, + }), }; public async run(): Promise { const auth = await readAuthConfig(this); - const { flags } = await this.parse(DatabasePostgresDelete); - let { projectId } = flags; + let { projectId, postgresId } = flags; - if (!projectId) { + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !postgresId) { console.log(chalk.blue.bold("\n Listing all Projects \n")); - const projects = await getProjects(auth, this); - if (projects.length === 0) { - this.log(chalk.yellow("No projects found.")); - return; + if (!projectId) { + const answers = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project.projectId, + })), + message: "Select a project to delete the PostgreSQL instance from:", + name: "selectedProject", + type: "list", + }, + ]); + projectId = answers.selectedProject; } - const answers = await inquirer.prompt([ - { - choices: projects.map((project: any) => ({ - name: project.name, - value: project.projectId, - })), - message: "Select a project to delete the PostgreSQL database from:", - name: "selectedProject", - type: "list", - }, - ]); - - projectId = answers.selectedProject; - } - - try { - const project = await getProject(projectId, auth, this); + const projectSelected = await getProject(projectId, auth, this); - if (!project.postgres || project.postgres.length === 0) { - this.log( - chalk.yellow("No PostgreSQL databases found in this project."), - ); - return; + if (!projectSelected.postgres || projectSelected.postgres.length === 0) { + this.error(chalk.yellow("No PostgreSQL instances found in this project.")); } - const appAnswers = await inquirer.prompt([ - { - choices: project.postgres.map((db: any) => ({ - name: db.name, - value: db.postgresId, - })), - message: "Select the PostgreSQL database to delete:", - name: "selectedApp", - type: "list", - }, - ]); - - const postgresId = appAnswers.selectedApp; + if (!postgresId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.postgres.map((db) => ({ + name: db.name, + value: db.postgresId, + })), + message: "Select the PostgreSQL instance to delete:", + name: "selectedDb", + type: "list", + }, + ]); + postgresId = dbAnswers.selectedDb; + } + } + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { const confirmAnswers = await inquirer.prompt([ { default: false, - message: "Are you sure you want to delete this postgres database?", + message: "Are you sure you want to delete this PostgreSQL instance?", name: "confirmDelete", type: "confirm", }, ]); if (!confirmAnswers.confirmDelete) { - this.log(chalk.yellow("Database deletion cancelled.")); - return; + this.error(chalk.yellow("PostgreSQL deletion cancelled.")); } + } - const deleteResponse = await axios.post( + try { + const response = await axios.post( `${auth.url}/api/trpc/postgres.remove`, { json: { @@ -106,14 +112,12 @@ export default class DatabasePostgresDelete extends Command { }, ); - if (!deleteResponse.data.result.data.json) { - this.error(chalk.red("Error deleting PostgreSQL database")); + if (!response.data.result.data.json) { + this.error(chalk.red("Error deleting PostgreSQL instance")); } - - this.log(chalk.green("PostgreSQL database deleted successfully.")); - } catch (error) { - // @ts-expect-error - TS2339: Property 'data' does not exist on type 'AxiosError'. - this.error(chalk.red(`Failed to delete application: ${error.message}`)); + this.log(chalk.green("PostgreSQL instance deleted successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error deleting PostgreSQL instance: ${error.message}`)); } } } diff --git a/src/commands/database/postgres/deploy.ts b/src/commands/database/postgres/deploy.ts index 36675c1..e782ec9 100644 --- a/src/commands/database/postgres/deploy.ts +++ b/src/commands/database/postgres/deploy.ts @@ -1,4 +1,4 @@ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; import { readAuthConfig } from "../../../utils/utils.js"; import chalk from "chalk"; import { getProject, getProjects } from "../../../utils/shared.js"; @@ -7,83 +7,114 @@ import type { Answers } from "../../app/create.js"; import axios from "axios"; export default class DatabasePostgresDeploy extends Command { - static description = "Deploy an postgres to a project."; + static description = "Deploy a PostgreSQL instance to a project."; - static examples = ["$ <%= config.bin %> app deploy"]; + static examples = ["$ <%= config.bin %> postgres deploy"]; + + static flags = { + projectId: Flags.string({ + char: "p", + description: "ID of the project", + required: false, + }), + postgresId: Flags.string({ + char: "d", + description: "ID of the PostgreSQL instance to deploy", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + }; public async run(): Promise { const auth = await readAuthConfig(this); + const { flags } = await this.parse(DatabasePostgresDeploy); + let { projectId, postgresId } = flags; - console.log(chalk.blue.bold("\n Listing all Projects \n")); - - const projects = await getProjects(auth, this); + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !postgresId) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to deploy the postgres in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to deploy the PostgreSQL instance from:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - const projectId = project.projectId; + const projectSelected = await getProject(projectId, auth, this); - const projectSelected = await getProject(projectId, auth, this); + if (projectSelected.postgres.length === 0) { + this.error(chalk.yellow("No PostgreSQL instances found in this project.")); + } - if (projectSelected.postgres.length === 0) { - this.error(chalk.yellow("No postgres found in this project.")); + if (!postgresId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.postgres.map((db) => ({ + name: db.name, + value: db.postgresId, + })), + message: "Select the PostgreSQL instance to deploy:", + name: "selectedDb", + type: "list", + }, + ]); + postgresId = dbAnswers.selectedDb; + } } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.postgres.map((app) => ({ - name: app.name, - value: app.postgresId, - })), - message: "Select the postgres to deploy:", - name: "selectedApp", - type: "list", - }, - ]); - - const postgresId = appAnswers.selectedApp; - - const confirmAnswers = await inquirer.prompt([ - { - default: false, - message: "Are you sure you want to deploy this postgres?", - name: "confirmDelete", - type: "confirm", - }, - ]); + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirmAnswers = await inquirer.prompt([ + { + default: false, + message: "Are you sure you want to deploy this PostgreSQL instance?", + name: "confirmDeploy", + type: "confirm", + }, + ]); - if (!confirmAnswers.confirmDelete) { - this.error(chalk.yellow("postgres deployment cancelled.")); + if (!confirmAnswers.confirmDeploy) { + this.error(chalk.yellow("PostgreSQL deployment cancelled.")); + } } - const response = await axios.post( - `${auth.url}/api/trpc/postgres.deploy`, - { - json: { - postgresId, + try { + const response = await axios.post( + `${auth.url}/api/trpc/postgres.deploy`, + { + json: { + postgresId, + }, }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - }, - ); + ); - if (response.status !== 200) { - this.error(chalk.red("Error deploying postgres")); + if (response.status !== 200) { + this.error(chalk.red("Error deploying PostgreSQL instance")); + } + this.log(chalk.green("PostgreSQL instance deployed successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error deploying PostgreSQL instance: ${error.message}`)); } - this.log(chalk.green("Postgres deployed successful.")); } } diff --git a/src/commands/database/postgres/stop.ts b/src/commands/database/postgres/stop.ts index 1fcfc29..da30323 100644 --- a/src/commands/database/postgres/stop.ts +++ b/src/commands/database/postgres/stop.ts @@ -1,89 +1,120 @@ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; +import { readAuthConfig } from "../../../utils/utils.js"; import chalk from "chalk"; -import inquirer from "inquirer"; -import axios from "axios"; import { getProject, getProjects } from "../../../utils/shared.js"; -import { readAuthConfig } from "../../../utils/utils.js"; +import inquirer from "inquirer"; import type { Answers } from "../../app/create.js"; +import axios from "axios"; export default class DatabasePostgresStop extends Command { - static description = "Stop an postgres from a project."; + static description = "Stop a PostgreSQL instance in a project."; static examples = ["$ <%= config.bin %> postgres stop"]; + static flags = { + projectId: Flags.string({ + char: "p", + description: "ID of the project", + required: false, + }), + postgresId: Flags.string({ + char: "d", + description: "ID of the PostgreSQL instance to stop", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + }; + public async run(): Promise { const auth = await readAuthConfig(this); + const { flags } = await this.parse(DatabasePostgresStop); + let { projectId, postgresId } = flags; - console.log(chalk.blue.bold("\n Listing all Projects \n")); - - const projects = await getProjects(auth, this); + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !postgresId) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to stop the postgres in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to stop the PostgreSQL instance from:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - const projectId = project.projectId; + const projectSelected = await getProject(projectId, auth, this); - const projectSelected = await getProject(projectId, auth, this); + if (projectSelected.postgres.length === 0) { + this.error(chalk.yellow("No PostgreSQL instances found in this project.")); + } - if (projectSelected.postgres.length === 0) { - this.error(chalk.yellow("No postgres found in this project.")); + if (!postgresId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.postgres.map((db) => ({ + name: db.name, + value: db.postgresId, + })), + message: "Select the PostgreSQL instance to stop:", + name: "selectedDb", + type: "list", + }, + ]); + postgresId = dbAnswers.selectedDb; + } } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.postgres.map((app) => ({ - name: app.name, - value: app.postgresId, - })), - message: "Select the postgres to stop:", - name: "selectedApp", - type: "list", - }, - ]); - - const postgresId = appAnswers.selectedApp; - - const confirmAnswers = await inquirer.prompt([ - { - default: false, - message: "Are you sure you want to stop this postgres?", - name: "confirmDelete", - type: "confirm", - }, - ]); + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirmAnswers = await inquirer.prompt([ + { + default: false, + message: "Are you sure you want to stop this PostgreSQL instance?", + name: "confirmStop", + type: "confirm", + }, + ]); - if (!confirmAnswers.confirmDelete) { - this.error(chalk.yellow("postgres stop cancelled.")); + if (!confirmAnswers.confirmStop) { + this.error(chalk.yellow("PostgreSQL stop cancelled.")); + } } - const response = await axios.post( - `${auth.url}/api/trpc/postgres.stop`, - { - json: { - postgresId, + try { + const response = await axios.post( + `${auth.url}/api/trpc/postgres.stop`, + { + json: { + postgresId, + }, }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - }, - ); + ); - if (response.status !== 200) { - this.error(chalk.red("Error stopping postgres")); + if (response.status !== 200) { + this.error(chalk.red("Error stopping PostgreSQL instance")); + } + this.log(chalk.green("PostgreSQL instance stopped successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error stopping PostgreSQL instance: ${error.message}`)); } - this.log(chalk.green("Postgres stop successful.")); } } diff --git a/src/commands/database/redis/create.ts b/src/commands/database/redis/create.ts index 6a419e5..58c06a7 100644 --- a/src/commands/database/redis/create.ts +++ b/src/commands/database/redis/create.ts @@ -8,7 +8,7 @@ import { getProjects } from "../../../utils/shared.js"; import type { Answers } from "../../app/create.js"; export default class DatabaseRedisCreate extends Command { - static description = "Create a new Redis database within a project."; + static description = "Create a new Redis instance within a project."; static examples = ["$ <%= config.bin %> redis create"]; @@ -18,102 +18,169 @@ export default class DatabaseRedisCreate extends Command { description: "ID of the project", required: false, }), + name: Flags.string({ + char: "n", + description: "Instance name", + required: false, + }), + description: Flags.string({ + char: "d", + description: "Instance description", + required: false, + }), + databasePassword: Flags.string({ + description: "Redis password", + required: false, + }), + dockerImage: Flags.string({ + description: "Docker image", + default: "redis:7", + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + appName: Flags.string({ + description: "App name", + required: false, + }), }; public async run(): Promise { const auth = await readAuthConfig(this); - const { flags } = await this.parse(DatabaseRedisCreate); + let { + projectId, + name, + description, + databasePassword, + dockerImage, + appName + } = flags; - let { projectId } = flags; - - if (!projectId) { + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !name || !appName || !databasePassword) { console.log(chalk.blue.bold("\n Listing all Projects \n")); - const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to create the Redis instance in:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } + + if (!name || !appName || !databasePassword) { + const redisDetails = await inquirer.prompt([ + { + message: "Enter the name:", + name: "name", + type: "input", + validate: (input) => (input ? true : "Instance name is required"), + default: name, + }, + { + message: "Enter the instance description (optional):", + name: "description", + type: "input", + default: description, + }, + { + message: "Redis password:", + name: "databasePassword", + type: "password", + default: databasePassword, + }, + { + default: dockerImage || "redis:7", + message: "Docker Image (default: redis:7):", + name: "dockerImage", + type: "input", + }, + ]); + + name = redisDetails.name; + description = redisDetails.description; + databasePassword = redisDetails.databasePassword; + dockerImage = redisDetails.dockerImage; + + const appNamePrompt = await inquirer.prompt([ + { + default: appName || `${slugify(name)}`, + message: "Enter the App name:", + name: "appName", + type: "input", + validate: (input) => (input ? true : "App name is required"), + }, + ]); + + appName = appNamePrompt.appName; + } + } + + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirm = await inquirer.prompt([ { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to create the Redis database in:", - name: "project", - type: "list", + type: 'confirm', + name: 'proceed', + message: 'Do you want to create this Redis instance?', + default: false, }, ]); - projectId = project.projectId; + if (!confirm.proceed) { + this.error(chalk.yellow("Redis creation cancelled.")); + return; + } + } - const dbDetails = await inquirer.prompt([ - { - message: "Enter the name:", - name: "name", - type: "input", - validate: (input) => (input ? true : "Database name is required"), - }, - { - message: "Enter the database description (optional):", - name: "description", - type: "input", - }, + try { + console.log(JSON.stringify({ + name, + description, + databasePassword, + dockerImage, + appName, + projectId, + }, null, 2)); + + const response = await axios.post( + `${auth.url}/api/trpc/redis.create`, { - message: "Database password (optional):", - name: "databasePassword", - type: "password", + json: { + name, + description, + databasePassword, + dockerImage, + appName, + projectId, + }, }, { - default: "redis:7", - message: "Docker Image (default: redis:7):", - name: "dockerImage", - type: "input", + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - ]); + ); - const appName = await inquirer.prompt([ - { - default: `${slugify(project.name)}-${dbDetails.name}`, - message: "Enter the App name:", - name: "appName", - type: "input", - validate: (input) => (input ? true : "App name is required"), - }, - ]); - - try { - const response = await axios.post( - `${auth.url}/api/trpc/redis.create`, - { - json: { - ...dbDetails, - appName: appName.appName, - projectId: project.projectId, - }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", - }, - }, - ); - - if (!response.data.result.data.json) { - this.error(chalk.red("Error creating Redis database")); - } - - this.log( - chalk.green( - `Redis database '${dbDetails.name}' created successfully.`, - ), - ); - } catch (error) { - this.error( - // @ts-ignore - chalk.red(`Failed to create Redis database: ${error.message}`), - ); + if (!response.data.result.data.json) { + this.error(chalk.red("Error creating Redis instance", response.data.result.data.json)); } + + this.log(chalk.green(`Redis instance '${name}' created successfully.`)); + } catch (error: any) { + this.error(chalk.red(`Error creating Redis instance: ${error.message}`)); } } } diff --git a/src/commands/database/redis/delete.ts b/src/commands/database/redis/delete.ts index e47c5f1..06d6b6d 100644 --- a/src/commands/database/redis/delete.ts +++ b/src/commands/database/redis/delete.ts @@ -1,13 +1,13 @@ import { Command, Flags } from "@oclif/core"; -import axios from "axios"; -import chalk from "chalk"; -import inquirer from "inquirer"; - import { readAuthConfig } from "../../../utils/utils.js"; +import chalk from "chalk"; import { getProject, getProjects } from "../../../utils/shared.js"; +import inquirer from "inquirer"; +import type { Answers } from "../../app/create.js"; +import axios from "axios"; export default class DatabaseRedisDelete extends Command { - static description = "Delete an redis database from a project."; + static description = "Delete a Redis instance from a project."; static examples = [ "$ <%= config.bin %> redis delete", @@ -20,77 +20,84 @@ export default class DatabaseRedisDelete extends Command { description: "ID of the project", required: false, }), + redisId: Flags.string({ + char: "r", + description: "ID of the Redis instance to delete", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), }; public async run(): Promise { const auth = await readAuthConfig(this); - const { flags } = await this.parse(DatabaseRedisDelete); - let { projectId } = flags; + let { projectId, redisId } = flags; - if (!projectId) { + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !redisId) { console.log(chalk.blue.bold("\n Listing all Projects \n")); - const projects = await getProjects(auth, this); - if (projects.length === 0) { - this.log(chalk.yellow("No projects found.")); - return; + if (!projectId) { + const answers = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project.projectId, + })), + message: "Select a project to delete the Redis instance from:", + name: "selectedProject", + type: "list", + }, + ]); + projectId = answers.selectedProject; } - const answers = await inquirer.prompt([ - { - choices: projects.map((project: any) => ({ - name: project.name, - value: project.projectId, - })), - message: "Select a project to delete the redis database from:", - name: "selectedProject", - type: "list", - }, - ]); + const projectSelected = await getProject(projectId, auth, this); - projectId = answers.selectedProject; - } - - try { - const project = await getProject(projectId, auth, this); - - if (!project.redis || project.redis.length === 0) { - this.log(chalk.yellow("No redis databases found in this project.")); - return; + if (!projectSelected.redis || projectSelected.redis.length === 0) { + this.error(chalk.yellow("No Redis instances found in this project.")); } - // Permitir al usuario seleccionar una aplicación - const appAnswers = await inquirer.prompt([ - { - choices: project.redis.map((db: any) => ({ - name: db.name, - value: db.redisId, - })), - message: "Select the redis database to delete:", - name: "selectedApp", - type: "list", - }, - ]); - - const redisId = appAnswers.selectedApp; + if (!redisId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.redis.map((db) => ({ + name: db.name, + value: db.redisId, + })), + message: "Select the Redis instance to delete:", + name: "selectedDb", + type: "list", + }, + ]); + redisId = dbAnswers.selectedDb; + } + } + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { const confirmAnswers = await inquirer.prompt([ { default: false, - message: "Are you sure you want to delete this redis database?", + message: "Are you sure you want to delete this Redis instance?", name: "confirmDelete", type: "confirm", }, ]); if (!confirmAnswers.confirmDelete) { - this.log(chalk.yellow("Database deletion cancelled.")); - return; + this.error(chalk.yellow("Redis deletion cancelled.")); } + } - const deleteResponse = await axios.post( + try { + const response = await axios.post( `${auth.url}/api/trpc/redis.remove`, { json: { @@ -105,16 +112,12 @@ export default class DatabaseRedisDelete extends Command { }, ); - if (!deleteResponse.data.result.data.json) { - this.error(chalk.red("Error deleting redis database")); + if (!response.data.result.data.json) { + this.error(chalk.red("Error deleting Redis instance")); } - - this.log(chalk.green("Redis database deleted successfully.")); - } catch (error) { - this.error( - // @ts-expect-error - TS2339: Property 'data' does not exist on type 'AxiosError'. - chalk.red(`Failed to delete redis database: ${error.message}`), - ); + this.log(chalk.green("Redis instance deleted successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error deleting Redis instance: ${error.message}`)); } } } diff --git a/src/commands/database/redis/deploy.ts b/src/commands/database/redis/deploy.ts index fdda563..96e748a 100644 --- a/src/commands/database/redis/deploy.ts +++ b/src/commands/database/redis/deploy.ts @@ -1,4 +1,4 @@ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; import { readAuthConfig } from "../../../utils/utils.js"; import chalk from "chalk"; import { getProject, getProjects } from "../../../utils/shared.js"; @@ -7,83 +7,114 @@ import type { Answers } from "../../app/create.js"; import axios from "axios"; export default class DatabaseRedisDeploy extends Command { - static description = "Deploy an redis to a project."; + static description = "Deploy a Redis instance to a project."; - static examples = ["$ <%= config.bin %> app deploy"]; + static examples = ["$ <%= config.bin %> redis deploy"]; + + static flags = { + projectId: Flags.string({ + char: "p", + description: "ID of the project", + required: false, + }), + redisId: Flags.string({ + char: "r", + description: "ID of the Redis instance to deploy", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + }; public async run(): Promise { const auth = await readAuthConfig(this); + const { flags } = await this.parse(DatabaseRedisDeploy); + let { projectId, redisId } = flags; - console.log(chalk.blue.bold("\n Listing all Projects \n")); - - const projects = await getProjects(auth, this); + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !redisId) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to deploy the redis in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to deploy the Redis instance from:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - const projectId = project.projectId; + const projectSelected = await getProject(projectId, auth, this); - const projectSelected = await getProject(projectId, auth, this); + if (projectSelected.redis.length === 0) { + this.error(chalk.yellow("No Redis instances found in this project.")); + } - if (projectSelected.redis.length === 0) { - this.error(chalk.yellow("No redis found in this project.")); + if (!redisId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.redis.map((db) => ({ + name: db.name, + value: db.redisId, + })), + message: "Select the Redis instance to deploy:", + name: "selectedDb", + type: "list", + }, + ]); + redisId = dbAnswers.selectedDb; + } } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.redis.map((app) => ({ - name: app.name, - value: app.redisId, - })), - message: "Select the redis to deploy:", - name: "selectedApp", - type: "list", - }, - ]); - - const redisId = appAnswers.selectedApp; - - const confirmAnswers = await inquirer.prompt([ - { - default: false, - message: "Are you sure you want to deploy this redis?", - name: "confirmDelete", - type: "confirm", - }, - ]); + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirmAnswers = await inquirer.prompt([ + { + default: false, + message: "Are you sure you want to deploy this Redis instance?", + name: "confirmDeploy", + type: "confirm", + }, + ]); - if (!confirmAnswers.confirmDelete) { - this.error(chalk.yellow("redis deployment cancelled.")); + if (!confirmAnswers.confirmDeploy) { + this.error(chalk.yellow("Redis deployment cancelled.")); + } } - const response = await axios.post( - `${auth.url}/api/trpc/redis.deploy`, - { - json: { - redisId, + try { + const response = await axios.post( + `${auth.url}/api/trpc/redis.deploy`, + { + json: { + redisId, + }, }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - }, - ); + ); - if (response.status !== 200) { - this.error(chalk.red("Error deploying redis")); + if (response.status !== 200) { + this.error(chalk.red("Error deploying Redis instance")); + } + this.log(chalk.green("Redis instance deployed successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error deploying Redis instance: ${error.message}`)); } - this.log(chalk.green("Redis deployed successful.")); } } diff --git a/src/commands/database/redis/stop.ts b/src/commands/database/redis/stop.ts index 4e0dfb3..cf86704 100644 --- a/src/commands/database/redis/stop.ts +++ b/src/commands/database/redis/stop.ts @@ -1,89 +1,120 @@ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; +import { readAuthConfig } from "../../../utils/utils.js"; import chalk from "chalk"; -import inquirer from "inquirer"; -import axios from "axios"; import { getProject, getProjects } from "../../../utils/shared.js"; -import { readAuthConfig } from "../../../utils/utils.js"; +import inquirer from "inquirer"; import type { Answers } from "../../app/create.js"; +import axios from "axios"; export default class DatabaseRedisStop extends Command { - static description = "Stop an redis from a project."; + static description = "Stop a Redis instance in a project."; static examples = ["$ <%= config.bin %> redis stop"]; + static flags = { + projectId: Flags.string({ + char: "p", + description: "ID of the project", + required: false, + }), + redisId: Flags.string({ + char: "r", + description: "ID of the Redis instance to stop", + required: false, + }), + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, + }), + }; + public async run(): Promise { const auth = await readAuthConfig(this); + const { flags } = await this.parse(DatabaseRedisStop); + let { projectId, redisId } = flags; - console.log(chalk.blue.bold("\n Listing all Projects \n")); - - const projects = await getProjects(auth, this); + // Modo interactivo si no se proporcionan los flags necesarios + if (!projectId || !redisId) { + console.log(chalk.blue.bold("\n Listing all Projects \n")); + const projects = await getProjects(auth, this); - const { project } = await inquirer.prompt([ - { - choices: projects.map((project) => ({ - name: project.name, - value: project, - })), - message: "Select a project to stop the redis in:", - name: "project", - type: "list", - }, - ]); + if (!projectId) { + const { project } = await inquirer.prompt([ + { + choices: projects.map((project) => ({ + name: project.name, + value: project, + })), + message: "Select a project to stop the Redis instance from:", + name: "project", + type: "list", + }, + ]); + projectId = project.projectId; + } - const projectId = project.projectId; + const projectSelected = await getProject(projectId, auth, this); - const projectSelected = await getProject(projectId, auth, this); + if (projectSelected.redis.length === 0) { + this.error(chalk.yellow("No Redis instances found in this project.")); + } - if (projectSelected.redis.length === 0) { - this.error(chalk.yellow("No redis found in this project.")); + if (!redisId) { + const dbAnswers = await inquirer.prompt([ + { + // @ts-ignore + choices: projectSelected.redis.map((db) => ({ + name: db.name, + value: db.redisId, + })), + message: "Select the Redis instance to stop:", + name: "selectedDb", + type: "list", + }, + ]); + redisId = dbAnswers.selectedDb; + } } - const appAnswers = await inquirer.prompt([ - { - // @ts-ignore - choices: projectSelected.redis.map((app) => ({ - name: app.name, - value: app.redisId, - })), - message: "Select the redis to stop:", - name: "selectedApp", - type: "list", - }, - ]); - - const redisId = appAnswers.selectedApp; - - const confirmAnswers = await inquirer.prompt([ - { - default: false, - message: "Are you sure you want to stop this redis?", - name: "confirmDelete", - type: "confirm", - }, - ]); + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirmAnswers = await inquirer.prompt([ + { + default: false, + message: "Are you sure you want to stop this Redis instance?", + name: "confirmStop", + type: "confirm", + }, + ]); - if (!confirmAnswers.confirmDelete) { - this.error(chalk.yellow("redis stop cancelled.")); + if (!confirmAnswers.confirmStop) { + this.error(chalk.yellow("Redis stop cancelled.")); + } } - const response = await axios.post( - `${auth.url}/api/trpc/redis.stop`, - { - json: { - redisId, + try { + const response = await axios.post( + `${auth.url}/api/trpc/redis.stop`, + { + json: { + redisId, + }, }, - }, - { - headers: { - Authorization: `Bearer ${auth.token}`, - "Content-Type": "application/json", + { + headers: { + Authorization: `Bearer ${auth.token}`, + "Content-Type": "application/json", + }, }, - }, - ); + ); - if (response.status !== 200) { - this.error(chalk.red("Error stopping redis")); + if (response.status !== 200) { + this.error(chalk.red("Error stopping Redis instance")); + } + this.log(chalk.green("Redis instance stopped successfully.")); + } catch (error: any) { + this.error(chalk.red(`Error stopping Redis instance: ${error.message}`)); } - this.log(chalk.green("Redis stop successful.")); } } diff --git a/src/commands/project/create.ts b/src/commands/project/create.ts index 1a70414..4b43a88 100644 --- a/src/commands/project/create.ts +++ b/src/commands/project/create.ts @@ -1,76 +1,91 @@ import { Command, Flags } from "@oclif/core"; import axios from "axios"; import chalk from "chalk"; -import inquirer, { type Answers, type QuestionCollection } from "inquirer"; - +import inquirer from "inquirer"; import { readAuthConfig } from "../../utils/utils.js"; export default class ProjectCreate extends Command { - static override description = - "Create a new project with an optional description."; + static description = "Create a new project."; - static override examples = [ - "$ <%= config.bin %> <%= command.id %> -n MyProject -d 'This is my project description'", - "$ <%= config.bin %> <%= command.id %> -n MyProject", - "$ <%= config.bin %> <%= command.id %>", + static examples = [ + "$ <%= config.bin %> project create", + "$ <%= config.bin %> project create -n MyProject -d 'Project description'", + "$ <%= config.bin %> project create --name MyProject --skipConfirm", ]; - static override flags = { + static flags = { + name: Flags.string({ + char: "n", + description: "Name of the project", + required: false, + }), description: Flags.string({ char: "d", description: "Description of the project", required: false, }), - name: Flags.string({ - char: "n", - description: "Name of the project", - required: false, + skipConfirm: Flags.boolean({ + char: "y", + description: "Skip confirmation prompt", + default: false, }), }; public async run(): Promise { const auth = await readAuthConfig(this); - - console.log(chalk.blue.bold("\n Create a New Project \n")); - const { flags } = await this.parse(ProjectCreate); + let { name, description } = flags; - let answers: Answers = {}; - - const questions: QuestionCollection[] = []; + // Modo interactivo si no se proporcionan los flags necesarios + if (!name) { + const answers = await inquirer.prompt([ + { + message: "Enter the project name:", + name: "name", + type: "input", + validate: (input) => (input ? true : "Project name is required"), + }, + { + message: "Enter the project description (optional):", + name: "description", + type: "input", + default: description || "", + }, + ]); - if (!flags.name) { - questions.push({ - message: chalk.green("Enter the project name:"), - name: "name", - type: "input", - validate: (input) => (input ? true : "Project name is required"), - }); + name = answers.name; + description = answers.description; } - if (!flags.description) { - questions.push({ - default: "", - message: chalk.green("Enter the project description (optional):"), - name: "description", - type: "input", - }); - } + // Confirmar si no se especifica --skipConfirm + if (!flags.skipConfirm) { + const confirm = await inquirer.prompt([ + { + type: 'confirm', + name: 'proceed', + message: 'Do you want to create this project?', + default: false, + }, + ]); - if (questions.length > 0) { - answers = await inquirer.prompt(questions); + if (!confirm.proceed) { + this.error(chalk.yellow("Project creation cancelled.")); + return; + } } - const name = flags.name || answers.name; - const description = flags.description || answers.description; - try { + console.log(JSON.stringify({ + name, + description, + }, null, 2)); + const response = await axios.post( `${auth.url}/api/trpc/project.create`, { json: { - description, name, + description, }, }, { @@ -82,13 +97,12 @@ export default class ProjectCreate extends Command { ); if (!response.data.result.data.json) { - this.error(chalk.red("Error`")); + this.error(chalk.red("Error creating project", response.data.result.data.json)); } this.log(chalk.green(`Project '${name}' created successfully.`)); - } catch (error) { - // @ts-expect-error hola - this.error(chalk.red(`Failed to create project: ${error.message}`)); + } catch (error: any) { + this.error(chalk.red(`Error creating project: ${error.message}`)); } } } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index c5db541..62661eb 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -15,10 +15,19 @@ export type AuthConfig = { }; export const readAuthConfig = async (command: Command): Promise => { + // Primero intentar leer desde variables de entorno + const envToken = process.env.DOKPLOY_AUTH_TOKEN; + const envUrl = process.env.DOKPLOY_URL; + + if (envToken && envUrl) { + return { token: envToken, url: envUrl }; + } + + // Si no hay variables de entorno, usar el archivo de configuración if (!fs.existsSync(configPath)) { command.error( chalk.red( - "No configuration file found. Please authenticate first using the 'authenticate' command.", + "No configuration file found and no environment variables set. Please authenticate first using the 'authenticate' command or set DOKPLOY_URL and DOKPLOY_AUTH_TOKEN environment variables.", ), ); } @@ -30,7 +39,7 @@ export const readAuthConfig = async (command: Command): Promise => { if (!url || !token) { command.error( chalk.red( - "Incomplete authentication details. Please authenticate again using the 'authenticate' command.", + "Incomplete authentication details. Please authenticate again using the 'authenticate' command or set environment variables.", ), ); } From 6d186d550e51beaea00835c12a17236bc62ac12b Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Sun, 23 Feb 2025 21:44:04 -0600 Subject: [PATCH 2/2] feat: improve token verification with environment variable support - Add support for DOKPLOY_AUTH_TOKEN and DOKPLOY_URL environment variables - Enhance error messaging for authentication configuration - Improve token validation process with more robust error handling - Update version to v0.2.6 --- package.json | 2 +- src/commands/verify.ts | 57 +++++++++++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 71cc2a3..396cd04 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@dokploy/cli", "description": "A CLI to manage dokploy server remotely", - "version": "v0.2.4", + "version": "v0.2.6", "author": "Mauricio Siu", "licenses": [{ "type": "MIT", diff --git a/src/commands/verify.ts b/src/commands/verify.ts index d77da35..352d06a 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -17,28 +17,46 @@ export default class Verify extends Command { async run() { console.log(chalk.blue.bold("\nVerifying Authentication Token")); - if (!fs.existsSync(configPath)) { - this.error( - chalk.red( - "No configuration file found. Please authenticate first using `authenticate` command.", - ), - ); - } + let token: string; + let url: string; - const configFileContent = fs.readFileSync(configPath, "utf8"); - const config = JSON.parse(configFileContent); - const { token, url } = config; + // Verificar variables de entorno primero + const envToken = process.env.DOKPLOY_AUTH_TOKEN; + const envUrl = process.env.DOKPLOY_URL; - if (!url || !token) { - this.error( - chalk.red( - "Incomplete authentication details. Please authenticate again using `authenticate` command.", - ), - ); + if (envToken && envUrl) { + token = envToken; + url = envUrl; + this.log(chalk.green("Using environment variables for authentication")); + } else { + // Si no hay variables de entorno, verificar archivo de configuración + if (!fs.existsSync(configPath)) { + this.error( + chalk.red( + "No configuration found. Please either:\n" + + "1. Authenticate using `authenticate` command\n" + + "2. Set DOKPLOY_URL and DOKPLOY_AUTH_TOKEN environment variables", + ), + ); + } + + try { + const config = JSON.parse(fs.readFileSync(configPath, "utf8")); + token = config.token; + url = config.url; + this.log(chalk.green("Using configuration file for authentication")); + } catch (error) { + this.error( + chalk.red( + "Invalid configuration file. Please authenticate again using `authenticate` command.", + ), + ); + } } + // Validar el token contra el servidor try { - console.log(`\n${chalk.blue("Validating token...")}`); + console.log(chalk.blue("Validating token with server...")); const response = await axios.post( `${url}/api/trpc/auth.verifyToken`, @@ -52,7 +70,7 @@ export default class Verify extends Command { ); if (response.data.result.data.json) { - this.log(chalk.green("Token is valid.")); + this.log(chalk.green("\n✓ Token is valid")); } else { this.error( chalk.red( @@ -60,10 +78,9 @@ export default class Verify extends Command { ), ); } - } catch (error) { + } catch (error: any) { this.error( chalk.red( - // @ts-ignore `Failed to verify token: ${error.message}. Please authenticate again using 'authenticate' command.`, ), );