Merge pull request #1 from Dokploy/feat/cli-oclf

Feat/cli oclf
This commit is contained in:
Mauricio Siu
2024-06-22 23:01:07 -06:00
committed by GitHub
75 changed files with 9411 additions and 1 deletions

1
.eslintignore Normal file
View File

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

3
.eslintrc.json Normal file
View File

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

56
.github/workflows/onPushToMain.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
# test
name: version, tag and github release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- 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
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 "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
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

18
.github/workflows/onRelease.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
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 }}

23
.github/workflows/test.yml vendored Normal file
View File

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

14
.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
*-debug.log
*-error.log
**/.DS_Store
/.idea
/dist
/tmp
/node_modules
oclif.manifest.json
yarn.lock
package-lock.json

15
.mocharc.json Normal file
View File

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

1
.prettierrc.json Normal file
View File

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

20
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach",
"port": 9229,
"skipFiles": ["<node_internals>/**"]
},
{
"type": "node",
"request": "launch",
"name": "Execute Command",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/bin/dev",
"args": ["hello", "world"]
}
]
}

3
bin/dev.cmd Normal file
View File

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

6
bin/dev.js Executable file
View File

@@ -0,0 +1,6 @@
#!/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})

3
bin/run.cmd Normal file
View File

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

5
bin/run.js Executable file
View File

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

4
config.json Normal file
View File

@@ -0,0 +1,4 @@
{
"token": "icsy0appti460sbh5be1sevami702rc8a57l2e8h",
"url": "http://localhost:3000"
}

80
package.json Normal file
View File

@@ -0,0 +1,80 @@
{
"name": "dokploy",
"description": "A CLI to manage dokploy server remotely",
"version": "0.0.0",
"author": "Mauricio Siu",
"bin": {
"dokploy": "./bin/run.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"
},
"devDependencies": {
"@oclif/prettier-config": "^0.2.1",
"@oclif/test": "^4",
"@types/chai": "^4",
"@types/inquirer": "9.0.7",
"@types/mocha": "^10",
"@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"
},
"engines": {
"node": ">=18.0.0"
},
"files": [
"/bin",
"/dist",
"/oclif.manifest.json"
],
"homepage": "https://github.com/Dokploy/cli",
"keywords": [
"oclif"
],
"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"
},
"types": "dist/index.d.ts"
}

5309
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

397
readme.md
View File

