mirror of
https://github.com/Dokploy/cli.git
synced 2026-06-15 20:25:22 +02:00
feat: add list and create project
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"extends": ["oclif", "prettier"]
|
"extends": ["oclif", "oclif-typescript", "prettier"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"token": "hola",
|
"token": "5qdderpq2ejrjlu90hrnbjohvgc8j1u1k7i00um4",
|
||||||
"url": "http://localhost:3000"
|
"url": "http://localhost:3000"
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
"@oclif/plugin-plugins": "^5",
|
"@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",
|
||||||
"inquirer": "^9.2.23",
|
"inquirer": "^9.2.23",
|
||||||
"superjson": "^2.2.1"
|
"superjson": "^2.2.1"
|
||||||
},
|
},
|
||||||
|
|||||||
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
@@ -20,6 +20,9 @@ dependencies:
|
|||||||
chalk:
|
chalk:
|
||||||
specifier: ^5.3.0
|
specifier: ^5.3.0
|
||||||
version: 5.3.0
|
version: 5.3.0
|
||||||
|
cli-table3:
|
||||||
|
specifier: ^0.6.5
|
||||||
|
version: 0.6.5
|
||||||
inquirer:
|
inquirer:
|
||||||
specifier: ^9.2.23
|
specifier: ^9.2.23
|
||||||
version: 9.2.23
|
version: 9.2.23
|
||||||
@@ -783,6 +786,13 @@ packages:
|
|||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
picocolors: 1.0.1
|
picocolors: 1.0.1
|
||||||
|
|
||||||
|
/@colors/colors@1.5.0:
|
||||||
|
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
|
||||||
|
engines: {node: '>=0.1.90'}
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
/@cspotcode/source-map-support@0.8.1:
|
/@cspotcode/source-map-support@0.8.1:
|
||||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -2263,6 +2273,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
|
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
/cli-table3@0.6.5:
|
||||||
|
resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==}
|
||||||
|
engines: {node: 10.* || >= 12.*}
|
||||||
|
dependencies:
|
||||||
|
string-width: 4.2.3
|
||||||
|
optionalDependencies:
|
||||||
|
'@colors/colors': 1.5.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cli-width@4.1.0:
|
/cli-width@4.1.0:
|
||||||
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
|
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
|
|||||||
@@ -83,23 +83,17 @@ export default class Authenticate extends Command {
|
|||||||
try {
|
try {
|
||||||
console.log(`\n${chalk.blue("Validating server...")}`);
|
console.log(`\n${chalk.blue("Validating server...")}`);
|
||||||
|
|
||||||
const response = await axios.post(
|
await axios.post(
|
||||||
`${url}/api/trpc/auth.verifyToken`,
|
`${url}/api/trpc/auth.verifyToken`,
|
||||||
{
|
{},
|
||||||
json: {
|
|
||||||
token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.data.result.data.json) {
|
|
||||||
this.error(chalk.red("Invalid token"));
|
|
||||||
}
|
|
||||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||||
this.log(chalk.green("Authentication details saved successfully."));
|
this.log(chalk.green("Authentication details saved successfully."));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
94
src/commands/project/create.ts
Normal file
94
src/commands/project/create.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { Command, Flags } from "@oclif/core";
|
||||||
|
import axios from "axios";
|
||||||
|
import chalk from "chalk";
|
||||||
|
import inquirer, { type Answers, type QuestionCollection } from "inquirer";
|
||||||
|
|
||||||
|
import { readAuthConfig } from "../../utils/utils.js";
|
||||||
|
|
||||||
|
export default class ProjectCreate extends Command {
|
||||||
|
static override description =
|
||||||
|
"Create a new project with an optional description.";
|
||||||
|
|
||||||
|
static override examples = [
|
||||||
|
"$ <%= config.bin %> <%= command.id %> -n MyProject -d 'This is my project description'",
|
||||||
|
"$ <%= config.bin %> <%= command.id %> -n MyProject",
|
||||||
|
"$ <%= config.bin %> <%= command.id %>",
|
||||||
|
];
|
||||||
|
|
||||||
|
static override flags = {
|
||||||
|
description: Flags.string({
|
||||||
|
char: "d",
|
||||||
|
description: "Description of the project",
|
||||||
|
required: false,
|
||||||
|
}),
|
||||||
|
name: Flags.string({
|
||||||
|
char: "n",
|
||||||
|
description: "Name of the project",
|
||||||
|
required: false,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
public async run(): Promise<void> {
|
||||||
|
const auth = await readAuthConfig(this);
|
||||||
|
|
||||||
|
console.log(chalk.blue.bold("\n Create a New Project \n"));
|
||||||
|
|
||||||
|
const { flags } = await this.parse(ProjectCreate);
|
||||||
|
|
||||||
|
let answers: Answers = {};
|
||||||
|
|
||||||
|
const questions: QuestionCollection[] = [];
|
||||||
|
|
||||||
|
if (!flags.name) {
|
||||||
|
questions.push({
|
||||||
|
message: chalk.green("Enter the project name:"),
|
||||||
|
name: "name",
|
||||||
|
type: "input",
|
||||||
|
validate: (input) => (input ? true : "Project name is required"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!flags.description) {
|
||||||
|
questions.push({
|
||||||
|
default: "",
|
||||||
|
message: chalk.green("Enter the project description (optional):"),
|
||||||
|
name: "description",
|
||||||
|
type: "input",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (questions.length > 0) {
|
||||||
|
answers = await inquirer.prompt(questions);
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = flags.name || answers.name;
|
||||||
|
const description = flags.description || answers.description;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${auth.url}/api/trpc/project.createCLI`,
|
||||||
|
{
|
||||||
|
json: {
|
||||||
|
description,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${auth.token}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.data.result.data.json) {
|
||||||
|
this.error(chalk.red("Error`"));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(chalk.green(`Project '${name}' created successfully.`));
|
||||||
|
} catch (error) {
|
||||||
|
// @ts-expect-error hola
|
||||||
|
this.error(chalk.red(`Failed to create project: ${error.message}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/commands/project/list.ts
Normal file
60
src/commands/project/list.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { Command } from "@oclif/core";
|
||||||
|
import axios from "axios";
|
||||||
|
import chalk from "chalk";
|
||||||
|
import Table from "cli-table3";
|
||||||
|
|
||||||
|
import { readAuthConfig } from "../../utils/utils.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 response = await axios.get(`${auth.url}/api/trpc/project.all`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${auth.token}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.data.result.data.json) {
|
||||||
|
this.error(chalk.red("Error fetching projects"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const projects = response.data.result.data.json;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// @ts-expect-error error is not defined
|
||||||
|
this.error(chalk.red(`Failed to list projects: ${error.message}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,13 +42,10 @@ export default class Verify extends Command {
|
|||||||
|
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
`${url}/api/trpc/auth.verifyToken`,
|
`${url}/api/trpc/auth.verifyToken`,
|
||||||
{
|
{},
|
||||||
json: {
|
|
||||||
token,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
4
src/utils/http.ts
Normal file
4
src/utils/http.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": "Dokploy CLI",
|
||||||
|
};
|
||||||
36
src/utils/utils.ts
Normal file
36
src/utils/utils.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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 const readAuthConfig = async (
|
||||||
|
command: Command,
|
||||||
|
): Promise<{ token: string; url: string }> => {
|
||||||
|
if (!fs.existsSync(configPath)) {
|
||||||
|
command.error(
|
||||||
|
chalk.red(
|
||||||
|
"No configuration file found. Please authenticate first using the 'authenticate' command.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const configFileContent = fs.readFileSync(configPath, "utf8");
|
||||||
|
const config = JSON.parse(configFileContent);
|
||||||
|
const { token, url } = config;
|
||||||
|
|
||||||
|
if (!url || !token) {
|
||||||
|
command.error(
|
||||||
|
chalk.red(
|
||||||
|
"Incomplete authentication details. Please authenticate again using the 'authenticate' command.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { token, url };
|
||||||
|
};
|
||||||
14
test/commands/project/create.test.ts
Normal file
14
test/commands/project/create.test.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import {runCommand} from '@oclif/test'
|
||||||
|
import {expect} from 'chai'
|
||||||
|
|
||||||
|
describe('project:create', () => {
|
||||||
|
it('runs project:create cmd', async () => {
|
||||||
|
const {stdout} = await runCommand('project:create')
|
||||||
|
expect(stdout).to.contain('hello world')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('runs project:create --name oclif', async () => {
|
||||||
|
const {stdout} = await runCommand('project:create --name oclif')
|
||||||
|
expect(stdout).to.contain('hello oclif')
|
||||||
|
})
|
||||||
|
})
|
||||||
14
test/commands/project/list.test.ts
Normal file
14
test/commands/project/list.test.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import {runCommand} from '@oclif/test'
|
||||||
|
import {expect} from 'chai'
|
||||||
|
|
||||||
|
describe('project:list', () => {
|
||||||
|
it('runs project:list cmd', async () => {
|
||||||
|
const {stdout} = await runCommand('project:list')
|
||||||
|
expect(stdout).to.contain('hello world')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('runs project:list --name oclif', async () => {
|
||||||
|
const {stdout} = await runCommand('project:list --name oclif')
|
||||||
|
expect(stdout).to.contain('hello oclif')
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user