@forgehive/forge-cli 0.3.9 ā 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/load.js +1 -0
- package/dist/tasks/auth/switch.js +24 -8
- package/dist/tasks/conf/info.d.ts +6 -0
- package/dist/tasks/conf/info.js +24 -1
- 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/task/run.d.ts +2 -2
- package/dist/tasks/task/run.js +7 -10
- 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 +7 -4
- package/src/runner.ts +25 -3
- package/src/tasks/auth/list.ts +22 -8
- package/src/tasks/auth/load.ts +1 -0
- package/src/tasks/auth/switch.ts +24 -8
- package/src/tasks/conf/info.ts +23 -1
- 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/task/run.ts +7 -11
- package/src/tasks/types.ts +1 -0
- package/src/test/tasks/create.test.ts +4 -3
|
@@ -35,11 +35,10 @@ export const newTask = createTask({
|
|
|
35
35
|
boundaries,
|
|
36
36
|
fn: async function (argv, boundaries) {
|
|
37
37
|
console.log('input:', argv)
|
|
38
|
-
console.log('boundaries:', boundaries)
|
|
38
|
+
console.log('boundaries:', Object.keys(boundaries))
|
|
39
39
|
// Your task implementation goes here
|
|
40
|
-
const status = { status: 'Ok' }
|
|
41
40
|
|
|
42
|
-
return
|
|
41
|
+
return {}
|
|
43
42
|
}
|
|
44
43
|
})
|
|
45
44
|
|
|
@@ -94,5 +93,7 @@ describe('Create task', () => {
|
|
|
94
93
|
const forgeContent = await fs.promises.readFile(path_1.default.join(rootDir, 'forge.json'), 'utf-8');
|
|
95
94
|
const forgeConf = JSON.parse(forgeContent);
|
|
96
95
|
expect(forgeConf.tasks['sample:newTask']).toBeDefined();
|
|
96
|
+
expect(forgeConf.tasks['sample:newTask'].uuid).toBeDefined();
|
|
97
|
+
expect(typeof forgeConf.tasks['sample:newTask'].uuid).toBe('string');
|
|
97
98
|
});
|
|
98
99
|
});
|
package/forge.json
CHANGED
|
@@ -118,6 +118,20 @@
|
|
|
118
118
|
"docs:download": {
|
|
119
119
|
"path": "src/tasks/docs/download.ts",
|
|
120
120
|
"handler": "download"
|
|
121
|
+
},
|
|
122
|
+
"project:create": {
|
|
123
|
+
"path": "src/tasks/project/create.ts",
|
|
124
|
+
"handler": "create"
|
|
125
|
+
},
|
|
126
|
+
"project:link": {
|
|
127
|
+
"path": "src/tasks/project/link.ts",
|
|
128
|
+
"handler": "link",
|
|
129
|
+
"uuid": "cb9f82e1-d397-46d9-9f0d-2b0e3becbfa1"
|
|
130
|
+
},
|
|
131
|
+
"project:unlink": {
|
|
132
|
+
"path": "src/tasks/project/unlink.ts",
|
|
133
|
+
"handler": "unlink",
|
|
134
|
+
"uuid": "414d37de-793c-4d01-899d-69515f5e0948"
|
|
121
135
|
}
|
|
122
136
|
},
|
|
123
137
|
"runners": {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgehive/forge-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.11",
|
|
4
4
|
"description": "TypeScript CLI application",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"@forgehive/task": "^0.2.5",
|
|
18
18
|
"esbuild": "^0.25.0",
|
|
19
19
|
"handlebars": "^4.7.8",
|
|
20
|
-
"minimist": "^1.2.8"
|
|
20
|
+
"minimist": "^1.2.8",
|
|
21
|
+
"typescript": "^5.3.3"
|
|
21
22
|
}
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
@@ -27,11 +28,13 @@
|
|
|
27
28
|
"esbuild": "^0.25.0",
|
|
28
29
|
"handlebars": "^4.7.8",
|
|
29
30
|
"minimist": "^1.2.8",
|
|
31
|
+
"typescript": "^5.3.3",
|
|
32
|
+
"uuid": "^11.1.0",
|
|
30
33
|
"@forgehive/hive-sdk": "0.1.2",
|
|
31
34
|
"@forgehive/record-tape": "0.2.5",
|
|
32
|
-
"@forgehive/task": "0.2.5",
|
|
33
35
|
"@forgehive/runner": "0.2.5",
|
|
34
|
-
"@forgehive/schema": "0.1.4"
|
|
36
|
+
"@forgehive/schema": "0.1.4",
|
|
37
|
+
"@forgehive/task": "0.2.5"
|
|
35
38
|
},
|
|
36
39
|
"devDependencies": {
|
|
37
40
|
"@types/archiver": "^6.0.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'
|
|
@@ -34,6 +33,10 @@ 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'
|
|
36
35
|
|
|
36
|
+
import { create as createProject } from './tasks/project/create'
|
|
37
|
+
import { link as linkProject } from './tasks/project/link'
|
|
38
|
+
import { unlink as unlinkProject } from './tasks/project/unlink'
|
|
39
|
+
|
|
37
40
|
interface CliParsedArguments extends RunnerParsedArguments {
|
|
38
41
|
action: string;
|
|
39
42
|
}
|
|
@@ -81,6 +84,11 @@ runner.load('auth:switch', switchProfile)
|
|
|
81
84
|
runner.load('auth:list', listProfiles)
|
|
82
85
|
runner.load('auth:remove', removeProfile)
|
|
83
86
|
|
|
87
|
+
// Project commands
|
|
88
|
+
runner.load('project:create', createProject)
|
|
89
|
+
runner.load('project:link', linkProject)
|
|
90
|
+
runner.load('project:unlink', unlinkProject)
|
|
91
|
+
|
|
84
92
|
// Set handler
|
|
85
93
|
runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
86
94
|
const parsedArgs = runner.parseArguments(data)
|
|
@@ -167,7 +175,7 @@ runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
|
167
175
|
})
|
|
168
176
|
} else if (taskName === 'auth:switch' || taskName === 'auth:remove') {
|
|
169
177
|
result = await task.run({
|
|
170
|
-
profileName: action
|
|
178
|
+
profileName: String(action)
|
|
171
179
|
})
|
|
172
180
|
} else if (taskName === 'docs:download') {
|
|
173
181
|
const { path } = args as { path?: string }
|
|
@@ -175,6 +183,20 @@ runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
|
175
183
|
result = await task.run({
|
|
176
184
|
path
|
|
177
185
|
})
|
|
186
|
+
} else if (taskName === 'project:create') {
|
|
187
|
+
const { projectName, description } = args as { projectName?: string, description?: string }
|
|
188
|
+
|
|
189
|
+
result = await task.run({
|
|
190
|
+
projectName,
|
|
191
|
+
description
|
|
192
|
+
})
|
|
193
|
+
} else if (taskName === 'project:link') {
|
|
194
|
+
const { uuid } = args as { uuid: string }
|
|
195
|
+
result = await task.run({
|
|
196
|
+
uuid
|
|
197
|
+
})
|
|
198
|
+
} else if (taskName === 'project:unlink') {
|
|
199
|
+
result = await task.run({})
|
|
178
200
|
} else {
|
|
179
201
|
result = await task.run(args)
|
|
180
202
|
|
|
@@ -183,7 +205,7 @@ runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
|
183
205
|
}
|
|
184
206
|
}
|
|
185
207
|
|
|
186
|
-
if (taskName === 'task:describe' || taskName === 'task:list') {
|
|
208
|
+
if (taskName === 'task:describe' || taskName === 'task:list' || taskName === 'auth:list') {
|
|
187
209
|
silent = true
|
|
188
210
|
}
|
|
189
211
|
|
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/load.ts
CHANGED
|
@@ -43,6 +43,7 @@ export const load = createTask({
|
|
|
43
43
|
profiles = JSON.parse(content) as Profiles
|
|
44
44
|
} catch (_error) {
|
|
45
45
|
console.log('Creating profiles.json')
|
|
46
|
+
console.log('===============================================')
|
|
46
47
|
await fs.writeFile(profilesPath, '{"profiles": [], "default": ""}')
|
|
47
48
|
}
|
|
48
49
|
|
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
|
})
|
package/src/tasks/conf/info.ts
CHANGED
|
@@ -27,7 +27,29 @@ export const info = createTask({
|
|
|
27
27
|
const packageJsonContent = await readFile(packageJsonPath)
|
|
28
28
|
const packageJson = JSON.parse(packageJsonContent)
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
let config
|
|
31
|
+
try {
|
|
32
|
+
config = await loadConfig({})
|
|
33
|
+
} catch (error: unknown) {
|
|
34
|
+
if (error instanceof Error && error.message.includes('ENOENT')) {
|
|
35
|
+
console.log('============ Forge CLI Information ============')
|
|
36
|
+
console.log('===============================================')
|
|
37
|
+
console.log()
|
|
38
|
+
console.log(`Version: ${packageJson.version}`)
|
|
39
|
+
console.log()
|
|
40
|
+
console.log('ā No forge.json file found in current directory.')
|
|
41
|
+
console.log(' Run "forge init" to create a new Forge project.')
|
|
42
|
+
console.log()
|
|
43
|
+
console.log('===============================================')
|
|
44
|
+
return {
|
|
45
|
+
version: packageJson.version,
|
|
46
|
+
profile: null,
|
|
47
|
+
paths: null,
|
|
48
|
+
error: 'No forge.json found'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
throw error
|
|
52
|
+
}
|
|
31
53
|
|
|
32
54
|
// Display human-friendly information
|
|
33
55
|
console.log('===============================================')
|
|
@@ -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
|
+
|