@@ -1 +1,396 @@
# cli
dokploy
=================
A CLI to manage dokploy server remotely
[![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)
<!-- toc -->
* [Usage](#usage)
* [Commands](#commands)
<!-- tocstop -->
# Usage
<!-- usage -->
```sh-session
$ npm install -g dokploy
$ dokploy COMMAND
running command...
$ dokploy (--version)
dokploy/0.0.0 darwin-arm64 node-v18.18.0
$ dokploy --help [COMMAND]
USAGE
$ dokploy COMMAND
...
```
<!-- usagestop -->
# Commands
<!-- commands -->
* [`dokploy hello PERSON`](#dokploy-hello-person)
* [`dokploy hello world`](#dokploy-hello-world)
* [`dokploy help [COMMAND]`](#dokploy-help-command)
* [`dokploy plugins`](#dokploy-plugins)
* [`dokploy plugins add PLUGIN`](#dokploy-plugins-add-plugin)
* [`dokploy plugins:inspect PLUGIN...`](#dokploy-pluginsinspect-plugin)
* [`dokploy plugins install PLUGIN`](#dokploy-plugins-install-plugin)
* [`dokploy plugins link PATH`](#dokploy-plugins-link-path)
* [`dokploy plugins remove [PLUGIN]`](#dokploy-plugins-remove-plugin)
* [`dokploy plugins reset`](#dokploy-plugins-reset)
* [`dokploy plugins uninstall [PLUGIN]`](#dokploy-plugins-uninstall-plugin)
* [`dokploy plugins unlink [PLUGIN]`](#dokploy-plugins-unlink-plugin)
* [`dokploy plugins update`](#dokploy-plugins-update)
## `dokploy hello PERSON`
Say hello
```
USAGE
$ dokploy hello PERSON -f <value>
ARGUMENTS
PERSON Person to say hello to
FLAGS
-f, --from=<value> (required) Who is saying hello
DESCRIPTION
Say hello
EXAMPLES
$ dokploy hello friend --from oclif
hello friend from oclif! (./src/commands/hello/index.ts)
```
_See code: [src/commands/hello/index.ts](https://github.com/Dokploy/cli/blob/v0.0.0/src/commands/hello/index.ts)_
## `dokploy hello world`
Say hello world
```
USAGE
$ dokploy hello world
DESCRIPTION
Say hello world
EXAMPLES
$ dokploy hello world
hello world! (./src/commands/hello/world.ts)
```
_See code: [src/commands/hello/world.ts](https://github.com/Dokploy/cli/blob/v0.0.0/src/commands/hello/world.ts)_
## `dokploy help [COMMAND]`
Display help for dokploy.
```
USAGE
$ dokploy help [COMMAND...] [-n]
ARGUMENTS
COMMAND... Command to show help for.
FLAGS
-n, --nested-commands Include all nested commands in the output.
DESCRIPTION
Display help for dokploy.
```
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.1.0/src/commands/help.ts)_
## `dokploy plugins`
List installed plugins.
```
USAGE
$ dokploy plugins [--json] [--core]
FLAGS
--core Show core plugins.
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
List installed plugins.
EXAMPLES
$ dokploy plugins
```
_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.2.1/src/commands/plugins/index.ts)_
## `dokploy plugins add PLUGIN`
Installs a plugin into dokploy.
```
USAGE
$ dokploy plugins add PLUGIN... [--json] [-f] [-h] [-s | -v]
ARGUMENTS
PLUGIN... Plugin to install.
FLAGS
-f, --force Force npm to fetch remote resources even if a local copy exists on disk.
-h, --help Show CLI help.
-s, --silent Silences npm output.
-v, --verbose Show verbose npm output.
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Installs a plugin into dokploy.
Uses bundled npm executable to install plugins into /Users/mauricio/.local/share/dokploy
Installation of a user-installed plugin will override a core plugin.
Use the DOKPLOY_NPM_LOG_LEVEL environment variable to set the npm loglevel.
Use the DOKPLOY_NPM_REGISTRY environment variable to set the npm registry.
ALIASES
$ dokploy plugins add
EXAMPLES
Install a plugin from npm registry.
$ dokploy plugins add myplugin
Install a plugin from a github url.
$ dokploy plugins add https://github.com/someuser/someplugin
Install a plugin from a github slug.
$ dokploy plugins add someuser/someplugin
```
## `dokploy plugins:inspect PLUGIN...`
Displays installation properties of a plugin.
```
USAGE
$ dokploy plugins inspect PLUGIN...
ARGUMENTS
PLUGIN... [default: .] Plugin to inspect.
FLAGS
-h, --help Show CLI help.
-v, --verbose
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Displays installation properties of a plugin.
EXAMPLES
$ dokploy plugins inspect myplugin
```
_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.2.1/src/commands/plugins/inspect.ts)_
## `dokploy plugins install PLUGIN`
Installs a plugin into dokploy.
```
USAGE
$ dokploy plugins install PLUGIN... [--json] [-f] [-h] [-s | -v]
ARGUMENTS
PLUGIN... Plugin to install.
FLAGS
-f, --force Force npm to fetch remote resources even if a local copy exists on disk.
-h, --help Show CLI help.
-s, --silent Silences npm output.
-v, --verbose Show verbose npm output.
GLOBAL FLAGS
--json Format output as json.
DESCRIPTION
Installs a plugin into dokploy.
Uses bundled npm executable to install plugins into /Users/mauricio/.local/share/dokploy
Installation of a user-installed plugin will override a core plugin.
Use the DOKPLOY_NPM_LOG_LEVEL environment variable to set the npm loglevel.
Use the DOKPLOY_NPM_REGISTRY environment variable to set the npm registry.
ALIASES
$ dokploy plugins add
EXAMPLES
Install a plugin from npm registry.
$ dokploy plugins install myplugin
Install a plugin from a github url.
$ dokploy plugins install https://github.com/someuser/someplugin
Install a plugin from a github slug.
$ dokploy plugins install someuser/someplugin
```
_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.2.1/src/commands/plugins/install.ts)_
## `dokploy plugins link PATH`
Links a plugin into the CLI for development.
```
USAGE
$ dokploy plugins link PATH [-h] [--install] [-v]
ARGUMENTS
PATH [default: .] path to plugin
FLAGS
-h, --help Show CLI help.
-v, --verbose
--[no-]install Install dependencies after linking the plugin.
DESCRIPTION
Links a plugin into the CLI for development.
Installation of a linked plugin will override a user-installed or core plugin.
e.g. If you have a user-installed or core plugin that has a 'hello' command, installing a linked plugin with a 'hello'
command will override the user-installed or core plugin implementation. This is useful for development work.
EXAMPLES
$ dokploy plugins link myplugin
```
_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.2.1/src/commands/plugins/link.ts)_
## `dokploy plugins remove [PLUGIN]`
Removes a plugin from the CLI.
```
USAGE
$ dokploy plugins remove [PLUGIN...] [-h] [-v]
ARGUMENTS
PLUGIN... plugin to uninstall
FLAGS
-h, --help Show CLI help.
-v, --verbose
DESCRIPTION
Removes a plugin from the CLI.
ALIASES
$ dokploy plugins unlink
$ dokploy plugins remove
EXAMPLES
$ dokploy plugins remove myplugin
```
## `dokploy plugins reset`
Remove all user-installed and linked plugins.
```
USAGE
$ dokploy plugins reset [--hard] [--reinstall]
FLAGS
--hard Delete node_modules and package manager related files in addition to uninstalling plugins.
--reinstall Reinstall all plugins after uninstalling.
```
_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.2.1/src/commands/plugins/reset.ts)_
## `dokploy plugins uninstall [PLUGIN]`
Removes a plugin from the CLI.
```
USAGE
$ dokploy plugins uninstall [PLUGIN...] [-h] [-v]
ARGUMENTS
PLUGIN... plugin to uninstall
FLAGS
-h, --help Show CLI help.
-v, --verbose
DESCRIPTION
Removes a plugin from the CLI.
ALIASES
$ dokploy plugins unlink
$ dokploy plugins remove
EXAMPLES
$ dokploy plugins uninstall myplugin
```
_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.2.1/src/commands/plugins/uninstall.ts)_
## `dokploy plugins unlink [PLUGIN]`
Removes a plugin from the CLI.
```
USAGE
$ dokploy plugins unlink [PLUGIN...] [-h] [-v]
ARGUMENTS
PLUGIN... plugin to uninstall
FLAGS
-h, --help Show CLI help.
-v, --verbose
DESCRIPTION
Removes a plugin from the CLI.
ALIASES
$ dokploy plugins unlink
$ dokploy plugins remove
EXAMPLES
$ dokploy plugins unlink myplugin
```
## `dokploy plugins update`
Update installed plugins.
```
USAGE
$ dokploy plugins update [-h] [-v]
FLAGS
-h, --help Show CLI help.
-v, --verbose
DESCRIPTION
Update installed plugins.
```
_See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.2.1/src/commands/plugins/update.ts)_
<!-- commandsstop -->

103
src/commands/app/create.ts Normal file
View File

@@ -0,0 +1,103 @@
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,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(AppCreate);
let { projectId } = flags;
if (!projectId) {
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 a project to create the application in:",
name: "project",
type: "list",
},
]);
projectId = project.projectId;
const appDetails = await inquirer.prompt([
{
message: "Enter the application name:",
name: "name",
type: "input",
validate: (input) => (input ? true : "Application name is required"),
},
{
message: "Enter the application description (optional):",
name: "appDescription",
type: "input",
},
]);
const appName = await inquirer.prompt([
{
default: `${slugify(project.name)}-${appDetails.name}`,
message: "Enter the App name: (optional):",
name: "appName",
type: "input",
validate: (input) => (input ? true : "App name is required"),
},
]);
const response = await axios.post(
`${auth.url}/api/trpc/application.create`,
{
json: {
...appDetails,
appName: appName.appName,
projectId: project.projectId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error creating application"));
}
this.log(
chalk.green(`Application '${appDetails.name}' created successfully.`),
);
}
}
}

108
src/commands/app/delete.ts Normal file
View File

@@ -0,0 +1,108 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { getProject, getProjects } 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,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(AppDelete);
let { projectId } = flags;
if (!projectId) {
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 a project to create the application in:",
name: "project",
type: "list",
},
]);
projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.applications.length === 0) {
this.error(chalk.yellow("No applications found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.applications.map((app) => ({
name: app.name,
value: app.applicationId,
})),
message: "Select the application to delete:",
name: "selectedApp",
type: "list",
},
]);
const applicationId = appAnswers.selectedApp;
// // Confirmar eliminación
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."));
}
const deleteResponse = await axios.post(
`${auth.url}/api/trpc/application.delete`,
{
json: {
applicationId,
},
},
{
headers: {
Authorization: `Bearer ${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."));
}
}
}

View File

@@ -0,0 +1,89 @@
import { Command } 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 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"];
public async run(): Promise<void> {
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 a project to deploy the application in:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.applications.length === 0) {
this.error(chalk.yellow("No applications found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.applications.map((app) => ({
name: app.name,
value: app.applicationId,
})),
message: "Select the application to deploy:",
name: "selectedApp",
type: "list",
},
]);
const applicationId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to deploy this application?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("Application deployment cancelled."));
}
const response = await axios.post(
`${auth.url}/api/trpc/application.deploy`,
{
json: {
applicationId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error deploying application"));
}
this.log(chalk.green("Application deploy successful."));
}
}

89
src/commands/app/stop.ts Normal file
View File

@@ -0,0 +1,89 @@
import { Command } from "@oclif/core";
import { readAuthConfig } from "../../utils/utils.js";
import chalk from "chalk";
import inquirer from "inquirer";
import { getProject, getProjects } 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"];
public async run(): Promise<void> {
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 a project to stop the application in:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.applications.length === 0) {
this.error(chalk.yellow("No applications found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.applications.map((app) => ({
name: app.name,
value: app.applicationId,
})),
message: "Select the application to stop:",
name: "selectedApp",
type: "list",
},
]);
const applicationId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to stop this application?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("Application stop cancelled."));
}
const response = await axios.post(
`${auth.url}/api/trpc/application.stop`,
{
json: {
applicationId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error stopping application"));
}
this.log(chalk.green("Application stop successful."));
}
}

View File

@@ -0,0 +1,106 @@
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.post(
`${url}/api/trpc/auth.verifyToken`,
{},
{
headers: {
Authorization: `Bearer ${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

@@ -0,0 +1,129 @@
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 } 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,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMariadbCreate);
let { projectId } = flags;
if (!projectId) {
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 a project to create the MariaDB database in:",
name: "project",
type: "list",
},
]);
projectId = project.projectId;
const dbDetails = await inquirer.prompt([
{
message: "Enter the name:",
name: "name",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
},
{
message: "Database name:",
name: "databaseName",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
},
{
message: "Enter the database description (optional):",
name: "description",
type: "input",
},
{
message: "Database Root Password (optional):",
name: "databaseRootPassword",
type: "password",
},
{
message: "Database password (optional):",
name: "databasePassword",
type: "password",
},
{
default: "mariadb:11",
message: "Docker Image (default: mariadb:11):",
name: "dockerImage",
type: "input",
},
{
default: "mariadb",
message: "Database User: (default: mariadb):",
name: "databaseUser",
type: "input",
},
]);
const appName = await inquirer.prompt([
{
default: `${slugify(project.name)}-${dbDetails.name}`,
message: "Enter the App name:",
name: "appName",
type: "input",
validate: (input) => (input ? true : "App name is required"),
},
]);
const response = await axios.post(
`${auth.url}/api/trpc/mariadb.create`,
{
json: {
...dbDetails,
appName: appName.appName,
projectId: project.projectId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error creating MariaDB database"));
}
this.log(
chalk.green(
`MariaDB database '${dbDetails.name}' created successfully.`,
),
);
}
}
}

