Skip to content

Commit

Permalink
feat(cli): Add workspace operations
Browse files Browse the repository at this point in the history
  • Loading branch information
rajdip-b committed Sep 17, 2024
1 parent 85cb8ab commit 1c07dd2
Show file tree
Hide file tree
Showing 29 changed files with 617 additions and 30 deletions.
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

pnpm lint && pnpm format && pnpm test:api && pnpm test:api-client
pnpm lint && pnpm format
2 changes: 1 addition & 1 deletion apps/cli/.eslintrc.js → apps/cli/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ module.exports = {
'@typescript-eslint/space-before-function-paren': 'off',
'@typescript-eslint/strict-boolean-expressions': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'space-before-function-paren': 'off',
'space-before-function-paren': 'off'
}
}
4 changes: 3 additions & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
"description": "CLI for keyshade",
"main": "index.ts",
"private": false,
"type": "module",
"scripts": {
"build": "tsc && tsc-alias",
"start": "node dist/src/index.js",
"dev": "pnpm build && node dist/index.js"
"dev": "pnpm build && node dist/index.js",
"lint": "eslint \"src/**/*.ts\" --fix"
},
"keywords": [],
"author": "",
Expand Down
28 changes: 23 additions & 5 deletions apps/cli/src/commands/base.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { fetchProfileConfig } from '@/util/configuration'
import { Logger } from '@/util/logger'
import { getDefaultProfile } from '@/util/profile'
import type { Command } from 'commander'
import ControllerInstance from '@/util/controller-instance'

