71 Commits

Author SHA1 Message Date
Mauricio Siu
c7088565ac Merge pull request #42 from immanuwell/fix-cli-version-from-package
fix: read CLI version from package metadata
2026-06-12 15:01:41 -06:00
Dokploy Bot
468543e309 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@b3c2e1e5af

Updated: 2026-06-08 15:21:38 UTC
2026-06-08 15:21:38 +00:00
Dokploy Bot
90d0d15013 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@1f4f94042f

Updated: 2026-06-08 15:21:22 UTC
2026-06-08 15:21:22 +00:00
Dokploy Bot
befe6e830a chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@e9a0932b23

Updated: 2026-06-07 08:11:25 UTC
2026-06-07 08:11:25 +00:00
Dokploy Bot
a80497db88 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@6b68fcab8c

Updated: 2026-06-07 07:29:42 UTC
2026-06-07 07:29:42 +00:00
Dokploy Bot
0429c61fdd chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@a0288f83d5

Updated: 2026-06-07 06:19:22 UTC
2026-06-07 06:19:22 +00:00
Dokploy Bot
a93ed49886 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@4900204107

Updated: 2026-06-07 06:15:56 UTC
2026-06-07 06:15:56 +00:00
Dokploy Bot
8e552f974a chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@c968a2755e

Updated: 2026-06-06 23:46:02 UTC
2026-06-06 23:46:02 +00:00
Dokploy Bot
18c44440ba chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@e6fc3db08f

Updated: 2026-06-06 20:22:45 UTC
2026-06-06 20:22:45 +00:00
Dokploy Bot
865ac21c03 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@b29a87aaa8

Updated: 2026-06-06 19:58:51 UTC
2026-06-06 19:58:51 +00:00
Dokploy Bot
9390b36ec0 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@60867d0b60

Updated: 2026-06-02 08:31:52 UTC
2026-06-02 08:31:52 +00:00
Dokploy Bot
8bbdc2ce11 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@6a0acd9cad

Updated: 2026-06-02 08:18:54 UTC
2026-06-02 08:18:54 +00:00
Dokploy Bot
2d0df3ed37 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@798e98c181

Updated: 2026-06-02 08:09:02 UTC
2026-06-02 08:09:02 +00:00
Dokploy Bot
91f779fcaf chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@84a8083d91

Updated: 2026-06-02 08:06:29 UTC
2026-06-02 08:06:29 +00:00
Dokploy Bot
49ed66ac12 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@8640f138d3

Updated: 2026-06-02 08:01:33 UTC
2026-06-02 08:01:33 +00:00
Dokploy Bot
cd876c92a7 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@6ff2ca0173

Updated: 2026-05-31 21:29:52 UTC
2026-05-31 21:29:52 +00:00
Dokploy Bot
b6331a6471 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@30b3e1fe48

Updated: 2026-05-30 22:02:33 UTC
2026-05-30 22:02:33 +00:00
Dokploy Bot
2df05732e6 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@d7d642230c

Updated: 2026-05-30 07:12:20 UTC
2026-05-30 07:12:20 +00:00
Dokploy Bot
5985ee7229 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@4ba0f71220

Updated: 2026-05-30 07:07:24 UTC
2026-05-30 07:07:24 +00:00
Dokploy Bot
044d22110a chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@8018027330

Updated: 2026-05-30 07:03:33 UTC
2026-05-30 07:03:33 +00:00
Dokploy Bot
a593c3025d chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@a07106d649

Updated: 2026-05-22 23:21:57 UTC
2026-05-22 23:21:57 +00:00
Dokploy Bot
f552309885 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@6e342ee2f2

Updated: 2026-05-13 07:10:46 UTC
2026-05-13 07:10:46 +00:00
Dokploy Bot
74e5482e35 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@1fdbe87d84

Updated: 2026-05-13 06:50:13 UTC
2026-05-13 06:50:13 +00:00
Dokploy Bot
a6aa7ab5f4 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@67278d8783

Updated: 2026-05-13 06:43:20 UTC
2026-05-13 06:43:20 +00:00
Dokploy Bot
22d0dd10e0 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@aff200f84f

Updated: 2026-05-13 06:10:35 UTC
2026-05-13 06:10:35 +00:00
Mauricio Siu
784e25b529 Update package.json 2026-05-12 13:16:02 -06:00
Dokploy Bot
2d42e63a7b chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@7a568aadac

