refactor: migrate to biome and update CLI structure

- Removed ESLint configuration and ignore files in favor of Biome for linting.
- Introduced biome.json for configuration and updated package.json to reflect new dependencies.
- Added OpenAPI specification for Dokploy API and generated CLI commands from it.
- Refactored CLI entry point to use Commander instead of Oclif.
- Implemented new authentication command and removed deprecated commands.
- Updated TypeScript configuration and added build scripts for improved development workflow.
- Cleaned up unused files and commands to streamline the codebase.
This commit is contained in:
Mauricio Siu
2026-04-15 18:37:45 -06:00
parent 761f6e795f
commit ac02c614a6
55 changed files with 58685 additions and 10607 deletions

View File

@@ -1 +0,0 @@
/dist

View File

@@ -1,3 +0,0 @@
{
"extends": [ "prettier"]
}

View File

@@ -1,3 +0,0 @@
@echo off
node --loader ts-node/esm --no-warnings=ExperimentalWarning "%~dp0\dev" %*

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env -S node --loader ts-node/esm --no-warnings=ExperimentalWarning
// eslint-disable-next-line n/shebang
import {execute} from '@oclif/core'
await execute({development: true, dir: import.meta.url})

View File

@@ -1,3 +0,0 @@
@echo off
node "%~dp0\run" %*

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env node
import {execute} from '@oclif/core'
await execute({dir: import.meta.url})
import "../dist/index.js";

42
biome.json Normal file
View File

@@ -0,0 +1,42 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": true,
"includes": ["**", "!**/dist", "!node_modules/**", "!src/generated/**"],
"maxSize": 2097152
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"linter": {
"rules": {
"complexity": {
"noUselessCatch": "off"
},
"correctness": {
"noUnusedImports": "error",
"noUnusedFunctionParameters": "error",
"noUnusedVariables": "off"
},
"style": {
"noNonNullAssertion": "off",
"noParameterAssign": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error",
"noUnusedTemplateLiteral": "error",
"useNumberNamespace": "error",
"noInferrableTypes": "error",
"noUselessElse": "error"
},
"suspicious": {
"noExplicitAny": "off",
"noRedeclare": "off"
}
}
}
}

48976
openapi.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,88 +1,54 @@
{
"name": "@dokploy/cli",
"description": "A CLI to manage dokploy server remotely",
"version": "v0.2.8",
"version": "0.3.0",
"author": "Mauricio Siu",
"licenses": [{
"type": "MIT",
"url": "https://github.com/Dokploy/cli/blob/master/LICENSE"
}],
"licenses": [
{
"type": "MIT",
"url": "https://github.com/Dokploy/cli/blob/master/LICENSE"
}
],
"publishConfig": {
"access": "public"
},
"bin": {
"dokploy": "./bin/run.js"
"dokploy": "./dist/index.js"
},
"bugs": "https://github.com/Dokploy/cli/issues",
"dependencies": {
"@oclif/core": "^3",
"@oclif/plugin-help": "^6",
"@oclif/plugin-plugins": "^5",
"axios": "^1.7.2",
"chalk": "^5.3.0",
"cli-table3": "^0.6.5",
"inquirer": "^9.2.23",
"slugify": "^1.6.6",
"superjson": "^2.2.1"
"commander": "^13.1.0"
},
"devDependencies": {
"@oclif/prettier-config": "^0.2.1",
"@oclif/test": "^4",
"@types/chai": "^4",
"@types/inquirer": "9.0.7",
"@types/mocha": "^10",
"@biomejs/biome": "2.1.1",
"@types/node": "^18",
"chai": "^4",
"eslint": "^8",
"eslint-config-oclif": "^5",
"eslint-config-oclif-typescript": "^3",
"eslint-config-prettier": "^9",
"mocha": "^10",
"oclif": "^4",
"shx": "^0.3.3",
"ts-node": "^10",
"tsx": "^4.21.0",
"typescript": "^5"
},
"engines": {
"node": ">=18.0.0"
},
"files": [
"/bin",
"/dist",
"/oclif.manifest.json"
"/dist"
],
"homepage": "https://github.com/Dokploy/cli",
"keywords": [
"oclif"
"dokploy",
"cli"
],
"license": "MIT",
"main": "dist/index.js",
"type": "module",
"oclif": {
"bin": "dokploy",
"dirname": "dokploy",
"commands": "./dist/commands",
"plugins": [
"@oclif/plugin-help",
"@oclif/plugin-plugins"
],
"topicSeparator": " ",
"topics": {
"hello": {
"description": "Say hello to the world and others"
}
}
},
"repository": "Dokploy/cli",
"scripts": {
"build": "shx rm -rf dist && tsc -b",
"lint": "eslint . --ext .ts",
"postpack": "shx rm -f oclif.manifest.json",
"posttest": "pnpm run lint",
"prepack": "oclif manifest && oclif readme",
"test": "mocha --forbid-only \"test/**/*.test.ts\"",
"version": "oclif readme && git add README.md",
"publish" :"npm publish"
"build": "tsc -b",
"generate": "tsx scripts/generate.ts",
"prebuild": "pnpm run generate",
"dev": "tsx src/index.ts",
"lint": "biome check --write .",
"publish": "npm publish"
},
"types": "dist/index.d.ts"
}

5680
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

286
scripts/generate.ts Normal file
View File

