Skip to content

Commit 5fb728e

Browse files
committed
feat: add command add-project
1 parent 0a20b0c commit 5fb728e

File tree

3 files changed

+131
-11
lines changed

3 files changed

+131
-11
lines changed

src/__tests__/cli-commands.test.ts

Lines changed: 95 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/* eslint-env jest */
22

3-
import { getVersion, runDoctor } from '../cli-commands.js'
3+
import { getVersion, runDoctor, addProjectWithGithubOrgs } from '../cli-commands.js'
44
import { getPackageJson } from '../utils.js'
5-
import { APIHealthResponse } from '../types.js'
5+
import { APIHealthResponse, APIProjectDetails, APIGithubOrgDetails, APIErrorResponse } from '../types.js'
6+
import { mockApiHealthResponse, mockAPIProjectResponse, mockAPIGithubOrgResponse } from './fixtures.js'
67
import nock from 'nock'
78

89
const pkg = getPackageJson()
@@ -32,12 +33,7 @@ describe('CLI Commands', () => {
3233
let apiHealthResponse: APIHealthResponse
3334
beforeEach(() => {
3435
nock.cleanAll()
35-
apiHealthResponse = {
36-
status: 'ok',
37-
timestamp: new Date().toISOString(),
38-
version: '0.1.0-beta3',
39-
name: 'visionBoard'
40-
}
36+
apiHealthResponse = mockApiHealthResponse
4137
})
4238

4339
it('should return success when API is available and compatible', async () => {
@@ -88,4 +84,95 @@ describe('CLI Commands', () => {
8884
expect(result.messages).toHaveLength(1)
8985
})
9086
})
87+
88+
describe('addProjectWithGithubOrgs', () => {
89+
let mockProject: APIProjectDetails
90+
let mockGithubOrg1: APIGithubOrgDetails
91+
let mockGithubOrg2: APIGithubOrgDetails
92+
93+
beforeEach(() => {
94+
nock.cleanAll()
95+
96+
// Setup mock project data using fixtures
97+
mockProject = { ...mockAPIProjectResponse }
98+
99+
// Create simplified GitHub org responses for tests
100+
mockGithubOrg1 = { ...mockAPIGithubOrgResponse }
101+
102+
mockGithubOrg2 = {
103+
...mockAPIGithubOrgResponse,
104+
id: 789,
105+
name: 'org2',
106+
login: 'org2'
107+
}
108+
})
109+
110+
it('should create a project and add GitHub organizations successfully', async () => {
111+
// Mock API calls
112+
nock('http://localhost:3000')
113+
.post('/api/v1/project', { name: 'Test Project' })
114+
.reply(201, mockProject)
115+
116+
nock('http://localhost:3000')
117+
.post('/api/v1/project/123/gh-org', { githubOrgUrl: 'https://github.com/org1' })
118+
.reply(201, mockGithubOrg1)
119+
120+
nock('http://localhost:3000')
121+
.post('/api/v1/project/123/gh-org', { githubOrgUrl: 'https://github.com/org2' })
122+
.reply(201, mockGithubOrg2)
123+
124+
// Execute the function
125+
const result = await addProjectWithGithubOrgs('Test Project', [
126+
'https://github.com/org1',
127+
'https://github.com/org2'
128+
])
129+
130+
// Verify the result
131+
expect(result.success).toBe(true)
132+
expect(result.messages).toContain('✅ Project created successfully')
133+
expect(result.messages).toHaveLength(1)
134+
expect(nock.isDone()).toBe(true) // Verify all mocked endpoints were called
135+
})
136+
137+
it('should handle failure when project creation fails', async () => {
138+
// Mock failed project creation
139+
nock('http://localhost:3000')
140+
.post('/api/v1/project', { name: 'Existing Project' })
141+
.reply(409, { errors: [{ message: 'Project already exists' }] } as APIErrorResponse)
142+
143+
// Execute the function
144+
const result = await addProjectWithGithubOrgs('Existing Project', [
145+
'https://github.com/org1'
146+
])
147+
148+
// Verify the result
149+
expect(result.success).toBe(false)
150+
expect(result.messages[0]).toContain('❌ Failed to create the project')
151+
expect(result.messages[0]).toContain('Project (Existing Project) already exists')
152+
expect(result.messages).toHaveLength(1)
153+
})
154+
155+
it('should handle failure when adding GitHub organization fails', async () => {
156+
// Mock API calls
157+
nock('http://localhost:3000')
158+
.post('/api/v1/project', { name: 'Test Project' })
159+
.reply(201, mockProject)
160+
161+
// Mock failed GitHub org addition (already exists)
162+
nock('http://localhost:3000')
163+
.post('/api/v1/project/123/gh-org', { githubOrgUrl: 'https://github.com/existing-org' })
164+
.reply(409, { errors: [{ message: 'GitHub organization already exists in the project' }] } as APIErrorResponse)
165+
166+
// Execute the function
167+
const result = await addProjectWithGithubOrgs('Test Project', [
168+
'https://github.com/existing-org'
169+
])
170+
171+
// Verify the result
172+
expect(result.success).toBe(false)
173+
expect(result.messages[0]).toContain('❌ Failed to create the project')
174+
expect(result.messages[0]).toContain('GitHub organization (https://github.com/existing-org) already exists in the project')
175+
expect(result.messages).toHaveLength(1)
176+
})
177+
})
91178
})