Updated: 2026-05-12 19:14:34 UTC
2026-05-12 19:14:34 +00:00
Mauricio Siu
ad0df74ae9 Bump version from 0.29.2 to 0.29.3 2026-05-11 12:50:23 -06:00
Dokploy Bot
35599793b1 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@ccc8f6d047

Updated: 2026-05-11 17:58:23 UTC
2026-05-11 17:58:23 +00:00
immanuwell
05ca9f8ce4 fix: read CLI version from package metadata 2026-05-09 12:18:41 +04:00
Dokploy Bot
81d3d5e1e2 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@1c6fdc1b43

Updated: 2026-05-09 08:11:28 UTC
2026-05-09 08:11:28 +00:00
Dokploy Bot
b6a1dcb6bd chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@547ba2d04b

Updated: 2026-05-09 07:10:38 UTC
2026-05-09 07:10:38 +00:00
Dokploy Bot
752624838b chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@b9e97eb321

Updated: 2026-05-09 06:58:07 UTC
2026-05-09 06:58:07 +00:00
Dokploy Bot
ec8d8c18de chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@fef2de1ec5

Updated: 2026-05-09 05:51:28 UTC
2026-05-09 05:51:28 +00:00
Dokploy Bot
bc3fe40c4f chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@5177580d51

Updated: 2026-05-09 05:19:49 UTC
2026-05-09 05:19:49 +00:00
Dokploy Bot
049d9e2255 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@0f526af2c8

Updated: 2026-05-09 01:34:50 UTC
2026-05-09 01:34:50 +00:00
Dokploy Bot
036dc98cf7 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@8227a48ef4

Updated: 2026-05-09 01:06:16 UTC
2026-05-09 01:06:16 +00:00
Dokploy Bot
677d40dd38 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@d5d8914bf6

Updated: 2026-05-09 00:50:27 UTC
2026-05-09 00:50:27 +00:00
Dokploy Bot
dcd01689d8 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@746bb3ddc6

Updated: 2026-05-07 20:36:17 UTC
2026-05-07 20:36:17 +00:00
Dokploy Bot
b5e4da9cd9 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@e0c6ed699d

Updated: 2026-05-01 00:53:23 UTC
2026-05-01 00:53:23 +00:00
Dokploy Bot
ddcb947374 chore: bump version to 0.29.2
Source: Dokploy/dokploy@fb6b06f064

Release:
2026-04-25 05:09:09 +00:00
Dokploy Bot
6127c5e518 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@222b167a76

Updated: 2026-04-25 04:47:19 UTC
2026-04-25 04:47:19 +00:00
Dokploy Bot
637da35355 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@940d18ad25

Updated: 2026-04-25 03:52:56 UTC
2026-04-25 03:52:56 +00:00
Dokploy Bot
911e162617 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@29480cde90

Updated: 2026-04-24 18:50:04 UTC
2026-04-24 18:50:04 +00:00
Dokploy Bot
430c1746ba chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@d7af82731c

Updated: 2026-04-22 03:39:08 UTC
2026-04-22 03:39:08 +00:00
Dokploy Bot
138c1e044d chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@bad9731878

Updated: 2026-04-20 13:17:07 UTC
2026-04-20 13:17:07 +00:00
Dokploy Bot
56d8995b71 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@13248c8d8a

Updated: 2026-04-19 18:06:56 UTC
2026-04-19 18:06:56 +00:00
Dokploy Bot
90bc8ca394 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@b392e58001

Updated: 2026-04-18 04:41:52 UTC
2026-04-18 04:41:52 +00:00
Dokploy Bot
90e2ab63da chore: bump version to 0.29.0
Source: Dokploy/dokploy@425fef6e28

Release:
2026-04-17 20:49:59 +00:00
Dokploy Bot
1ea122e562 chore: bump version to v0.29.0
Source: Dokploy/dokploy@958372c5f9

Release:
2026-04-17 20:46:47 +00:00
Dokploy Bot
1b269016b5 chore: sync OpenAPI specification [skip ci]
Source: Dokploy/dokploy@7e13243c1d