/**
* The base class for all commands. All commands should extend this class.
Expand All @@ -17,6 +18,17 @@ export default abstract class BaseCommand {
protected apiKey: string | null = null
protected baseUrl: string | null = null

// Headers to be used by the API requests
protected headers: Record<string, string> | null = null

/**
* Technically the entrypoint to the entire application. This function
* is used to register the various commands across the entire CLI. The
* function is only called from index.ts to register the commands and
* should not be overridden.
*
* @param program The program to add the command to.
*/
readonly prepare = (program: Command): void => {
const argsCount = this.getArguments().length

Expand Down Expand Up @@ -82,7 +94,7 @@ export default abstract class BaseCommand {
}

getVersion(): string {
return null
return '1'
}

/**
Expand All @@ -102,11 +114,9 @@ export default abstract class BaseCommand {
/**
* The action that the command should take.
* @param data The data passed to the command.
* @param data.options The options passed to the command.
* @param data.args The arguments passed to the command.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
action({ options, args }: CommandActionData): Promise<void> | void {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-empty-pattern
action({}: CommandActionData): Promise<void> | void {}

/**
* If the command has subcommands, return them here.
Expand Down Expand Up @@ -154,5 +164,13 @@ export default abstract class BaseCommand {
this.baseUrl = defaultProfile.baseUrl
}
}

// Initialize the header
this.headers = {
'x-keyshade-token': this.apiKey
}

// Initialize Controller Instance
ControllerInstance.initialize(this.baseUrl)
}
}
30 changes: 30 additions & 0 deletions apps/cli/src/commands/workspace.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import BaseCommand from '@/commands/base.command'
import CreateWorkspace from '@/commands/workspace/create.workspace'
import DeleteWorkspace from '@/commands/workspace/delete.workspace'
import ExportWorkspace from '@/commands/workspace/export.workspace'
import GetWorkspace from '@/commands/workspace/get.workspace'
import ListWorkspace from '@/commands/workspace/list.workspace'
import SearchWorkspace from '@/commands/workspace/search.workspace'
import UpdateWorkspace from '@/commands/workspace/update.workspace'

export default class WorkspaceCommand extends BaseCommand {
getName(): string {
return 'workspace'
}

getDescription(): string {
return 'Manages the workspaces on keyshade'
}

getSubCommands(): BaseCommand[] {
return [
new CreateWorkspace(),
new DeleteWorkspace(),
new ExportWorkspace(),
new GetWorkspace(),
new ListWorkspace(),
new SearchWorkspace(),
new UpdateWorkspace()
]
}
}
71 changes: 71 additions & 0 deletions apps/cli/src/commands/workspace/create.workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import BaseCommand from '@/commands/base.command'
import {
type CommandActionData,
type CommandOption
} from '@/types/command/command.types'
import ControllerInstance from '@/util/controller-instance'
import { Logger } from '@/util/logger'
import { text } from '@clack/prompts'

export default class CreateWorkspace extends BaseCommand {
getName(): string {
return 'create'
}

getDescription(): string {
return 'Creates a new workspace'
}

getOptions(): CommandOption[] {
return [
{
short: '-n',
long: '--name <Workspace Name>',
description: 'Name of the workspace.'
},
{
short: '-i',
long: '--icon <Workspace Icon>',
description: 'Icon of the workspace.'
}
]
}

async action({ options }: CommandActionData): Promise<void> {
const { name, icon } = await this.parseInput(options)

const { data, error, success } =
await ControllerInstance.getInstance().workspaceController.createWorkspace(
{
name,
icon
},
this.headers
)

if (success) {
Logger.info(`Workspace ${data.name} (${data.slug}) created successfully!`)
Logger.info(`Created at ${data.createdAt}`)
Logger.info(`Updated at ${data.updatedAt}`)
} else {
Logger.error(`Failed to create workspace: ${error.message}`)
}
}

private async parseInput(options: CommandActionData['options']): Promise<{
name: string
icon?: string
}> {
let { name } = options
const { icon } = options

if (!name) {
name = await text({
message: 'Enter the name of the Workspace',
placeholder: 'My Workspace'
})
}

return { name, icon }
}
}
44 changes: 44 additions & 0 deletions apps/cli/src/commands/workspace/delete.workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import BaseCommand from '@/commands/base.command'
import {
type CommandActionData,
type CommandArgument
} from '@/types/command/command.types'
import ControllerInstance from '@/util/controller-instance'
import { Logger } from '@/util/logger'

export default class DeleteWorkspace extends BaseCommand {
getName(): string {
return 'delete'
}

getDescription(): string {
return 'Deletes an existing workspace'
}

getArguments(): CommandArgument[] {
return [
{
name: '<Workspace Slug>',
description: 'Slug of the workspace which you want to delete.'
}
]
}

async action({ args }: CommandActionData): Promise<void> {
const [workspaceSlug] = args

const { error, success } =
await ControllerInstance.getInstance().workspaceController.deleteWorkspace(
{
workspaceSlug
},
this.headers
)

if (success) {
Logger.info(`Workspace ${workspaceSlug} deleted successfully!`)
} else {
Logger.error(`Failed to delete workspace: ${error.message}`)
}
}
}
68 changes: 68 additions & 0 deletions apps/cli/src/commands/workspace/export.workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import BaseCommand from '@/commands/base.command'
import {
type CommandActionData,
type CommandArgument,
type CommandOption
} from '@/types/command/command.types'
import { Logger } from '@/util/logger'
import ControllerInstance from '@/util/controller-instance'
import { writeFileSync } from 'fs'

export default class ExportWorkspace extends BaseCommand {
getName(): string {
return 'export'
}

getDescription(): string {
return 'Exports all projects, environments, secrets, variables and roles of a workspace'
}

getArguments(): CommandArgument[] {
return [
{
name: '<Workspace Slug>',
description: 'Slug of the workspace which you want to export.'
}
]
}

getOptions(): CommandOption[] {
return [
{
short: '-s',
long: '--save-to-file <file>',
description: 'Saves the exported data to a file.'
}
]
}

async action({ args, options }: CommandActionData): Promise<void> {
const [workspaceSlug] = args
const { saveToFile } = options as { saveToFile: string }

Logger.info('Exporting workspace...')

const { data, error, success } =
await ControllerInstance.getInstance().workspaceController.exportWorkspaceData(
{
workspaceSlug
},
this.headers
)

if (success) {
if (saveToFile) {
const filePath = saveToFile
const fileContent = JSON.stringify(data, null, 2)

writeFileSync(saveToFile, fileContent)

Logger.info(`Workspace exported to file: ${filePath}`)
} else {
Logger.info(JSON.stringify(data))
}
} else {
Logger.error(`Failed exporting workspace: ${error.message}`)
}
}
}
50 changes: 50 additions & 0 deletions apps/cli/src/commands/workspace/get.workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import BaseCommand from '@/commands/base.command'
import {
type CommandActionData,
type CommandArgument
} from '@/types/command/command.types'
import { Logger } from '@/util/logger'
import ControllerInstance from '@/util/controller-instance'

export default class GetWorkspace extends BaseCommand {
getName(): string {
return 'get'
}

getDescription(): string {
return 'Fetches a particular workspace'
}

getArguments(): CommandArgument[] {
return [
{
name: '<Workspace Slug>',
description: 'Slug of the workspace which you want to fetch.'
}
]
}

async action({ args }: CommandActionData): Promise<void> {
const [workspaceSlug] = args

Logger.info('Fetching workspace...')

const { data, error, success } =
await ControllerInstance.getInstance().workspaceController.getWorkspace(
{
workspaceSlug
},
this.headers
)

if (success) {
Logger.info('Workspace fetched successfully:')
Logger.info(`Workspace: ${data.name} (${data.slug})`)
Logger.info(`Created at: ${data.createdAt}`)
Logger.info(`Updated at: ${data.updatedAt}`)
Logger.info(`Is default workspace: ${data.isDefault}`)
} else {
Logger.error(`Failed fetching workspace: ${error.message}`)
}
}
}
31 changes: 31 additions & 0 deletions apps/cli/src/commands/workspace/list.workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import BaseCommand from '@/commands/base.command'
import { Logger } from '@/util/logger'
import ControllerInstance from '@/util/controller-instance'

export default class ListWorkspace extends BaseCommand {
getName(): string {
return 'list'
}

getDescription(): string {
return 'Fetches all the workspace you have access to'
}

async action(): Promise<void> {
Logger.info('Fetching all workspaces...')

const { success, data, error } =
await ControllerInstance.getInstance().workspaceController.getWorkspacesOfUser(
{},
this.headers
)

if (success) {
data.items.forEach((workspace: any) => {
Logger.info(`- ${workspace.name} (${workspace.slug})`)
})
} else {
Logger.error(`Failed fetching workspaces: ${error.message}`)
}
}
}
Loading

0 comments on commit 1c07dd2

Please sign in to comment.