@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.
@@ -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 info = {
29
- version: packageJson.version,
30
- profile: {}
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
- info.profile = {
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
@@ -37,6 +37,7 @@ export const init = createTask({
37
37
  paths: {
38
38
  logs: 'logs/',
39
39
  fixtures: 'fixtures/',
40
+ fingerprints: 'fingerprints/',
40
41
  tasks: 'src/tasks/',
41
42
  runners: 'src/runners/',
42
43
  tests: 'src/tests/'
@@ -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 { analyzeTaskFile, TaskFingerprintOutput } from '../../utils/taskAnalysis'
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
- readFile: async (filePath: string): Promise<string> => {
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
- readFile,
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
- // Read and analyze the task file using the utility function
73
- const sourceCode = await readFile(filePath)
74
- const taskFingerprint = analyzeTaskFile(sourceCode, filePath)
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
- if (!taskFingerprint) {
77
- throw new Error('Could not extract fingerprint from task file: ' + filePath)
78
- }
54
+ const taskFingerprint = result.taskFingerprint
79
55
 
80
- // Create analysis result - clean output without extra fields
81
- const analysis: FingerprintAnalysis = {
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
- } catch (error: any) {
55
- if (error.response?.data?.error.includes('Bundle size')) {
56
- throw new Error('Bundle size exceeds the maximum allowed size of 25MB')
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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')
@@ -27,6 +27,7 @@ export interface ForgeConf {
27
27
  tasks: string
28
28
  runners: string
29
29
  fixtures: string
30
+ fingerprints: string
30
31
  tests: string
31
32
  }
32
33
  infra: Infra