Updated: 2026-04-17 20:11:43 UTC
2026-04-17 20:11:43 +00:00
Mauricio Siu
f829a9ac1f chore: update GitHub Actions workflow for release and npm checks
- Enhanced version checking logic in onPushToMain.yml to verify the existence of GitHub releases and npm versions before creating new releases or publishing to npm.
- Updated the GitHub release action to use softprops/action-gh-release for improved functionality and added generation of release notes.
2026-04-15 21:24:04 -06:00
Mauricio Siu
b4b4e6a655 chore: remove publish script from package.json 2026-04-15 21:16:28 -06:00
Mauricio Siu
8cb29cd8c7 chore: bump version to 0.3.1 in package.json 2026-04-15 21:14:37 -06:00
Mauricio Siu
edb15fd6cf chore: update GitHub Actions workflow to use GitHub token
- Replaced the usage of secrets.GH_TOKEN with github.token in onPushToMain.yml for improved security and access management during the release process.
2026-04-15 21:13:21 -06:00
Mauricio Siu
edb0e37890 Merge pull request #36 from Dokploy/feat/add-support-for-all-endpoints
Feat/add support for all endpoints
2026-04-15 21:07:12 -06:00
Mauricio Siu
0d54e9bcac chore: update GitHub workflows to disable strict Corepack mode and ignore scripts during installation
- Added environment variable COREPACK_ENABLE_STRICT set to 0 in both onPushToMain.yml and test.yml workflows.
- Modified pnpm install command to include --ignore-scripts flag, preventing scripts from running during installation.
2026-04-15 21:05:39 -06:00
Mauricio Siu
d5f9d5b8fc chore: add .npmrc file to configure build script behavior
- Created a new .npmrc file to set the ignore-build-scripts option to false, allowing build scripts to run during installation.
2026-04-15 21:04:03 -06:00
Mauricio Siu
52b6c8d735 chore: update package.json to include pnpm configuration
- Added pnpm configuration to specify only built dependencies for esbuild in package.json, enhancing dependency management.
2026-04-15 21:02:39 -06:00
Mauricio Siu
c073a037b1 feat: integrate Vitest for testing and update workflows
- Added Vitest as a testing framework in package.json and configured test scripts.
- Created new test files for CLI and client functionalities, ensuring comprehensive coverage.
- Updated GitHub Actions workflows to include testing steps and streamlined release process.
- Removed the deprecated onRelease workflow to consolidate CI/CD processes.
2026-04-15 21:00:48 -06:00
Mauricio Siu
0869e2d69a chore: update README and remove .mocharc.json
- Removed the .mocharc.json file as part of the project cleanup.
- Updated README to enhance clarity on usage, authentication options, and command examples.
- Added details on environment variable support and improved command structure for better user guidance.
2026-04-15 18:47:33 -06:00
Mauricio Siu
4d604a1a75 feat: add environment configuration support
- Introduced .env.example file to provide a template for environment variables.
- Updated .gitignore to exclude .env files from version control.
- Implemented loadEnvFile function in client.ts to load environment variables from a .env file into process.env, enhancing configuration management.
2026-04-15 18:42:13 -06:00
Mauricio Siu
70f542990d refactor: clean up imports and improve function formatting
- Reordered and grouped import statements for better readability in client.ts, index.ts, and auth.ts.
- Updated the readAuthConfig function to prioritize DOKPLOY_API_KEY over DOKPLOY_AUTH_TOKEN.
- Enhanced formatting of apiPost and apiGet functions for improved clarity and consistency.
2026-04-15 18:41:20 -06:00
Mauricio Siu
5b02a22e21 chore: update copyright year and ownership in LICENSE file
- Changed copyright from 2024 Mauricio Siu to 2026 Dokploy Technologies, Inc. in LICENSE.md.
2026-04-15 18:38:16 -06:00
Mauricio Siu
d51ea5b2f7 chore: remove Prettier configuration file
- Deleted .prettierrc.json as part of the migration to Biome for linting and formatting.
2026-04-15 18:37:50 -06:00
Mauricio Siu
ac02c614a6 refactor: migrate to biome and update CLI structure
- Removed ESLint configuration and ignore files in favor of Biome for linting.
- Introduced biome.json for configuration and updated package.json to reflect new dependencies.
- Added OpenAPI specification for Dokploy API and generated CLI commands from it.
- Refactored CLI entry point to use Commander instead of Oclif.
- Implemented new authentication command and removed deprecated commands.
- Updated TypeScript configuration and added build scripts for improved development workflow.
- Cleaned up unused files and commands to streamline the codebase.
2026-04-15 18:37:45 -06:00
Mauricio Siu
761f6e795f docs: update README to include environment management commands
- Added documentation for new commands related to environment management: `dokploy environment:create` and `dokploy environment:delete`.
- Enhanced clarity on usage instructions for managing environments within the project.
2025-10-05 02:07:59 -06:00
Mauricio Siu
1f1d6713aa Merge pull request #12 from Dokploy/fix/environments
Fix/environments
2025-10-05 02:05:23 -06:00
Mauricio Siu
1a01289349 Merge pull request #10 from Sebbev/fix/pull-and-push-command
Add environment selection for env pull and push commands
2025-10-05 01:26:31 -06:00
Sebastian Vallin
dcfb478035 fix(env): Correct prompt message in env push command 2025-09-17 16:26:10 +02:00
Sebastian Vallin
d7ef05239f feat: Add environment selection for env pull and push commands 2025-09-17 16:26:10 +02:00
69 changed files with 69753 additions and 10803 deletions