@@ -0,0 +1,286 @@
/**
* Generates CLI commands from the Dokploy OpenAPI spec.
*
* Usage: npx tsx scripts/generate.ts
*
* Reads openapi.json from the project root and generates:
* - src/generated/commands.ts (all CLI commands)
*/
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, "..");
const SPEC_PATH = path.join(ROOT, "openapi.json");
const OUT_PATH = path.join(ROOT, "src", "generated", "commands.ts");
interface OpenAPISpec {
paths: Record<string, Record<string, OperationObject>>;
}
interface OperationObject {
operationId?: string;
summary?: string;
description?: string;
tags?: string[];
parameters?: ParameterObject[];
requestBody?: {
content?: {
"application/json"?: {
schema?: SchemaObject;
};
};
};
responses?: Record<string, unknown>;
}
interface ParameterObject {
name: string;
in: string;
required?: boolean;
schema?: SchemaObject;
}
interface SchemaObject {
type?: string;
properties?: Record<string, SchemaProperty>;
required?: string[];
anyOf?: SchemaObject[];
items?: SchemaObject;
enum?: string[];
}
interface SchemaProperty {
type?: string;
anyOf?: SchemaObject[];
enum?: string[];
default?: unknown;
description?: string;
}
interface CommandInfo {
/** e.g. "application.create" */
endpoint: string;
/** e.g. "application" */
group: string;
/** e.g. "create" */
action: string;
method: "get" | "post";
description: string;
options: OptionInfo[];
}
interface OptionInfo {
name: string;
flag: string;
description: string;
required: boolean;
type: "string" | "number" | "boolean";
enumValues?: string[];
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function resolveType(prop: SchemaProperty): "string" | "number" | "boolean" {
const raw = prop.type ?? prop.anyOf?.find((s) => s.type && s.type !== "null")?.type;
if (raw === "number" || raw === "integer") return "number";
if (raw === "boolean") return "boolean";
return "string";
}
function resolveEnum(prop: SchemaProperty): string[] | undefined {
if (prop.enum) return prop.enum;
const inner = prop.anyOf?.find((s) => s.enum);
return inner?.enum;
}
function extractOptionsFromSchema(schema: SchemaObject | undefined): OptionInfo[] {
if (!schema?.properties) return [];
const required = new Set(schema.required ?? []);
return Object.entries(schema.properties).map(([name, prop]) => {
const type = resolveType(prop);
const enumValues = resolveEnum(prop);
let desc = prop.description ?? name;
if (enumValues) desc += ` (${enumValues.join(", ")})`;
return {
name,
flag: `--${name} <${type === "boolean" ? "" : "value"}>`.replace(/ <>/g, ""),
description: desc,
required: required.has(name),
type,
enumValues,
};
});
}
function extractOptionsFromParams(params: ParameterObject[]): OptionInfo[] {
return params.map((p) => {
const type = resolveType(p.schema ?? {});
const enumValues = resolveEnum(p.schema ?? {});
let desc = p.name;
if (enumValues) desc += ` (${enumValues.join(", ")})`;
return {
name: p.name,
flag: `--${p.name} <${type === "boolean" ? "" : "value"}>`.replace(/ <>/g, ""),
description: desc,
required: p.required ?? false,
type,
enumValues,
};
});
}
function camelToKebab(s: string): string {
return s.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
}
// ---------------------------------------------------------------------------
// Parse spec → CommandInfo[]
// ---------------------------------------------------------------------------
function parseSpec(spec: OpenAPISpec): CommandInfo[] {
const commands: CommandInfo[] = [];
for (const [pathKey, methods] of Object.entries(spec.paths)) {
for (const [method, op] of Object.entries(methods)) {
const endpoint = pathKey.replace(/^\//, "");
const [group, ...rest] = endpoint.split(".");
const action = rest.join(".");
if (!group || !action) continue;
const bodySchema = op.requestBody?.content?.["application/json"]?.schema;
const paramOptions = op.parameters ? extractOptionsFromParams(op.parameters) : [];
const bodyOptions = extractOptionsFromSchema(bodySchema);
const options = [...paramOptions, ...bodyOptions];
commands.push({
endpoint,
group,
action,
method: method as "get" | "post",
description: op.summary ?? op.description ?? `${group} ${action}`,
options,
});
}
}
return commands.sort((a, b) => a.endpoint.localeCompare(b.endpoint));
}
// ---------------------------------------------------------------------------
// Code generation
// ---------------------------------------------------------------------------
function generateOptionLine(opt: OptionInfo): string {
const flag = opt.type === "boolean"
? `--${opt.name}`
: `--${opt.name} <value>`;
const escaped = opt.description.replace(/'/g, "\\'");
return opt.required
? `.requiredOption('${flag}', '${escaped}')`
: `.option('${flag}', '${escaped}')`;
}
function generateCoercion(opt: OptionInfo): string {
if (opt.type === "number") {
return `if (opts["${opt.name}"] != null) opts["${opt.name}"] = Number(opts["${opt.name}"]);`;
}
if (opt.type === "boolean") {
return `if (opts["${opt.name}"] != null) opts["${opt.name}"] = opts["${opt.name}"] === true || opts["${opt.name}"] === "true";`;
}
return "";
}
function generateCommandCode(cmd: CommandInfo, groupVar: string): string {
const actionName = camelToKebab(cmd.action);
const optionLines = cmd.options.map(generateOptionLine).join("\n\t\t");
const coercions = cmd.options
.map(generateCoercion)
.filter(Boolean)
.map((c) => `\t\t\t${c}`)
.join("\n");
const apiCall = cmd.method === "post"
? `await apiPost("${cmd.endpoint}", opts)`
: `await apiGet("${cmd.endpoint}", opts)`;
const escapedDesc = cmd.description.replace(/'/g, "\\'");
return `
${groupVar}
.command('${actionName}')
.description('${escapedDesc}')
${optionLines}
.option('--json', 'Output raw JSON')
.action(async (opts: Record<string, any>) => {
const jsonOutput = opts.json; delete opts.json;
${coercions}
const data = ${apiCall};
if (jsonOutput) {
console.log(JSON.stringify(data, null, 2));
} else {
printOutput(data);
}
});`;
}
function generateFile(commands: CommandInfo[]): string {
// Group commands by their group name
const groups = new Map<string, CommandInfo[]>();
for (const cmd of commands) {
const existing = groups.get(cmd.group) ?? [];
existing.push(cmd);
groups.set(cmd.group, existing);
}
const groupBlocks: string[] = [];
for (const [group, cmds] of [...groups.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
const varName = `g_${group.replace(/[^a-zA-Z0-9]/g, "_")}`;
const kebabGroup = camelToKebab(group);
groupBlocks.push(`\tconst ${varName} = program.command('${kebabGroup}').description('${kebabGroup} commands');`);
for (const cmd of cmds) {
groupBlocks.push(generateCommandCode(cmd, varName));
}
}
return `// Auto-generated from openapi.json — do not edit manually.
// Run: npx tsx scripts/generate.ts
import type { Command } from "commander";
import chalk from "chalk";
import { apiPost, apiGet } from "../client.js";
function printOutput(data: unknown) {
if (data === null || data === undefined) {
console.log(chalk.green("OK"));
return;
}
if (typeof data === "string") {
console.log(data);
return;
}
console.log(JSON.stringify(data, null, 2));
}
export function registerGeneratedCommands(program: Command) {
${groupBlocks.join("\n")}
}
`;
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
const spec: OpenAPISpec = JSON.parse(fs.readFileSync(SPEC_PATH, "utf8"));
const commands = parseSpec(spec);
fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true });
fs.writeFileSync(OUT_PATH, generateFile(commands));
console.log(`Generated ${commands.length} commands → ${path.relative(ROOT, OUT_PATH)}`);

76
src/client.ts Normal file
View File

@@ -0,0 +1,76 @@
import axios, { type AxiosInstance } from "axios";
import chalk from "chalk";
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const configPath = path.join(__dirname, "..", "config.json");
export interface AuthConfig {
token: string;
url: string;
}
export function readAuthConfig(): AuthConfig {
const envToken = process.env.DOKPLOY_AUTH_TOKEN;
const envUrl = process.env.DOKPLOY_URL;
if (envToken && envUrl) {
return { token: envToken, url: envUrl };
}
if (!fs.existsSync(configPath)) {
console.error(
chalk.red(
"No configuration found. Please run 'dokploy auth' first or set DOKPLOY_URL and DOKPLOY_AUTH_TOKEN environment variables.",
),
);
process.exit(1);
}
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
const { token, url } = config;
if (!url || !token) {
console.error(
chalk.red(
"Incomplete auth config. Run 'dokploy auth' or set environment variables.",
),
);
process.exit(1);
}
return { token, url };
}
export function saveAuthConfig(url: string, token: string): void {
fs.writeFileSync(configPath, JSON.stringify({ url, token }, null, 2));
}
export function createClient(): AxiosInstance {
const auth = readAuthConfig();
return axios.create({
baseURL: `${auth.url}/api`,
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
});
}
export async function apiPost(endpoint: string, data?: Record<string, unknown>) {
const client = createClient();
const response = await client.post(`/trpc/${endpoint}`, data ? { json: data } : undefined);
return response.data?.result?.data?.json ?? response.data;
}
export async function apiGet(endpoint: string, params?: Record<string, unknown>) {
const client = createClient();
const query = params
? `?input=${encodeURIComponent(JSON.stringify(params))}`
: "";
const response = await client.get(`/trpc/${endpoint}${query}`);
return response.data?.result?.data?.json ?? response.data;
}

View File

@@ -1,182 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { type Project, getProjects } from "../../utils/shared.js";
import { slugify } from "../../utils/slug.js";
import { readAuthConfig } from "../../utils/utils.js";
export interface Answers {
project: Project;
}
export default class AppCreate extends Command {
static description = "Create a new application within a project.";
static examples = ["$ <%= config.bin %> app create"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(AppCreate);
let { projectId, environmentId, name, description, appName } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !name || !appName) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to create the application in:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
environmentId = environment.environmentId;
}
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"),
},
]);
appName = appNamePrompt.appName;
}
}
// 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 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: {
name,
appDescription: description,
appName,
projectId,
environmentId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error creating application"));
}
this.log(chalk.green(`Application '${name}' created successfully.`));
} catch (error: any) {
this.error(chalk.red(`Error creating application: ${error.message}`));
}
}
}

View File

@@ -1,158 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { getProject, getProjects, type Application } from "../../utils/shared.js";
import { readAuthConfig } from "../../utils/utils.js";
import type { Answers } from "./create.js";
export default class AppDelete extends Command {
static description = "Delete an application from a project.";
static examples = [
"$ <%= config.bin %> app delete",
"$ <%= config.bin %> app delete -p <projectId>",
];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(AppDelete);
let { projectId, environmentId, applicationId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !applicationId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to delete the application from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar application del environment
if (!applicationId) {
if (!selectedEnvironment?.applications || selectedEnvironment.applications.length === 0) {
this.error(chalk.yellow("No applications found in this environment."));
}
const appAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.applications.map((app: Application) => ({
name: app.name,
value: app.applicationId,
})),
message: "Select the application to delete:",
name: "selectedApp",
type: "list",
},
]);
applicationId = appAnswers.selectedApp;
}
}
// 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 application?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("Application deletion cancelled."));
}
}
try {
const deleteResponse = await axios.post(
`${auth.url}/api/trpc/application.delete`,
{
json: {
applicationId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!deleteResponse.data.result.data.json) {
this.error(chalk.red("Error deleting application"));
}
this.log(chalk.green("Application deleted successfully."));
} catch (error: any) {
this.error(chalk.red(`Failed to delete application: ${error.message}`));
}
}
}

