@forgehive/forge-cli 0.3.11 → 0.3.13
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 +12 -2
- package/dist/tasks/task/run.js +39 -10
- package/dist/tasks/types.d.ts +3 -0
- package/logs/task:list.log +1 -0
- package/logs/test:guidance.log +1 -0
- package/logs/test:uuid.log +1 -0
- package/logs/test:uuidCheck.log +1 -0
- package/package.json +10 -10
- 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 +49 -10
- package/src/tasks/types.ts +3 -0
package/dist/tasks/task/run.d.ts
CHANGED
|
@@ -17,7 +17,12 @@ 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, projectUuid?: string) => Promise<{
|
|
21
|
+
success: boolean;
|
|
22
|
+
logUuid?: string;
|
|
23
|
+
taskUuid?: string;
|
|
24
|
+
skipRemoteLog?: boolean;
|
|
25
|
+
}>;
|
|
21
26
|
}>) => Promise<any>, {
|
|
22
27
|
loadConf: (args: {}) => Promise<Promise<ForgeConf>>;
|
|
23
28
|
loadCurrentProfile: (args: {}) => Promise<Promise<Profile>>;
|
|
@@ -32,5 +37,10 @@ export declare const run: import("@forgehive/task").TaskInstanceType<(argv: {
|
|
|
32
37
|
}) => Promise<Promise<any>>;
|
|
33
38
|
ensureLogFolder: (logsPath: string) => Promise<void>;
|
|
34
39
|
ensureBuildsFolder: () => Promise<string>;
|
|
35
|
-
sendLogToAPI: (profile: Profile, projectName: string, record: ExecutionRecord) => Promise<
|
|
40
|
+
sendLogToAPI: (profile: Profile, projectName: string, record: ExecutionRecord, taskUuid?: string, projectUuid?: string) => Promise<{
|
|
41
|
+
success: boolean;
|
|
42
|
+
logUuid?: string;
|
|
43
|
+
taskUuid?: string;
|
|
44
|
+
skipRemoteLog?: boolean;
|
|
45
|
+
}>;
|
|
36
46
|
}>;
|
package/dist/tasks/task/run.js
CHANGED
|
@@ -50,10 +50,27 @@ const boundaries = {
|
|
|
50
50
|
}
|
|
51
51
|
return buildsPath;
|
|
52
52
|
},
|
|
53
|
-
sendLogToAPI: async (profile, projectName, record) => {
|
|
53
|
+
sendLogToAPI: async (profile, projectName, record, taskUuid, projectUuid) => {
|
|
54
|
+
// Check if we have required UUIDs for the new endpoint
|
|
55
|
+
if (!projectUuid || !taskUuid) {
|
|
56
|
+
console.log('===============================================');
|
|
57
|
+
console.log('⚠️ Remote logging skipped - missing UUIDs');
|
|
58
|
+
console.log('');
|
|
59
|
+
console.log('To enable remote logging with enhanced features:');
|
|
60
|
+
if (!projectUuid) {
|
|
61
|
+
console.log('• Use "forge project:create" to create a new project, or');
|
|
62
|
+
console.log('• Use "forge project:link" to connect to an existing project');
|
|
63
|
+
}
|
|
64
|
+
if (!taskUuid) {
|
|
65
|
+
console.log('• Use "forge project:sync" to get the task to have UUID');
|
|
66
|
+
}
|
|
67
|
+
console.log('===============================================');
|
|
68
|
+
return { success: true, skipRemoteLog: true };
|
|
69
|
+
}
|
|
54
70
|
try {
|
|
55
71
|
const config = {
|
|
56
72
|
projectName,
|
|
73
|
+
projectUuid,
|
|
57
74
|
apiKey: profile.apiKey,
|
|
58
75
|
apiSecret: profile.apiSecret,
|
|
59
76
|
host: profile.url,
|
|
@@ -62,22 +79,29 @@ const boundaries = {
|
|
|
62
79
|
}
|
|
63
80
|
};
|
|
64
81
|
const client = new hive_sdk_1.HiveLogClient(config);
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
console.log('Sending execution log to Hive...');
|
|
83
|
+
const result = await client.sendLogByUuid(record, taskUuid);
|
|
84
|
+
if (result === 'success' || (typeof result === 'object' && 'uuid' in result)) {
|
|
67
85
|
console.log('===============================================');
|
|
68
|
-
console.log('Log sent to
|
|
69
|
-
|
|
86
|
+
console.log('✅ Log sent to Hive successfully');
|
|
87
|
+
console.log(` Profile: ${profile.name}`);
|
|
88
|
+
console.log(` Host: ${profile.url}`);
|
|
89
|
+
if (typeof result === 'object' && result && 'uuid' in result) {
|
|
90
|
+
const logResponse = result;
|
|
91
|
+
return { success: true, logUuid: logResponse.uuid, taskUuid };
|
|
92
|
+
}
|
|
93
|
+
return { success: true, taskUuid };
|
|
70
94
|
}
|
|
71
95
|
else {
|
|
72
|
-
console.error('Failed to send log to
|
|
73
|
-
return false;
|
|
96
|
+
console.error('❌ Failed to send log to Hive:', profile.url);
|
|
97
|
+
return { success: false };
|
|
74
98
|
}
|
|
75
99
|
}
|
|
76
100
|
catch (e) {
|
|
77
|
-
console.error('Failed to send log to
|
|
101
|
+
console.error('❌ Failed to send log to Hive:', profile.url);
|
|
78
102
|
const error = e;
|
|
79
103
|
console.error('Error:', error.message);
|
|
80
|
-
return false;
|
|
104
|
+
return { success: false };
|
|
81
105
|
}
|
|
82
106
|
}
|
|
83
107
|
};
|
|
@@ -89,6 +113,8 @@ exports.run = (0, task_1.createTask)({
|
|
|
89
113
|
const forge = await loadConf({});
|
|
90
114
|
const taskDescriptor = forge.tasks[descriptorName];
|
|
91
115
|
const projectName = forge.project.name;
|
|
116
|
+
const projectUuid = forge.project.uuid;
|
|
117
|
+
const taskUuid = taskDescriptor?.uuid;
|
|
92
118
|
if (taskDescriptor === undefined) {
|
|
93
119
|
throw new Error('Task is not defined on forge.json');
|
|
94
120
|
}
|
|
@@ -149,7 +175,10 @@ exports.run = (0, task_1.createTask)({
|
|
|
149
175
|
await tape.save();
|
|
150
176
|
if (profile) {
|
|
151
177
|
try {
|
|
152
|
-
await sendLogToAPI(profile, projectName, logItem);
|
|
178
|
+
const logResult = await sendLogToAPI(profile, projectName, logItem, taskUuid, projectUuid);
|
|
179
|
+
if (logResult.success && !logResult.skipRemoteLog && taskUuid) {
|
|
180
|
+
console.log(`🔗 View execution logs: ${profile.url}/tasks/${taskUuid}?tab=logs`);
|
|
181
|
+
}
|
|
153
182
|
}
|
|
154
183
|
catch (e) {
|
|
155
184
|
console.error('Failed to send log to API:', e);
|
package/dist/tasks/types.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"input":{},"boundaries":{"loadConf":[{"input":[{}],"output":{"project":{"name":"forge-cli"},"paths":{"logs":"logs/","fixtures":"fixtures","fingerprints":"fingerprints/","tasks":"src/tasks/","runners":"src/runners/","tests":"src/tests/"},"infra":{"region":"us-west-2","bucket":""},"tasks":{"task:createTask":{"path":"src/tasks/task/createTask.ts","handler":"createTask"},"bundle:create":{"path":"src/tasks/bundle/create.ts","handler":"create"},"bundle:load":{"path":"src/tasks/bundle/load.ts","handler":"load"},"task:run":{"path":"src/tasks/task/run.ts","handler":"run"},"task:remove":{"path":"src/tasks/task/remove.ts","handler":"remove"},"conf:info":{"path":"src/tasks/conf/info.ts","handler":"info"},"runner:create":{"path":"src/tasks/runner/create.ts","handler":"create"},"runner:remove":{"path":"src/tasks/runner/remove.ts","handler":"remove"},"runner:bundle":{"path":"src/tasks/runner/bundle.ts","handler":"bundle"},"task:publish":{"path":"src/tasks/task/publish.ts","handler":"publish"},"task:download":{"path":"src/tasks/task/download.ts","handler":"download"},"auth:add":{"path":"src/tasks/auth/add.ts","handler":"add"},"auth:load":{"path":"src/tasks/auth/load.ts","handler":"load"},"auth:loadCurrent":{"path":"src/tasks/auth/loadCurrent.ts","handler":"loadCurrent"},"auth:switch":{"path":"src/tasks/auth/switch.ts","handler":"switchProfile"},"auth:list":{"path":"src/tasks/auth/list.ts","handler":"list"},"auth:remove":{"path":"src/tasks/auth/remove.ts","handler":"remove"},"task:replay":{"path":"src/tasks/task/replay.ts","handler":"replay"},"fixture:download":{"path":"src/tasks/fixture/download.ts","handler":"download"},"bundle:zip":{"path":"src/tasks/bundle/zip.ts","handler":"zip"},"task:list":{"path":"src/tasks/task/list.ts","handler":"list"},"task:describe":{"path":"src/tasks/task/describe.ts","handler":"describe"},"task:fingerprint":{"path":"src/tasks/task/fingerprint.ts","handler":"fingerprint"},"bundle:fingerprint":{"path":"src/tasks/bundle/fingerprint.ts","handler":"fingerprint"},"task:invoke":{"path":"src/tasks/task/invoke.ts","handler":"invoke"},"docs:download":{"path":"src/tasks/docs/download.ts","handler":"download"},"project:create":{"path":"src/tasks/project/create.ts","handler":"create"},"project:link":{"path":"src/tasks/project/link.ts","handler":"link","uuid":"cb9f82e1-d397-46d9-9f0d-2b0e3becbfa1"},"project:unlink":{"path":"src/tasks/project/unlink.ts","handler":"unlink","uuid":"414d37de-793c-4d01-899d-69515f5e0948"}},"runners":{}},"timing":{"startTime":1755627445111,"endTime":1755627445112,"duration":1}}]},"metadata":{"environment":"cli"},"metrics":[],"type":"success","output":{"taskCount":29},"timing":{"startTime":1755627445111,"endTime":1755627445112,"duration":1}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"input":{},"boundaries":{},"taskName":"test:guidance","metadata":{"environment":"cli"},"metrics":[],"type":"success","output":{},"timing":{"startTime":1755627427045,"endTime":1755627427045,"duration":0}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"uuid":"0198bfeb-69c0-77e8-882b-a76dcb9300f9","input":{},"boundaries":{},"taskName":"test:uuid","metadata":{"environment":"cli"},"metrics":[],"type":"success","output":{},"timing":{"startTime":1755566533056,"endTime":1755566533057,"duration":1}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"input":{},"boundaries":{},"taskName":"test:uuidCheck","metadata":{"environment":"cli"},"metrics":[],"type":"success","output":{},"timing":{"startTime":1755567492456,"endTime":1755567492456,"duration":0}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgehive/forge-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.13",
|
|
4
4
|
"description": "TypeScript CLI application",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public",
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@forgehive/hive-sdk": "^0.1.
|
|
14
|
-
"@forgehive/record-tape": "^0.2.
|
|
15
|
-
"@forgehive/runner": "^0.2.
|
|
13
|
+
"@forgehive/hive-sdk": "^0.1.4",
|
|
14
|
+
"@forgehive/record-tape": "^0.2.6",
|
|
15
|
+
"@forgehive/runner": "^0.2.6",
|
|
16
16
|
"@forgehive/schema": "^0.1.4",
|
|
17
|
-
"@forgehive/task": "^0.2.
|
|
17
|
+
"@forgehive/task": "^0.2.6",
|
|
18
18
|
"esbuild": "^0.25.0",
|
|
19
19
|
"handlebars": "^4.7.8",
|
|
20
20
|
"minimist": "^1.2.8",
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"minimist": "^1.2.8",
|
|
31
31
|
"typescript": "^5.3.3",
|
|
32
32
|
"uuid": "^11.1.0",
|
|
33
|
-
"@forgehive/
|
|
34
|
-
"@forgehive/
|
|
35
|
-
"@forgehive/
|
|
36
|
-
"@forgehive/
|
|
37
|
-
"@forgehive/
|
|
33
|
+
"@forgehive/record-tape": "0.2.6",
|
|
34
|
+
"@forgehive/hive-sdk": "0.1.4",
|
|
35
|
+
"@forgehive/task": "0.2.6",
|
|
36
|
+
"@forgehive/runner": "0.2.6",
|
|
37
|
+
"@forgehive/schema": "0.1.4"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/archiver": "^6.0.3",
|
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
|
+
})
|