2
.env.example Normal file
View File

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

View File

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

View File

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

View File

@@ -1,56 +1,63 @@
# test name: release and publish
name: version, tag and github release
on: on:
push: push:
branches: [main] branches: [main]
env:
COREPACK_ENABLE_STRICT: 0
jobs: jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v6
with:
version: latest
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with:
node-version: latest
cache: pnpm
registry-url: https://registry.npmjs.org
- run: pnpm install --ignore-scripts
- run: pnpm run build
- name: Check if version already exists - name: Check if version already exists
id: version-check id: version-check
run: | run: |
package_version=$(node -p "require('./package.json').version") 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 "") echo "version=$package_version" >> $GITHUB_OUTPUT
if [ -n "$exists" ]; if gh api repos/${{ github.repository }}/releases/tags/v$package_version >/dev/null 2>&1; then
then echo "GitHub release v$package_version already exists, skipping"
echo "Version v$package_version already exists" echo "release_exists=true" >> $GITHUB_OUTPUT
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 else
echo "Version v$package_version does not exist. Creating release..." echo "release_exists=false" >> $GITHUB_OUTPUT
echo "skipped=false" >> $GITHUB_OUTPUT fi
echo "tag=v$package_version" >> $GITHUB_OUTPUT
if npm view @dokploy/cli@$package_version version >/dev/null 2>&1; then
echo "npm version $package_version already exists, skipping"
echo "npm_exists=true" >> $GITHUB_OUTPUT
else
echo "npm_exists=false" >> $GITHUB_OUTPUT
fi fi
env: env:
GH_TOKEN: ${{ secrets.GH_TOKEN }} GH_TOKEN: ${{ github.token }}
- name: Setup git
if: ${{ steps.version-check.outputs.skipped == 'false' }} - name: Create GitHub Release
run: | if: steps.version-check.outputs.release_exists == 'false'
git config --global user.email ${{ secrets.GH_EMAIL }} uses: softprops/action-gh-release@v2
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: with:
name: ${{ steps.version-check.outputs.tag }} tag_name: v${{ steps.version-check.outputs.version }}
tag: ${{ steps.version-check.outputs.tag }} name: v${{ steps.version-check.outputs.version }}
commit: ${{ github.ref_name }} generate_release_notes: true
token: ${{ secrets.GH_TOKEN }}
skipIfReleaseExists: true - name: Publish to npm
if: steps.version-check.outputs.npm_exists == 'false'
run: pnpm publish --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

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

View File

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

3
.gitignore vendored
View File

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

View File

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

1
.npmrc Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

42
biome.json Normal file
View File

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

58872
openapi.json Normal file

File diff suppressed because it is too large Load Diff

View File

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

6330
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

212
readme.md
View File

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

286
scripts/generate.ts Normal file
View File

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