View File

@@ -0,0 +1,113 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { getProject, getProjects } 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,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMariadbDelete);
let { projectId } = flags;
if (!projectId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
if (projects.length === 0) {
this.log(chalk.yellow("No projects found."));
return;
}
const answers = await inquirer.prompt([
{
type: "list",
name: "selectedProject",
message: "Select a project to delete the MariaDB database from:",
choices: projects.map((project: any) => ({
name: project.name,
value: project.projectId,
})),
},
]);
projectId = answers.selectedProject;
}
try {
const project = await getProject(projectId, auth, this);
if (!project.mariadb || project.mariadb.length === 0) {
this.log(chalk.yellow("No MariaDB databases found in this project."));
return;
}
const appAnswers = await inquirer.prompt([
{
type: "list",
name: "selectedDb",
message: "Select the MariaDB database to delete:",
choices: project.mariadb.map((db: any) => ({
name: db.name,
value: db.mariadbId,
})),
},
]);
const mariadbId = appAnswers.selectedDb;
const confirmAnswers = await inquirer.prompt([
{
type: "confirm",
name: "confirmDelete",
message: "Are you sure you want to delete this MariaDB database?",
default: false,
},
]);
if (!confirmAnswers.confirmDelete) {
this.log(chalk.yellow("Database deletion cancelled."));
return;
}
const deleteResponse = await axios.post(
`${auth.url}/api/trpc/mariadb.remove`,
{
json: {
mariadbId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (!deleteResponse.data.result.data.json) {
this.error(chalk.red("Error deleting mariadb database"));
}
this.log(chalk.green("MariaDB database deleted successfully."));
} catch (error) {
this.error(
// @ts-ignore
chalk.red(`Failed to delete MariaDB database: ${error.message}`),
);
}
}
}

View File

@@ -0,0 +1,89 @@
import { Command } 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 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"];
public async run(): Promise<void> {
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 a project to deploy the mariadb in:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.mariadb.length === 0) {
this.error(chalk.yellow("No mariadb found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.mariadb.map((app) => ({
name: app.name,
value: app.mariadbId,
})),
message: "Select the mariadb to deploy:",
name: "selectedApp",
type: "list",
},
]);
const mariadbId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to deploy this mariadb?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("mariadb deployment cancelled."));
}
const response = await axios.post(
`${auth.url}/api/trpc/mariadb.deploy`,
{
json: {
mariadbId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error deploying mariadb"));
}
this.log(chalk.green("Mariadb deploy successful."));
}
}

View File

@@ -0,0 +1,89 @@
import { Command } from "@oclif/core";
import chalk from "chalk";
import inquirer from "inquirer";
import axios from "axios";
import { getProject, getProjects } from "../../../utils/shared.js";
import { readAuthConfig } from "../../../utils/utils.js";
import 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"];
public async run(): Promise<void> {
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 a project to stop the mariadb in:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.mariadb.length === 0) {
this.error(chalk.yellow("No mariadb found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.mariadb.map((app) => ({
name: app.name,
value: app.mariadbId,
})),
message: "Select the mariadb to stop:",
name: "selectedApp",
type: "list",
},
]);
const mariadbId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to stop this mariadb?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("Mariadb stop cancelled."));
}
const response = await axios.post(
`${auth.url}/api/trpc/mariadb.stop`,
{
json: {
mariadbId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error stopping mariadb"));
}
this.log(chalk.green("Mariadb stop successful."));
}
}

View File

