@forgehive/forge-cli 0.3.6 → 0.3.7
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 +3 -0
- package/dist/tasks/bundle/fingerprint.d.ts +12 -2
- package/dist/tasks/bundle/fingerprint.js +26 -12
- package/dist/tasks/conf/info.d.ts +12 -1
- package/dist/tasks/conf/info.js +36 -12
- package/dist/tasks/init.js +1 -0
- package/dist/tasks/task/fingerprint.d.ts +51 -8
- package/dist/tasks/task/fingerprint.js +12 -35
- package/dist/tasks/task/publish.d.ts +60 -4
- package/dist/tasks/task/publish.js +30 -8
- package/dist/tasks/types.d.ts +1 -0
- package/dist/utils/taskAnalysis.d.ts +27 -1
- package/dist/utils/taskAnalysis.js +760 -107
- package/forge.json +1 -0
- package/logs/bundle:fingerprint.log +1 -0
- package/package.json +10 -10
- package/specs/fingerprint.md +324 -60
- package/src/runner.ts +4 -0
- package/src/tasks/bundle/fingerprint.ts +32 -13
- package/src/tasks/conf/info.ts +36 -10
- package/src/tasks/init.ts +1 -0
- package/src/tasks/task/fingerprint.ts +13 -42
- package/src/tasks/task/publish.ts +35 -10
- package/src/tasks/types.ts +1 -0
- package/src/utils/taskAnalysis.ts +833 -118
package/src/tasks/conf/info.ts
CHANGED
|
@@ -8,41 +8,67 @@ import * as fs from 'fs'
|
|
|
8
8
|
import * as path from 'path'
|
|
9
9
|
|
|
10
10
|
import { loadCurrent as loadCurrentProfile } from '../auth/loadCurrent'
|
|
11
|
+
import { load as loadConfig } from '../conf/load'
|
|
11
12
|
|
|
12
13
|
const schema = new Schema({})
|
|
13
14
|
|
|
14
15
|
const boundaries = {
|
|
15
16
|
readFile: async (filePath: string): Promise<string> => fs.promises.readFile(filePath, 'utf-8'),
|
|
17
|
+
loadConfig: loadConfig.asBoundary(),
|
|
16
18
|
loadCurrentProfile: loadCurrentProfile.asBoundary()
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export const info = createTask({
|
|
20
22
|
schema,
|
|
21
23
|
boundaries,
|
|
22
|
-
fn: async function (_argv, { loadCurrentProfile, readFile }) {
|
|
24
|
+
fn: async function (_argv, { loadCurrentProfile, loadConfig, readFile }) {
|
|
23
25
|
const packageJsonPath = path.join(__dirname, '../../../package.json')
|
|
24
26
|
|
|
25
27
|
const packageJsonContent = await readFile(packageJsonPath)
|
|
26
28
|
const packageJson = JSON.parse(packageJsonContent)
|
|
27
29
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const config = await loadConfig({})
|
|
31
|
+
|
|
32
|
+
// Display human-friendly information
|
|
33
|
+
console.log('===============================================')
|
|
34
|
+
console.log('============ Forge CLI Information ============')
|
|
35
|
+
console.log()
|
|
36
|
+
console.log(`Version: ${packageJson.version}`)
|
|
37
|
+
console.log()
|
|
38
|
+
|
|
39
|
+
console.log('Configuration Paths:')
|
|
40
|
+
console.log(` Logs: ${config.paths.logs}`)
|
|
41
|
+
console.log(` Fixtures: ${config.paths.fixtures}`)
|
|
42
|
+
console.log(` Fingerprints: ${config.paths.fingerprints}`)
|
|
43
|
+
console.log()
|
|
32
44
|
|
|
33
45
|
let profile
|
|
34
46
|
try {
|
|
35
47
|
profile = await loadCurrentProfile({})
|
|
48
|
+
console.log('Current Profile:')
|
|
49
|
+
console.log(` Name: ${profile.name}`)
|
|
50
|
+
console.log(` URL: ${profile.url}`)
|
|
51
|
+
console.log(` API Key: ${profile.apiKey}`)
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.log('Current Profile: No default profile set')
|
|
54
|
+
console.log(' Run "forge task:run auth:add" to create a profile.')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log()
|
|
58
|
+
console.log('===============================================')
|
|
36
59
|
|
|
37
|
-
|
|
60
|
+
return {
|
|
61
|
+
version: packageJson.version,
|
|
62
|
+
profile: profile ? {
|
|
38
63
|
name: profile.name,
|
|
39
64
|
url: profile.url,
|
|
40
65
|
apiKey: profile.apiKey
|
|
66
|
+
} : null,
|
|
67
|
+
paths: {
|
|
68
|
+
logs: config.paths.logs,
|
|
69
|
+
fixtures: config.paths.fixtures,
|
|
70
|
+
fingerprints: config.paths.fingerprints
|
|
41
71
|
}
|
|
42
|
-
} catch (error) {
|
|
43
|
-
console.log('No default profile set. Please run forge task:run auth:add to create a profile.')
|
|
44
72
|
}
|
|
45
|
-
|
|
46
|
-
return info
|
|
47
73
|
}
|
|
48
74
|
})
|
package/src/tasks/init.ts
CHANGED
|
@@ -4,16 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { createTask } from '@forgehive/task'
|
|
6
6
|
import { Schema } from '@forgehive/schema'
|
|
7
|
-
import fs from 'fs/promises'
|
|
8
7
|
import path from 'path'
|
|
9
|
-
import os from 'os'
|
|
10
8
|
|
|
11
9
|
import { load as loadConf } from '../conf/load'
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
interface FingerprintAnalysis {
|
|
15
|
-
taskFingerprint: TaskFingerprintOutput
|
|
16
|
-
}
|
|
10
|
+
import { fingerprint as bundleFingerprint } from '../bundle/fingerprint'
|
|
17
11
|
|
|
18
12
|
const description = 'Analyze a specific task and generate detailed fingerprint without bundling'
|
|
19
13
|
|
|
@@ -26,21 +20,7 @@ const boundaries = {
|
|
|
26
20
|
return process.cwd()
|
|
27
21
|
},
|
|
28
22
|
loadConf: loadConf.asBoundary(),
|
|
29
|
-
|
|
30
|
-
return fs.readFile(filePath, 'utf-8')
|
|
31
|
-
},
|
|
32
|
-
writeFile: async (filePath: string, content: string): Promise<void> => {
|
|
33
|
-
return fs.writeFile(filePath, content)
|
|
34
|
-
},
|
|
35
|
-
ensureForgeFolder: async (): Promise<string> => {
|
|
36
|
-
const forgePath = path.join(os.homedir(), '.forge')
|
|
37
|
-
try {
|
|
38
|
-
await fs.access(forgePath)
|
|
39
|
-
} catch {
|
|
40
|
-
await fs.mkdir(forgePath, { recursive: true })
|
|
41
|
-
}
|
|
42
|
-
return forgePath
|
|
43
|
-
}
|
|
23
|
+
bundleFingerprint: bundleFingerprint.asBoundary()
|
|
44
24
|
}
|
|
45
25
|
|
|
46
26
|
export const fingerprint = createTask({
|
|
@@ -49,9 +29,7 @@ export const fingerprint = createTask({
|
|
|
49
29
|
fn: async function ({ descriptorName }, {
|
|
50
30
|
getCwd,
|
|
51
31
|
loadConf,
|
|
52
|
-
|
|
53
|
-
writeFile,
|
|
54
|
-
ensureForgeFolder
|
|
32
|
+
bundleFingerprint
|
|
55
33
|
}) {
|
|
56
34
|
const cwd = await getCwd()
|
|
57
35
|
const forgeJson = await loadConf({})
|
|
@@ -63,37 +41,30 @@ export const fingerprint = createTask({
|
|
|
63
41
|
}
|
|
64
42
|
|
|
65
43
|
const filePath = path.join(cwd, taskDescriptor.path)
|
|
66
|
-
const forgePath = await ensureForgeFolder()
|
|
67
|
-
const fingerprintFile = path.join(forgePath, `${descriptorName}.task-fingerprint.json`)
|
|
68
44
|
|
|
69
45
|
console.log(`Analyzing task: ${descriptorName}`)
|
|
70
46
|
console.log(`Task file: ${filePath}`)
|
|
71
47
|
|
|
72
|
-
//
|
|
73
|
-
const
|
|
74
|
-
|
|
48
|
+
// Use bundle:fingerprint with filePath to analyze the task file directly
|
|
49
|
+
const result = await bundleFingerprint({
|
|
50
|
+
descriptorName,
|
|
51
|
+
filePath
|
|
52
|
+
})
|
|
75
53
|
|
|
76
|
-
|
|
77
|
-
throw new Error('Could not extract fingerprint from task file: ' + filePath)
|
|
78
|
-
}
|
|
54
|
+
const taskFingerprint = result.taskFingerprint
|
|
79
55
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
taskFingerprint
|
|
56
|
+
if (!taskFingerprint) {
|
|
57
|
+
throw new Error('Could not extract fingerprint from task file')
|
|
83
58
|
}
|
|
84
59
|
|
|
85
|
-
// Write fingerprint to file
|
|
86
|
-
await writeFile(fingerprintFile, JSON.stringify(analysis, null, 2))
|
|
87
|
-
|
|
88
60
|
console.log('Task fingerprint generated successfully')
|
|
89
61
|
console.log(`Input properties: ${Object.keys(taskFingerprint.inputSchema.properties).join(', ')}`)
|
|
90
|
-
console.log(`Boundaries: ${taskFingerprint.boundaries.join(', ')}`)
|
|
91
|
-
console.log(`Fingerprint saved to: ${fingerprintFile}`)
|
|
62
|
+
console.log(`Boundaries: ${taskFingerprint.boundaries.map(b => b.name).join(', ')}`)
|
|
92
63
|
|
|
93
64
|
return {
|
|
94
65
|
taskName: descriptorName,
|
|
95
66
|
fingerprint: taskFingerprint,
|
|
96
|
-
fingerprintFile,
|
|
67
|
+
fingerprintFile: result.fingerprintFile,
|
|
97
68
|
analysis: {
|
|
98
69
|
inputSchemaProps: Object.keys(taskFingerprint.inputSchema.properties),
|
|
99
70
|
boundaryCount: taskFingerprint.boundaries.length,
|
|
@@ -14,8 +14,10 @@ import { load as loadConf } from '../conf/load'
|
|
|
14
14
|
import { create as bundleCreate } from '../bundle/create'
|
|
15
15
|
import { load as bundleLoad } from '../bundle/load'
|
|
16
16
|
import { zip as bundleZip } from '../bundle/zip'
|
|
17
|
+
import { fingerprint as bundleFingerprint } from '../bundle/fingerprint'
|
|
17
18
|
import { loadCurrent as loadCurrentProfile } from '../auth/loadCurrent'
|
|
18
19
|
import { Profile } from '../types'
|
|
20
|
+
import { TaskFingerprintOutput } from '../../utils/taskAnalysis'
|
|
19
21
|
|
|
20
22
|
const schema = new Schema({
|
|
21
23
|
descriptorName: Schema.string()
|
|
@@ -30,14 +32,14 @@ const boundaries = {
|
|
|
30
32
|
bundleCreate: bundleCreate.asBoundary(),
|
|
31
33
|
bundleLoad: bundleLoad.asBoundary(),
|
|
32
34
|
bundleZip: bundleZip.asBoundary(),
|
|
35
|
+
bundleFingerprint: bundleFingerprint.asBoundary(),
|
|
33
36
|
readFileUtf8: async (filePath: string): Promise<string> => {
|
|
34
37
|
return fs.readFile(filePath, 'utf-8')
|
|
35
38
|
},
|
|
36
39
|
readFileBinary: async (filePath: string): Promise<Buffer> => {
|
|
37
40
|
return fs.readFile(filePath)
|
|
38
41
|
},
|
|
39
|
-
|
|
40
|
-
publishTask: async (data: any, profile: Profile): Promise<any> => {
|
|
42
|
+
publishTask: async (data: Record<string, unknown>, profile: Profile): Promise<{ bundleUploadUrl?: string }> => {
|
|
41
43
|
const publishUrl = `${profile.url}/api/tasks/publish`
|
|
42
44
|
const authToken = `${profile.apiKey}:${profile.apiSecret}`
|
|
43
45
|
|
|
@@ -50,17 +52,18 @@ const boundaries = {
|
|
|
50
52
|
})
|
|
51
53
|
|
|
52
54
|
return response.data
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
} catch (error: unknown) {
|
|
56
|
+
if (error && typeof error === 'object' && 'response' in error) {
|
|
57
|
+
const axiosError = error as { response?: { data?: { error?: string } } }
|
|
58
|
+
if (axiosError.response?.data?.error?.includes('Bundle size')) {
|
|
59
|
+
throw new Error('Bundle size exceeds the maximum allowed size of 25MB')
|
|
60
|
+
}
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
throw new Error('Failed to publish task source code and metadata')
|
|
60
64
|
}
|
|
61
65
|
},
|
|
62
|
-
|
|
63
|
-
uploadBundleWithPresignedUrl: async (presignedUrl: string, bundleContent: Buffer): Promise<any> => {
|
|
66
|
+
uploadBundleWithPresignedUrl: async (presignedUrl: string, bundleContent: Buffer): Promise<boolean> => {
|
|
64
67
|
const response = await axios.put(presignedUrl, bundleContent, {
|
|
65
68
|
headers: {
|
|
66
69
|
'Content-Type': 'application/octet-stream'
|
|
@@ -91,6 +94,7 @@ export const publish = createTask({
|
|
|
91
94
|
bundleCreate,
|
|
92
95
|
bundleLoad,
|
|
93
96
|
bundleZip,
|
|
97
|
+
bundleFingerprint,
|
|
94
98
|
readFileUtf8,
|
|
95
99
|
readFileBinary,
|
|
96
100
|
publishTask,
|
|
@@ -130,6 +134,25 @@ export const publish = createTask({
|
|
|
130
134
|
|
|
131
135
|
console.log('Bundle zipped...')
|
|
132
136
|
|
|
137
|
+
// Generate task fingerprint
|
|
138
|
+
console.log('Generating task fingerprint...')
|
|
139
|
+
let taskFingerprintData: TaskFingerprintOutput | null = null
|
|
140
|
+
try {
|
|
141
|
+
const fingerprintResult = await bundleFingerprint({
|
|
142
|
+
descriptorName,
|
|
143
|
+
filePath: entryPoint
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// Bundle fingerprint returns the fingerprint data directly
|
|
147
|
+
if (fingerprintResult.taskFingerprint) {
|
|
148
|
+
taskFingerprintData = fingerprintResult.taskFingerprint
|
|
149
|
+
console.log('Task fingerprint generated successfully')
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.warn('Failed to generate task fingerprint:', error instanceof Error ? error.message : String(error))
|
|
153
|
+
console.warn('Publishing without fingerprint data...')
|
|
154
|
+
}
|
|
155
|
+
|
|
133
156
|
// Load the bundled task
|
|
134
157
|
const bundle = await bundleLoad({
|
|
135
158
|
bundlePath: outputFile
|
|
@@ -161,7 +184,8 @@ export const publish = createTask({
|
|
|
161
184
|
schemaDescriptor: JSON.stringify(schemaDescriptor),
|
|
162
185
|
boundaries,
|
|
163
186
|
sourceCode,
|
|
164
|
-
bundleSize
|
|
187
|
+
bundleSize,
|
|
188
|
+
...(taskFingerprintData && { fingerprint: taskFingerprintData })
|
|
165
189
|
}
|
|
166
190
|
|
|
167
191
|
// Publish metadata to hive api server
|
|
@@ -178,7 +202,8 @@ export const publish = createTask({
|
|
|
178
202
|
|
|
179
203
|
return {
|
|
180
204
|
descriptor: taskDescriptor,
|
|
181
|
-
publish: true
|
|
205
|
+
publish: true,
|
|
206
|
+
fingerprint: taskFingerprintData !== null
|
|
182
207
|
}
|
|
183
208
|
} else {
|
|
184
209
|
throw new Error('Bundle upload failed')
|