Skip to content

Commit

Permalink
feat: add list and create project
Browse files Browse the repository at this point in the history
  • Loading branch information
Siumauricio committed Jun 5, 2024
1 parent 9f033de commit bec0dfa
Show file tree
Hide file tree
Showing 12 changed files with 249 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": ["oclif", "prettier"]
"extends": ["oclif", "oclif-typescript", "prettier"]
}
2 changes: 1 addition & 1 deletion config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"token": "hola",
"token": "5qdderpq2ejrjlu90hrnbjohvgc8j1u1k7i00um4",
"url": "http://localhost:3000"
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@oclif/plugin-plugins": "^5",
"axios": "^1.7.2",
"chalk": "^5.3.0",
"cli-table3": "^0.6.5",
"inquirer": "^9.2.23",
"superjson": "^2.2.1"
},
Expand Down
19 changes: 19 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 3 additions & 9 deletions src/commands/authenticate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,23 +83,17 @@ export default class Authenticate extends Command {
try {
console.log(`\n${chalk.blue("Validating server...")}`);

const response = await axios.post(
await axios.post(
`${url}/api/trpc/auth.verifyToken`,
{
json: {
token,
},
},
{},
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
},
);

if (!response.data.result.data.json) {
this.error(chalk.red("Invalid token"));
}
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
this.log(chalk.green("Authentication details saved successfully."));
} catch (error) {
Expand Down
94 changes: 94 additions & 0 deletions src/commands/project/create.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 60 additions & 0 deletions src/commands/project/list.ts
Original file line number Diff line number Diff line change
@@ -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}`));
}
}
}
7 changes: 2 additions & 5 deletions src/commands/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,10 @@ export default class Verify extends Command {

const response = await axios.post(
`${url}/api/trpc/auth.verifyToken`,
{
json: {
token,
},
},
{},
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
},
Expand Down
4 changes: 4 additions & 0 deletions src/utils/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const headers = {
"Content-Type": "application/json",
"User-Agent": "Dokploy CLI",
};
36 changes: 36 additions & 0 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 14 additions & 0 deletions test/commands/project/create.test.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 14 additions & 0 deletions test/commands/project/list.test.ts
Original file line number Diff line number Diff line change
@@ -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')
})
})

0 comments on commit bec0dfa

Please sign in to comment.