@@ -0,0 +1,131 @@
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 } 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,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMongoCreate);
let { projectId } = flags;
if (!projectId) {
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 a project to create the MongoDB database in:",
name: "project",
type: "list",
},
]);
projectId = project.projectId;
const dbDetails = await inquirer.prompt([
{
message: "Enter the name:",
name: "name",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
},
{
message: "Database name:",
name: "databaseName",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
},
{
message: "Enter the database description (optional):",
name: "description",
type: "input",
},
{
message: "Database password (optional):",
name: "databasePassword",
type: "password",
},
{
default: "mongo:6",
message: "Docker Image (default: mongo:6):",
name: "dockerImage",
type: "input",
},
{
default: "mongo",
message: "Database User: (default: mongo):",
name: "databaseUser",
type: "input",
},
]);
const appName = await inquirer.prompt([
{
default: `${slugify(project.name)}-${dbDetails.name}`,
message: "Enter the App name:",
name: "appName",
type: "input",
validate: (input) => (input ? true : "App name is required"),
},
]);
try {
const response = await axios.post(
`${auth.url}/api/trpc/mongo.create`,
{
json: {
...dbDetails,
appName: appName.appName,
projectId: project.projectId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error creating MongoDB database"));
}
this.log(
chalk.green(
`MongoDB database '${dbDetails.name}' created successfully.`,
),
);
} catch (error) {
this.error(
// @ts-ignore
chalk.red(`Failed to create MongoDB database: ${error.message}`),
);
}
}
}
}

View File

@@ -0,0 +1,119 @@
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 } 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,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMongoDelete);
let { projectId } = flags;
if (!projectId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
if (projects.length === 0) {
this.log(chalk.yellow("No projects found."));
return;
}
const answers = await inquirer.prompt([
{
choices: projects.map((project: any) => ({
name: project.name,
value: project.projectId,
})),
message: "Select a project to delete the MongoDB database from:",
name: "selectedProject",
type: "list",
},
]);
projectId = answers.selectedProject;
}
try {
const project = await getProject(projectId, auth, this);
if (!project.mongo || project.mongo.length === 0) {
this.log(chalk.yellow("No MongoDB databases found in this project."));
return;
}
const appAnswers = await inquirer.prompt([
{
choices: project.mongo.map((db: any) => ({
name: db.name,
value: db.mongoId,
})),
message: "Select the MongoDB database to delete:",
name: "selectedApp",
type: "list",
},
]);
const mongoId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to delete this MongoDB database?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.log(chalk.yellow("Database deletion cancelled."));
return;
}
const deleteResponse = await axios.post(
`${auth.url}/api/trpc/mongo.remove`,
{
json: {
mongoId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (!deleteResponse.data.result.data.json) {
this.error(chalk.red("Error deleting MongoDB database"));
}
this.log(chalk.green("MongoDB database deleted successfully."));
} catch (error) {
this.error(
// @ts-ignore
chalk.red(`Failed to delete MongoDB database: ${error.message}`),
);
}
}
}

View File

@@ -0,0 +1,89 @@
import { Command } 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 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"];
public async run(): Promise<void> {
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 a project to deploy the mongo in:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.mongo.length === 0) {
this.error(chalk.yellow("No mongo found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.mongo.map((app) => ({
name: app.name,
value: app.mongoId,
})),
message: "Select the mongo to deploy:",
name: "selectedApp",
type: "list",
},
]);
const mongoId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to deploy this mongo?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("mongo deployment cancelled."));
}
const response = await axios.post(
`${auth.url}/api/trpc/mongo.deploy`,
{
json: {
mongoId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error deploying mongo"));
}
this.log(chalk.green("Mongo deploy successful."));
}
}

View File

@@ -0,0 +1,89 @@
import { Command } from "@oclif/core";
import chalk from "chalk";
import inquirer from "inquirer";
import axios from "axios";
import { getProject, getProjects } from "../../../utils/shared.js";
import { readAuthConfig } from "../../../utils/utils.js";
import 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"];
public async run(): Promise<void> {
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 a project to stop the mongo in:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.mongo.length === 0) {
this.error(chalk.yellow("No mongo found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.mongo.map((app) => ({
name: app.name,
value: app.mongoId,
})),
message: "Select the mongo to stop:",
name: "selectedApp",
type: "list",
},
]);
const mongoId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to stop this mongo?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("mongo stop cancelled."));
}
const response = await axios.post(
`${auth.url}/api/trpc/mongo.stop`,
{
json: {
mongoId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error stopping mongo"));
}
this.log(chalk.green("Mongo stop successful."));
}
}

View File

@@ -0,0 +1,137 @@
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 } 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,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMysqlCreate);
let { projectId } = flags;
if (!projectId) {
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 a project to create the MySQL database in:",
name: "project",
type: "list",
},
]);
projectId = project.projectId;
const dbDetails = await inquirer.prompt([
{
message: "Enter the name:",
name: "name",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
},
{
message: "Database name:",
name: "databaseName",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
},
{
message: "Enter the database description (optional):",
name: "description",
type: "input",
},
{
message: "Database Root Password (optional):",
name: "databaseRootPassword",
type: "password",
},
{
message: "Database password (optional):",
name: "databasePassword",
type: "password",
},
{
default: "mysql:8",
message: "Docker Image (default: mysql:8):",
name: "dockerImage",
type: "input",
},
{
default: "mysql",
message: "Database User: (default: mysql):",
name: "databaseUser",
type: "input",
},
]);
const appName = await inquirer.prompt([
{
default: `${slugify(project.name)}-${dbDetails.name}`,
message: "Enter the App name:",
name: "appName",
type: "input",
validate: (input) => (input ? true : "App name is required"),
},
]);
try {
const response = await axios.post(
`${auth.url}/api/trpc/mysql.create`,
{
json: {
...dbDetails,
appName: appName.appName,
projectId: project.projectId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error creating MySQL database"));
}
this.log(
chalk.green(
`MySQL database '${dbDetails.name}' created successfully.`,
),
);
} catch (error) {
this.error(
// @ts-ignore
chalk.red(`Failed to create MySQL database: ${error.message}`),
);
}
}
}
}

View File

