Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): Add project command #451

Merged
merged 3 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/api/src/project/controller/project.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export class ProjectController {
async syncFork(
@CurrentUser() user: User,
@Param('projectSlug') projectSlug: Project['slug'],
@Param('hardSync') hardSync: boolean = false
@Query('hardSync') hardSync: boolean = false
) {
return await this.service.syncFork(user, projectSlug, hardSync)
}
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/project/dto/fork.project/fork.project.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Workspace } from '@prisma/client'
import { IsOptional, IsString } from 'class-validator'
import { IsBoolean, IsOptional, IsString } from 'class-validator'

export class ForkProject {
@IsString()
Expand All @@ -10,7 +10,7 @@ export class ForkProject {
@IsOptional()
name?: string

@IsString()
@IsBoolean()
@IsOptional()
storePrivateKey?: boolean
}
28 changes: 15 additions & 13 deletions apps/api/src/workspace-role/service/workspace-role.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,20 +110,22 @@ export class WorkspaceRoleService {
})
)

// Create the project associations
const projectSlugToIdMap = await this.getProjectSlugToIdMap(
dto.projectSlugs
)

if (dto.projectSlugs && dto.projectSlugs.length > 0) {
op.push(
this.prisma.projectWorkspaceRoleAssociation.createMany({
data: dto.projectSlugs.map((projectSlug) => ({
roleId: workspaceRoleId,
projectId: projectSlugToIdMap.get(projectSlug)
}))
})
if (dto.projectSlugs) {
// Create the project associations
const projectSlugToIdMap = await this.getProjectSlugToIdMap(
dto.projectSlugs
)

if (dto.projectSlugs && dto.projectSlugs.length > 0) {
op.push(
this.prisma.projectWorkspaceRoleAssociation.createMany({
data: dto.projectSlugs.map((projectSlug) => ({
roleId: workspaceRoleId,
projectId: projectSlugToIdMap.get(projectSlug)
}))
})
)
}
}

const workspaceRole = (await this.prisma.$transaction(op))[0]
Expand Down
4 changes: 2 additions & 2 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
"name": "cli",
"version": "1.0.0",
"description": "CLI for keyshade",
"main": "index.ts",
"main": "dist/src/index.js",
"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/src/index.js",
"lint": "eslint \"src/**/*.ts\" --fix"
},
"keywords": [],
Expand Down
19 changes: 12 additions & 7 deletions apps/cli/src/commands/base.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
import { fetchProfileConfig } from '@/util/configuration'
import { Logger } from '@/util/logger'
import { getDefaultProfile } from '@/util/profile'
import type { Command } from 'commander'
import { Option, type Command } from 'commander'
import ControllerInstance from '@/util/controller-instance'

/**
Expand Down Expand Up @@ -55,13 +55,18 @@ export default abstract class BaseCommand {
}
})

this.getOptions().forEach((option) =>
command.option(
this.getOptions().forEach((option) => {
const newOption: Option = new Option(
`${option.short}, ${option.long}`,
option.description,
option.defaultValue
)
)
option.description
).default(option.defaultValue)

option.choices &&
option.choices.length > 0 &&
newOption.choices(option.choices)

command.addOption(newOption)
})
this.getArguments().forEach((argument) =>
command.argument(argument.name, argument.description)
)
Expand Down
34 changes: 34 additions & 0 deletions apps/cli/src/commands/project.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import BaseCommand from './base.command'
import CreateProject from './project/create.project'
import DeleteProject from './project/delete.project'
import ForkProject from './project/fork.project'
import GetProject from './project/get.project'
import ListProjectForks from './project/list-forks.project'
import ListProject from './project/list.project'
import SyncProject from './project/sync.project'
import UnlinkProject from './project/unlink.project'
import UpdateProject from './project/update.project'

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

getDescription(): string {
return 'Manage projects of a workspace'
}

getSubCommands(): BaseCommand[] {
return [
new CreateProject(),
new DeleteProject(),
new ForkProject(),
new GetProject(),
new ListProjectForks(),
new ListProject(),
new SyncProject(),
new UnlinkProject(),
new UpdateProject()
]
}
}
103 changes: 103 additions & 0 deletions apps/cli/src/commands/project/create.project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type {
CommandActionData,
CommandArgument,
CommandOption
} from '@/types/command/command.types'
import BaseCommand from '../base.command'
import { text } from '@clack/prompts'
import ControllerInstance from '@/util/controller-instance'
import { Logger } from '@/util/logger'

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

getDescription(): string {
return 'Creates a project'
}

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

getOptions(): CommandOption[] {
return [
{
short: '-n',
long: '--name <string>',
description: 'Name of the project'
},
{
short: '-d',
long: '--description <string>',
description: 'Description of the project. Defaults to project name'
},
{
short: '-k',
long: '--store-private-key',
description: 'Store the private key in the project. Defaults to true',
defaultValue: true
},
{
short: '-a',
long: '--access-level <string>',
description: 'Access level of the project. Defaults to PRIVATE.',
defaultValue: 'PRIVATE',
choices: ['GLOBAL', 'PRIVATE', 'INTERNAL']
}
]
}

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

const parsedData = await this.parseOptions(options)

const { data, error, success } =
await ControllerInstance.getInstance().projectController.createProject(
{
workspaceSlug,
...parsedData
},
this.headers
)

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

async parseOptions(options: CommandActionData['options']): Promise<{
name: string
description?: string
storePrivateKey: boolean
accessLevel: string
}> {
let { name, description } = options
const { storePrivateKey, accessLevel } = options

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

if (!description) {
description = name
}

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

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

getDescription(): string {
return 'Deletes a project'
}

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

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

const { error, success } =
await ControllerInstance.getInstance().projectController.deleteProject(
{
projectSlug
},
this.headers
)

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

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

getDescription(): string {
return 'Forks a project'
}

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

getOptions(): CommandOption[] {
return [
{
short: '-n',
long: '--name <Workspace Name>',
description: 'Name of the workspace.'
},
{
short: '-k',
long: '--store-private-key <boolean>',
description: 'Store the private key in the project. Defaults to true',
defaultValue: true
},
{
short: '-w',
long: '--workspace <string>',
description: 'Workspace slug to fork the project in'
}
]
}

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

console.log(options)

const { data, error, success } =
await ControllerInstance.getInstance().projectController.forkProject(
{
projectSlug,
...options
},
this.headers
)

if (success) {
Logger.info(`Project ${data.name} (${data.slug}) forked successfully!`)
Logger.info(`Created at ${data.createdAt}`)
Logger.info(`Updated at ${data.updatedAt}`)
} else {
Logger.error(`Failed to fork project: ${error.message}`)
}
}
}
Loading
Loading