View File

@@ -1,157 +0,0 @@
import { Command, Flags } from "@oclif/core";
import { readAuthConfig } from "../../utils/utils.js";
import chalk from "chalk";
import { getProject, getProjects, type Application } from "../../utils/shared.js";
import inquirer from "inquirer";
import type { Answers } from "./create.js";
import axios from "axios";
export default class AppDeploy extends Command {
static description = "Deploy an application to a project.";
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,
}),
environmentId: Flags.string({
char: 'e',
description: 'ID of the environment',
required: false,
}),
skipConfirm: Flags.boolean({
char: 'y',
description: 'Skip confirmation prompt',
default: false,
})
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(AppDeploy);
let { projectId, applicationId, environmentId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !applicationId || !environmentId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to deploy the application from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar application del environment
if (!applicationId) {
if (!selectedEnvironment?.applications || selectedEnvironment.applications.length === 0) {
this.error(chalk.yellow("No applications found in this environment."));
}
const appAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.applications.map((app: Application) => ({
name: app.name,
value: app.applicationId,
})),
message: "Select the application to deploy:",
name: "selectedApp",
type: "list",
},
]);
applicationId = appAnswers.selectedApp;
}
}
// 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."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/application.deploy`,
{
json: {
applicationId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,153 +0,0 @@
import { Command, Flags } from "@oclif/core";
import { readAuthConfig } from "../../utils/utils.js";
import chalk from "chalk";
import inquirer from "inquirer";
import { getProject, getProjects, type Application } from "../../utils/shared.js";
import type { Answers } from "./create.js";
import axios from "axios";
export default class AppStop extends Command {
static description = "Stop an application from a project.";
static examples = ["$ <%= config.bin %> app stop"];
static flags = {
projectId: Flags.string({
char: 'p',
description: 'ID of the project',
required: false,
}),
environmentId: Flags.string({
char: 'e',
description: 'ID of the environment',
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(AppStop);
let { projectId, environmentId, applicationId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !applicationId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to stop the application from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar application del environment
if (!applicationId) {
if (!selectedEnvironment?.applications || selectedEnvironment.applications.length === 0) {
this.error(chalk.yellow("No applications found in this environment."));
}
const appAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.applications.map((app: Application) => ({
name: app.name,
value: app.applicationId,
})),
message: "Select the application to stop:",
name: "selectedApp",
type: "list",
},
]);
applicationId = appAnswers.selectedApp;
}
}
// 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."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/application.stop`,
{
json: {
applicationId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

32
src/commands/auth.ts Normal file
View File

@@ -0,0 +1,32 @@
import type { Command } from "commander";
import chalk from "chalk";
import axios from "axios";
import { saveAuthConfig } from "../client.js";
export function registerAuthCommand(program: Command) {
program
.command("auth")
.description("Authenticate with your Dokploy server")
.requiredOption("-u, --url <url>", "Server URL (e.g., https://panel.dokploy.com)")
.requiredOption("-t, --token <token>", "API key from your Dokploy dashboard")
.action(async (opts: { url: string; token: string }) => {
const url = opts.url.replace(/\/+$/, "");
console.log(chalk.blue("Validating credentials..."));
try {
await axios.get(`${url}/api/trpc/user.get`, {
headers: {
"x-api-key": opts.token,
"Content-Type": "application/json",
},
});
saveAuthConfig(url, opts.token);
console.log(chalk.green("Authenticated successfully."));
} catch (error: any) {
console.error(chalk.red(`Authentication failed: ${error.message}`));
process.exit(1);
}
});
}

View File

@@ -1,105 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer, { type Answers, type QuestionCollection } from "inquirer";
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const configPath = path.join(__dirname, "..", "..", "config.json");
export default class Authenticate extends Command {
static description = "Authenticate the user by saving server URL and token";
static examples = [
"$ <%= config.bin %> <%= command.id %> --url=https://panel.dokploy.com --token=MRTHGZDGMRZWM43EMZSHGZTTMRTHGZDGONSGMZDTMY",
"$ <%= config.bin %> <%= command.id %> -u https://panel.dokploy.com -t MRTHGZDGMRZWM43EMZSHGZTTMRTHGZDGONSGMZDTMY",
];
static flags = {
token: Flags.string({
char: "t",
description: "Authentication token",
}),
url: Flags.string({
char: "u",
description: "Server URL",
}),
};
async run() {
console.log(
chalk.blue.bold("\n Welcome to Dokploy CLI Authentication \n"),
);
const { flags } = await this.parse(Authenticate);
let answers: Answers = {};
const questions: QuestionCollection[] = [];
let config: { token?: string; url?: string } = {};
if (fs.existsSync(configPath)) {
const configFileContent = fs.readFileSync(configPath, "utf8");
config = JSON.parse(configFileContent);
}
if (!flags.url) {
questions.push({
default: config.url,
message: chalk.green(
"Enter your server URL (e.g., https://panel.dokploy.com): ",
),
name: "url",
type: "input",
validate: (input) => (input ? true : "Server URL is required"),
});
}
if (!flags.token) {
questions.push({
default: config.token,
message: chalk.green(
"Enter your authentication token (e.g., MRTHGZDGMRZWM43EMZSHGZTTMRTHGZDGONSGMZDTMY=): ",
),
name: "token",
type: "input",
validate: (input) =>
input ? true : "Authentication token is required",
});
}
if (questions.length > 0) {
answers = await inquirer.prompt(questions);
}
const url = flags.url || answers.url;
const token = flags.token || answers.token;
config.token = token;
config.url = url;
try {
console.log(`\n${chalk.blue("Validating server...")}`);
await axios.get(
`${url}/api/trpc/user.get`,
{
headers: {
"x-api-key": token,
"Content-Type": "application/json",
},
},
);
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
this.log(chalk.green("Authentication details saved successfully."));
} catch (error) {
this.error(
// @ts-expect-error - Type
chalk.red(`Failed to save authentication details: ${error.message}`),
);
}
}
}

View File

@@ -1,250 +0,0 @@
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 { getProjects, type Database } from "../../../utils/shared.js";
import { slugify } from "../../../utils/slug.js";
import type { Answers } from "../../app/create.js";
export default class DatabaseMariadbCreate extends Command {
static description = "Create a new MariaDB database within a project.";
static examples = ["$ <%= config.bin %> mariadb create"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMariadbCreate);
let {
projectId,
environmentId,
name,
databaseName,
description,
databaseRootPassword,
databasePassword,
databaseUser,
dockerImage,
appName
} = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !name || !databaseName || !appName) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to create the MariaDB instance in:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
environmentId = environment.environmentId;
}
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",
},
]);
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;
}
}
// 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 MariaDB instance?',
default: false,
},
]);
if (!confirm.proceed) {
this.error(chalk.yellow("MariaDB creation cancelled."));
return;
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mariadb.create`,
{
json: {
name,
databaseName,
description,
databaseRootPassword,
databasePassword,
databaseUser,
dockerImage,
appName,
projectId,
environmentId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error creating MariaDB instance", response.data.result.data.json));
}
this.log(chalk.green(`MariaDB instance '${name}' created successfully.`));
} catch (error: any) {
this.error(chalk.red(`Error creating MariaDB instance: ${error.message}`));
}
}
}

View File

@@ -1,152 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { getProject, getProjects, type Database } from "../../../utils/shared.js";
import { readAuthConfig } from "../../../utils/utils.js";
export default class DatabaseMariadbDelete extends Command {
static description = "Delete a MariaDB database from a project.";
static examples = [
"$ <%= config.bin %> mariadb delete",
"$ <%= config.bin %> mariadb delete -p <projectId>",
];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMariadbDelete);
let { projectId, environmentId, mariadbId } = flags;
if (!projectId || !environmentId || !mariadbId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
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",
},
]);
selectedProject = projects.find(p => p.projectId === answers.selectedProject);
projectId = answers.selectedProject;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar MariaDB del environment
if (!mariadbId) {
if (!selectedEnvironment?.mariadb || selectedEnvironment.mariadb.length === 0) {
this.error(chalk.yellow("No MariaDB instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.mariadb.map((db: Database) => ({
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([
{
default: false,
message: "Are you sure you want to delete this MariaDB instance?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("MariaDB deletion cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mariadb.remove`,
{
json: {
mariadbId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error deleting MariaDB instance"));
}
this.log(chalk.green("MariaDB instance deleted successfully."));
} catch (error: any) {
this.error(chalk.red(`Error deleting MariaDB instance: ${error.message}`));
}
}
}