@@ -0,0 +1,120 @@
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 } 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,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMysqlDelete);
let { projectId } = flags;
if (!projectId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
if (projects.length === 0) {
this.log(chalk.yellow("No projects found."));
return;
}
const answers = await inquirer.prompt([
{
choices: projects.map((project: any) => ({
name: project.name,
value: project.projectId,
})),
message: "Select a project to delete the MySQL database from:",
name: "selectedProject",
type: "list",
},
]);
projectId = answers.selectedProject;
}
try {
const project = await getProject(projectId, auth, this);
if (!project.mysql || project.mysql.length === 0) {
this.log(chalk.yellow("No MySQL databases found in this project."));
return;
}
// Permitir al usuario seleccionar una aplicación
const appAnswers = await inquirer.prompt([
{
choices: project.mysql.map((app: any) => ({
name: app.name,
value: app.mysqlId,
})),
message: "Select the MySQL database to delete:",
name: "selectedApp",
type: "list",
},
]);
const mysqlId = appAnswers.selectedApp;
// Confirmar eliminación
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to delete this mysql database?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.log(chalk.yellow("Application deletion cancelled."));
return;
}
// Eliminar la aplicación seleccionada
const deleteResponse = await axios.post(
`${auth.url}/api/trpc/mysql.remove`,
{
json: {
mysqlId,
},
},
{
headers: {
Authorization: `Bearer ${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) {
// @ts-expect-error - TS2339: Property 'data' does not exist on type 'AxiosError<any>'.
this.error(chalk.red(`Failed to delete application: ${error.message}`));
}
}
}

View File

@@ -0,0 +1,89 @@
import { Command } 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 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"];
public async run(): Promise<void> {
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 a project to deploy the mysql in:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.mysql.length === 0) {
this.error(chalk.yellow("No mysql found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.mysql.map((app) => ({
name: app.name,
value: app.mysqlId,
})),
message: "Select the mysql to deploy:",
name: "selectedApp",
type: "list",
},
]);
const mysqlId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to deploy this mysql?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("mysql deployment cancelled."));
}
const response = await axios.post(
`${auth.url}/api/trpc/mysql.deploy`,
{
json: {
mysqlId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error deploying mysql"));
}
this.log(chalk.green("Mysql deployed successful."));
}
}

View File

@@ -0,0 +1,89 @@
import { Command } from "@oclif/core";
import chalk from "chalk";
import inquirer from "inquirer";
import axios from "axios";
import { getProject, getProjects } from "../../../utils/shared.js";
import { readAuthConfig } from "../../../utils/utils.js";
import 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"];
public async run(): Promise<void> {
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 a project to stop the mysql in:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.mysql.length === 0) {
this.error(chalk.yellow("No mysql found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.mysql.map((app) => ({
name: app.name,
value: app.mysqlId,
})),
message: "Select the mysql to stop:",
name: "selectedApp",
type: "list",
},
]);
const mysqlId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to stop this mysql?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("mysql stop cancelled."));
}
const response = await axios.post(
`${auth.url}/api/trpc/mysql.stop`,
{
json: {
mysqlId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error stopping mysql"));
}
this.log(chalk.green("Mysql stop successful."));
}
}

View File

@@ -0,0 +1,130 @@
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 } 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,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabasePostgresCreate);
let { projectId } = flags;
if (!projectId) {
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 a project to create the PostgreSQL database in:",
name: "project",
type: "list",
},
]);
projectId = project.projectId;
const dbDetails = await inquirer.prompt([
{
message: "Enter the name:",
name: "name",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
},
{
message: "Database name:",
name: "databaseName",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
},
{
message: "Enter the database description (optional):",
name: "description",
type: "input",
},
{
message: "Database password (optional):",
name: "databasePassword",
type: "password",
},
{
default: "postgres:15",
message: "Docker Image (default: postgres:15):",
name: "dockerImage",
type: "input",
},
{
default: "postgres",
message: "Database User: (default: postgres):",
name: "databaseUser",
type: "input",
},
]);
const appName = await inquirer.prompt([
{
default: `${slugify(project.name)}-${dbDetails.name}`,
message: "Enter the App name:",
name: "appName",
type: "input",
validate: (input) => (input ? true : "App name is required"),
},
]);
try {
const response = await axios.post(
`${auth.url}/api/trpc/postgres.create`,
{
json: {
...dbDetails,
appName: appName.appName,
projectId: project.projectId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error creating PostgreSQL database"));
}
this.log(
chalk.green(
`PostgreSQL database '${dbDetails.name}' created successfully.`,
),
);
} catch (error) {
this.error(
// @ts-ignore
chalk.red(`Failed to create PostgreSQL database: ${error.message}`),
);
}
}
}
}

View File

@@ -0,0 +1,119 @@
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 } 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,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabasePostgresDelete);
let { projectId } = flags;
if (!projectId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
if (projects.length === 0) {
this.log(chalk.yellow("No projects found."));
return;
}
const answers = await inquirer.prompt([
{
choices: projects.map((project: any) => ({
name: project.name,
value: project.projectId,
})),
message: "Select a project to delete the PostgreSQL database from:",
name: "selectedProject",
type: "list",
},
]);
projectId = answers.selectedProject;
}
try {
const project = await getProject(projectId, auth, this);
if (!project.postgres || project.postgres.length === 0) {
this.log(
chalk.yellow("No PostgreSQL databases found in this project."),
);
return;
}
const appAnswers = await inquirer.prompt([
{
choices: project.postgres.map((db: any) => ({
name: db.name,
value: db.postgresId,
})),
message: "Select the PostgreSQL database to delete:",
name: "selectedApp",
type: "list",
},
]);
const postgresId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to delete this postgres database?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.log(chalk.yellow("Database deletion cancelled."));
return;
}
const deleteResponse = await axios.post(
`${auth.url}/api/trpc/postgres.remove`,
{
json: {
postgresId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (!deleteResponse.data.result.data.json) {
this.error(chalk.red("Error deleting PostgreSQL database"));
}
this.log(chalk.green("PostgreSQL database deleted successfully."));
} catch (error) {
// @ts-expect-error - TS2339: Property 'data' does not exist on type 'AxiosError<any>'.
this.error(chalk.red(`Failed to delete application: ${error.message}`));
}
}
}

View File

@@ -0,0 +1,89 @@
import { Command } 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 type { Answers } from "../../app/create.js";
import axios from "axios";
export default class DatabasePostgresDeploy extends Command {
static description = "Deploy an postgres to a project.";
static examples = ["$ <%= config.bin %> app deploy"];
public async run(): Promise<void> {
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 a project to deploy the postgres in:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.postgres.length === 0) {
this.error(chalk.yellow("No postgres found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.postgres.map((app) => ({
name: app.name,
value: app.postgresId,
})),
message: "Select the postgres to deploy:",
name: "selectedApp",
type: "list",
},
]);
const postgresId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to deploy this postgres?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("postgres deployment cancelled."));
}
const response = await axios.post(
`${auth.url}/api/trpc/postgres.deploy`,
{
json: {
postgresId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error deploying postgres"));
}
this.log(chalk.green("Postgres deployed successful."));
}
}

View File

@@ -0,0 +1,89 @@
import { Command } from "@oclif/core";
import chalk from "chalk";
import inquirer from "inquirer";
import axios from "axios";
import { getProject, getProjects } from "../../../utils/shared.js";
import { readAuthConfig } from "../../../utils/utils.js";
import type { Answers } from "../../app/create.js";
export default class DatabasePostgresStop extends Command {
static description = "Stop an postgres from a project.";
static examples = ["$ <%= config.bin %> postgres stop"];
public async run(): Promise<void> {
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 a project to stop the postgres in:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.postgres.length === 0) {
this.error(chalk.yellow("No postgres found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.postgres.map((app) => ({
name: app.name,
value: app.postgresId,
})),
message: "Select the postgres to stop:",
name: "selectedApp",
type: "list",
},
]);
const postgresId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to stop this postgres?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("postgres stop cancelled."));
}
const response = await axios.post(
`${auth.url}/api/trpc/postgres.stop`,
{
json: {
postgresId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error stopping postgres"));
}
this.log(chalk.green("Postgres stop successful."));
}
}

View File

@@ -0,0 +1,119 @@
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 } from "../../../utils/shared.js";
import type { Answers } from "../../app/create.js";
export default class DatabaseRedisCreate extends Command {
static description = "Create a new Redis database within a project.";
static examples = ["$ <%= config.bin %> redis create"];
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(DatabaseRedisCreate);
let { projectId } = flags;
if (!projectId) {
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 a project to create the Redis database in:",
name: "project",
type: "list",
},
]);
projectId = project.projectId;
const dbDetails = await inquirer.prompt([
{
message: "Enter the name:",
name: "name",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
},
{
message: "Enter the database description (optional):",
name: "description",
type: "input",
},
{
message: "Database password (optional):",
name: "databasePassword",
type: "password",
},
{
default: "redis:7",
message: "Docker Image (default: redis:7):",
name: "dockerImage",
type: "input",
},
]);
const appName = await inquirer.prompt([
{
default: `${slugify(project.name)}-${dbDetails.name}`,
message: "Enter the App name:",
name: "appName",
type: "input",
validate: (input) => (input ? true : "App name is required"),
},
]);
try {
const response = await axios.post(
`${auth.url}/api/trpc/redis.create`,
{
json: {
...dbDetails,
appName: appName.appName,
projectId: project.projectId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error creating Redis database"));
}
this.log(
chalk.green(
`Redis database '${dbDetails.name}' created successfully.`,
),
);
} catch (error) {
this.error(
// @ts-ignore
chalk.red(`Failed to create Redis database: ${error.message}`),
);
}
}
}
}

View File

@@ -0,0 +1,120 @@
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 } from "../../../utils/shared.js";
export default class DatabaseRedisDelete extends Command {
static description = "Delete an redis database 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,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseRedisDelete);
let { projectId } = flags;
if (!projectId) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
if (projects.length === 0) {
this.log(chalk.yellow("No projects found."));
return;
}
const answers = await inquirer.prompt([
{
choices: projects.map((project: any) => ({
name: project.name,
value: project.projectId,
})),
message: "Select a project to delete the redis database from:",
name: "selectedProject",
type: "list",
},
]);
projectId = answers.selectedProject;
}
try {
const project = await getProject(projectId, auth, this);
if (!project.redis || project.redis.length === 0) {
this.log(chalk.yellow("No redis databases found in this project."));
return;
}
// Permitir al usuario seleccionar una aplicación
const appAnswers = await inquirer.prompt([
{
choices: project.redis.map((db: any) => ({
name: db.name,
value: db.redisId,
})),
message: "Select the redis database to delete:",
name: "selectedApp",
type: "list",
},
]);
const redisId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to delete this redis database?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.log(chalk.yellow("Database deletion cancelled."));
return;
}
const deleteResponse = await axios.post(
`${auth.url}/api/trpc/redis.remove`,
{
json: {
redisId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (!deleteResponse.data.result.data.json) {
this.error(chalk.red("Error deleting redis database"));
}
this.log(chalk.green("Redis database deleted successfully."));
} catch (error) {
this.error(
// @ts-expect-error - TS2339: Property 'data' does not exist on type 'AxiosError<any>'.
chalk.red(`Failed to delete redis database: ${error.message}`),
);
}
}
}

View File

@@ -0,0 +1,89 @@
import { Command } 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 type { Answers } from "../../app/create.js";
import axios from "axios";
export default class DatabaseRedisDeploy extends Command {
static description = "Deploy an redis to a project.";
static examples = ["$ <%= config.bin %> app deploy"];
public async run(): Promise<void> {
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 a project to deploy the redis in:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.redis.length === 0) {
this.error(chalk.yellow("No redis found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.redis.map((app) => ({
name: app.name,
value: app.redisId,
})),
message: "Select the redis to deploy:",
name: "selectedApp",
type: "list",
},
]);
const redisId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to deploy this redis?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("redis deployment cancelled."));
}
const response = await axios.post(
`${auth.url}/api/trpc/redis.deploy`,
{
json: {
redisId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error deploying redis"));
}
this.log(chalk.green("Redis deployed successful."));
}
}

View File

@@ -0,0 +1,89 @@
import { Command } from "@oclif/core";
import chalk from "chalk";
import inquirer from "inquirer";
import axios from "axios";
import { getProject, getProjects } from "../../../utils/shared.js";
import { readAuthConfig } from "../../../utils/utils.js";
import type { Answers } from "../../app/create.js";
export default class DatabaseRedisStop extends Command {
static description = "Stop an redis from a project.";
static examples = ["$ <%= config.bin %> redis stop"];
public async run(): Promise<void> {
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 a project to stop the redis in:",
name: "project",
type: "list",
},
]);
const projectId = project.projectId;
const projectSelected = await getProject(projectId, auth, this);
if (projectSelected.redis.length === 0) {
this.error(chalk.yellow("No redis found in this project."));
}
const appAnswers = await inquirer.prompt([
{
// @ts-ignore
choices: projectSelected.redis.map((app) => ({
name: app.name,
value: app.redisId,
})),
message: "Select the redis to stop:",
name: "selectedApp",
type: "list",
},
]);
const redisId = appAnswers.selectedApp;
const confirmAnswers = await inquirer.prompt([
{
default: false,
message: "Are you sure you want to stop this redis?",
name: "confirmDelete",
type: "confirm",
},
]);
if (!confirmAnswers.confirmDelete) {
this.error(chalk.yellow("redis stop cancelled."));
}
const response = await axios.post(
`${auth.url}/api/trpc/redis.stop`,
{
json: {
redisId,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (response.status !== 200) {
this.error(chalk.red("Error stopping redis"));
}
this.log(chalk.green("Redis stop successful."));
}
}

View File

@@ -0,0 +1,94 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer, { type Answers, type QuestionCollection } from "inquirer";
import { readAuthConfig } from "../../utils/utils.js";
export default class ProjectCreate extends Command {
static override description =
"Create a new project with an optional description.";
static override examples = [
"$ <%= config.bin %> <%= command.id %> -n MyProject -d 'This is my project description'",
"$ <%= config.bin %> <%= command.id %> -n MyProject",
"$ <%= config.bin %> <%= command.id %>",
];
static override flags = {
description: Flags.string({
char: "d",
description: "Description of the project",
required: false,
}),
name: Flags.string({
char: "n",
description: "Name of the project",
required: false,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
console.log(chalk.blue.bold("\n Create a New Project \n"));
const { flags } = await this.parse(ProjectCreate);
let answers: Answers = {};
const questions: QuestionCollection[] = [];
if (!flags.name) {
questions.push({
message: chalk.green("Enter the project name:"),
name: "name",
type: "input",
validate: (input) => (input ? true : "Project name is required"),
});
}
if (!flags.description) {
questions.push({
default: "",
message: chalk.green("Enter the project description (optional):"),
name: "description",
type: "input",
});
}
if (questions.length > 0) {
answers = await inquirer.prompt(questions);
}
const name = flags.name || answers.name;
const description = flags.description || answers.description;
try {
const response = await axios.post(
`${auth.url}/api/trpc/project.createCLI`,
{
json: {
description,
name,
},
},
{
headers: {
Authorization: `Bearer ${auth.token}`,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error`"));
}
this.log(chalk.green(`Project '${name}' created successfully.`));
} catch (error) {
// @ts-expect-error hola
this.error(chalk.red(`Failed to create project: ${error.message}`));
}
}
}

