@forgehive/forge-cli 0.3.10 → 0.3.11
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 +25 -2
- package/dist/tasks/auth/list.d.ts +5 -1
- package/dist/tasks/auth/list.js +20 -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/unlink.d.ts +11 -0
- package/dist/tasks/project/unlink.js +65 -0
- package/dist/tasks/task/createTask.js +5 -4
- package/dist/tasks/types.d.ts +1 -0
- package/dist/test/tasks/create.test.js +4 -3
- package/forge.json +14 -0
- package/package.json +4 -3
- package/src/runner.ts +25 -3
- package/src/tasks/auth/list.ts +22 -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/unlink.ts +74 -0
- package/src/tasks/task/createTask.ts +5 -4
- package/src/tasks/types.ts +1 -0
- package/src/test/tasks/create.test.ts +4 -3
package/src/tasks/auth/list.ts
CHANGED
|
@@ -25,19 +25,33 @@ 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
|
+
console.log('')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log('Available profiles:\n')
|
|
39
|
+
|
|
40
|
+
const tableData = profiles.profiles.map(profile => ({
|
|
41
|
+
Name: profile.name,
|
|
42
|
+
'API Key': profile.apiKey,
|
|
43
|
+
URL: profile.url
|
|
44
|
+
}))
|
|
29
45
|
|
|
30
|
-
|
|
31
|
-
const isDefault = profile.name === profiles.default
|
|
32
|
-
const prefix = isDefault ? '* ' : ' '
|
|
33
|
-
console.log(`${prefix}${profile.name} - API Key: ${profile.apiKey}`)
|
|
34
|
-
})
|
|
46
|
+
console.table(tableData, ['Name', 'API Key', 'URL'])
|
|
35
47
|
|
|
36
48
|
console.log('\nUse auth:add to create or update a profile')
|
|
37
|
-
console.log('
|
|
49
|
+
console.log('Use auth:switch [name] or auth:switch [index] to switch profiles')
|
|
50
|
+
console.log('========================================')
|
|
38
51
|
|
|
39
52
|
return {
|
|
40
|
-
default: profiles.default
|
|
53
|
+
default: profiles.default,
|
|
54
|
+
profiles: tableData
|
|
41
55
|
}
|
|
42
56
|
}
|
|
43
57
|
})
|
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
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// TASK: create
|
|
2
|
+
// Run this task with:
|
|
3
|
+
// forge task:run project:create
|
|
4
|
+
|
|
5
|
+
import { createTask } from '@forgehive/task'
|
|
6
|
+
import { Schema } from '@forgehive/schema'
|
|
7
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
8
|
+
import fs from 'fs/promises'
|
|
9
|
+
import path from 'path'
|
|
10
|
+
|
|
11
|
+
import { load as loadConf } from '../conf/load'
|
|
12
|
+
import { loadCurrent as loadCurrentProfile } from '../auth/loadCurrent'
|
|
13
|
+
import { type ForgeConf, type Profile } from '../types'
|
|
14
|
+
|
|
15
|
+
const name = 'project:create'
|
|
16
|
+
const description = 'Create a new project in ForgeHive'
|
|
17
|
+
|
|
18
|
+
const schema = new Schema({
|
|
19
|
+
projectName: Schema.string().optional(),
|
|
20
|
+
description: Schema.string().optional()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const boundaries = {
|
|
24
|
+
loadConf: loadConf.asBoundary(),
|
|
25
|
+
loadCurrentProfile: loadCurrentProfile.asBoundary(),
|
|
26
|
+
writeFile: async (filePath: string, content: string): Promise<void> => {
|
|
27
|
+
await fs.writeFile(filePath, content, 'utf-8')
|
|
28
|
+
},
|
|
29
|
+
createProject: async (profile: Profile, payload: { projectName: string; description: string; uuid: string }): Promise<Response> => {
|
|
30
|
+
const authToken = `${profile.apiKey}:${profile.apiSecret}`
|
|
31
|
+
return await fetch(`${profile.url}/api/projects`, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
'Authorization': `Bearer ${authToken}`
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify(payload)
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const create = createTask({
|
|
43
|
+
name,
|
|
44
|
+
description,
|
|
45
|
+
schema,
|
|
46
|
+
boundaries,
|
|
47
|
+
fn: async function (argv, { loadConf, loadCurrentProfile, writeFile, createProject }) {
|
|
48
|
+
const { projectName: inputProjectName, description } = argv
|
|
49
|
+
|
|
50
|
+
// Load current configuration
|
|
51
|
+
const conf = await loadConf({})
|
|
52
|
+
|
|
53
|
+
// Use provided projectName or fall back to forge.json project name
|
|
54
|
+
const projectName = inputProjectName || conf.project.name
|
|
55
|
+
|
|
56
|
+
if (!projectName) {
|
|
57
|
+
throw new Error('Project name is required. Provide --projectName or ensure forge.json has a project name.')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check if project already has a UUID, generate one if not
|
|
61
|
+
let projectUuid = conf.project.uuid
|
|
62
|
+
if (!projectUuid) {
|
|
63
|
+
projectUuid = uuidv4()
|
|
64
|
+
|
|
65
|
+
// Update forge.json with the new UUID
|
|
66
|
+
const forgePath = path.join(process.cwd(), 'forge.json')
|
|
67
|
+
const updatedConf: ForgeConf = {
|
|
68
|
+
...conf,
|
|
69
|
+
project: {
|
|
70
|
+
...conf.project,
|
|
71
|
+
uuid: projectUuid
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await writeFile(forgePath, JSON.stringify(updatedConf, null, 2))
|
|
76
|
+
console.log(`Generated and saved project UUID: ${projectUuid}`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Load current profile for API authentication
|
|
80
|
+
const profile = await loadCurrentProfile({})
|
|
81
|
+
|
|
82
|
+
// Prepare API request payload
|
|
83
|
+
const payload = {
|
|
84
|
+
projectName,
|
|
85
|
+
description: description || '',
|
|
86
|
+
uuid: projectUuid
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Make API request to create project
|
|
90
|
+
const response = await createProject(profile, payload)
|
|
91
|
+
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
const errorText = await response.text()
|
|
94
|
+
throw new Error(`Failed to create project: ${response.status} ${response.statusText} - ${errorText}`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = await response.json()
|
|
98
|
+
|
|
99
|
+
console.log('Project created successfully!')
|
|
100
|
+
console.log(`Project UUID: ${result.project.uuid}`)
|
|
101
|
+
console.log(`Project Name: ${result.project.projectName}`)
|
|
102
|
+
console.log(`\n🌐 View your project on the dashboard: ${profile.url}/dashboard/projects/${result.project.uuid}`)
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
project: result.project,
|
|
107
|
+
localUuid: projectUuid
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// TASK: link
|
|
2
|
+
// Run this task with:
|
|
3
|
+
// forge project:link [uuid]
|
|
4
|
+
|
|
5
|
+
import { createTask } from '@forgehive/task'
|
|
6
|
+
import { Schema } from '@forgehive/schema'
|
|
7
|
+
import fs from 'fs/promises'
|
|
8
|
+
import path from 'path'
|
|
9
|
+
|
|
10
|
+
import { load as loadConf } from '../conf/load'
|
|
11
|
+
import { loadCurrent as loadCurrentProfile } from '../auth/loadCurrent'
|
|
12
|
+
import { type ForgeConf, type Profile } from '../types'
|
|
13
|
+
|
|
14
|
+
const name = 'project:link'
|
|
15
|
+
const description = 'Link an existing remote project to the local project by UUID'
|
|
16
|
+
|
|
17
|
+
const schema = new Schema({
|
|
18
|
+
uuid: Schema.string()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const boundaries = {
|
|
22
|
+
loadConf: loadConf.asBoundary(),
|
|
23
|
+
loadCurrentProfile: loadCurrentProfile.asBoundary(),
|
|
24
|
+
writeFile: async (filePath: string, content: string): Promise<void> => {
|
|
25
|
+
await fs.writeFile(filePath, content, 'utf-8')
|
|
26
|
+
},
|
|
27
|
+
fetchProject: async (profile: Profile, uuid: string): Promise<Response> => {
|
|
28
|
+
const authToken = `${profile.apiKey}:${profile.apiSecret}`
|
|
29
|
+
console.log(`Fetching project ${uuid} from ${profile.url}...`)
|
|
30
|
+
return await fetch(`${profile.url}/api/projects/${uuid}`, {
|
|
31
|
+
method: 'GET',
|
|
32
|
+
headers: {
|
|
33
|
+
'Authorization': `Bearer ${authToken}`
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const link = createTask({
|
|
40
|
+
name,
|
|
41
|
+
description,
|
|
42
|
+
schema,
|
|
43
|
+
boundaries,
|
|
44
|
+
fn: async function ({ uuid }, { loadConf, loadCurrentProfile, writeFile, fetchProject }) {
|
|
45
|
+
// Load current configuration and profile
|
|
46
|
+
const conf = await loadConf({})
|
|
47
|
+
|
|
48
|
+
// Check if project already has a UUID
|
|
49
|
+
if (conf.project.uuid) {
|
|
50
|
+
throw new Error(`Project is already linked to UUID: ${conf.project.uuid}. Use a different project or remove the existing UUID from forge.json first.`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const profile = await loadCurrentProfile({})
|
|
54
|
+
|
|
55
|
+
console.log(`Checking if project ${uuid} exists on ${profile.url}...`)
|
|
56
|
+
|
|
57
|
+
// Check if project exists on remote
|
|
58
|
+
const response = await fetchProject(profile, uuid)
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
if (response.status === 404) {
|
|
62
|
+
throw new Error(`Project with UUID ${uuid} not found on ${profile.url}. Please verify the UUID is correct.`)
|
|
63
|
+
} else if (response.status === 401) {
|
|
64
|
+
throw new Error('Authentication failed. Please check your profile credentials with \'forge auth:list\'.')
|
|
65
|
+
} else {
|
|
66
|
+
const errorText = await response.text()
|
|
67
|
+
throw new Error(`Failed to verify project: ${response.status} ${response.statusText} - ${errorText}`)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const projectData = await response.json()
|
|
72
|
+
const project = projectData.project
|
|
73
|
+
|
|
74
|
+
console.log(`✓ Found project: ${project.projectName}`)
|
|
75
|
+
console.log(` Description: ${project.description || 'No description'}`)
|
|
76
|
+
console.log(` Tasks: ${project.tasks.length} task(s)`)
|
|
77
|
+
|
|
78
|
+
// Update forge.json with the UUID
|
|
79
|
+
const forgePath = path.join(process.cwd(), 'forge.json')
|
|
80
|
+
const updatedConf: ForgeConf = {
|
|
81
|
+
...conf,
|
|
82
|
+
project: {
|
|
83
|
+
...conf.project,
|
|
84
|
+
uuid: uuid
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await writeFile(forgePath, JSON.stringify(updatedConf, null, 2))
|
|
89
|
+
|
|
90
|
+
console.log(`\n✓ Successfully linked project ${uuid} to local forge.json`)
|
|
91
|
+
console.log(` Local project name: ${conf.project.name}`)
|
|
92
|
+
console.log(` Remote project name: ${project.projectName}`)
|
|
93
|
+
console.log(`\n🌐 View your project on the dashboard: ${profile.url}/dashboard/projects/${uuid}`)
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
success: true,
|
|
97
|
+
linkedProject: {
|
|
98
|
+
uuid: project.uuid,
|
|
99
|
+
name: project.projectName,
|
|
100
|
+
description: project.description,
|
|
101
|
+
tasksCount: project.tasks.length
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// TASK: unlink
|
|
2
|
+
// Run this task with:
|
|
3
|
+
// forge project:unlink
|
|
4
|
+
|
|
5
|
+
import { createTask } from '@forgehive/task'
|
|
6
|
+
import { Schema } from '@forgehive/schema'
|
|
7
|
+
import fs from 'fs/promises'
|
|
8
|
+
import path from 'path'
|
|
9
|
+
|
|
10
|
+
import { load as loadConf } from '../conf/load'
|
|
11
|
+
import { type ForgeConf } from '../types'
|
|
12
|
+
|
|
13
|
+
const name = 'project:unlink'
|
|
14
|
+
const description = 'Remove the project UUID link from local forge.json'
|
|
15
|
+
|
|
16
|
+
const schema = new Schema({
|
|
17
|
+
// No parameters needed for unlink
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const boundaries = {
|
|
21
|
+
loadConf: loadConf.asBoundary(),
|
|
22
|
+
writeFile: async (filePath: string, content: string): Promise<void> => {
|
|
23
|
+
await fs.writeFile(filePath, content, 'utf-8')
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const unlink = createTask({
|
|
28
|
+
name,
|
|
29
|
+
description,
|
|
30
|
+
schema,
|
|
31
|
+
boundaries,
|
|
32
|
+
fn: async function (argv, { loadConf, writeFile }) {
|
|
33
|
+
// Load current configuration
|
|
34
|
+
const conf = await loadConf({})
|
|
35
|
+
|
|
36
|
+
// Check if project has a UUID to unlink
|
|
37
|
+
if (!conf.project.uuid) {
|
|
38
|
+
throw new Error('No project UUID found in forge.json. The project is not currently linked to a remote project.')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const currentUuid = conf.project.uuid
|
|
42
|
+
|
|
43
|
+
// Remove the UUID from the project configuration
|
|
44
|
+
const updatedConf: ForgeConf = {
|
|
45
|
+
...conf,
|
|
46
|
+
project: {
|
|
47
|
+
...conf.project,
|
|
48
|
+
uuid: undefined
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Clean up undefined values by creating a new object without the uuid field
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
54
|
+
const { uuid, ...projectWithoutUuid } = updatedConf.project
|
|
55
|
+
const finalConf: ForgeConf = {
|
|
56
|
+
...updatedConf,
|
|
57
|
+
project: projectWithoutUuid
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Write the updated configuration back to forge.json
|
|
61
|
+
const forgePath = path.join(process.cwd(), 'forge.json')
|
|
62
|
+
await writeFile(forgePath, JSON.stringify(finalConf, null, 2))
|
|
63
|
+
|
|
64
|
+
console.log(`✓ Successfully unlinked project from UUID: ${currentUuid}`)
|
|
65
|
+
console.log(' The project is no longer linked to a remote project.')
|
|
66
|
+
console.log(' You can now link to a different project using \'forge project:link [uuid]\'')
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
unlinkedUuid: currentUuid
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createTask } from '@forgehive/task'
|
|
2
2
|
import { Schema } from '@forgehive/schema'
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
3
4
|
|
|
4
5
|
import Handlebars from 'handlebars'
|
|
5
6
|
import path from 'path'
|
|
@@ -38,11 +39,10 @@ export const {{ taskName }} = createTask({
|
|
|
38
39
|
boundaries,
|
|
39
40
|
fn: async function (argv, boundaries) {
|
|
40
41
|
console.log('input:', argv)
|
|
41
|
-
console.log('boundaries:', boundaries)
|
|
42
|
+
console.log('boundaries:', Object.keys(boundaries))
|
|
42
43
|
// Your task implementation goes here
|
|
43
|
-
const status = { status: 'Ok' }
|
|
44
44
|
|
|
45
|
-
return
|
|
45
|
+
return {}
|
|
46
46
|
}
|
|
47
47
|
})
|
|
48
48
|
|
|
@@ -155,7 +155,8 @@ export const createTaskCommand = createTask({
|
|
|
155
155
|
|
|
156
156
|
forge.tasks[descriptor] = {
|
|
157
157
|
path: `${taskPath}/${fileName}`,
|
|
158
|
-
handler: taskName
|
|
158
|
+
handler: taskName,
|
|
159
|
+
uuid: uuidv4()
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
await persistConf(forge, cwd)
|