106
src/client.ts Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,237 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { readAuthConfig } from "../../../utils/utils.js";
import { getProjects, type Database } from "../../../utils/shared.js";
import { slugify } from "../../../utils/slug.js";
import type { Answers } from "../../app/create.js";
export default class DatabaseMongoCreate extends Command {
static description = "Create a new MongoDB database within a project.";
static examples = ["$ <%= config.bin %> mongo create"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
required: false,
}),
name: Flags.string({
char: "n",
description: "Database name",
required: false,
}),
databaseName: Flags.string({
description: "MongoDB database name",
required: false,
}),
description: Flags.string({
char: "d",
description: "Database description",
required: false,
}),
databasePassword: Flags.string({
description: "Database password",
required: false,
}),
databaseUser: Flags.string({
description: "Database user",
default: "mongo",
}),
dockerImage: Flags.string({
description: "Docker image",
default: "mongo:6",
}),
skipConfirm: Flags.boolean({
char: "y",
description: "Skip confirmation prompt",
default: false,
}),
appName: Flags.string({
description: "App name",
required: false,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMongoCreate);
let {
projectId,
environmentId,
name,
databaseName,
description,
databasePassword,
databaseUser,
dockerImage,
appName
} = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !name || !databaseName || !appName || !databasePassword) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to create the MongoDB instance in:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
environmentId = environment.environmentId;
}
if (!name || !databaseName || !appName || !databasePassword) {
const dbDetails = await inquirer.prompt([
{
message: "Enter the name:",
name: "name",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
default: name,
},
{
message: "Database name:",
name: "databaseName",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
default: databaseName,
},
{
message: "Enter the database description (optional):",
name: "description",
type: "input",
default: description,
},
{
message: "Database password (optional):",
name: "databasePassword",
type: "password",
default: databasePassword,
},
{
default: dockerImage || "mongo:6",
message: "Docker Image (default: mongo:6):",
name: "dockerImage",
type: "input",
},
{
default: databaseUser || "mongo",
message: "Database User: (default: mongo):",
name: "databaseUser",
type: "input",
},
]);
name = dbDetails.name;
databaseName = dbDetails.databaseName;
description = dbDetails.description;
databasePassword = dbDetails.databasePassword;
dockerImage = dbDetails.dockerImage;
databaseUser = dbDetails.databaseUser;
const appNamePrompt = await inquirer.prompt([
{
default: appName || `${slugify(name)}`,
message: "Enter the App name:",
name: "appName",
type: "input",
validate: (input) => (input ? true : "App name is required"),
},
]);
appName = appNamePrompt.appName;
}
}
// Confirmar si no se especifica --skipConfirm
if (!flags.skipConfirm) {
const confirm = await inquirer.prompt([
{
type: 'confirm',
name: 'proceed',
message: 'Do you want to create this MongoDB instance?',
default: false,
},
]);
if (!confirm.proceed) {
this.error(chalk.yellow("MongoDB creation cancelled."));
return;
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mongo.create`,
{
json: {
name,
databaseName,
description,
databasePassword,
databaseUser,
dockerImage,
appName,
projectId,
environmentId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error creating MongoDB instance"));
}
this.log(chalk.green(`MongoDB instance '${name}' created successfully.`));
} catch (error: any) {
this.error(chalk.red(`Error creating MongoDB instance: ${error.message}`));
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,252 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { slugify } from "../../../utils/slug.js";
import { readAuthConfig } from "../../../utils/utils.js";
import { getProjects, type Database } from "../../../utils/shared.js";
import type { Answers } from "../../app/create.js";
export default class DatabaseMysqlCreate extends Command {
static description = "Create a new MySQL database within a project.";
static examples = ["$ <%= config.bin %> mysql create"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
required: false,
}),
name: Flags.string({
char: "n",
description: "Database name",
required: false,
}),
databaseName: Flags.string({
description: "MySQL database name",
required: false,
}),
description: Flags.string({
char: "d",
description: "Database description",
required: false,
}),
databaseRootPassword: Flags.string({
description: "Database root password",
required: false,
}),
databasePassword: Flags.string({
description: "Database password",
required: false,
}),
databaseUser: Flags.string({
description: "Database user",
default: "mysql",
}),
dockerImage: Flags.string({
description: "Docker image",
default: "mysql:8",
}),
skipConfirm: Flags.boolean({
char: "y",
description: "Skip confirmation prompt",
default: false,
}),
appName: Flags.string({
description: "App name",
required: false,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabaseMysqlCreate);
let {
projectId,
environmentId,
name,
databaseName,
description,
databaseRootPassword,
databasePassword,
databaseUser,
dockerImage,
appName
} = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !name || !databaseName || !appName || !databasePassword || !databaseRootPassword) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to create the MySQL instance in:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
environmentId = environment.environmentId;
}
if (!name || !databaseName || !appName || !databasePassword || !databaseRootPassword) {
const dbDetails = await inquirer.prompt([
{
message: "Enter the name:",
name: "name",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
default: name,
},
{
message: "Database name:",
name: "databaseName",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
default: databaseName,
},
{
message: "Enter the database description (optional):",
name: "description",
type: "input",
default: description,
},
{
message: "Database Root Password:",
name: "databaseRootPassword",
type: "password",
default: databaseRootPassword,
},
{
message: "Database password:",
name: "databasePassword",
type: "password",
default: databasePassword,
},
{
default: dockerImage || "mysql:8",
message: "Docker Image (default: mysql:8):",
name: "dockerImage",
type: "input",
},
{
default: databaseUser || "mysql",
message: "Database User: (default: mysql):",
name: "databaseUser",
type: "input",
},
]);
name = dbDetails.name;
databaseName = dbDetails.databaseName;
description = dbDetails.description;
databaseRootPassword = dbDetails.databaseRootPassword;
databasePassword = dbDetails.databasePassword;
dockerImage = dbDetails.dockerImage;
databaseUser = dbDetails.databaseUser;
const appNamePrompt = await inquirer.prompt([
{
default: appName || `${slugify(name)}`,
message: "Enter the App name:",
name: "appName",
type: "input",
validate: (input) => (input ? true : "App name is required"),
},
]);
appName = appNamePrompt.appName;
}
}
// Confirmar si no se especifica --skipConfirm
if (!flags.skipConfirm) {
const confirm = await inquirer.prompt([
{
type: 'confirm',
name: 'proceed',
message: 'Do you want to create this MySQL instance?',
default: false,
},
]);
if (!confirm.proceed) {
this.error(chalk.yellow("MySQL creation cancelled."));
return;
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/mysql.create`,
{
json: {
name,
databaseName,
description,
databaseRootPassword,
databasePassword,
databaseUser,
dockerImage,
appName,
projectId,
environmentId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error creating MySQL instance", response.data.result.data.json));
}
this.log(chalk.green(`MySQL instance '${name}' created successfully.`));
} catch (error: any) {
this.error(chalk.red(`Error creating MySQL instance: ${error.message}`));
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,237 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { slugify } from "../../../utils/slug.js";
import { readAuthConfig } from "../../../utils/utils.js";
import { getProjects, type Database } from "../../../utils/shared.js";
import type { Answers } from "../../app/create.js";
export default class DatabasePostgresCreate extends Command {
static description = "Create a new PostgreSQL database within a project.";
static examples = ["$ <%= config.bin %> postgres create"];
static flags = {
projectId: Flags.string({
char: "p",
description: "ID of the project",
required: false,
}),
environmentId: Flags.string({
char: "e",
description: "ID of the environment",
required: false,
}),
name: Flags.string({
char: "n",
description: "Database name",
required: false,
}),
databaseName: Flags.string({
description: "PostgreSQL database name",
required: false,
}),
description: Flags.string({
char: "d",
description: "Database description",
required: false,
}),
databasePassword: Flags.string({
description: "Database password",
required: false,
}),
databaseUser: Flags.string({
description: "Database user",
default: "postgres",
}),
dockerImage: Flags.string({
description: "Docker image",
default: "postgres:15",
}),
skipConfirm: Flags.boolean({
char: "y",
description: "Skip confirmation prompt",
default: false,
}),
appName: Flags.string({
description: "App name",
required: false,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(DatabasePostgresCreate);
let {
projectId,
environmentId,
name,
databaseName,
description,
databasePassword,
databaseUser,
dockerImage,
appName
} = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!projectId || !environmentId || !name || !databaseName || !appName || !databasePassword) {
console.log(chalk.blue.bold("\n Listing all Projects \n"));
const projects = await getProjects(auth, this);
let selectedProject;
// 1. Seleccionar proyecto
if (!projectId) {
const { project } = await inquirer.prompt<Answers>([
{
choices: projects.map((project) => ({
name: project.name,
value: project,
})),
message: "Select a project to create the PostgreSQL instance in:",
name: "project",
type: "list",
},
]);
selectedProject = project;
projectId = project.projectId;
} else {
selectedProject = projects.find(p => p.projectId === projectId);
}
// 2. Seleccionar environment del proyecto
if (!environmentId) {
if (!selectedProject?.environments || selectedProject.environments.length === 0) {
this.error(chalk.yellow("No environments found in this project."));
}
const { environment } = await inquirer.prompt([
{
choices: selectedProject.environments.map((env) => ({
name: `${env.name} (${env.description})`,
value: env,
})),
message: "Select an environment:",
name: "environment",
type: "list",
},
]);
environmentId = environment.environmentId;
}
if (!name || !databaseName || !appName || !databasePassword) {
const dbDetails = await inquirer.prompt([
{
message: "Enter the name:",
name: "name",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
default: name,
},
{
message: "Database name:",
name: "databaseName",
type: "input",
validate: (input) => (input ? true : "Database name is required"),
default: databaseName,
},
{
message: "Enter the database description (optional):",
name: "description",
type: "input",
default: description,
},
{
message: "Database password:",
name: "databasePassword",
type: "password",
default: databasePassword,
},
{
default: dockerImage || "postgres:15",
message: "Docker Image (default: postgres:15):",
name: "dockerImage",
type: "input",
},
{
default: databaseUser || "postgres",
message: "Database User: (default: postgres):",
name: "databaseUser",
type: "input",
},
]);
name = dbDetails.name;
databaseName = dbDetails.databaseName;
description = dbDetails.description;
databasePassword = dbDetails.databasePassword;
dockerImage = dbDetails.dockerImage;
databaseUser = dbDetails.databaseUser;
const appNamePrompt = await inquirer.prompt([
{
default: appName || `${slugify(name)}`,
message: "Enter the App name:",
name: "appName",
type: "input",
validate: (input) => (input ? true : "App name is required"),
},
]);
appName = appNamePrompt.appName;
}
}
// Confirmar si no se especifica --skipConfirm
if (!flags.skipConfirm) {
const confirm = await inquirer.prompt([
{
type: 'confirm',
name: 'proceed',
message: 'Do you want to create this PostgreSQL instance?',
default: false,
},
]);
if (!confirm.proceed) {
this.error(chalk.yellow("PostgreSQL creation cancelled."));
return;
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/postgres.create`,
{
json: {
name,
databaseName,
description,
databasePassword,
databaseUser,
dockerImage,
appName,
projectId,
environmentId,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error creating PostgreSQL instance", response.data.result.data.json));
}
this.log(chalk.green(`PostgreSQL instance '${name}' created successfully.`));
} catch (error: any) {
this.error(chalk.red(`Error creating PostgreSQL instance: ${error.message}`));
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,104 +0,0 @@
import { Command, Flags } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import inquirer from "inquirer";
import { readAuthConfig } from "../../utils/utils.js";
export default class ProjectCreate extends Command {
static description = "Create a new project.";
static examples = [
"$ <%= config.bin %> project create",
"$ <%= config.bin %> project create -n MyProject -d 'Project description'",
"$ <%= config.bin %> project create --name MyProject --skipConfirm",
];
static flags = {
name: Flags.string({
char: "n",
description: "Name of the project",
required: false,
}),
description: Flags.string({
char: "d",
description: "Description of the project",
required: false,
}),
skipConfirm: Flags.boolean({
char: "y",
description: "Skip confirmation prompt",
default: false,
}),
};
public async run(): Promise<void> {
const auth = await readAuthConfig(this);
const { flags } = await this.parse(ProjectCreate);
let { name, description } = flags;
// Modo interactivo si no se proporcionan los flags necesarios
if (!name) {
const answers = await inquirer.prompt([
{
message: "Enter the project name:",
name: "name",
type: "input",
validate: (input) => (input ? true : "Project name is required"),
},
{
message: "Enter the project description (optional):",
name: "description",
type: "input",
default: description || "",
},
]);
name = answers.name;
description = answers.description;
}
// Confirmar si no se especifica --skipConfirm
if (!flags.skipConfirm) {
const confirm = await inquirer.prompt([
{
type: 'confirm',
name: 'proceed',
message: 'Do you want to create this project?',
default: false,
},
]);
if (!confirm.proceed) {
this.error(chalk.yellow("Project creation cancelled."));
return;
}
}
try {
const response = await axios.post(
`${auth.url}/api/trpc/project.create`,
{
json: {
name,
description,
},
},
{
headers: {
"x-api-key": auth.token,
"Content-Type": "application/json",
},
},
);
if (!response.data.result.data.json) {
this.error(chalk.red("Error creating project", response.data.result.data.json));
}
this.log(chalk.green(`Project '${name}' created successfully.`));
} catch (error: any) {
this.error(chalk.red(`Error creating project: ${error.message}`));
}
}
}

View File

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

View File

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

View File

@@ -1,88 +0,0 @@
import { Command } from "@oclif/core";
import axios from "axios";
import chalk from "chalk";
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const configPath = path.join(__dirname, "..", "..", "config.json");
export default class Verify extends Command {
static description = "Verify if the saved authentication token is valid";
static examples = ["$ <%= config.bin %> <%= command.id %>"];
async run() {
console.log(chalk.blue.bold("\nVerifying Authentication Token"));
let token: string;
let url: string;
// Verificar variables de entorno primero
const envToken = process.env.DOKPLOY_AUTH_TOKEN;
const envUrl = process.env.DOKPLOY_URL;
if (envToken && envUrl) {
token = envToken;
url = envUrl;
this.log(chalk.green("Using environment variables for authentication"));
} else {
// Si no hay variables de entorno, verificar archivo de configuración
if (!fs.existsSync(configPath)) {
this.error(
chalk.red(
"No configuration found. Please either:\n" +
"1. Authenticate using `authenticate` command\n" +
"2. Set DOKPLOY_URL and DOKPLOY_AUTH_TOKEN environment variables",
),
);
}
try {
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
token = config.token;
url = config.url;
this.log(chalk.green("Using configuration file for authentication"));
} catch (error) {
this.error(
chalk.red(
"Invalid configuration file. Please authenticate again using `authenticate` command.",
),
);
}
}
// Validar el token contra el servidor
try {
console.log(chalk.blue("Validating token with server..."));
const response = await axios.get(
`${url}/api/trpc/user.get`,
{
headers: {
"x-api-key": token,
"Content-Type": "application/json",
},
},
);
if (response.data.result.data.json) {
this.log(chalk.green("\n✓ Token is valid"));
} else {
this.error(
chalk.red(
"Invalid token. Please authenticate again using `authenticate` command.",
),
);
}
} catch (error: any) {
this.error(
chalk.red(
`Failed to verify token: ${error.message}. Please authenticate again using 'authenticate' command.`,
),
);
}
}
}

8720
src/generated/commands.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,35 @@
export {run} from '@oclif/core' #!/usr/bin/env node
import { readFileSync } from "node:fs";
import chalk from "chalk";
import { program } from "commander";
import { registerAuthCommand } from "./commands/auth.js";
import { registerGeneratedCommands } from "./generated/commands.js";
const packageJson = JSON.parse(
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
) as { version: string };
const pkg = {
name: "dokploy",
version: packageJson.version,
description: "Dokploy CLI - Manage your Dokploy server",
};
program
.name(pkg.name)
.version(pkg.version)
.description(pkg.description)
.action(() => {
program.help();
});
registerAuthCommand(program);
registerGeneratedCommands(program);
const argv = process.argv.filter((arg) => arg !== "--");
program.parseAsync(argv).catch((err) => {
console.error(chalk.red(err.message));
process.exit(1);
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,48 +0,0 @@
import type { Command } from "@oclif/core";
import chalk from "chalk";
import * as fs from "node:fs";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const configPath = path.join(__dirname, "..", "..", "config.json");
export type AuthConfig = {
token: string;
url: string;
};
export const readAuthConfig = async (command: Command): Promise<AuthConfig> => {
// Primero intentar leer desde variables de entorno
const envToken = process.env.DOKPLOY_AUTH_TOKEN;
const envUrl = process.env.DOKPLOY_URL;
if (envToken && envUrl) {
return { token: envToken, url: envUrl };
}
// Si no hay variables de entorno, usar el archivo de configuración
if (!fs.existsSync(configPath)) {
command.error(
chalk.red(
"No configuration file found and no environment variables set. Please authenticate first using the 'authenticate' command or set DOKPLOY_URL and DOKPLOY_AUTH_TOKEN environment variables.",
),
);
}
const configFileContent = fs.readFileSync(configPath, "utf8");
const config = JSON.parse(configFileContent);
const { token, url } = config;
if (!url || !token) {
command.error(
chalk.red(
"Incomplete authentication details. Please authenticate again using the 'authenticate' command or set environment variables.",
),
);
}
return { token, url };
};

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

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

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

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

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

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

View File

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

1
tsconfig.tsbuildinfo Normal file
View File

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