src/cli-commands.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CommandResult } from './types.js'
22
import { isApiAvailable, isApiCompatible, getPackageJson } from './utils.js'
3-
import { getAPIDetails } from './api-client.js'
3+
import { getAPIDetails, createProject, addGithubOrgToProject } from './api-client.js'
44

55
const pkg = getPackageJson()
66

@@ -36,3 +36,24 @@ export const runDoctor = async (): Promise<CommandResult> => {
3636
success
3737
}
3838
}
39+
40+
export const addProjectWithGithubOrgs = async (name: string, githubOrgUrls: string[]): Promise<CommandResult> => {
41+
const messages: string[] = []
42+
let success = true
43+
try {
44+
const project = await createProject(name)
45+
// Add GitHub organizations sequentially to avoid race conditions
46+
for (const githubOrgUrl of githubOrgUrls) {
47+
await addGithubOrgToProject(project.id, githubOrgUrl)
48+
}
49+
messages.push('✅ Project created successfully')
50+
} catch (error) {
51+
messages.push(`❌ Failed to create the project: ${error instanceof Error ? error.message : 'Unknown error'}`)
52+
success = false
53+
}
54+
55+
return {
56+
messages,
57+
success
58+
}
59+
}

src/index.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#!/usr/bin/env node
22

33
import { Command } from 'commander'
4-
4+
// @ts-ignore
5+
import { stringToArray } from '@ulisesgascon/string-to-array'
56
import { handleCommandResult } from './utils.js'
6-
import { getVersion, runDoctor } from './cli-commands.js'
7+
import { getVersion, runDoctor, addProjectWithGithubOrgs } from './cli-commands.js'
78

89
const program = new Command()
910

@@ -22,4 +23,15 @@ program
2223
handleCommandResult(result)
2324
})
2425

26+
program
27+
.command('add-project')
28+
.description('Add a project with GitHub organizations')
29+
.requiredOption('-n, --name <name>', 'Project name')
30+
.option('-g, --github-orgs <githubOrgUrls...>', 'GitHub organization URLs')
31+
.action(async (options) => {
32+
const githubOrgs = options.githubOrgs ? stringToArray(options.githubOrgs[0]) : []
33+
const result = await addProjectWithGithubOrgs(options.name, githubOrgs)
34+
handleCommandResult(result)
35+
})
36+
2537
program.parse(process.argv)

0 commit comments

Comments
 (0)