Merge pull request #36 from Dokploy/feat/add-support-for-all-endpoints

Feat/add support for all endpoints
This commit is contained in:
Mauricio Siu
2026-04-15 21:07:12 -06:00
committed by GitHub
69 changed files with 59833 additions and 10819 deletions

2
.env.example Normal file
View File

@@ -0,0 +1,2 @@
DOKPLOY_URL=""
DOKPLOY_API_KEY=""

View File

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

View File

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

View File

@@ -1,56 +1,62 @@
# test
name: version, tag and github release
name: release and publish
on:
push:
branches: [main]
env:
COREPACK_ENABLE_STRICT: 0
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GH_TOKEN }}
- uses: pnpm/action-setup@v6
with:
version: latest
- uses: actions/setup-node@v4
with:
node-version: latest
cache: pnpm
registry-url: https://registry.npmjs.org
- run: pnpm install --ignore-scripts
- run: pnpm run build
- name: Check if version already exists
id: version-check
run: |
package_version=$(node -p "require('./package.json').version")
exists=$(gh api repos/${{ github.repository }}/releases/tags/v$package_version >/dev/null 2>&1 && echo "true" || echo "")
if [ -n "$exists" ];
then
if [ -n "$exists" ]; then
echo "Version v$package_version already exists"
echo "::warning file=package.json,line=1::Version v$package_version already exists - no release will be created. If you want to create a new release, please update the version in package.json and push again."
echo "skipped=true" >> $GITHUB_OUTPUT
else
echo "Version v$package_version does not exist. Creating release..."
echo "Version v$package_version does not exist"
echo "skipped=false" >> $GITHUB_OUTPUT
echo "tag=v$package_version" >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
- name: Setup git
if: ${{ steps.version-check.outputs.skipped == 'false' }}
run: |
git config --global user.email ${{ secrets.GH_EMAIL }}
git config --global user.name ${{ secrets.GH_USERNAME }}
- name: Generate oclif README
if: ${{ steps.version-check.outputs.skipped == 'false' }}
id: oclif-readme
run: |
pnpm install
pnpm exec oclif readme
if [ -n "$(git status --porcelain)" ]; then
git add .
git commit -am "chore: update README.md"
git push -u origin ${{ github.ref_name }}
fi
- name: Create Github Release
if: steps.version-check.outputs.skipped == 'false'
uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5
if: ${{ steps.version-check.outputs.skipped == 'false' }}
with:
name: ${{ steps.version-check.outputs.tag }}
tag: ${{ steps.version-check.outputs.tag }}
commit: ${{ github.ref_name }}
token: ${{ secrets.GH_TOKEN }}
skipIfReleaseExists: true
- name: Publish to npm
if: steps.version-check.outputs.skipped == 'false'
run: pnpm publish --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -1,18 +0,0 @@
name: publish
on:
release:
types: [released]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: latest
- run: pnpm install
- uses: JS-DevTools/npm-publish@19c28f1ef146469e409470805ea4279d47c3d35c
with:
token: ${{ secrets.NPM_TOKEN }}

View File