View File

@@ -1,153 +0,0 @@
import { Command, Flags } from "@oclif/core";
import { readAuthConfig } from "../../../utils/utils.js";
import chalk from "chalk";
import { getProject, getProjects, type Database } from "../../../utils/shared.js";
import inquirer from "inquirer";
import type { Answers } from "../../app/create.js";
import axios from "axios";
export default class DatabaseMariadbDeploy extends Command {
static description = "Deploy an mariadb to a project.";
static examples = ["$ <%= config.bin %> app deploy"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMariadbDeploy);
let { projectId, environmentId, mariadbId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !mariadbId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to deploy the MariaDB in:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar MariaDB del environment
if (!mariadbId) {
if (!selectedEnvironment?.mariadb || selectedEnvironment.mariadb.length === 0) {
this.error(chalk.yellow("No MariaDB instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.mariadb.map((db: Database) => ({
name: db.name,
value: db.mariadbId,
})),
message: "Select the MariaDB instance to deploy:",
name: "selectedDb",
type: "list",
},
]);
mariadbId = 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 deploy this MariaDB instance?",
name: "confirmDeploy",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDeploy) {
this.error(chalk.yellow("MariaDB deployment cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mariadb.deploy`,
{
json: {
mariadbId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,153 +0,0 @@
import { Command, Flags } from "@oclif/core";
import chalk from "chalk";
import inquirer from "inquirer";
import axios from "axios";
import { getProject, getProjects, type Database } from "../../../utils/shared.js";
import { readAuthConfig } from "../../../utils/utils.js";
import type { Answers } from "../../app/create.js";
export default class DatabaseMariadbStop extends Command {
static description = "Stop an mariadb from a project.";
static examples = ["$ <%= config.bin %> mariadb stop"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMariadbStop);
let { projectId, environmentId, mariadbId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !mariadbId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to stop the MariaDB instance from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar MariaDB del environment
if (!mariadbId) {
if (!selectedEnvironment?.mariadb || selectedEnvironment.mariadb.length === 0) {
this.error(chalk.yellow("No MariaDB instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.mariadb.map((db: Database) => ({
name: db.name,
value: db.mariadbId,
})),
message: "Select the MariaDB instance to stop:",
name: "selectedDb",
type: "list",
},
]);
mariadbId = 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 stop this MariaDB instance?",
name: "confirmStop",
type: "confirm",
},
]);
if (!confirmAnswers.confirmStop) {
this.error(chalk.yellow("MariaDB stop cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mariadb.stop`,
{
json: {
mariadbId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,237 +0,0 @@
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 { getProjects, type Database } from "../../../utils/shared.js";
import { slugify } from "../../../utils/slug.js";
import type { Answers } from "../../app/create.js";
export default class DatabaseMongoCreate extends Command {
static description = "Create a new MongoDB database within a project.";
static examples = ["$ <%= config.bin %> mongo create"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMongoCreate);
let {
projectId,
environmentId,
name,
databaseName,
description,
databasePassword,
databaseUser,
dockerImage,
appName
} = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !name || !databaseName || !appName || !databasePassword) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to create the MongoDB instance in:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
environmentId = environment.environmentId;
}
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",
},
]);
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;
}
}
// 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 MongoDB instance?',
default: false,
},
]);
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,
environmentId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,156 +0,0 @@
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 { getProject, getProjects, type Database } from "../../../utils/shared.js";
export default class DatabaseMongoDelete extends Command {
static description = "Delete a MongoDB database from a project.";
static examples = [
"$ <%= config.bin %> mongo delete",
"$ <%= config.bin %> mongo delete -p <projectId>",
];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMongoDelete);
let { projectId, environmentId, mongoId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !mongoId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
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",
},
]);
selectedProject = projects.find(p => p.projectId === answers.selectedProject);
projectId = answers.selectedProject;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar MongoDB del environment
if (!mongoId) {
if (!selectedEnvironment?.mongo || selectedEnvironment.mongo.length === 0) {
this.error(chalk.yellow("No MongoDB instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.mongo.map((db: Database) => ({
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 instance?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("MongoDB deletion cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mongo.remove`,
{
json: {
mongoId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error deleting MongoDB instance"));
}
this.log(chalk.green("MongoDB instance deleted successfully."));
} catch (error: any) {
this.error(chalk.red(`Error deleting MongoDB instance: ${error.message}`));
}
}
}

View File

@@ -1,153 +0,0 @@
import { Command, Flags } from "@oclif/core";
import { readAuthConfig } from "../../../utils/utils.js";
import chalk from "chalk";
import { getProject, getProjects, type Database } from "../../../utils/shared.js";
import inquirer from "inquirer";
import type { Answers } from "../../app/create.js";
import axios from "axios";
export default class DatabaseMongoDeploy extends Command {
static description = "Deploy an mongo to a project.";
static examples = ["$ <%= config.bin %> app deploy"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMongoDeploy);
let { projectId, environmentId, mongoId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !mongoId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to deploy the MongoDB instance from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar MongoDB del environment
if (!mongoId) {
if (!selectedEnvironment?.mongo || selectedEnvironment.mongo.length === 0) {
this.error(chalk.yellow("No MongoDB instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.mongo.map((db: Database) => ({
name: db.name,
value: db.mongoId,
})),
message: "Select the MongoDB instance to deploy:",
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 deploy this MongoDB instance?",
name: "confirmDeploy",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDeploy) {
this.error(chalk.yellow("MongoDB deployment cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mongo.deploy`,
{
json: {
mongoId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,153 +0,0 @@
import { Command, Flags } from "@oclif/core";
import chalk from "chalk";
import inquirer from "inquirer";
import axios from "axios";
import { getProject, getProjects, type Database } from "../../../utils/shared.js";
import { readAuthConfig } from "../../../utils/utils.js";
import type { Answers } from "../../app/create.js";
export default class DatabaseMongoStop extends Command {
static description = "Stop an mongo from a project.";
static examples = ["$ <%= config.bin %> mongo stop"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMongoStop);
let { projectId, environmentId, mongoId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !mongoId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to stop the MongoDB instance from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar MongoDB del environment
if (!mongoId) {
if (!selectedEnvironment?.mongo || selectedEnvironment.mongo.length === 0) {
this.error(chalk.yellow("No MongoDB instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.mongo.map((db: Database) => ({
name: db.name,
value: db.mongoId,
})),
message: "Select the MongoDB instance to stop:",
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 stop this MongoDB instance?",
name: "confirmStop",
type: "confirm",
},
]);
if (!confirmAnswers.confirmStop) {
this.error(chalk.yellow("MongoDB stop cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mongo.stop`,
{
json: {
mongoId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,252 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { slugify } from "../../../utils/slug.js";
import { readAuthConfig } from "../../../utils/utils.js";
import { getProjects, type Database } from "../../../utils/shared.js";
import type { Answers } from "../../app/create.js";
export default class DatabaseMysqlCreate extends Command {
static description = "Create a new MySQL database within a project.";
static examples = ["$ <%= config.bin %> mysql create"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMysqlCreate);
let {
projectId,
environmentId,
name,
databaseName,
description,
databaseRootPassword,
databasePassword,
databaseUser,
dockerImage,
appName
} = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !name || !databaseName || !appName || !databasePassword || !databaseRootPassword) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to create the MySQL instance in:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
environmentId = environment.environmentId;
}
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",
},
]);
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;
}
}
// 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 MySQL instance?',
default: false,
},
]);
if (!confirm.proceed) {
this.error(chalk.yellow("MySQL creation cancelled."));
return;
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mysql.create`,
{
json: {
name,
databaseName,
description,
databaseRootPassword,
databasePassword,
databaseUser,
dockerImage,
appName,
projectId,
environmentId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,156 +0,0 @@
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 { getProject, getProjects, type Database } from "../../../utils/shared.js";
export default class DatabaseMysqlDelete extends Command {
static description = "Delete a MySQL database from a project.";
static examples = [
"$ <%= config.bin %> mysql delete",
"$ <%= config.bin %> mysql delete -p <projectId>",
];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMysqlDelete);
let { projectId, environmentId, mysqlId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !mysqlId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
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",
},
]);
selectedProject = projects.find(p => p.projectId === answers.selectedProject);
projectId = answers.selectedProject;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar MySQL del environment
if (!mysqlId) {
if (!selectedEnvironment?.mysql || selectedEnvironment.mysql.length === 0) {
this.error(chalk.yellow("No MySQL instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.mysql.map((db: Database) => ({
name: db.name,
value: db.mysqlId,
})),
message: "Select the MySQL instance to delete:",
name: "selectedDb",
type: "list",
},
]);
mysqlId = 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 MySQL instance?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("MySQL deletion cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mysql.remove`,
{
json: {
mysqlId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error deleting MySQL instance"));
}
this.log(chalk.green("MySQL instance deleted successfully."));
} catch (error: any) {
this.error(chalk.red(`Error deleting MySQL instance: ${error.message}`));
}
}
}

View File

@@ -1,153 +0,0 @@
import { Command, Flags } from "@oclif/core";
import { readAuthConfig } from "../../../utils/utils.js";
import chalk from "chalk";
import { getProject, getProjects, type Database } from "../../../utils/shared.js";
import inquirer from "inquirer";
import type { Answers } from "../../app/create.js";
import axios from "axios";
export default class DatabaseMysqlDeploy extends Command {
static description = "Deploy an mysql to a project.";
static examples = ["$ <%= config.bin %> app deploy"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
required: false,
}),
mysqlId: Flags.string({
char: "m",
description: "ID of the MySQL instance to deploy",
required: false,
}),
skipConfirm: Flags.boolean({
char: "y",
description: "Skip confirmation prompt",
default: false,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMysqlDeploy);
let { projectId, environmentId, mysqlId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !mysqlId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to deploy the MySQL instance from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar MySQL del environment
if (!mysqlId) {
if (!selectedEnvironment?.mysql || selectedEnvironment.mysql.length === 0) {
this.error(chalk.yellow("No MySQL instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.mysql.map((db: Database) => ({
name: db.name,
value: db.mysqlId,
})),
message: "Select the MySQL instance to deploy:",
name: "selectedDb",
type: "list",
},
]);
mysqlId = 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 deploy this MySQL instance?",
name: "confirmDeploy",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDeploy) {
this.error(chalk.yellow("MySQL deployment cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mysql.deploy`,
{
json: {
mysqlId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,153 +0,0 @@
import { Command, Flags } from "@oclif/core";
import chalk from "chalk";
import inquirer from "inquirer";
import axios from "axios";
import { getProject, getProjects, type Database } from "../../../utils/shared.js";
import { readAuthConfig } from "../../../utils/utils.js";
import type { Answers } from "../../app/create.js";
export default class DatabaseMysqlStop extends Command {
static description = "Stop an mysql from a project.";
static examples = ["$ <%= config.bin %> mysql stop"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMysqlStop);
let { projectId, environmentId, mysqlId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !mysqlId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to stop the MySQL instance from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar MySQL del environment
if (!mysqlId) {
if (!selectedEnvironment?.mysql || selectedEnvironment.mysql.length === 0) {
this.error(chalk.yellow("No MySQL instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.mysql.map((db: Database) => ({
name: db.name,
value: db.mysqlId,
})),
message: "Select the MySQL instance to stop:",
name: "selectedDb",
type: "list",
},
]);
mysqlId = 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 stop this MySQL instance?",
name: "confirmStop",
type: "confirm",
},
]);
if (!confirmAnswers.confirmStop) {
this.error(chalk.yellow("MySQL stop cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mysql.stop`,
{
json: {
mysqlId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,237 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { slugify } from "../../../utils/slug.js";
import { readAuthConfig } from "../../../utils/utils.js";
import { getProjects, type Database } from "../../../utils/shared.js";
import type { Answers } from "../../app/create.js";
export default class DatabasePostgresCreate extends Command {
static description = "Create a new PostgreSQL database within a project.";
static examples = ["$ <%= config.bin %> postgres create"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabasePostgresCreate);
let {
projectId,
environmentId,
name,
databaseName,
description,
databasePassword,
databaseUser,
dockerImage,
appName
} = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !name || !databaseName || !appName || !databasePassword) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to create the PostgreSQL instance in:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
environmentId = environment.environmentId;
}
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",
},
]);
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;
}
}
// 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 PostgreSQL instance?',
default: false,
},
]);
if (!confirm.proceed) {
this.error(chalk.yellow("PostgreSQL creation cancelled."));
return;
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/postgres.create`,
{
json: {
name,
databaseName,
description,
databasePassword,
databaseUser,
dockerImage,
appName,
projectId,
environmentId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,156 +0,0 @@
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 { getProject, getProjects, type Database } from "../../../utils/shared.js";
export default class DatabasePostgresDelete extends Command {
static description = "Delete a PostgreSQL database from a project.";
static examples = [
"$ <%= config.bin %> postgres delete",
"$ <%= config.bin %> postgres delete -p <projectId>",
];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabasePostgresDelete);
let { projectId, environmentId, postgresId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !postgresId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
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",
},
]);
selectedProject = projects.find(p => p.projectId === answers.selectedProject);
projectId = answers.selectedProject;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar PostgreSQL del environment
if (!postgresId) {
if (!selectedEnvironment?.postgres || selectedEnvironment.postgres.length === 0) {
this.error(chalk.yellow("No PostgreSQL instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.postgres.map((db: Database) => ({
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 PostgreSQL instance?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("PostgreSQL deletion cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/postgres.remove`,
{
json: {
postgresId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error deleting PostgreSQL instance"));
}
this.log(chalk.green("PostgreSQL instance deleted successfully."));
} catch (error: any) {
this.error(chalk.red(`Error deleting PostgreSQL instance: ${error.message}`));
}
}
}

View File

@@ -1,153 +0,0 @@
import { Command, Flags } from "@oclif/core";
import { readAuthConfig } from "../../../utils/utils.js";
import chalk from "chalk";
import { getProject, getProjects, type Database } from "../../../utils/shared.js";
import inquirer from "inquirer";
import type { Answers } from "../../app/create.js";
import axios from "axios";
export default class DatabasePostgresDeploy extends Command {
static description = "Deploy a PostgreSQL instance to a project.";
static examples = ["$ <%= config.bin %> postgres deploy"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabasePostgresDeploy);
let { projectId, environmentId, postgresId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !postgresId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to deploy the PostgreSQL instance from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar PostgreSQL del environment
if (!postgresId) {
if (!selectedEnvironment?.postgres || selectedEnvironment.postgres.length === 0) {
this.error(chalk.yellow("No PostgreSQL instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.postgres.map((db: Database) => ({
name: db.name,
value: db.postgresId,
})),
message: "Select the PostgreSQL instance to deploy:",
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 deploy this PostgreSQL instance?",
name: "confirmDeploy",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDeploy) {
this.error(chalk.yellow("PostgreSQL deployment cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/postgres.deploy`,
{
json: {
postgresId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,153 +0,0 @@
import { Command, Flags } from "@oclif/core";
import { readAuthConfig } from "../../../utils/utils.js";
import chalk from "chalk";
import { getProject, getProjects, type Database } from "../../../utils/shared.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 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,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabasePostgresStop);
let { projectId, environmentId, postgresId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !postgresId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to stop the PostgreSQL instance from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar PostgreSQL del environment
if (!postgresId) {
if (!selectedEnvironment?.postgres || selectedEnvironment.postgres.length === 0) {
this.error(chalk.yellow("No PostgreSQL instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.postgres.map((db: Database) => ({
name: db.name,
value: db.postgresId,
})),
message: "Select the PostgreSQL instance to stop:",
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 stop this PostgreSQL instance?",
name: "confirmStop",
type: "confirm",
},
]);
if (!confirmAnswers.confirmStop) {
this.error(chalk.yellow("PostgreSQL stop cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/postgres.stop`,
{
json: {
postgresId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,209 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { slugify } from "../../../utils/slug.js";
import { readAuthConfig } from "../../../utils/utils.js";
import { getProjects, type Database } from "../../../utils/shared.js";
import type { Answers } from "../../app/create.js";
export default class DatabaseRedisCreate extends Command {
static description = "Create a new Redis instance within a project.";
static examples = ["$ <%= config.bin %> redis create"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseRedisCreate);
let {
projectId,
name,
description,
databasePassword,
dockerImage,
appName
} = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !name || !appName || !databasePassword) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to create the Redis instance in:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
environmentId = environment.environmentId;
}
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([
{
type: 'confirm',
name: 'proceed',
message: 'Do you want to create this Redis instance?',
default: false,
},
]);
if (!confirm.proceed) {
this.error(chalk.yellow("Redis creation cancelled."));
return;
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/redis.create`,
{
json: {
name,
description,
databasePassword,
dockerImage,
appName,
projectId,
environmentId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,156 +0,0 @@
import { Command, Flags } from "@oclif/core";
import { readAuthConfig } from "../../../utils/utils.js";
import chalk from "chalk";
import { getProject, getProjects, type Database } 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 a Redis instance from a project.";
static examples = [
"$ <%= config.bin %> redis delete",
"$ <%= config.bin %> redis delete -p <projectId>",
];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseRedisDelete);
let { projectId, environmentId, redisId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !redisId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
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",
},
]);
selectedProject = projects.find(p => p.projectId === answers.selectedProject);
projectId = answers.selectedProject;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar Redis del environment
if (!redisId) {
if (!selectedEnvironment?.redis || selectedEnvironment.redis.length === 0) {
this.error(chalk.yellow("No Redis instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.redis.map((db: Database) => ({
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 instance?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("Redis deletion cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/redis.remove`,
{
json: {
redisId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error deleting Redis instance"));
}
this.log(chalk.green("Redis instance deleted successfully."));
} catch (error: any) {
this.error(chalk.red(`Error deleting Redis instance: ${error.message}`));
}
}
}

View File

@@ -1,153 +0,0 @@
import { Command, Flags } from "@oclif/core";
import { readAuthConfig } from "../../../utils/utils.js";
import chalk from "chalk";
import { getProject, getProjects, type Database } from "../../../utils/shared.js";
import inquirer from "inquirer";
import type { Answers } from "../../app/create.js";
import axios from "axios";
export default class DatabaseRedisDeploy extends Command {
static description = "Deploy a Redis instance to a project.";
static examples = ["$ <%= config.bin %> redis deploy"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseRedisDeploy);
let { projectId, environmentId, redisId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !redisId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to deploy the Redis instance from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar Redis del environment
if (!redisId) {
if (!selectedEnvironment?.redis || selectedEnvironment.redis.length === 0) {
this.error(chalk.yellow("No Redis instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.redis.map((db: Database) => ({
name: db.name,
value: db.redisId,
})),
message: "Select the Redis instance to deploy:",
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 deploy this Redis instance?",
name: "confirmDeploy",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDeploy) {
this.error(chalk.yellow("Redis deployment cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/redis.deploy`,
{
json: {
redisId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,153 +0,0 @@
import { Command, Flags } from "@oclif/core";
import { readAuthConfig } from "../../../utils/utils.js";
import chalk from "chalk";
import { getProject, getProjects, type Database } from "../../../utils/shared.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 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,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
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<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseRedisStop);
let { projectId, environmentId, redisId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !redisId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
let selectedEnvironment;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to stop the Redis instance from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
selectedEnvironment = environment;
environmentId = environment.environmentId;
} else {
selectedEnvironment = selectedProject?.environments?.find(e => e.environmentId === environmentId);
}
// 3. Seleccionar Redis del environment
if (!redisId) {
if (!selectedEnvironment?.redis || selectedEnvironment.redis.length === 0) {
this.error(chalk.yellow("No Redis instances found in this environment."));
}
const dbAnswers = await inquirer.prompt([
{
choices: selectedEnvironment.redis.map((db: Database) => ({
name: db.name,
value: db.redisId,
})),
message: "Select the Redis instance to stop:",
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 stop this Redis instance?",
name: "confirmStop",
type: "confirm",
},
]);
if (!confirmAnswers.confirmStop) {
this.error(chalk.yellow("Redis stop cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/redis.stop`,
{
json: {
redisId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
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}`));
}
}
}

View File

@@ -1,94 +0,0 @@
import {Args, Command, Flags} from '@oclif/core'
import {readAuthConfig} from "../../utils/utils.js";
import chalk from "chalk";
import {getProject, getProjects} from "../../utils/shared.js";
import inquirer from "inquirer";
import {Answers} from "../app/create.js";
import fs from 'fs';
export default class EnvPull extends Command {
static override args = {
file: Args.string({description: 'write to file', required: true}),
}
static override description = 'Store remote environment variables in local'
static override examples = [
'<%= config.bin %> <%= command.id %> .env.stage.local',
]
static override flags = {}
public async run(): Promise<void> {
const {args} = await this.parse(EnvPull)
if (fs.existsSync(args.file)) {
const {override} = await inquirer.prompt<any>([
{
message: `Do you want to override ${args.file} file?`,
name: "override",
default: false,
type: "confirm",
},
]);
if (!override) {
return
}
}
const auth = await readAuthConfig(this);
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
const {project} = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select the project:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
const {environment} = await inquirer.prompt<any>([
{
choices: projectSelected.environments.map((environment: any) => ({
name: environment.name,
value: environment,
})),
message: "Select the environment:",
name: "environment",
type: "list",
},
]);
const choices = [
...environment.applications.map((app: any) => ({
name: `${app.name} (Application)`,
value: app.env,
})),
...environment.compose.map((compose: any) => ({
name: `${compose.name} (Compose)`,
value: compose.env,
})),
]
const {env} = await inquirer.prompt<any>([
{
choices,
message: "Select a service to pull the environment variables:",
name: "env",
type: "list",
},
]);
fs.writeFileSync(args.file, env || "")
this.log(chalk.green("Environment variable write to file successful."));
}
}

View File

@@ -1,143 +0,0 @@
import {Args, Command, Flags} from '@oclif/core'
import fs from "fs";
import chalk from "chalk";
import inquirer from "inquirer";
import {readAuthConfig} from "../../utils/utils.js";
import {getProject, getProjects} from "../../utils/shared.js";
import {Answers} from "../app/create.js";
import axios from "axios";
export default class EnvPush extends Command {
static override args = {
file: Args.string({description: '.env file to push', required: true}),
}
static override description = 'Push dotenv file to remote service'
static override examples = [
'<%= config.bin %> <%= command.id %> .env.stage.local',
]
static override flags = {}
public async run(): Promise<void> {
const {args, flags} = await this.parse(EnvPush)
if (!fs.existsSync(args.file)) {
console.log(chalk.red.bold(`\n File ${args.file} doesn't exists \n`));
return;
}
const {override} = await inquirer.prompt<any>([
{
message: `This command will override entire remote environment variables. Do you want to continue?`,
name: "override",
default: false,
type: "confirm",
},
]);
if (!override) {
return
}
const fileContent = fs.readFileSync(args.file, 'utf-8');
const auth = await readAuthConfig(this);
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
const {project} = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select the project:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
const {environment} = await inquirer.prompt<any>([
{
choices: projectSelected.environments.map((environment: any) => ({
name: environment.name,
value: environment,
})),
message: "Select the environment:",
name: "environment",
type: "list",
},
]);
const choices = [
...environment.applications.map((app: any) => ({
name: `${app.name} (Application)`,
value: {serviceType: 'app', service: app},
})),
...environment.compose.map((compose: any) => ({
name: `${compose.name} (Compose)`,
value: {serviceType: 'compose', service: compose}
})),
]
const {result: {serviceType, service}} = await inquirer.prompt<any>([
{
choices,
message: "Select a service to push the environment variables:",
name: "result",
type: "list",
},
]);
if (serviceType === 'app') {
const {applicationId} = service;
const response = await axios.post(
`${auth.url}/api/trpc/application.update`,
{
json: {
applicationId,
env: fileContent
}
}, {
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
}
)
if (response.status !== 200) {
this.error(chalk.red("Error stopping application"));
}
this.log(chalk.green("Environment variable push successful."));
}
if (serviceType === 'compose') {
const {composeId} = service;
const response = await axios.post(
`${auth.url}/api/trpc/compose.update`,
{
json: {
composeId,
env: fileContent
}
}, {
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
}
)
if (response.status !== 200) {
this.error(chalk.red("Error stopping application"));
}
this.log(chalk.green("Environment variable push successful."));
}
}
}

View File

@@ -1,131 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { getProjects } from "../../utils/shared.js";
import { readAuthConfig } from "../../utils/utils.js";
import type { Answers } from "../app/create.js";
export default class EnvironmentCreate extends Command {
static description = "Create a new environment within a project.";
static examples = ["$ <%= config.bin %> environment create"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
name: Flags.string({
char: "n",
description: "Environment name",
required: false,
}),
description: Flags.string({
char: "d",
description: "Environment description",
required: false,
}),
skipConfirm: Flags.boolean({
char: "y",
description: "Skip confirmation prompt",
default: false,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(EnvironmentCreate);
let { projectId, name, description } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !name) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to create the environment in:",
name: "project",
type: "list",
},
]);
projectId = project.projectId;
}
// 2. Ingresar detalles del environment
if (!name) {
const envDetails = await inquirer.prompt([
{
message: "Enter the environment name:",
name: "name",
type: "input",
validate: (input) => (input ? true : "Environment name is required"),
default: name,
},
{
message: "Enter the environment description (optional):",
name: "description",
type: "input",
default: description,
},
]);
name = envDetails.name;
description = envDetails.description;
}
}
// 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 environment?',
default: false,
},
]);
if (!confirm.proceed) {
this.error(chalk.yellow("Environment creation cancelled."));
return;
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/environment.create`,
{
json: {
name,
description,
projectId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error creating environment"));
}
this.log(chalk.green(`Environment '${name}' created successfully.`));
} catch (error: any) {
this.error(chalk.red(`Error creating environment: ${error.message}`));
}
}
}

View File

@@ -1,129 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { getProjects } from "../../utils/shared.js";
import { readAuthConfig } from "../../utils/utils.js";
import type { Answers } from "../app/create.js";
export default class EnvironmentDelete extends Command {
static description = "Delete an environment from a project.";
static examples = [
"$ <%= config.bin %> environment delete",
"$ <%= config.bin %> environment delete -p <projectId>",
];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment to delete",
required: false,
}),
skipConfirm: Flags.boolean({
char: "y",
description: "Skip confirmation prompt",
default: false,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(EnvironmentDelete);
let { projectId, environmentId } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to delete the environment from:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment to delete:",
name: "environment",
type: "list",
},
]);
environmentId = environment.environmentId;
}
}
// 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 environment? This action cannot be undone.",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("Environment deletion cancelled."));
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/environment.remove`,
{
json: {
environmentId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error deleting environment"));
}
this.log(chalk.green("Environment deleted successfully."));
} catch (error: any) {
this.error(chalk.red(`Error deleting environment: ${error.message}`));
}
}
}

View File

@@ -1,104 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { readAuthConfig } from "../../utils/utils.js";
export default class ProjectCreate extends Command {
static description = "Create a new project.";
static examples = [
"$ <%= config.bin %> project create",
"$ <%= config.bin %> project create -n MyProject -d 'Project description'",
"$ <%= config.bin %> project create --name MyProject --skipConfirm",
];
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,
}),
skipConfirm: Flags.boolean({
char: "y",
description: "Skip confirmation prompt",
default: false,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(ProjectCreate);
let { name, description } = flags;
// 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 || "",
},
]);
name = answers.name;
description = answers.description;
}
// 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 (!confirm.proceed) {
this.error(chalk.yellow("Project creation cancelled."));
return;
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/project.create`,
{
json: {
name,
description,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error creating project", response.data.result.data.json));
}
this.log(chalk.green(`Project '${name}' created successfully.`));
} catch (error: any) {
this.error(chalk.red(`Error creating project: ${error.message}`));
}
}
}

View File

@@ -1,195 +0,0 @@
import { Command, Flags } from "@oclif/core";
import chalk from "chalk";
import inquirer from "inquirer";
import { readAuthConfig } from "../../utils/utils.js";
import { getProjects } from "../../utils/shared.js";
export default class ProjectInfo extends Command {
static description =
"Get detailed information about a project, including the number of applications and databases.";
static examples = [
"$ <%= config.bin %> project info",
"$ <%= config.bin %> project info -p <projectId>",
];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(ProjectInfo);
if (flags.projectId) {
await this.showProjectInfo(auth, flags.projectId);
} else {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
try {
const projects = await getProjects(auth, this);
if (projects.length === 0) {
this.log(chalk.yellow("No projects found."));
return;
}
const answers = await inquirer.prompt([
{
choices: projects.map((project) => ({
name: project.name,
value: project.projectId,
})),
message: "Select a project to view details:",
name: "selectedProject",
type: "list",
},
]);
const selectedProjectId = answers.selectedProject;
await this.showProjectInfo(auth, selectedProjectId);
} catch (error) {
// @ts-expect-error hola
this.error(chalk.red(`Failed to fetch project list: ${error.message}`));
}
}
}
private async showProjectInfo(
auth: { token: string; url: string },
projectId: string,
) {
console.log(
chalk.blue.bold(`\n Information for Project ID: ${projectId} \n`),
);
try {
const projects = await getProjects(auth, this);
const projectInfo = projects.find(p => p.projectId === projectId);
if (!projectInfo) {
this.error(chalk.red("Project not found."));
return;
}
this.log(chalk.green(`Project Name: ${projectInfo.name}`));
this.log(
chalk.green(
`Description: ${projectInfo?.description || "No description"}`,
),
);
// Contar totales de todos los environments
let totalApplications = 0;
let totalCompose = 0;
let totalMariaDB = 0;
let totalMongoDB = 0;
let totalMySQL = 0;
let totalPostgreSQL = 0;
let totalRedis = 0;
if (projectInfo.environments && projectInfo.environments.length > 0) {
this.log(chalk.green(`Number of Environments: ${projectInfo.environments.length}`));
// Mostrar información por environment
projectInfo.environments.forEach((env, envIndex) => {
this.log(chalk.blue(`\nEnvironment ${envIndex + 1}: ${env.name} (${env.description})`));
// Contar recursos por environment
const envApps = env.applications?.length || 0;
const envCompose = env.compose?.length || 0;
const envMariaDB = env.mariadb?.length || 0;
const envMongoDB = env.mongo?.length || 0;
const envMySQL = env.mysql?.length || 0;
const envPostgreSQL = env.postgres?.length || 0;
const envRedis = env.redis?.length || 0;
totalApplications += envApps;
totalCompose += envCompose;
totalMariaDB += envMariaDB;
totalMongoDB += envMongoDB;
totalMySQL += envMySQL;
totalPostgreSQL += envPostgreSQL;
totalRedis += envRedis;
this.log(` Applications: ${envApps}`);
this.log(` Compose Services: ${envCompose}`);
this.log(` MariaDB: ${envMariaDB}`);
this.log(` MongoDB: ${envMongoDB}`);
this.log(` MySQL: ${envMySQL}`);
this.log(` PostgreSQL: ${envPostgreSQL}`);
this.log(` Redis: ${envRedis}`);
// Mostrar detalles de applications
if (envApps > 0) {
this.log(chalk.cyan(" Applications:"));
env.applications.forEach((app, index) => {
this.log(` ${index + 1}. ${app.name}`);
});
}
// Mostrar detalles de databases
if (envMariaDB > 0) {
this.log(chalk.cyan(" MariaDB Databases:"));
env.mariadb.forEach((db, index) => {
this.log(` ${index + 1}. ${db.name}`);
});
}
if (envMongoDB > 0) {
this.log(chalk.cyan(" MongoDB Databases:"));
env.mongo.forEach((db, index) => {
this.log(` ${index + 1}. ${db.name}`);
});
}
if (envMySQL > 0) {
this.log(chalk.cyan(" MySQL Databases:"));
env.mysql.forEach((db, index) => {
this.log(` ${index + 1}. ${db.name}`);
});
}
if (envPostgreSQL > 0) {
this.log(chalk.cyan(" PostgreSQL Databases:"));
env.postgres.forEach((db, index) => {
this.log(` ${index + 1}. ${db.name}`);
});
}
if (envRedis > 0) {
this.log(chalk.cyan(" Redis Databases:"));
env.redis.forEach((db, index) => {
this.log(` ${index + 1}. ${db.name}`);
});
}
});
} else {
this.log(chalk.yellow("No environments found in this project."));
}
// Mostrar totales
this.log(chalk.green.bold("\n📊 Project Totals:"));
this.log(chalk.green(`Total Applications: ${totalApplications}`));
this.log(chalk.green(`Total Compose Services: ${totalCompose}`));
this.log(chalk.green(`Total MariaDB Databases: ${totalMariaDB}`));
this.log(chalk.green(`Total MongoDB Databases: ${totalMongoDB}`));
this.log(chalk.green(`Total MySQL Databases: ${totalMySQL}`));
this.log(chalk.green(`Total PostgreSQL Databases: ${totalPostgreSQL}`));
this.log(chalk.green(`Total Redis Databases: ${totalRedis}`));
} catch (error) {
this.error(
// @ts-expect-error
chalk.red(`Failed to fetch project information: ${error.message}`),
);
}
}
}

View File

@@ -1,49 +0,0 @@
import { Command } from "@oclif/core";
import chalk from "chalk";
import Table from "cli-table3";
import { readAuthConfig } from "../../utils/utils.js";
import { getProjects } from "../../utils/shared.js";
export default class ProjectList extends Command {
static description = "List all projects.";
static examples = ["$ <%= config.bin %> project list"];
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
console.log(chalk.blue.bold("\n Listing all Projects \n"));
try {
const projects = await getProjects(auth, this);
if (projects.length === 0) {
this.log(chalk.yellow("No projects found."));
} else {
this.log(chalk.green("Projects:"));
const table = new Table({
colWidths: [10, 30, 50],
head: [
chalk.cyan("Index"),
chalk.cyan("Name"),
chalk.cyan("Description"),
],
});
const index = 1;
for (const project of projects) {
table.push([
chalk.white(index + 1),
chalk.white(project.name),
chalk.gray(project.description || "No description"),
]);
}
this.log(table.toString());
}
} catch (error) {
// @ts-expect-error error is not defined
this.error(chalk.red(`Failed to list projects: ${error?.message}`));
}
}
}

View File

@@ -1,88 +0,0 @@
import { Command } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const configPath = path.join(__dirname, "..", "..", "config.json");
export default class Verify extends Command {
static description = "Verify if the saved authentication token is valid";
static examples = ["$ <%= config.bin %> <%= command.id %>"];
async run() {
console.log(chalk.blue.bold("\nVerifying Authentication Token"));
let token: string;
let url: string;
// Verificar variables de entorno primero
const envToken = process.env.DOKPLOY_AUTH_TOKEN;
const envUrl = process.env.DOKPLOY_URL;
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(chalk.blue("Validating token with server..."));
const response = await axios.get(
`${url}/api/trpc/user.get`,
{
headers: {
"x-api-key": token,
"Content-Type": "application/json",
},
},
);
if (response.data.result.data.json) {
this.log(chalk.green("\n✓ Token is valid"));
} else {
this.error(
chalk.red(
"Invalid token. Please authenticate again using `authenticate` command.",
),
);
}
} catch (error: any) {
this.error(
chalk.red(
`Failed to verify token: ${error.message}. Please authenticate again using 'authenticate' command.`,
),
);
}
}
}

8720
src/generated/commands.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,21 @@
export {run} from '@oclif/core'
#!/usr/bin/env node
import { program } from "commander";
import chalk from "chalk";
import { registerAuthCommand } from "./commands/auth.js";
import { registerGeneratedCommands } from "./generated/commands.js";
const pkg = { name: "dokploy", version: "0.3.0", description: "Dokploy CLI - Manage your Dokploy server" };
program
.name(pkg.name)
.version(pkg.version)
.description(pkg.description);
registerAuthCommand(program);
registerGeneratedCommands(program);
program.parseAsync(process.argv).catch((err) => {
console.error(chalk.red(err.message));
process.exit(1);
});

View File

@@ -1,4 +0,0 @@
export const headers = {
"Content-Type": "application/json",
"User-Agent": "Dokploy CLI",
};

View File

@@ -1,114 +0,0 @@
import type { Command } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import type { AuthConfig } from "./utils.js";
export type Application = {
applicationId: string;
name: string;
// Add other application properties as needed
};
export type Database = {
mariadbId?: string;
mongoId?: string;
mysqlId?: string;
postgresId?: string;
redisId?: string;
name: string;
// Add other database properties as needed
};
export type Environment = {
name: string;
environmentId: string;
description: string;
createdAt: string;
env: string;
projectId: string;
applications: Application[];
mariadb: Database[];
mongo: Database[];
mysql: Database[];
postgres: Database[];
redis: Database[];
compose: any[];
};
export type Project = {
adminId: string;
name: string;
projectId?: string | undefined;
description?: string | undefined;
environments?: Environment[];
};
export const getProjects = async (
auth: AuthConfig,
command: Command,
): Promise<Project[]> => {
try {
const response = await axios.get(`${auth.url}/api/trpc/project.all`, {
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
});
if (!response.data.result.data.json) {
command.error(chalk.red("Error fetching projects"));
}
const projects = response.data.result.data.json;
if (projects.length === 0) {
command.log(chalk.yellow("No projects found."));
return [];
}
return projects;
} catch (error) {
// @ts-expect-error TODO: Fix this
command.error(chalk.red(`Failed to fetch project list: ${error.message}`));
}
};
export const getProject = async (
projectId: string | undefined,
auth: AuthConfig,
command: Command,
) => {
try {
if (!projectId) {
command.error(chalk.red("Project ID is required"));
}
const response = await axios.get(`${auth.url}/api/trpc/project.one`, {
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
params: {
input: JSON.stringify({
json: { projectId },
}),
},
});
if (!response.data.result.data.json) {
command.error(chalk.red("Error fetching project"));
}
const project = response.data.result.data.json;
if (!project) {
command.error(chalk.red("Error fetching project"));
}
return project;
} catch (error) {
// @ts-expect-error TODO: Fix this
command.error(chalk.red(`Failed to fetch project: ${error.message}`));
}
};

View File

@@ -1,14 +0,0 @@
import slug from "./slugify.js";
export const slugify = (text: string | undefined) => {
if (!text) {
return "";
}
const cleanedText = text.trim().replaceAll(/[^\d\sA-Za-z]/g, "");
return slug(cleanedText, {
lower: true,
strict: true,
trim: true,
});
};

View File

@@ -1,3 +0,0 @@
import slugify from "slugify";
export default slugify as unknown as typeof slugify.default;

View File

@@ -1,48 +0,0 @@
import type { Command } from "@oclif/core";
import chalk from "chalk";
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const configPath = path.join(__dirname, "..", "..", "config.json");
export type AuthConfig = {
token: string;
url: string;
};
export const readAuthConfig = async (command: Command): Promise<AuthConfig> => {
// 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 and no environment variables set. Please authenticate first using the 'authenticate' command or set DOKPLOY_URL and DOKPLOY_AUTH_TOKEN environment variables.",
),
);
}
const configFileContent = fs.readFileSync(configPath, "utf8");
const config = JSON.parse(configFileContent);
const { token, url } = config;
if (!url || !token) {
command.error(
chalk.red(
"Incomplete authentication details. Please authenticate again using the 'authenticate' command or set environment variables.",
),
);
}
return { token, url };
};

View File

@@ -7,6 +7,7 @@
"strict": true,
"target": "es2022",
"moduleResolution": "node16",
"skipLibCheck": true
},
"include": ["./src/**/*"],
"ts-node": {

1
tsconfig.tsbuildinfo Normal file
View File

@@ -0,0 +1 @@
{"root":["./src/client.ts","./src/index.ts","./src/commands/auth.ts","./src/generated/commands.ts"],"version":"5.9.3"}