View File

@@ -0,0 +1,167 @@
import { Command, Flags } from "@oclif/core";
import chalk from "chalk";
import inquirer from "inquirer";
import { readAuthConfig } from "../../utils/utils.js";
import { getProject, 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 projectInfo = await getProject(projectId, auth, this);
this.log(chalk.green(`Project Name: ${projectInfo.name}`));
this.log(
chalk.green(
`Description: ${projectInfo?.description || "No description"}`,
),
);
this.log(
chalk.green(
`Number of Applications: ${projectInfo.applications.length}`,
),
);
this.log(
chalk.green(
`Number of Compose Services: ${projectInfo.compose.length}`,
),
);
this.log(
chalk.green(
`Number of MariaDB Databases: ${projectInfo.mariadb.length}`,
),
);
this.log(
chalk.green(`Number of MongoDB Databases: ${projectInfo.mongo.length}`),
);
this.log(
chalk.green(`Number of MySQL Databases: ${projectInfo.mysql.length}`),
);
this.log(
chalk.green(
`Number of PostgreSQL Databases: ${projectInfo.postgres.length}`,
),
);
this.log(
chalk.green(`Number of Redis Databases: ${projectInfo.redis.length}`),
);
if (projectInfo.applications.length > 0) {
this.log(chalk.blue("\nApplications:"));
projectInfo.applications.forEach((app, index: number) => {
this.log(` ${index + 1}. ${app.name}`);
});
}
if (projectInfo.compose.length > 0) {
this.log(chalk.blue("\nCompose Services:"));
projectInfo.compose.forEach((service, index: number) => {
this.log(` ${index + 1}. ${service.name}`);
});
}
if (projectInfo.mariadb.length > 0) {
this.log(chalk.blue("\nMariaDB Databases:"));
projectInfo.mariadb.forEach((db, index: number) => {
this.log(` ${index + 1}. ${db.name}`);
});
}
if (projectInfo.mongo.length > 0) {
this.log(chalk.blue("\nMongoDB Databases:"));
projectInfo.mongo.forEach((db, index: number) => {
this.log(` ${index + 1}. ${db.name}`);
});
}
if (projectInfo.mysql.length > 0) {
this.log(chalk.blue("\nMySQL Databases:"));
projectInfo.mysql.forEach((db, index: number) => {
this.log(` ${index + 1}. ${db.name}`);
});
}
if (projectInfo.postgres.length > 0) {
this.log(chalk.blue("\nPostgreSQL Databases:"));
projectInfo.postgres.forEach((db, index: number) => {
this.log(` ${index + 1}. ${db.name}`);
});
}
if (projectInfo.redis.length > 0) {
this.log(chalk.blue("\nRedis Databases:"));
projectInfo.redis.forEach((db, index: number) => {
this.log(` ${index + 1}. ${db.name}`);
});
}
} catch (error) {
this.error(
// @ts-expect-error
chalk.red(`Failed to fetch project information: ${error.message}`),
);
}
}
}