@@ -1,22 +1,30 @@
name: tests
on:
push:
branches-ignore: [main]
workflow_dispatch:
env:
COREPACK_ENABLE_STRICT: 0
jobs:
unit-tests:
test:
strategy:
matrix:
os: ['ubuntu-latest', 'windows-latest']
node_version: [lts/-1, lts/*, latest]
os: [ubuntu-latest, windows-latest]
node_version: [lts/*, latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v6
with:
version: latest
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node_version }}
cache: pnpm
- run: pnpm install
- run: pnpm install --ignore-scripts
- run: pnpm run build
- run: pnpm run test

3
.gitignore vendored
View File

@@ -11,5 +11,4 @@ oclif.manifest.json
yarn.lock
package-lock.json
.env

View File

@@ -1,15 +0,0 @@
{
"require": [
"ts-node/register"
],
"watch-extensions": [
"ts"
],
"recursive": true,
"reporter": "spec",
"timeout": 60000,
"node-option": [
"loader=ts-node/esm",
"experimental-specifier-resolution=node"
]
}

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
ignore-build-scripts=false

View File

@@ -1 +0,0 @@
"@oclif/prettier-config"

View File

@@ -1,113 +1,63 @@
# Contributing
Hey, thanks for your interest in contributing to Dokploy CLI! We appreciate your help and taking your time to contribute.
Before you start, please first discuss the feature/bug you want to add with the owners and comunity via github issues.
We have a few guidelines to follow when contributing to this project:
- [Commit Convention](#commit-convention)
- [Setup](#setup)
- [Development](#development)
- [Build](#build)
- [Pull Request](#pull-request)
## Commit Convention
Before you craete a Pull Request, please make sure your commit message follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
### Commit Message Format
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
#### Type
Must be one of the following:
* **feat**: A new feature
* **fix**: A bug fix
* **docs**: Documentation only changes
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
* **refactor**: A code change that neither fixes a bug nor adds a feature
* **perf**: A code change that improves performance
* **test**: Adding missing tests or correcting existing tests
* **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
* **ci**: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
* **chore**: Other changes that don't modify `src` or `test` files
* **revert**: Reverts a previous commit
Example:
```
feat: add new feature
```
Thanks for your interest in contributing to Dokploy CLI!
Before you start, please discuss the feature/bug via [GitHub issues](https://github.com/Dokploy/cli/issues).
## Setup
Before you start, please make the clone based on the `main` branch.
```bash
git clone https://github.com/Dokploy/cli.git
cd cli
pnpm install
```
Create a `.env` file with your credentials:
```env
DOKPLOY_URL="https://your-server.dokploy.com"
DOKPLOY_API_KEY="YOUR_API_KEY"
```
## Development
First step is to authenticate, you can connect to a dokploy localhost or a remote dokploy server.
Authenticate
```bash
./bin/dev.js authenticate
```
# Run in dev mode
pnpm run dev -- project all
Let's take the example to create a new command for application called `start`.
# Regenerate commands from OpenAPI spec
pnpm run generate
You can use the generators from OCLIF to create a new command.
```bash
oclif generate command application:start
```
To run the command, you can use the following command:
```bash
./bin/dev.js application:start or ./bin/dev.js start
```
## Build
```bash
# Build
pnpm run build
# Lint & format
pnpm run lint
```
## Publish
### Updating commands
```bash
pnpm run publish
Commands in `src/generated/commands.ts` are auto-generated from `openapi.json`. Never edit that file manually. To update:
1. Replace `openapi.json` with the latest spec from the [Dokploy repo](https://github.com/Dokploy/dokploy)
2. Run `pnpm run generate`
## Commit convention
Follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/):
```
feat: add new feature
fix: resolve bug
docs: update readme
chore: bump version
```
## Pull requests
## Pull Request
- The `main` branch is the source of truth and should always reflect the latest stable release.
- Create a new branch for each feature or bug fix.
- Make sure to add tests for your changes.
- Make sure to update the documentation for any changes Go to the [docs.dokploy.com](https://docs.dokploy.com) website to see the changes.
- When creating a pull request, please provide a clear and concise description of the changes made.
- If you include a video or screenshot, would be awesome so we can see the changes in action.
- If your pull request fixes an open issue, please reference the issue in the pull request description.
- Once your pull request is merged, you will be automatically added as a contributor to the project.
- Branch from `main`
- Provide a clear description of your changes
- Reference any related issues
- Include a screenshot/video if applicable
Thank you for your contribution!

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 Mauricio Siu
Copyright (c) 2026 Dokploy Technologies, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

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,59 @@
{
"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",
"typescript": "^5"
"tsx": "^4.21.0",
"typescript": "^5",
"vitest": "^4.1.4"
},
"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 .",
"test": "vitest run",
"publish": "npm publish"
},
"types": "dist/index.d.ts"
"types": "dist/index.d.ts",
"pnpm": {
"onlyBuiltDependencies": ["esbuild"]
}
}

6330
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

217
readme.md
View File

@@ -1,131 +1,144 @@
# Dokploy CLI
<!-- ![Dokploy Logo](https://via.placeholder.com/150x150.png?text=Dokploy+CLI) -->
Dokploy CLI is a powerful and versatile command-line tool designed to remotely manage your Dokploy server. It simplifies the process of creating, deploying, and managing applications and databases.
<!-- [![oclif](https://img.shields.io/badge/cli-oclif-brightgreen.svg)](https://oclif.io)
[![Version](https://img.shields.io/npm/v/dokploy.svg)](https://npmjs.org/package/dokploy)
[![Downloads/week](https://img.shields.io/npm/dw/dokploy.svg)](https://npmjs.org/package/dokploy)
[![License](https://img.shields.io/npm/l/dokploy.svg)](https://github.com/yourusername/dokploy/blob/master/package.json) -->
## Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [Commands](#commands)
- [Authentication](#authentication)
- [Project Management](#project-management)
- [Application Management](#application-management)
- [Environment Management](#environment-management)
- [Database Management](#database-management)
- [Contributing](#contributing)
- [Support](#support)
- [License](#license)
Dokploy CLI is a command-line tool to manage your Dokploy server remotely. It provides **449 commands** auto-generated from the Dokploy OpenAPI spec, covering every API endpoint.
## Installation
```sh-session
$ npm install -g @dokploy/cli
```bash
npm install -g @dokploy/cli
```
## Authentication
### Option 1: Using the `auth` command
```bash
dokploy auth -u https://panel.dokploy.com -t YOUR_API_KEY
```
### Option 2: Environment variables
```bash
export DOKPLOY_URL="https://panel.dokploy.com"
export DOKPLOY_API_KEY="YOUR_API_KEY"
```
### Option 3: `.env` file
Create a `.env` file in your working directory:
```env
DOKPLOY_URL="https://panel.dokploy.com"
DOKPLOY_API_KEY="YOUR_API_KEY"
```
The CLI loads it automatically. Shell environment variables take priority over the `.env` file.
## Usage
```sh-session
$ dokploy COMMAND
running command...
$ dokploy --version
dokploy/0.0.0 darwin-arm64 node-v18.18.0
$ dokploy --help [COMMAND]
USAGE
$ dokploy COMMAND
...
```bash
dokploy <group> <action> [options]
```
## Commands
### Examples
### Authentication
```bash
# List all projects
dokploy project all
- `dokploy authenticate`: Authenticate with the Dokploy server.
- `dokploy verify`: Verify current authentication.
# Get a specific project
dokploy project one --projectId abc123
### Project Management
# Create an application
dokploy application create --name "my-app" --environmentId env123
- `dokploy project:create`: Create a new project.
- `dokploy project:info`: Get information about an existing project.
- `dokploy project:list`: List all projects.
# Deploy an application
dokploy application deploy --applicationId app123
### Environment Management
# Create a postgres database
dokploy postgres create --name "my-db" --environmentId env123
- `dokploy environment:create`: Create a new environment.
- `dokploy environment:delete`: Delete an existing environment.
# Stop a database
dokploy postgres stop --postgresId pg123
### Application Management
- `dokploy app:create`: Create a new application.
- `dokploy app:delete`: Delete an existing application.
- `dokploy app:deploy`: Deploy an application.
- `dokploy app:stop`: Stop a running application.
### Enviroment Management
- `dokploy env pull <file>`: Pull environment variables from Dokploy in a <file>.
- `dokploy env push <file>`: Push environment variables to Dokploy from a <file>.
### Database Management
Dokploy supports various types of databases:
#### MariaDB
- `dokploy database:mariadb:create`
- `dokploy database:mariadb:delete`
- `dokploy database:mariadb:deploy`
- `dokploy database:mariadb:stop`
#### MongoDB
- `dokploy database:mongo:create`
- `dokploy database:mongo:delete`
- `dokploy database:mongo:deploy`
- `dokploy database:mongo:stop`
#### MySQL
- `dokploy database:mysql:create`
- `dokploy database:mysql:delete`
- `dokploy database:mysql:deploy`
- `dokploy database:mysql:stop`
#### PostgreSQL
- `dokploy database:postgres:create`
- `dokploy database:postgres:delete`
- `dokploy database:postgres:deploy`
- `dokploy database:postgres:stop`
#### Redis
- `dokploy database:redis:create`
- `dokploy database:redis:delete`
- `dokploy database:redis:deploy`
- `dokploy database:redis:stop`
For more information about a specific command, use:
```sh-session
$ dokploy [COMMAND] --help
# Get raw JSON output
dokploy project all --json
```
### Getting help
```bash
# List all groups
dokploy --help
# List actions in a group
dokploy application --help
# See options for a specific action
dokploy application deploy --help
```
## Available command groups
| Group | Commands | Group | Commands |
|---|---|---|---|
| `admin` | 1 | `notification` | 38 |
| `ai` | 9 | `organization` | 10 |
| `application` | 29 | `patch` | 12 |
| `backup` | 11 | `port` | 4 |
| `bitbucket` | 7 | `postgres` | 14 |
| `certificates` | 4 | `preview-deployment` | 4 |
| `cluster` | 4 | `project` | 8 |
| `compose` | 28 | `redirects` | 4 |
| `deployment` | 8 | `redis` | 14 |
| `destination` | 6 | `registry` | 7 |
| `docker` | 7 | `rollback` | 2 |
| `domain` | 9 | `schedule` | 6 |
| `environment` | 7 | `security` | 4 |
| `gitea` | 8 | `server` | 16 |
| `github` | 6 | `settings` | 49 |
| `gitlab` | 7 | `ssh-key` | 6 |
| `git-provider` | 2 | `sso` | 10 |
| `license-key` | 6 | `stripe` | 7 |
| `mariadb` | 14 | `swarm` | 3 |
| `mongo` | 14 | `user` | 18 |
| `mounts` | 6 | `volume-backups` | 6 |
| `mysql` | 14 | | |
## Development
```bash
# Install dependencies
pnpm install
# Run in dev mode
pnpm run dev -- project all
# Regenerate commands from OpenAPI spec
pnpm run generate
# Build
pnpm run build
# Lint & format
pnpm run lint
```
### Updating commands
Commands are auto-generated from `openapi.json`. To update:
1. Replace `openapi.json` with the latest spec from the [Dokploy repo](https://github.com/Dokploy/dokploy)
2. Run `pnpm run generate`
3. Build with `pnpm run build`
## Contributing
If you want to contribute to Dokploy CLI, please check out our [Contributing Guide](https://github.com/Dokploy/cli/blob/main/CONTRIBUTING.md).
## Support
If you encounter any issues or have any questions, please [open an issue](https://github.com/yourusername/dokploy/issues) in our GitHub repository.
If you encounter any issues or have any questions, please [open an issue](https://github.com/Dokploy/cli/issues) in our GitHub repository.
## License

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)}`);

106
src/client.ts Normal file
View File

@@ -0,0 +1,106 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import axios, { type AxiosInstance } from "axios";
import chalk from "chalk";
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;
}
function loadEnvFile(): void {
const envPath = path.resolve(process.cwd(), ".env");
if (!fs.existsSync(envPath)) return;
const content = fs.readFileSync(envPath, "utf8");
for (const line of content.split("\n")) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) continue;
const eqIndex = trimmed.indexOf("=");
if (eqIndex === -1) continue;
const key = trimmed.slice(0, eqIndex).trim();
const value = trimmed.slice(eqIndex + 1).trim().replace(/^["']|["']$/g, "");
if (!process.env[key]) {
process.env[key] = value;
}
}
}
export function readAuthConfig(): AuthConfig {
loadEnvFile();
const envToken =
process.env.DOKPLOY_API_KEY ?? 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}`));
}
}
}

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

@@ -0,0 +1,38 @@
import axios from "axios";
import chalk from "chalk";
import type { Command } from "commander";
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,30 @@
export {run} from '@oclif/core'
#!/usr/bin/env node
import chalk from "chalk";
import { program } from "commander";
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)
.action(() => {
program.help();
});
registerAuthCommand(program);
registerGeneratedCommands(program);
const argv = process.argv.filter((arg) => arg !== "--");
program.parseAsync(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 };
};

86
tests/cli.test.ts Normal file
View File

@@ -0,0 +1,86 @@
import { execFileSync } from "node:child_process";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, "..");
const CLI = path.join(ROOT, "dist", "index.js");
function run(...args: string[]): string {
return execFileSync("node", [CLI, ...args], {
encoding: "utf8",
env: { ...process.env, NO_COLOR: "1" },
});
}
describe("CLI", () => {
it("should show help with --help", () => {
const output = run("--help");
expect(output).toContain("Dokploy CLI");
expect(output).toContain("auth");
expect(output).toContain("application");
expect(output).toContain("project");
});
it("should show version with --version", () => {
const output = run("--version");
expect(output.trim()).toMatch(/^\d+\.\d+\.\d+$/);
});
it("should show subcommands for application", () => {
const output = run("application", "--help");
expect(output).toContain("create");
expect(output).toContain("deploy");
expect(output).toContain("delete");
expect(output).toContain("stop");
});
it("should show subcommands for postgres", () => {
const output = run("postgres", "--help");
expect(output).toContain("create");
expect(output).toContain("deploy");
expect(output).toContain("remove");
});
it("should show options for a specific command", () => {
const output = run("application", "create", "--help");
expect(output).toContain("--name");
expect(output).toContain("--environmentId");
});
it("should show auth command options", () => {
const output = run("auth", "--help");
expect(output).toContain("--url");
expect(output).toContain("--token");
});
it("should show all expected command groups", () => {
const output = run("--help");
const expectedGroups = [
"application",
"postgres",
"mysql",
"redis",
"mongo",
"mariadb",
"compose",
"docker",
"project",
"server",
"domain",
"backup",
"settings",
"user",
"environment",
];
for (const group of expectedGroups) {
expect(output).toContain(group);
}
});
it("should exit with 0 when no args provided", () => {
const output = run();
expect(output).toContain("Usage:");
});
});

56
tests/client.test.ts Normal file
View File

@@ -0,0 +1,56 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
describe("readAuthConfig", () => {
const originalEnv = { ...process.env };
beforeEach(() => {
delete process.env.DOKPLOY_URL;
delete process.env.DOKPLOY_API_KEY;
delete process.env.DOKPLOY_AUTH_TOKEN;
});
afterEach(() => {
process.env = { ...originalEnv };
vi.restoreAllMocks();
});
it("should read from DOKPLOY_API_KEY env var", async () => {
process.env.DOKPLOY_URL = "https://test.dokploy.com";
process.env.DOKPLOY_API_KEY = "test-key-123";
const { readAuthConfig } = await import("../src/client.js");
const config = readAuthConfig();
expect(config.url).toBe("https://test.dokploy.com");
expect(config.token).toBe("test-key-123");
});
it("should read from DOKPLOY_AUTH_TOKEN env var as fallback", async () => {
process.env.DOKPLOY_URL = "https://test.dokploy.com";
process.env.DOKPLOY_AUTH_TOKEN = "auth-token-456";
const { readAuthConfig } = await import("../src/client.js");
const config = readAuthConfig();
expect(config.url).toBe("https://test.dokploy.com");
expect(config.token).toBe("auth-token-456");
});
it("should prefer DOKPLOY_API_KEY over DOKPLOY_AUTH_TOKEN", async () => {
process.env.DOKPLOY_URL = "https://test.dokploy.com";
process.env.DOKPLOY_API_KEY = "api-key";
process.env.DOKPLOY_AUTH_TOKEN = "auth-token";
const { readAuthConfig } = await import("../src/client.js");
const config = readAuthConfig();
expect(config.token).toBe("api-key");
});
});
describe("saveAuthConfig", () => {
it("should write config with correct structure", async () => {
const { saveAuthConfig } = await import("../src/client.js");
expect(typeof saveAuthConfig).toBe("function");
});
});

63
tests/generator.test.ts Normal file
View File

@@ -0,0 +1,63 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, "..");
describe("generator", () => {
const specPath = path.join(ROOT, "openapi.json");
const generatedPath = path.join(ROOT, "src", "generated", "commands.ts");
it("openapi.json should exist", () => {
expect(fs.existsSync(specPath)).toBe(true);
});
it("generated commands file should exist", () => {
expect(fs.existsSync(generatedPath)).toBe(true);
});
it("generated file should export registerGeneratedCommands", () => {
const content = fs.readFileSync(generatedPath, "utf8");
expect(content).toContain("export function registerGeneratedCommands");
});
it("generated file should import from client", () => {
const content = fs.readFileSync(generatedPath, "utf8");
expect(content).toContain('from "../client.js"');
});
it("generated file should contain command groups from the spec", () => {
const spec = JSON.parse(fs.readFileSync(specPath, "utf8"));
const content = fs.readFileSync(generatedPath, "utf8");
// Only check groups that have a dot-separated action (group.action)
const groups = new Set<string>();
for (const p of Object.keys(spec.paths)) {
const clean = p.replace(/^\//, "");
const [group, ...rest] = clean.split(".");
if (group && rest.length > 0) groups.add(group);
}
for (const group of groups) {
expect(content).toContain(`"${group}.`);
}
});
it("number of apiPost/apiGet calls should match valid endpoints", () => {
const spec = JSON.parse(fs.readFileSync(specPath, "utf8"));
const content = fs.readFileSync(generatedPath, "utf8");
// Count only endpoints with group.action pattern
let validEndpoints = 0;
for (const p of Object.keys(spec.paths)) {
const clean = p.replace(/^\//, "");
const [group, ...rest] = clean.split(".");
if (group && rest.length > 0) validEndpoints++;
}
const apiCalls = (content.match(/await api(Post|Get)\(/g) || []).length;
expect(apiCalls).toBe(validEndpoints);
});
});

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"}