@forgehive/forge-cli 0.3.10 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runner.js +32 -6
- package/dist/tasks/auth/add.d.ts +16 -0
- package/dist/tasks/auth/add.js +56 -4
- package/dist/tasks/auth/clear.d.ts +16 -0
- package/dist/tasks/auth/clear.js +54 -0
- package/dist/tasks/auth/list.d.ts +7 -1
- package/dist/tasks/auth/list.js +28 -8
- package/dist/tasks/auth/switch.js +24 -8
- package/dist/tasks/project/create.d.ts +27 -0
- package/dist/tasks/project/create.js +96 -0
- package/dist/tasks/project/link.d.ts +22 -0
- package/dist/tasks/project/link.js +95 -0
- package/dist/tasks/project/sync.d.ts +116 -0
- package/dist/tasks/project/sync.js +152 -0
- package/dist/tasks/project/unlink.d.ts +11 -0
- package/dist/tasks/project/unlink.js +65 -0
- package/dist/tasks/task/createTask.d.ts +12 -0
- package/dist/tasks/task/createTask.js +55 -5
- package/dist/tasks/task/run.d.ts +10 -2
- package/dist/tasks/task/run.js +14 -6
- package/dist/tasks/types.d.ts +4 -0
- package/dist/test/tasks/create.test.js +4 -3
- package/forge.json +14 -0
- package/package.json +6 -5
- package/src/runner.ts +32 -7
- package/src/tasks/auth/add.ts +66 -4
- package/src/tasks/auth/clear.ts +63 -0
- package/src/tasks/auth/list.ts +30 -8
- package/src/tasks/auth/switch.ts +24 -8
- package/src/tasks/project/README.md +268 -0
- package/src/tasks/project/create.ts +111 -0
- package/src/tasks/project/link.ts +106 -0
- package/src/tasks/project/sync.ts +202 -0
- package/src/tasks/project/unlink.ts +74 -0
- package/src/tasks/task/createTask.ts +72 -5
- package/src/tasks/task/run.ts +17 -6
- package/src/tasks/types.ts +4 -0
- package/src/test/tasks/create.test.ts +4 -3
package/src/runner.ts
CHANGED
|
@@ -20,7 +20,6 @@ import { describe as describeTask } from './tasks/task/describe'
|
|
|
20
20
|
import { fingerprint as fingerprintTask } from './tasks/task/fingerprint'
|
|
21
21
|
import { invoke as invokeTask } from './tasks/task/invoke'
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
import { create as createRunner } from './tasks/runner/create'
|
|
25
24
|
import { remove as removeRunner } from './tasks/runner/remove'
|
|
26
25
|
import { bundle as bundleRunner } from './tasks/runner/bundle'
|
|
@@ -33,6 +32,12 @@ import { add as addProfile } from './tasks/auth/add'
|
|
|
33
32
|
import { switchProfile } from './tasks/auth/switch'
|
|
34
33
|
import { list as listProfiles } from './tasks/auth/list'
|
|
35
34
|
import { remove as removeProfile } from './tasks/auth/remove'
|
|
35
|
+
import { clear as clearProfiles } from './tasks/auth/clear'
|
|
36
|
+
|
|
37
|
+
import { create as createProject } from './tasks/project/create'
|
|
38
|
+
import { link as linkProject } from './tasks/project/link'
|
|
39
|
+
import { unlink as unlinkProject } from './tasks/project/unlink'
|
|
40
|
+
import { sync as syncProject } from './tasks/project/sync'
|
|
36
41
|
|
|
37
42
|
interface CliParsedArguments extends RunnerParsedArguments {
|
|
38
43
|
action: string;
|
|
@@ -80,6 +85,13 @@ runner.load('auth:add', addProfile)
|
|
|
80
85
|
runner.load('auth:switch', switchProfile)
|
|
81
86
|
runner.load('auth:list', listProfiles)
|
|
82
87
|
runner.load('auth:remove', removeProfile)
|
|
88
|
+
runner.load('auth:clear', clearProfiles)
|
|
89
|
+
|
|
90
|
+
// Project commands
|
|
91
|
+
runner.load('project:create', createProject)
|
|
92
|
+
runner.load('project:link', linkProject)
|
|
93
|
+
runner.load('project:unlink', unlinkProject)
|
|
94
|
+
runner.load('project:sync', syncProject)
|
|
83
95
|
|
|
84
96
|
// Set handler
|
|
85
97
|
runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
@@ -93,7 +105,7 @@ runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
|
93
105
|
let silent = false
|
|
94
106
|
const task = runner.getTask(taskName)
|
|
95
107
|
if (!task) {
|
|
96
|
-
throw new Error(`
|
|
108
|
+
throw new Error(`Forge command "${taskName}" not found`)
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
try {
|
|
@@ -101,6 +113,8 @@ runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
|
101
113
|
|
|
102
114
|
const commandsWithDescriptor = ['task:create', 'task:remove', 'task:publish', 'task:describe', 'task:fingerprint']
|
|
103
115
|
const commandsWithRunner = ['runner:create', 'runner:remove']
|
|
116
|
+
const commandsWithoutParams = ['project:unlink', 'project:sync', 'auth:clear']
|
|
117
|
+
const silentCommands = ['task:describe', 'task:list', 'auth:list', 'info']
|
|
104
118
|
|
|
105
119
|
if (commandsWithDescriptor.includes(taskName)) {
|
|
106
120
|
result = await task.run({ descriptorName: action })
|
|
@@ -167,7 +181,7 @@ runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
|
167
181
|
})
|
|
168
182
|
} else if (taskName === 'auth:switch' || taskName === 'auth:remove') {
|
|
169
183
|
result = await task.run({
|
|
170
|
-
profileName: action
|
|
184
|
+
profileName: String(action)
|
|
171
185
|
})
|
|
172
186
|
} else if (taskName === 'docs:download') {
|
|
173
187
|
const { path } = args as { path?: string }
|
|
@@ -175,15 +189,26 @@ runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
|
175
189
|
result = await task.run({
|
|
176
190
|
path
|
|
177
191
|
})
|
|
192
|
+
} else if (taskName === 'project:create') {
|
|
193
|
+
const { projectName, description } = args as { projectName?: string, description?: string }
|
|
194
|
+
|
|
195
|
+
result = await task.run({
|
|
196
|
+
projectName,
|
|
197
|
+
description
|
|
198
|
+
})
|
|
199
|
+
} else if (taskName === 'project:link') {
|
|
200
|
+
const { uuid } = args as { uuid: string }
|
|
201
|
+
result = await task.run({
|
|
202
|
+
uuid
|
|
203
|
+
})
|
|
204
|
+
} else if (commandsWithoutParams.includes(taskName)) {
|
|
205
|
+
result = await task.run({})
|
|
178
206
|
} else {
|
|
179
207
|
result = await task.run(args)
|
|
180
208
|
|
|
181
|
-
if (taskName === 'info') {
|
|
182
|
-
silent = true
|
|
183
|
-
}
|
|
184
209
|
}
|
|
185
210
|
|
|
186
|
-
if (taskName
|
|
211
|
+
if (silentCommands.includes(taskName)) {
|
|
187
212
|
silent = true
|
|
188
213
|
}
|
|
189
214
|
|
package/src/tasks/auth/add.ts
CHANGED
|
@@ -24,23 +24,83 @@ const boundaries = {
|
|
|
24
24
|
const buildsPath = path.join(os.homedir(), '.forge')
|
|
25
25
|
const profilesPath = path.join(buildsPath, 'profiles.json')
|
|
26
26
|
await fs.writeFile(profilesPath, JSON.stringify(profiles, null, 2))
|
|
27
|
+
},
|
|
28
|
+
fetchMeInfo: async (apiKey: string, apiSecret: string, url: string): Promise<{
|
|
29
|
+
success: boolean
|
|
30
|
+
teamName?: string
|
|
31
|
+
teamUuid?: string
|
|
32
|
+
userName?: string
|
|
33
|
+
error?: string
|
|
34
|
+
}> => {
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch(`${url}/api/me`, {
|
|
37
|
+
method: 'GET',
|
|
38
|
+
headers: {
|
|
39
|
+
'Authorization': `Bearer ${apiKey}:${apiSecret}`,
|
|
40
|
+
'Content-Type': 'application/json'
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
if (response.ok) {
|
|
45
|
+
const data = await response.json()
|
|
46
|
+
return {
|
|
47
|
+
success: true,
|
|
48
|
+
teamName: data.team?.name,
|
|
49
|
+
teamUuid: data.team?.uuid,
|
|
50
|
+
userName: data.user?.name
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
const errorData = await response.json().catch(() => ({ error: 'Unknown error' }))
|
|
54
|
+
return { success: false, error: errorData.error || `HTTP ${response.status}` }
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return { success: false, error: error instanceof Error ? error.message : 'Network error' }
|
|
58
|
+
}
|
|
27
59
|
}
|
|
28
60
|
}
|
|
29
61
|
|
|
30
62
|
export const add = createTask({
|
|
31
63
|
schema,
|
|
32
64
|
boundaries,
|
|
33
|
-
fn: async function ({ name, apiKey, apiSecret, url }, { loadProfiles, persistProfiles }) {
|
|
65
|
+
fn: async function ({ name, apiKey, apiSecret, url }, { loadProfiles, persistProfiles, fetchMeInfo }) {
|
|
34
66
|
const profiles = await loadProfiles({})
|
|
35
67
|
|
|
68
|
+
console.log('Verifying credentials...')
|
|
69
|
+
|
|
70
|
+
// Fetch team and user information from /me endpoint
|
|
71
|
+
const meInfo = await fetchMeInfo(apiKey, apiSecret, url)
|
|
72
|
+
|
|
73
|
+
if (!meInfo.success) {
|
|
74
|
+
throw new Error(`Failed to verify credentials: ${meInfo.error}`)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log('✅ Credentials verified')
|
|
78
|
+
if (meInfo.userName) {
|
|
79
|
+
console.log(` User: ${meInfo.userName}`)
|
|
80
|
+
}
|
|
81
|
+
if (meInfo.teamName) {
|
|
82
|
+
console.log(` Team: ${meInfo.teamName}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Create profile with team information
|
|
86
|
+
const profile = {
|
|
87
|
+
name,
|
|
88
|
+
apiKey,
|
|
89
|
+
apiSecret,
|
|
90
|
+
url,
|
|
91
|
+
teamName: meInfo.teamName,
|
|
92
|
+
teamUuid: meInfo.teamUuid,
|
|
93
|
+
userName: meInfo.userName
|
|
94
|
+
}
|
|
95
|
+
|
|
36
96
|
// Check if profile with same name already exists
|
|
37
97
|
const existingProfileIndex = profiles.profiles.findIndex(p => p.name === name)
|
|
38
98
|
if (existingProfileIndex >= 0) {
|
|
39
99
|
// Replace existing profile
|
|
40
|
-
profiles.profiles[existingProfileIndex] =
|
|
100
|
+
profiles.profiles[existingProfileIndex] = profile
|
|
41
101
|
} else {
|
|
42
102
|
// Add new profile
|
|
43
|
-
profiles.profiles.push(
|
|
103
|
+
profiles.profiles.push(profile)
|
|
44
104
|
}
|
|
45
105
|
|
|
46
106
|
// Set as default profile
|
|
@@ -51,7 +111,9 @@ export const add = createTask({
|
|
|
51
111
|
|
|
52
112
|
return {
|
|
53
113
|
status: 'Ok',
|
|
54
|
-
message: `Profile '${name}' added and set as default
|
|
114
|
+
message: `Profile '${name}' added and set as default`,
|
|
115
|
+
teamName: meInfo.teamName,
|
|
116
|
+
userName: meInfo.userName
|
|
55
117
|
}
|
|
56
118
|
}
|
|
57
119
|
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// TASK: clear
|
|
2
|
+
// Run this task with:
|
|
3
|
+
// forge task:run auth:clear
|
|
4
|
+
|
|
5
|
+
import { createTask } from '@forgehive/task'
|
|
6
|
+
import { Schema } from '@forgehive/schema'
|
|
7
|
+
import path from 'path'
|
|
8
|
+
import fs from 'fs/promises'
|
|
9
|
+
import os from 'os'
|
|
10
|
+
|
|
11
|
+
import { load as loadProfiles } from './load'
|
|
12
|
+
import { type Profiles } from '../types'
|
|
13
|
+
|
|
14
|
+
const schema = new Schema({})
|
|
15
|
+
|
|
16
|
+
const boundaries = {
|
|
17
|
+
loadProfiles: loadProfiles.asBoundary(),
|
|
18
|
+
clearProfiles: async (): Promise<void> => {
|
|
19
|
+
const buildsPath = path.join(os.homedir(), '.forge')
|
|
20
|
+
const profilesPath = path.join(buildsPath, 'profiles.json')
|
|
21
|
+
|
|
22
|
+
// Create empty profiles structure
|
|
23
|
+
const emptyProfiles: Profiles = {
|
|
24
|
+
default: '',
|
|
25
|
+
profiles: []
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await fs.writeFile(profilesPath, JSON.stringify(emptyProfiles, null, 2))
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const clear = createTask({
|
|
33
|
+
schema,
|
|
34
|
+
boundaries,
|
|
35
|
+
fn: async function (_argv, { loadProfiles, clearProfiles }) {
|
|
36
|
+
const profiles = await loadProfiles({})
|
|
37
|
+
|
|
38
|
+
if (profiles.profiles.length === 0) {
|
|
39
|
+
console.log('No profiles found to clear.')
|
|
40
|
+
return { status: 'Ok', message: 'No profiles found' }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const profileCount = profiles.profiles.length
|
|
44
|
+
console.log(`Found ${profileCount} profile(s) to clear:`)
|
|
45
|
+
|
|
46
|
+
profiles.profiles.forEach(profile => {
|
|
47
|
+
console.log(` - ${profile.name} (${profile.teamName || 'Unknown team'})`)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
console.log('\\nClearing all profiles...')
|
|
51
|
+
|
|
52
|
+
// Clear all profiles
|
|
53
|
+
await clearProfiles()
|
|
54
|
+
|
|
55
|
+
console.log('✅ All profiles cleared successfully')
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
status: 'Ok',
|
|
59
|
+
message: `Cleared ${profileCount} profile(s)`,
|
|
60
|
+
clearedCount: profileCount
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
})
|
package/src/tasks/auth/list.ts
CHANGED
|
@@ -25,19 +25,41 @@ export const list = createTask({
|
|
|
25
25
|
return { status: 'Ok', profiles: [] }
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// Show current profile
|
|
29
|
+
const currentProfile = profiles.profiles.find(profile => profile.name === profiles.default)
|
|
30
|
+
if (currentProfile) {
|
|
31
|
+
console.log('Current Profile:')
|
|
32
|
+
console.log(` Name: ${currentProfile.name}`)
|
|
33
|
+
console.log(` API Key: ${currentProfile.apiKey}`)
|
|
34
|
+
console.log(` URL: ${currentProfile.url}`)
|
|
35
|
+
if (currentProfile.userName) {
|
|
36
|
+
console.log(` User: ${currentProfile.userName}`)
|
|
37
|
+
}
|
|
38
|
+
if (currentProfile.teamName) {
|
|
39
|
+
console.log(` Team: ${currentProfile.teamName}`)
|
|
40
|
+
}
|
|
41
|
+
console.log('')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log('Available profiles:\n')
|
|
45
|
+
|
|
46
|
+
const tableData = profiles.profiles.map(profile => ({
|
|
47
|
+
Name: profile.name,
|
|
48
|
+
'API Key': profile.apiKey,
|
|
49
|
+
URL: profile.url,
|
|
50
|
+
Team: profile.teamName || 'Unknown',
|
|
51
|
+
User: profile.userName || 'Unknown'
|
|
52
|
+
}))
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
const isDefault = profile.name === profiles.default
|
|
32
|
-
const prefix = isDefault ? '* ' : ' '
|
|
33
|
-
console.log(`${prefix}${profile.name} - API Key: ${profile.apiKey}`)
|
|
34
|
-
})
|
|
54
|
+
console.table(tableData, ['Name', 'API Key', 'URL', 'Team', 'User'])
|
|
35
55
|
|
|
36
56
|
console.log('\nUse auth:add to create or update a profile')
|
|
37
|
-
console.log('
|
|
57
|
+
console.log('Use auth:switch [name] or auth:switch [index] to switch profiles')
|
|
58
|
+
console.log('========================================')
|
|
38
59
|
|
|
39
60
|
return {
|
|
40
|
-
default: profiles.default
|
|
61
|
+
default: profiles.default,
|
|
62
|
+
profiles: tableData
|
|
41
63
|
}
|
|
42
64
|
}
|
|
43
65
|
})
|
package/src/tasks/auth/switch.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// TASK: switch
|
|
2
2
|
// Run this task with:
|
|
3
|
-
// forge
|
|
3
|
+
// forge auth:switch [name] or forge auth:switch [index]
|
|
4
4
|
|
|
5
5
|
import { createTask } from '@forgehive/task'
|
|
6
6
|
import { Schema } from '@forgehive/schema'
|
|
@@ -31,23 +31,39 @@ export const switchProfile = createTask({
|
|
|
31
31
|
// Load profiles
|
|
32
32
|
const profiles = await loadProfiles({})
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
if (profiles.profiles.length === 0) {
|
|
35
|
+
throw new Error('No profiles found. Use auth:add to create one.')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let targetProfile: string
|
|
36
39
|
|
|
37
|
-
if (
|
|
38
|
-
|
|
40
|
+
// Check if profileName is a number (index)
|
|
41
|
+
const indexInput = parseInt(profileName, 10)
|
|
42
|
+
if (!isNaN(indexInput)) {
|
|
43
|
+
// Using index
|
|
44
|
+
if (indexInput < 0 || indexInput >= profiles.profiles.length) {
|
|
45
|
+
throw new Error(`Profile index ${indexInput} is out of range. Use auth:list to see available profiles (0-${profiles.profiles.length - 1}).`)
|
|
46
|
+
}
|
|
47
|
+
targetProfile = profiles.profiles[indexInput].name
|
|
48
|
+
} else {
|
|
49
|
+
// Using profile name
|
|
50
|
+
const profileExists = profiles.profiles.some(profile => profile.name === profileName)
|
|
51
|
+
if (!profileExists) {
|
|
52
|
+
throw new Error(`Profile "${profileName}" not found. Use auth:list to see available profiles.`)
|
|
53
|
+
}
|
|
54
|
+
targetProfile = profileName
|
|
39
55
|
}
|
|
40
56
|
|
|
41
57
|
// Update default profile
|
|
42
|
-
profiles.default =
|
|
58
|
+
profiles.default = targetProfile
|
|
43
59
|
|
|
44
60
|
// Save updated profiles
|
|
45
61
|
await persistProfiles(profiles)
|
|
46
62
|
|
|
47
|
-
console.log(`Switched to profile: ${
|
|
63
|
+
console.log(`Switched to profile: ${targetProfile}`)
|
|
48
64
|
|
|
49
65
|
return {
|
|
50
|
-
default:
|
|
66
|
+
default: targetProfile
|
|
51
67
|
}
|
|
52
68
|
}
|
|
53
69
|
})
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Project Commands
|
|
2
|
+
|
|
3
|
+
This directory contains ForgeHive CLI commands for project management operations.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
### `project:create`
|
|
8
|
+
|
|
9
|
+
Creates a new project in ForgeHive with automatic UUID generation and API integration.
|
|
10
|
+
|
|
11
|
+
#### Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
forge project:create [--projectName="My Project"] [--description="Project description"]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
#### Parameters
|
|
18
|
+
|
|
19
|
+
| Parameter | Type | Required | Description |
|
|
20
|
+
|-----------|------|----------|-------------|
|
|
21
|
+
| `projectName` | string | No | Name of the project to create. If not provided, uses the project name from `forge.json` |
|
|
22
|
+
| `description` | string | No | Optional description for the project |
|
|
23
|
+
|
|
24
|
+
#### Behavior
|
|
25
|
+
|
|
26
|
+
1. **UUID Management**: Checks if the local `forge.json` has a project UUID
|
|
27
|
+
- If no UUID exists, generates a new UUID v4 and saves it to `forge.json`
|
|
28
|
+
- If UUID already exists, uses the existing one
|
|
29
|
+
|
|
30
|
+
2. **Authentication**: Uses the current profile from `auth:list` for API authentication
|
|
31
|
+
- Requires a valid profile set via `auth:add` and `auth:switch`
|
|
32
|
+
- Uses profile's `apiKey`, `apiSecret`, and `url` for the request
|
|
33
|
+
|
|
34
|
+
3. **API Request**: Makes a POST request to `/api/projects` with:
|
|
35
|
+
- `projectName`: The provided project name
|
|
36
|
+
- `description`: The provided description (or empty string if not provided)
|
|
37
|
+
- `uuid`: The project UUID (generated or existing)
|
|
38
|
+
|
|
39
|
+
4. **Response**: Returns the created project details including:
|
|
40
|
+
- Project UUID
|
|
41
|
+
- Project name
|
|
42
|
+
- Team information
|
|
43
|
+
- Creation timestamps
|
|
44
|
+
|
|
45
|
+
#### Examples
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Create a project using the name from forge.json
|
|
49
|
+
forge project:create
|
|
50
|
+
|
|
51
|
+
# Create a project with custom name
|
|
52
|
+
forge project:create --projectName="My New Project"
|
|
53
|
+
|
|
54
|
+
# Create a project with custom name and description
|
|
55
|
+
forge project:create --projectName="Analytics Dashboard" --description="Customer analytics and reporting platform"
|
|
56
|
+
|
|
57
|
+
# Create a project using forge.json name but with description
|
|
58
|
+
forge project:create --description="Using the default project name from configuration"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### Success Output
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Generated and saved project UUID: 550e8400-e29b-41d4-a716-446655440000
|
|
65
|
+
Project created successfully!
|
|
66
|
+
Project UUID: 550e8400-e29b-41d4-a716-446655440000
|
|
67
|
+
Project Name: My New Project
|
|
68
|
+
|
|
69
|
+
🌐 View your project on the dashboard: https://api.forgehive.com/dashboard/projects/550e8400-e29b-41d4-a716-446655440000
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Error Cases
|
|
73
|
+
|
|
74
|
+
- **No Project Name**: If no `--projectName` is provided and `forge.json` doesn't contain a project name
|
|
75
|
+
- **No Profile**: If no authentication profile is configured
|
|
76
|
+
- **Invalid Profile**: If the current profile is invalid or expired
|
|
77
|
+
- **API Errors**: Network issues, server errors, or validation failures
|
|
78
|
+
- **Duplicate Names**: If a project with the same name already exists in the team
|
|
79
|
+
|
|
80
|
+
#### File Changes
|
|
81
|
+
|
|
82
|
+
When run, this command may modify:
|
|
83
|
+
- `forge.json`: Adds or updates the project UUID if one doesn't exist
|
|
84
|
+
|
|
85
|
+
#### Dependencies
|
|
86
|
+
|
|
87
|
+
- Requires `@forgehive/task` framework
|
|
88
|
+
- Requires `@forgehive/schema` for validation
|
|
89
|
+
- Uses `uuid` package for UUID generation
|
|
90
|
+
- Integrates with `auth:loadCurrent` for profile management
|
|
91
|
+
- Integrates with `conf:load` for configuration management
|
|
92
|
+
|
|
93
|
+
#### API Integration
|
|
94
|
+
|
|
95
|
+
This command integrates with the ForgeHive Projects API. See the [Projects API Documentation](../../../../../../../hive/docs/projects-api.md) for detailed API specifications.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Project Configuration
|
|
100
|
+
|
|
101
|
+
Projects are configured via the `forge.json` file in your project root:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"project": {
|
|
106
|
+
"name": "My Project",
|
|
107
|
+
"uuid": "550e8400-e29b-41d4-a716-446655440000"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The UUID is automatically generated and managed by the CLI commands.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### `project:link`
|
|
117
|
+
|
|
118
|
+
Links an existing remote project to the local project by validating the UUID and updating forge.json.
|
|
119
|
+
|
|
120
|
+
#### Usage
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
forge project:link [uuid]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Parameters
|
|
127
|
+
|
|
128
|
+
| Parameter | Type | Required | Description |
|
|
129
|
+
|-----------|------|----------|-------------|
|
|
130
|
+
| `uuid` | string (UUID) | Yes | UUID of the existing remote project to link |
|
|
131
|
+
|
|
132
|
+
#### Behavior
|
|
133
|
+
|
|
134
|
+
1. **UUID Validation**: Validates the provided UUID format using regex
|
|
135
|
+
2. **Remote Verification**: Makes a GET request to `/api/projects/{uuid}` to verify the project exists
|
|
136
|
+
3. **Authentication**: Uses the current profile for API authentication
|
|
137
|
+
4. **Project Details**: Displays project information (name, description, task count) if found
|
|
138
|
+
5. **Local Update**: Updates the local `forge.json` with the verified UUID
|
|
139
|
+
|
|
140
|
+
#### Examples
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Link to an existing remote project
|
|
144
|
+
forge project:link 550e8400-e29b-41d4-a716-446655440000
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Success Output
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
Checking if project 550e8400-e29b-41d4-a716-446655440000 exists on https://api.forgehive.com...
|
|
151
|
+
✓ Found project: Customer Analytics
|
|
152
|
+
Description: Analytics platform for customer behavior analysis
|
|
153
|
+
Tasks: 3 task(s)
|
|
154
|
+
|
|
155
|
+
✓ Successfully linked project 550e8400-e29b-41d4-a716-446655440000 to local forge.json
|
|
156
|
+
Local project name: My Local Project
|
|
157
|
+
Remote project name: Customer Analytics
|
|
158
|
+
|
|
159
|
+
🌐 View your project on the dashboard: https://api.forgehive.com/dashboard/projects/550e8400-e29b-41d4-a716-446655440000
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Error Cases
|
|
163
|
+
|
|
164
|
+
- **Invalid UUID Format**: If the provided UUID doesn't match the expected format
|
|
165
|
+
- **Project Already Linked**: If the local project already has a UUID in forge.json
|
|
166
|
+
- **Project Not Found**: If the UUID doesn't exist on the remote server (404)
|
|
167
|
+
- **Authentication Failed**: If the current profile credentials are invalid (401)
|
|
168
|
+
- **No Profile**: If no authentication profile is configured
|
|
169
|
+
- **Network Issues**: Connection problems or server errors
|
|
170
|
+
|
|
171
|
+
#### Error Examples
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Invalid UUID format
|
|
175
|
+
forge project:link invalid-uuid
|
|
176
|
+
# Error: Invalid UUID format: invalid-uuid. Please provide a valid UUID.
|
|
177
|
+
|
|
178
|
+
# Project already linked
|
|
179
|
+
forge project:link 550e8400-e29b-41d4-a716-446655440000
|
|
180
|
+
# Error: Project is already linked to UUID: 123e4567-e89b-12d3-a456-426614174000. Use a different project or remove the existing UUID from forge.json first.
|
|
181
|
+
|
|
182
|
+
# Project not found
|
|
183
|
+
forge project:link 00000000-0000-0000-0000-000000000000
|
|
184
|
+
# Error: Project with UUID 00000000-0000-0000-0000-000000000000 not found on https://api.forgehive.com. Please verify the UUID is correct.
|
|
185
|
+
|
|
186
|
+
# Authentication failed
|
|
187
|
+
forge project:link 550e8400-e29b-41d4-a716-446655440000
|
|
188
|
+
# Error: Authentication failed. Please check your profile credentials with 'forge auth:list'.
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### File Changes
|
|
192
|
+
|
|
193
|
+
When run successfully, this command modifies:
|
|
194
|
+
- `forge.json`: Updates the `project.uuid` field with the verified remote project UUID
|
|
195
|
+
|
|
196
|
+
#### Dependencies
|
|
197
|
+
|
|
198
|
+
- Requires `@forgehive/task` framework
|
|
199
|
+
- Requires `@forgehive/schema` for validation
|
|
200
|
+
- Integrates with `auth:loadCurrent` for profile management
|
|
201
|
+
- Integrates with `conf:load` for configuration management
|
|
202
|
+
- Uses the ForgeHive Projects API `/api/projects/{uuid}` endpoint
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### `project:unlink`
|
|
207
|
+
|
|
208
|
+
Removes the project UUID link from the local forge.json, allowing the project to be linked to a different remote project.
|
|
209
|
+
|
|
210
|
+
#### Usage
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
forge project:unlink
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### Parameters
|
|
217
|
+
|
|
218
|
+
This command takes no parameters.
|
|
219
|
+
|
|
220
|
+
#### Behavior
|
|
221
|
+
|
|
222
|
+
1. **UUID Check**: Verifies that a project UUID exists in forge.json
|
|
223
|
+
2. **Local Update**: Removes the UUID field from the project configuration
|
|
224
|
+
3. **Confirmation**: Shows the UUID that was removed and next steps
|
|
225
|
+
|
|
226
|
+
#### Examples
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Unlink the current project
|
|
230
|
+
forge project:unlink
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### Success Output
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
✓ Successfully unlinked project from UUID: 550e8400-e29b-41d4-a716-446655440000
|
|
237
|
+
The project is no longer linked to a remote project.
|
|
238
|
+
You can now link to a different project using 'forge project:link [uuid]'
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### Error Cases
|
|
242
|
+
|
|
243
|
+
- **No UUID Found**: If the project is not currently linked (no UUID in forge.json)
|
|
244
|
+
|
|
245
|
+
#### Error Examples
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# No project linked
|
|
249
|
+
forge project:unlink
|
|
250
|
+
# Error: No project UUID found in forge.json. The project is not currently linked to a remote project.
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### File Changes
|
|
254
|
+
|
|
255
|
+
When run successfully, this command modifies:
|
|
256
|
+
- `forge.json`: Removes the `project.uuid` field completely
|
|
257
|
+
|
|
258
|
+
#### Dependencies
|
|
259
|
+
|
|
260
|
+
- Requires `@forgehive/task` framework
|
|
261
|
+
- Requires `@forgehive/schema` for validation
|
|
262
|
+
- Integrates with `conf:load` for configuration management
|
|
263
|
+
|
|
264
|
+
**Note**: This command only affects the local forge.json file. It does not make any API calls or modify anything on the remote server.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Project Configuration
|