View File

@@ -0,0 +1,49 @@
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}`));
}
}
}

72
src/commands/verify.ts Normal file
View File

@@ -0,0 +1,72 @@
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"));
if (!fs.existsSync(configPath)) {
this.error(
chalk.red(
"No configuration file found. Please authenticate first using `authenticate` command.",
),
);
}
const configFileContent = fs.readFileSync(configPath, "utf8");
const config = JSON.parse(configFileContent);
const { token, url } = config;
if (!url || !token) {
this.error(
chalk.red(
"Incomplete authentication details. Please authenticate again using `authenticate` command.",
),
);
}
try {
console.log(`\n${chalk.blue("Validating token...")}`);
const response = await axios.post(
`${url}/api/trpc/auth.verifyToken`,
{},
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
},
);
if (response.data.result.data.json) {
this.log(chalk.green("Token is valid."));
} else {
this.error(
chalk.red(
"Invalid token. Please authenticate again using `authenticate` command.",
),
);
}
} catch (error) {
this.error(
chalk.red(
// @ts-ignore
`Failed to verify token: ${error.message}. Please authenticate again using 'authenticate' command.`,
),
);
}
}
}

1
src/index.ts Normal file
View File

@@ -0,0 +1 @@
export {run} from '@oclif/core'

4
src/utils/http.ts Normal file
View File

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

81
src/utils/shared.ts Normal file
View File

@@ -0,0 +1,81 @@
import type { Command } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import type { AuthConfig } from "./utils.js";
export type Project = {
adminId: string;
name: string;
projectId?: string | undefined;
description?: string | undefined;
};
export const getProjects = async (
auth: AuthConfig,
command: Command,
): Promise<Project[]> => {
try {
const response = await axios.get(`${auth.url}/api/trpc/project.all`, {
headers: {
Authorization: `Bearer ${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 {
// @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: {
Authorization: `Bearer ${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 {
// @ts-expect-error TODO: Fix this
command.error(chalk.red(`Failed to fetch project: ${error.message}`));
}
};

14
src/utils/slug.ts Normal file
View File

@@ -0,0 +1,14 @@
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,
});
};

3
src/utils/slugify.ts Normal file
View File

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

39
src/utils/utils.ts Normal file
View File

@@ -0,0 +1,39 @@
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> => {
if (!fs.existsSync(configPath)) {
command.error(
chalk.red(
"No configuration file found. Please authenticate first using the 'authenticate' command.",
),
);
}
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.",
),
);
}
return { token, url };
};

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('app:create', () => {
it('runs app:create cmd', async () => {
const {stdout} = await runCommand('app:create')
expect(stdout).to.contain('hello world')
})
it('runs app:create --name oclif', async () => {
const {stdout} = await runCommand('app:create --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('app:delete', () => {
it('runs app:delete cmd', async () => {
const {stdout} = await runCommand('app:delete')
expect(stdout).to.contain('hello world')
})
it('runs app:delete --name oclif', async () => {
const {stdout} = await runCommand('app:delete --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('authenticate', () => {
it('runs authenticate cmd', async () => {
const {stdout} = await runCommand('authenticate')
expect(stdout).to.contain('hello world')
})
it('runs authenticate --name oclif', async () => {
const {stdout} = await runCommand('authenticate --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('check-server', () => {
it('runs check-server cmd', async () => {
const {stdout} = await runCommand('check-server')
expect(stdout).to.contain('hello world')
})
it('runs check-server --name oclif', async () => {
const {stdout} = await runCommand('check-server --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('database:create', () => {
it('runs database:create cmd', async () => {
const {stdout} = await runCommand('database:create')
expect(stdout).to.contain('hello world')
})
it('runs database:create --name oclif', async () => {
const {stdout} = await runCommand('database:create --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('database:mariadb:create', () => {
it('runs database:mariadb:create cmd', async () => {
const {stdout} = await runCommand('database:mariadb:create')
expect(stdout).to.contain('hello world')
})
it('runs database:mariadb:create --name oclif', async () => {
const {stdout} = await runCommand('database:mariadb:create --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('database:mariadb:delete', () => {
it('runs database:mariadb:delete cmd', async () => {
const {stdout} = await runCommand('database:mariadb:delete')
expect(stdout).to.contain('hello world')
})
it('runs database:mariadb:delete --name oclif', async () => {
const {stdout} = await runCommand('database:mariadb:delete --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('database:mongo:create', () => {
it('runs database:mongo:create cmd', async () => {
const {stdout} = await runCommand('database:mongo:create')
expect(stdout).to.contain('hello world')
})
it('runs database:mongo:create --name oclif', async () => {
const {stdout} = await runCommand('database:mongo:create --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('database:mongo:delete', () => {
it('runs database:mongo:delete cmd', async () => {
const {stdout} = await runCommand('database:mongo:delete')
expect(stdout).to.contain('hello world')
})
it('runs database:mongo:delete --name oclif', async () => {
const {stdout} = await runCommand('database:mongo:delete --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('database:mysql:create', () => {
it('runs database:mysql:create cmd', async () => {
const {stdout} = await runCommand('database:mysql:create')
expect(stdout).to.contain('hello world')
})
it('runs database:mysql:create --name oclif', async () => {
const {stdout} = await runCommand('database:mysql:create --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('database:mysql:delete', () => {
it('runs database:mysql:delete cmd', async () => {
const {stdout} = await runCommand('database:mysql:delete')
expect(stdout).to.contain('hello world')
})
it('runs database:mysql:delete --name oclif', async () => {
const {stdout} = await runCommand('database:mysql:delete --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('database:postgres:create', () => {
it('runs database:postgres:create cmd', async () => {
const {stdout} = await runCommand('database:postgres:create')
expect(stdout).to.contain('hello world')
})
it('runs database:postgres:create --name oclif', async () => {
const {stdout} = await runCommand('database:postgres:create --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('database:postgres:delete', () => {
it('runs database:postgres:delete cmd', async () => {
const {stdout} = await runCommand('database:postgres:delete')
expect(stdout).to.contain('hello world')
})
it('runs database:postgres:delete --name oclif', async () => {
const {stdout} = await runCommand('database:postgres:delete --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('database:redis:create', () => {
it('runs database:redis:create cmd', async () => {
const {stdout} = await runCommand('database:redis:create')
expect(stdout).to.contain('hello world')
})
it('runs database:redis:create --name oclif', async () => {
const {stdout} = await runCommand('database:redis:create --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('database:redis:delete', () => {
it('runs database:redis:delete cmd', async () => {
const {stdout} = await runCommand('database:redis:delete')
expect(stdout).to.contain('hello world')
})
it('runs database:redis:delete --name oclif', async () => {
const {stdout} = await runCommand('database:redis:delete --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,9 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('hello', () => {
it('runs hello', async () => {
const {stdout} = await runCommand('hello friend --from oclif')
expect(stdout).to.contain('hello friend from oclif!')
})
})

View File

@@ -0,0 +1,9 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('hello world', () => {
it('runs hello world cmd', async () => {
const {stdout} = await runCommand('hello world')
expect(stdout).to.contain('hello world!')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('project:create', () => {
it('runs project:create cmd', async () => {
const {stdout} = await runCommand('project:create')
expect(stdout).to.contain('hello world')
})
it('runs project:create --name oclif', async () => {
const {stdout} = await runCommand('project:create --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('project:info', () => {
it('runs project:info cmd', async () => {
const {stdout} = await runCommand('project:info')
expect(stdout).to.contain('hello world')
})
it('runs project:info --name oclif', async () => {
const {stdout} = await runCommand('project:info --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('project:list', () => {
it('runs project:list cmd', async () => {
const {stdout} = await runCommand('project:list')
expect(stdout).to.contain('hello world')
})
it('runs project:list --name oclif', async () => {
const {stdout} = await runCommand('project:list --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

View File

@@ -0,0 +1,14 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'
describe('verify', () => {
it('runs verify cmd', async () => {
const {stdout} = await runCommand('verify')
expect(stdout).to.contain('hello world')
})
it('runs verify --name oclif', async () => {
const {stdout} = await runCommand('verify --name oclif')
expect(stdout).to.contain('hello oclif')
})
})

9
test/tsconfig.json Normal file
View File

@@ -0,0 +1,9 @@
{
"extends": "../tsconfig",
"compilerOptions": {
"noEmit": true
},
"references": [
{"path": ".."}
]
}

15
tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"declaration": true,
"module": "Node16",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"target": "es2022",
"moduleResolution": "node16",
},
"include": ["./src/**/*"],
"ts-node": {
"esm": true
}
}