@forgehive/forge-cli 0.3.11 ā 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 +9 -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 +2 -0
- package/dist/tasks/auth/list.js +10 -2
- package/dist/tasks/project/sync.d.ts +116 -0
- package/dist/tasks/project/sync.js +152 -0
- package/dist/tasks/task/createTask.d.ts +12 -0
- package/dist/tasks/task/createTask.js +51 -2
- package/dist/tasks/task/run.d.ts +10 -2
- package/dist/tasks/task/run.js +14 -6
- package/dist/tasks/types.d.ts +3 -0
- package/package.json +3 -3
- package/src/runner.ts +9 -6
- package/src/tasks/auth/add.ts +66 -4
- package/src/tasks/auth/clear.ts +63 -0
- package/src/tasks/auth/list.ts +10 -2
- package/src/tasks/project/sync.ts +202 -0
- package/src/tasks/task/createTask.ts +68 -2
- package/src/tasks/task/run.ts +17 -6
- package/src/tasks/types.ts +3 -0
package/dist/tasks/task/run.d.ts
CHANGED
|
@@ -17,7 +17,11 @@ export declare const run: import("@forgehive/task").TaskInstanceType<(argv: {
|
|
|
17
17
|
}) => Promise<Promise<any>>;
|
|
18
18
|
ensureLogFolder: (logsPath: string) => Promise<void>;
|
|
19
19
|
ensureBuildsFolder: () => Promise<string>;
|
|
20
|
-
sendLogToAPI: (profile: Profile, projectName: string, record: ExecutionRecord) => Promise<
|
|
20
|
+
sendLogToAPI: (profile: Profile, projectName: string, record: ExecutionRecord, taskUuid?: string) => Promise<{
|
|
21
|
+
success: boolean;
|
|
22
|
+
logUuid?: string;
|
|
23
|
+
taskUuid?: string;
|
|
24
|
+
}>;
|
|
21
25
|
}>) => Promise<any>, {
|
|
22
26
|
loadConf: (args: {}) => Promise<Promise<ForgeConf>>;
|
|
23
27
|
loadCurrentProfile: (args: {}) => Promise<Promise<Profile>>;
|
|
@@ -32,5 +36,9 @@ export declare const run: import("@forgehive/task").TaskInstanceType<(argv: {
|
|
|
32
36
|
}) => Promise<Promise<any>>;
|
|
33
37
|
ensureLogFolder: (logsPath: string) => Promise<void>;
|
|
34
38
|
ensureBuildsFolder: () => Promise<string>;
|
|
35
|
-
sendLogToAPI: (profile: Profile, projectName: string, record: ExecutionRecord) => Promise<
|
|
39
|
+
sendLogToAPI: (profile: Profile, projectName: string, record: ExecutionRecord, taskUuid?: string) => Promise<{
|
|
40
|
+
success: boolean;
|
|
41
|
+
logUuid?: string;
|
|
42
|
+
taskUuid?: string;
|
|
43
|
+
}>;
|
|
36
44
|
}>;
|
package/dist/tasks/task/run.js
CHANGED
|
@@ -50,7 +50,7 @@ const boundaries = {
|
|
|
50
50
|
}
|
|
51
51
|
return buildsPath;
|
|
52
52
|
},
|
|
53
|
-
sendLogToAPI: async (profile, projectName, record) => {
|
|
53
|
+
sendLogToAPI: async (profile, projectName, record, taskUuid) => {
|
|
54
54
|
try {
|
|
55
55
|
const config = {
|
|
56
56
|
projectName,
|
|
@@ -63,21 +63,25 @@ const boundaries = {
|
|
|
63
63
|
};
|
|
64
64
|
const client = new hive_sdk_1.HiveLogClient(config);
|
|
65
65
|
const result = await client.sendLog(record);
|
|
66
|
-
if (result === 'success') {
|
|
66
|
+
if (result === 'success' || (typeof result === 'object' && 'uuid' in result)) {
|
|
67
67
|
console.log('===============================================');
|
|
68
68
|
console.log('Log sent to API... ', profile.name, profile.url);
|
|
69
|
-
|
|
69
|
+
if (typeof result === 'object' && result && 'uuid' in result) {
|
|
70
|
+
const logResponse = result;
|
|
71
|
+
return { success: true, logUuid: logResponse.uuid, taskUuid };
|
|
72
|
+
}
|
|
73
|
+
return { success: true, taskUuid };
|
|
70
74
|
}
|
|
71
75
|
else {
|
|
72
76
|
console.error('Failed to send log to API:', profile.url);
|
|
73
|
-
return false;
|
|
77
|
+
return { success: false };
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
catch (e) {
|
|
77
81
|
console.error('Failed to send log to API:', profile.url);
|
|
78
82
|
const error = e;
|
|
79
83
|
console.error('Error:', error.message);
|
|
80
|
-
return false;
|
|
84
|
+
return { success: false };
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
87
|
};
|
|
@@ -89,6 +93,7 @@ exports.run = (0, task_1.createTask)({
|
|
|
89
93
|
const forge = await loadConf({});
|
|
90
94
|
const taskDescriptor = forge.tasks[descriptorName];
|
|
91
95
|
const projectName = forge.project.name;
|
|
96
|
+
const taskUuid = taskDescriptor?.uuid;
|
|
92
97
|
if (taskDescriptor === undefined) {
|
|
93
98
|
throw new Error('Task is not defined on forge.json');
|
|
94
99
|
}
|
|
@@ -149,7 +154,10 @@ exports.run = (0, task_1.createTask)({
|
|
|
149
154
|
await tape.save();
|
|
150
155
|
if (profile) {
|
|
151
156
|
try {
|
|
152
|
-
await sendLogToAPI(profile, projectName, logItem);
|
|
157
|
+
const logResult = await sendLogToAPI(profile, projectName, logItem, taskUuid);
|
|
158
|
+
if (logResult.success && taskUuid) {
|
|
159
|
+
console.log(`š View execution logs: ${profile.url}/tasks/${taskUuid}?tab=logs`);
|
|
160
|
+
}
|
|
153
161
|
}
|
|
154
162
|
catch (e) {
|
|
155
163
|
console.error('Failed to send log to API:', e);
|
package/dist/tasks/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgehive/forge-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.12",
|
|
4
4
|
"description": "TypeScript CLI application",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public",
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@forgehive/hive-sdk": "^0.1.
|
|
13
|
+
"@forgehive/hive-sdk": "^0.1.3",
|
|
14
14
|
"@forgehive/record-tape": "^0.2.5",
|
|
15
15
|
"@forgehive/runner": "^0.2.5",
|
|
16
16
|
"@forgehive/schema": "^0.1.4",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"minimist": "^1.2.8",
|
|
31
31
|
"typescript": "^5.3.3",
|
|
32
32
|
"uuid": "^11.1.0",
|
|
33
|
-
"@forgehive/hive-sdk": "0.1.
|
|
33
|
+
"@forgehive/hive-sdk": "0.1.3",
|
|
34
34
|
"@forgehive/record-tape": "0.2.5",
|
|
35
35
|
"@forgehive/runner": "0.2.5",
|
|
36
36
|
"@forgehive/schema": "0.1.4",
|
package/src/runner.ts
CHANGED
|
@@ -32,10 +32,12 @@ import { add as addProfile } from './tasks/auth/add'
|
|
|
32
32
|
import { switchProfile } from './tasks/auth/switch'
|
|
33
33
|
import { list as listProfiles } from './tasks/auth/list'
|
|
34
34
|
import { remove as removeProfile } from './tasks/auth/remove'
|
|
35
|
+
import { clear as clearProfiles } from './tasks/auth/clear'
|
|
35
36
|
|
|
36
37
|
import { create as createProject } from './tasks/project/create'
|
|
37
38
|
import { link as linkProject } from './tasks/project/link'
|
|
38
39
|
import { unlink as unlinkProject } from './tasks/project/unlink'
|
|
40
|
+
import { sync as syncProject } from './tasks/project/sync'
|
|
39
41
|
|
|
40
42
|
interface CliParsedArguments extends RunnerParsedArguments {
|
|
41
43
|
action: string;
|
|
@@ -83,11 +85,13 @@ runner.load('auth:add', addProfile)
|
|
|
83
85
|
runner.load('auth:switch', switchProfile)
|
|
84
86
|
runner.load('auth:list', listProfiles)
|
|
85
87
|
runner.load('auth:remove', removeProfile)
|
|
88
|
+
runner.load('auth:clear', clearProfiles)
|
|
86
89
|
|
|
87
90
|
// Project commands
|
|
88
91
|
runner.load('project:create', createProject)
|
|
89
92
|
runner.load('project:link', linkProject)
|
|
90
93
|
runner.load('project:unlink', unlinkProject)
|
|
94
|
+
runner.load('project:sync', syncProject)
|
|
91
95
|
|
|
92
96
|
// Set handler
|
|
93
97
|
runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
@@ -101,7 +105,7 @@ runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
|
101
105
|
let silent = false
|
|
102
106
|
const task = runner.getTask(taskName)
|
|
103
107
|
if (!task) {
|
|
104
|
-
throw new Error(`
|
|
108
|
+
throw new Error(`Forge command "${taskName}" not found`)
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
try {
|
|
@@ -109,6 +113,8 @@ runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
|
109
113
|
|
|
110
114
|
const commandsWithDescriptor = ['task:create', 'task:remove', 'task:publish', 'task:describe', 'task:fingerprint']
|
|
111
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']
|
|
112
118
|
|
|
113
119
|
if (commandsWithDescriptor.includes(taskName)) {
|
|
114
120
|
result = await task.run({ descriptorName: action })
|
|
@@ -195,17 +201,14 @@ runner.setHandler(async (data: ParsedArgs): Promise<unknown> => {
|
|
|
195
201
|
result = await task.run({
|
|
196
202
|
uuid
|
|
197
203
|
})
|
|
198
|
-
} else if (taskName
|
|
204
|
+
} else if (commandsWithoutParams.includes(taskName)) {
|
|
199
205
|
result = await task.run({})
|
|
200
206
|
} else {
|
|
201
207
|
result = await task.run(args)
|
|
202
208
|
|
|
203
|
-
if (taskName === 'info') {
|
|
204
|
-
silent = true
|
|
205
|
-
}
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
if (taskName
|
|
211
|
+
if (silentCommands.includes(taskName)) {
|
|
209
212
|
silent = true
|
|
210
213
|
}
|
|
211
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
|
@@ -32,6 +32,12 @@ export const list = createTask({
|
|
|
32
32
|
console.log(` Name: ${currentProfile.name}`)
|
|
33
33
|
console.log(` API Key: ${currentProfile.apiKey}`)
|
|
34
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
|
+
}
|
|
35
41
|
console.log('')
|
|
36
42
|
}
|
|
37
43
|
|
|
@@ -40,10 +46,12 @@ export const list = createTask({
|
|
|
40
46
|
const tableData = profiles.profiles.map(profile => ({
|
|
41
47
|
Name: profile.name,
|
|
42
48
|
'API Key': profile.apiKey,
|
|
43
|
-
URL: profile.url
|
|
49
|
+
URL: profile.url,
|
|
50
|
+
Team: profile.teamName || 'Unknown',
|
|
51
|
+
User: profile.userName || 'Unknown'
|
|
44
52
|
}))
|
|
45
53
|
|
|
46
|
-
console.table(tableData, ['Name', 'API Key', 'URL'])
|
|
54
|
+
console.table(tableData, ['Name', 'API Key', 'URL', 'Team', 'User'])
|
|
47
55
|
|
|
48
56
|
console.log('\nUse auth:add to create or update a profile')
|
|
49
57
|
console.log('Use auth:switch [name] or auth:switch [index] to switch profiles')
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// TASK: sync
|
|
2
|
+
// Run this task with:
|
|
3
|
+
// forge task:run project:sync
|
|
4
|
+
|
|
5
|
+
import { createTask } from '@forgehive/task'
|
|
6
|
+
import { Schema } from '@forgehive/schema'
|
|
7
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
8
|
+
|
|
9
|
+
import { load } from '../conf/load'
|
|
10
|
+
import { loadCurrent } from '../auth/loadCurrent'
|
|
11
|
+
import { type ForgeConf } from '../types'
|
|
12
|
+
import path from 'path'
|
|
13
|
+
import fs from 'fs/promises'
|
|
14
|
+
|
|
15
|
+
const schema = new Schema({})
|
|
16
|
+
|
|
17
|
+
const boundaries = {
|
|
18
|
+
loadConf: load.asBoundary(),
|
|
19
|
+
loadCurrentProfile: loadCurrent.asBoundary(),
|
|
20
|
+
getCwd: async (): Promise<string> => {
|
|
21
|
+
return process.cwd()
|
|
22
|
+
},
|
|
23
|
+
persistConf: async (forge: ForgeConf, cwd: string): Promise<void> => {
|
|
24
|
+
const forgePath = path.join(cwd, 'forge.json')
|
|
25
|
+
await fs.writeFile(forgePath, JSON.stringify(forge, null, 2))
|
|
26
|
+
},
|
|
27
|
+
syncTasksToHive: async (
|
|
28
|
+
projectUuid: string,
|
|
29
|
+
tasks: Array<{ uuid: string; name: string }>,
|
|
30
|
+
apiKey: string,
|
|
31
|
+
apiSecret: string,
|
|
32
|
+
baseUrl: string
|
|
33
|
+
): Promise<{
|
|
34
|
+
success: boolean
|
|
35
|
+
error?: string
|
|
36
|
+
data?: {
|
|
37
|
+
projectUuid: string
|
|
38
|
+
projectName: string
|
|
39
|
+
summary: {
|
|
40
|
+
total: number
|
|
41
|
+
created: number
|
|
42
|
+
updated: number
|
|
43
|
+
errors: number
|
|
44
|
+
}
|
|
45
|
+
results: {
|
|
46
|
+
created: Array<{ uuid: string; taskName: string; action: string }>
|
|
47
|
+
updated: Array<{ uuid: string; taskName: string; previousName: string; action: string }>
|
|
48
|
+
errors: Array<{ uuid: string; taskName: string; error: string }>
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}> => {
|
|
52
|
+
try {
|
|
53
|
+
const url = `${baseUrl}/api/projects/${projectUuid}/sync`
|
|
54
|
+
const response = await fetch(url, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
'Authorization': `Bearer ${apiKey}:${apiSecret}`
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify({ tasks })
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
if (response.ok) {
|
|
64
|
+
const data = await response.json()
|
|
65
|
+
return { success: true, data }
|
|
66
|
+
} else {
|
|
67
|
+
const errorData = await response.json().catch(() => ({ error: `HTTP ${response.status} - ${response.statusText}` }))
|
|
68
|
+
return { success: false, error: errorData.error || `HTTP ${response.status} - ${response.statusText}` }
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return { success: false, error: error instanceof Error ? error.message : 'Network error' }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const sync = createTask({
|
|
77
|
+
schema,
|
|
78
|
+
boundaries,
|
|
79
|
+
fn: async function (_argv, {
|
|
80
|
+
loadConf,
|
|
81
|
+
loadCurrentProfile,
|
|
82
|
+
getCwd,
|
|
83
|
+
persistConf,
|
|
84
|
+
syncTasksToHive
|
|
85
|
+
}) {
|
|
86
|
+
const cwd = await getCwd()
|
|
87
|
+
const forge = await loadConf({})
|
|
88
|
+
|
|
89
|
+
// Check if project has UUID
|
|
90
|
+
if (!forge.project.uuid) {
|
|
91
|
+
throw new Error('Project does not have a UUID. Please run "forge project:link" to connect to a Hive project.')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log(`
|
|
95
|
+
==================================================
|
|
96
|
+
Starting project sync to Hive!
|
|
97
|
+
Project: ${forge.project.name}
|
|
98
|
+
UUID: ${forge.project.uuid}
|
|
99
|
+
==================================================
|
|
100
|
+
`)
|
|
101
|
+
|
|
102
|
+
// Ensure all tasks have UUIDs and collect them for sync
|
|
103
|
+
let configUpdated = false
|
|
104
|
+
const tasksToSync: Array<{ uuid: string; name: string }> = []
|
|
105
|
+
|
|
106
|
+
if (!forge.tasks) {
|
|
107
|
+
forge.tasks = {}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const [taskDescriptor, taskData] of Object.entries(forge.tasks)) {
|
|
111
|
+
if (!taskData.uuid) {
|
|
112
|
+
taskData.uuid = uuidv4()
|
|
113
|
+
configUpdated = true
|
|
114
|
+
console.log(` ā Generated UUID for task: ${taskDescriptor}`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Use the task descriptor (key) as the name for the API
|
|
118
|
+
const taskName = taskDescriptor
|
|
119
|
+
tasksToSync.push({
|
|
120
|
+
uuid: taskData.uuid,
|
|
121
|
+
name: taskName
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Save config if we added UUIDs
|
|
126
|
+
if (configUpdated) {
|
|
127
|
+
await persistConf(forge, cwd)
|
|
128
|
+
console.log(' š¾ Updated forge.json with new UUIDs')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (tasksToSync.length === 0) {
|
|
132
|
+
console.log(' ā¹ļø No tasks found to sync')
|
|
133
|
+
return { status: 'no-tasks', message: 'No tasks found in project' }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log(` š Found ${tasksToSync.length} tasks to sync`)
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const profile = await loadCurrentProfile({})
|
|
140
|
+
const result = await syncTasksToHive(
|
|
141
|
+
forge.project.uuid,
|
|
142
|
+
tasksToSync,
|
|
143
|
+
profile.apiKey,
|
|
144
|
+
profile.apiSecret,
|
|
145
|
+
profile.url
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if (result.success && result.data) {
|
|
149
|
+
const { summary, results } = result.data
|
|
150
|
+
|
|
151
|
+
console.log('\\n ā
Sync completed successfully!')
|
|
152
|
+
console.log(` Total tasks: ${summary.total}`)
|
|
153
|
+
console.log(` Created: ${summary.created}`)
|
|
154
|
+
console.log(` Updated: ${summary.updated}`)
|
|
155
|
+
console.log(` Errors: ${summary.errors}`)
|
|
156
|
+
|
|
157
|
+
if (results.created.length > 0) {
|
|
158
|
+
console.log('\\n š Created tasks:')
|
|
159
|
+
results.created.forEach(task => {
|
|
160
|
+
console.log(` ⢠${task.taskName} (${task.uuid})`)
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (results.updated.length > 0) {
|
|
165
|
+
console.log('\\n š Updated tasks:')
|
|
166
|
+
results.updated.forEach(task => {
|
|
167
|
+
console.log(` ⢠${task.taskName} (was: ${task.previousName}) (${task.uuid})`)
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (results.errors.length > 0) {
|
|
172
|
+
console.log('\\n ā Tasks with errors:')
|
|
173
|
+
results.errors.forEach(task => {
|
|
174
|
+
console.log(` ⢠${task.taskName}: ${task.error} (${task.uuid})`)
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const projectUrl = `${profile.url}/dashboard/projects/${forge.project.uuid}`
|
|
179
|
+
console.log(`\\n š View your project: ${projectUrl}`)
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
status: 'success',
|
|
183
|
+
summary,
|
|
184
|
+
results,
|
|
185
|
+
projectUrl
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
throw new Error(result.error || 'Unknown sync error')
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
192
|
+
|
|
193
|
+
if (errorMessage.includes('No default profile')) {
|
|
194
|
+
console.log('\\n ā ļø No authentication profile found. Run "forge auth:add" to configure.')
|
|
195
|
+
return { status: 'no-auth', message: 'No authentication profile configured' }
|
|
196
|
+
} else {
|
|
197
|
+
console.log(`\\n ā Sync failed: ${errorMessage}`)
|
|
198
|
+
return { status: 'error', message: errorMessage }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
})
|
|
@@ -8,6 +8,7 @@ import fs from 'fs/promises'
|
|
|
8
8
|
import { camelCase } from '../../utils/camelCase'
|
|
9
9
|
|
|
10
10
|
import { load } from '../conf/load'
|
|
11
|
+
import { loadCurrent } from '../auth/loadCurrent'
|
|
11
12
|
import { type TaskName, type ForgeConf } from '../types'
|
|
12
13
|
|
|
13
14
|
// Define the template content directly in the code
|
|
@@ -55,6 +56,7 @@ const schema = new Schema({
|
|
|
55
56
|
const boundaries = {
|
|
56
57
|
// Load boundaries
|
|
57
58
|
loadConf: load.asBoundary(),
|
|
59
|
+
loadCurrentProfile: loadCurrent.asBoundary(),
|
|
58
60
|
loadTemplate: async (): Promise<string> => {
|
|
59
61
|
return TASK_TEMPLATE
|
|
60
62
|
},
|
|
@@ -106,6 +108,40 @@ const boundaries = {
|
|
|
106
108
|
persistConf: async (forge: ForgeConf, cwd: string): Promise<void> => {
|
|
107
109
|
const forgePath = path.join(cwd, 'forge.json')
|
|
108
110
|
await fs.writeFile(forgePath, JSON.stringify(forge, null, 2))
|
|
111
|
+
},
|
|
112
|
+
createTaskInHive: async (
|
|
113
|
+
projectUuid: string,
|
|
114
|
+
taskUuid: string,
|
|
115
|
+
taskName: string,
|
|
116
|
+
description: string,
|
|
117
|
+
apiKey: string,
|
|
118
|
+
apiSecret: string,
|
|
119
|
+
baseUrl: string
|
|
120
|
+
): Promise<{ success: boolean; taskUrl?: string; error?: string }> => {
|
|
121
|
+
try {
|
|
122
|
+
const url = `${baseUrl}/api/projects/${projectUuid}/tasks/${taskUuid}`
|
|
123
|
+
const response = await fetch(url, {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: {
|
|
126
|
+
'Content-Type': 'application/json',
|
|
127
|
+
'Authorization': `Bearer ${apiKey}:${apiSecret}`
|
|
128
|
+
},
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
taskName,
|
|
131
|
+
description
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
if (response.ok) {
|
|
136
|
+
const taskUrl = `${baseUrl}/tasks/${taskUuid}`
|
|
137
|
+
return { success: true, taskUrl }
|
|
138
|
+
} else {
|
|
139
|
+
const errorData = await response.json().catch(() => ({ error: 'Unknown error' }))
|
|
140
|
+
return { success: false, error: errorData.error || `HTTP ${response.status}` }
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return { success: false, error: error instanceof Error ? error.message : 'Network error' }
|
|
144
|
+
}
|
|
109
145
|
}
|
|
110
146
|
}
|
|
111
147
|
|
|
@@ -118,7 +154,9 @@ export const createTaskCommand = createTask({
|
|
|
118
154
|
loadConf,
|
|
119
155
|
persistConf,
|
|
120
156
|
parseTaskName,
|
|
121
|
-
getCwd
|
|
157
|
+
getCwd,
|
|
158
|
+
loadCurrentProfile,
|
|
159
|
+
createTaskInHive
|
|
122
160
|
}) {
|
|
123
161
|
const { taskName, fileName, descriptor, dir } = await parseTaskName(descriptorName)
|
|
124
162
|
const cwd = await getCwd()
|
|
@@ -153,14 +191,42 @@ export const createTaskCommand = createTask({
|
|
|
153
191
|
forge.tasks = {}
|
|
154
192
|
}
|
|
155
193
|
|
|
194
|
+
const taskUuid = uuidv4()
|
|
156
195
|
forge.tasks[descriptor] = {
|
|
157
196
|
path: `${taskPath}/${fileName}`,
|
|
158
197
|
handler: taskName,
|
|
159
|
-
uuid:
|
|
198
|
+
uuid: taskUuid
|
|
160
199
|
}
|
|
161
200
|
|
|
162
201
|
await persistConf(forge, cwd)
|
|
163
202
|
|
|
203
|
+
// Try to create task in Hive if user has profile and project UUID
|
|
204
|
+
if (forge.project.uuid) {
|
|
205
|
+
try {
|
|
206
|
+
const profile = await loadCurrentProfile({})
|
|
207
|
+
const result = await createTaskInHive(
|
|
208
|
+
forge.project.uuid,
|
|
209
|
+
taskUuid,
|
|
210
|
+
descriptor,
|
|
211
|
+
'Add task description here',
|
|
212
|
+
profile.apiKey,
|
|
213
|
+
profile.apiSecret,
|
|
214
|
+
profile.url
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if (result.success && result.taskUrl) {
|
|
218
|
+
console.log('\\nā
Task created successfully in Hive!')
|
|
219
|
+
console.log(`š View your task: ${result.taskUrl}`)
|
|
220
|
+
} else {
|
|
221
|
+
console.log(`\nā ļø Task created locally but could not sync to Hive: ${result.error}`)
|
|
222
|
+
console.log(`š Host: ${profile.url}`)
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
// Silently continue if no profile is configured
|
|
226
|
+
console.log('\\nš Task created locally. Configure a profile with \'forge auth:add\' to sync with Hive.')
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
164
230
|
return { taskPath, fileName }
|
|
165
231
|
}
|
|
166
232
|
})
|