@forgehive/forge-cli 0.3.17 → 0.5.1
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 +71 -3
- package/dist/tasks/auth/add.d.ts +2 -2
- package/dist/tasks/auth/add.js +4 -4
- package/dist/tasks/auth/clear.d.ts +3 -3
- package/dist/tasks/auth/list.d.ts +3 -3
- package/dist/tasks/auth/load.d.ts +1 -1
- package/dist/tasks/auth/loadCurrent.d.ts +3 -3
- package/dist/tasks/auth/remove.d.ts +2 -2
- package/dist/tasks/auth/remove.js +1 -1
- package/dist/tasks/auth/switch.d.ts +2 -2
- package/dist/tasks/auth/switch.js +1 -1
- package/dist/tasks/bundle/create.d.ts +2 -2
- package/dist/tasks/bundle/create.js +2 -2
- package/dist/tasks/bundle/fingerprint.d.ts +2 -2
- package/dist/tasks/bundle/fingerprint.js +2 -2
- package/dist/tasks/bundle/load.js +1 -1
- package/dist/tasks/bundle/zip.js +4 -4
- package/dist/tasks/conf/info.d.ts +5 -5
- package/dist/tasks/conf/load.d.ts +1 -1
- package/dist/tasks/docs/download.js +4 -2
- package/dist/tasks/fixture/download.d.ts +4 -4
- package/dist/tasks/fixture/download.js +1 -1
- package/dist/tasks/init.js +1 -1
- package/dist/tasks/project/create.d.ts +5 -5
- package/dist/tasks/project/create.js +21 -25
- package/dist/tasks/project/link.d.ts +4 -4
- package/dist/tasks/project/link.js +1 -1
- package/dist/tasks/project/sync.d.ts +7 -7
- package/dist/tasks/project/sync.js +7 -3
- package/dist/tasks/project/unlink.d.ts +3 -3
- package/dist/tasks/runner/bundle.d.ts +2 -2
- package/dist/tasks/runner/bundle.js +2 -2
- package/dist/tasks/runner/create.d.ts +2 -2
- package/dist/tasks/runner/create.js +1 -1
- package/dist/tasks/runner/remove.d.ts +2 -2
- package/dist/tasks/runner/remove.js +1 -1
- package/dist/tasks/task/createTask.d.ts +4 -4
- package/dist/tasks/task/createTask.js +3 -2
- package/dist/tasks/task/describe.d.ts +2 -2
- package/dist/tasks/task/describe.js +1 -1
- package/dist/tasks/task/download.d.ts +4 -4
- package/dist/tasks/task/download.js +2 -2
- package/dist/tasks/task/fingerprint.d.ts +2 -2
- package/dist/tasks/task/fingerprint.js +1 -1
- package/dist/tasks/task/invoke.d.ts +4 -4
- package/dist/tasks/task/invoke.js +2 -2
- package/dist/tasks/task/list.d.ts +3 -3
- package/dist/tasks/task/publish.d.ts +4 -4
- package/dist/tasks/task/publish.js +1 -1
- package/dist/tasks/task/remove.d.ts +2 -2
- package/dist/tasks/task/remove.js +1 -1
- package/dist/tasks/task/replay.d.ts +14 -7
- package/dist/tasks/task/replay.js +33 -23
- package/dist/tasks/task/run.d.ts +6 -6
- package/dist/tasks/task/run.js +18 -23
- package/dist/tasks/types.d.ts +1 -0
- package/dist/test/tasks/create.test.js +3 -2
- package/dist/utils/taskAnalysis.d.ts +6 -0
- package/dist/utils/taskAnalysis.js +82 -41
- package/package.json +11 -11
- package/pnpm-workspace.yaml +2 -0
- package/src/runner.ts +80 -3
- package/src/tasks/auth/add.ts +4 -4
- package/src/tasks/auth/remove.ts +1 -1
- package/src/tasks/auth/switch.ts +1 -1
- package/src/tasks/bundle/create.ts +2 -2
- package/src/tasks/bundle/fingerprint.ts +2 -2
- package/src/tasks/bundle/load.ts +1 -1
- package/src/tasks/bundle/zip.ts +4 -4
- package/src/tasks/docs/download.ts +5 -2
- package/src/tasks/fixture/download.ts +1 -1
- package/src/tasks/init.ts +1 -1
- package/src/tasks/project/create.ts +21 -27
- package/src/tasks/project/link.ts +1 -1
- package/src/tasks/project/sync.ts +9 -1
- package/src/tasks/runner/bundle.ts +2 -2
- package/src/tasks/runner/create.ts +1 -1
- package/src/tasks/runner/remove.ts +1 -1
- package/src/tasks/task/createTask.ts +3 -2
- package/src/tasks/task/describe.ts +1 -1
- package/src/tasks/task/download.ts +2 -2
- package/src/tasks/task/fingerprint.ts +1 -1
- package/src/tasks/task/invoke.ts +2 -2
- package/src/tasks/task/publish.ts +1 -1
- package/src/tasks/task/remove.ts +1 -1
- package/src/tasks/task/replay.ts +38 -24
- package/src/tasks/task/run.ts +19 -26
- package/src/tasks/types.ts +1 -0
- package/src/test/tasks/create.test.ts +3 -2
- package/src/utils/taskAnalysis.ts +90 -41
- package/logs/bundle:fingerprint.log +0 -1
- package/logs/task:fingerprint.log +0 -10
- package/logs/task:list.log +0 -1
- package/logs/test:guidance.log +0 -1
- package/logs/test:uuid.log +0 -1
- package/logs/test:uuidCheck.log +0 -1
|
@@ -17,7 +17,7 @@ import { type ForgeConf } from '../types'
|
|
|
17
17
|
const description = 'Describe a task with detailed information about its schema, boundaries and configuration'
|
|
18
18
|
|
|
19
19
|
const schema = new Schema({
|
|
20
|
-
descriptorName: Schema.string()
|
|
20
|
+
descriptorName: Schema.string().describe('The task descriptor name to describe (e.g. domain:taskName)')
|
|
21
21
|
})
|
|
22
22
|
|
|
23
23
|
const boundaries = {
|
|
@@ -13,8 +13,8 @@ import { loadCurrent as loadCurrentProfile } from '../auth/loadCurrent'
|
|
|
13
13
|
import { Profile, type ForgeConf } from '../types'
|
|
14
14
|
|
|
15
15
|
const schema = new Schema({
|
|
16
|
-
descriptorName: Schema.string(),
|
|
17
|
-
uuid: Schema.string()
|
|
16
|
+
descriptorName: Schema.string().describe('The task descriptor name (e.g. domain:taskName)'),
|
|
17
|
+
uuid: Schema.string().describe('The UUID of the task version to download')
|
|
18
18
|
})
|
|
19
19
|
|
|
20
20
|
const boundaries = {
|
|
@@ -12,7 +12,7 @@ import { fingerprint as bundleFingerprint } from '../bundle/fingerprint'
|
|
|
12
12
|
const description = 'Analyze a specific task and generate detailed fingerprint without bundling'
|
|
13
13
|
|
|
14
14
|
const schema = new Schema({
|
|
15
|
-
descriptorName: Schema.string()
|
|
15
|
+
descriptorName: Schema.string().describe('The task descriptor name to fingerprint (e.g. domain:taskName)')
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
const boundaries = {
|
package/src/tasks/task/invoke.ts
CHANGED
|
@@ -14,8 +14,8 @@ const name = 'task:invoke'
|
|
|
14
14
|
const description = 'Invoke a deployed task remotely using the Hive API'
|
|
15
15
|
|
|
16
16
|
const schema = new Schema({
|
|
17
|
-
descriptorName: Schema.string(),
|
|
18
|
-
json: Schema.string()
|
|
17
|
+
descriptorName: Schema.string().describe('The task descriptor name (e.g. domain:taskName)'),
|
|
18
|
+
json: Schema.string().describe('JSON string of arguments to pass to the task')
|
|
19
19
|
})
|
|
20
20
|
|
|
21
21
|
const boundaries = {
|
|
@@ -20,7 +20,7 @@ import { Profile } from '../types'
|
|
|
20
20
|
import { TaskFingerprintOutput } from '../../utils/taskAnalysis'
|
|
21
21
|
|
|
22
22
|
const schema = new Schema({
|
|
23
|
-
descriptorName: Schema.string()
|
|
23
|
+
descriptorName: Schema.string().describe('The task descriptor name to publish (e.g. domain:taskName)')
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
const boundaries = {
|
package/src/tasks/task/remove.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { load } from '../conf/load'
|
|
|
11
11
|
import { type ForgeConf } from '../types'
|
|
12
12
|
|
|
13
13
|
const schema = new Schema({
|
|
14
|
-
descriptorName: Schema.string()
|
|
14
|
+
descriptorName: Schema.string().describe('The task descriptor name to remove (e.g. domain:taskName)')
|
|
15
15
|
})
|
|
16
16
|
|
|
17
17
|
const boundaries = {
|
package/src/tasks/task/replay.ts
CHANGED
|
@@ -7,8 +7,8 @@ import { Schema } from '@forgehive/schema'
|
|
|
7
7
|
import fs from 'fs/promises'
|
|
8
8
|
import path from 'path'
|
|
9
9
|
import os from 'os'
|
|
10
|
-
import axios from 'axios'
|
|
11
10
|
|
|
11
|
+
import { createClientFromForgeConf, type ExecutionRecord } from '@forgehive/hive-sdk'
|
|
12
12
|
import { create as bundleCreate } from '../bundle/create'
|
|
13
13
|
import { load as bundleLoad } from '../bundle/load'
|
|
14
14
|
import { load as loadConf } from '../conf/load'
|
|
@@ -30,9 +30,9 @@ interface Fixture {
|
|
|
30
30
|
const description = 'Replay a task execution from a specified path'
|
|
31
31
|
|
|
32
32
|
const schema = new Schema({
|
|
33
|
-
descriptorName: Schema.string(),
|
|
34
|
-
path: Schema.string(),
|
|
35
|
-
cache: Schema.string().optional()
|
|
33
|
+
descriptorName: Schema.string().describe('The task descriptor name to replay (e.g. domain:taskName)'),
|
|
34
|
+
path: Schema.string().describe('Path to the execution log fixture to replay'),
|
|
35
|
+
cache: Schema.string().describe('Cache mode for boundaries during replay').optional()
|
|
36
36
|
})
|
|
37
37
|
|
|
38
38
|
const boundaries = {
|
|
@@ -66,32 +66,47 @@ const boundaries = {
|
|
|
66
66
|
|
|
67
67
|
return true
|
|
68
68
|
},
|
|
69
|
-
sendLogToAPI: async (
|
|
69
|
+
sendLogToAPI: async (
|
|
70
|
+
profile: Profile,
|
|
71
|
+
record: ExecutionRecord,
|
|
72
|
+
fixtureUUID: string
|
|
73
|
+
): Promise<{ success: boolean; logUuid?: string }> => {
|
|
70
74
|
try {
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}, {
|
|
80
|
-
headers: {
|
|
81
|
-
Authorization: `Bearer ${authToken}`,
|
|
82
|
-
'Content-Type': 'application/json'
|
|
75
|
+
// Use createClientFromForgeConf to automatically load forge.json with task UUIDs
|
|
76
|
+
const client = createClientFromForgeConf('./forge.json', {
|
|
77
|
+
apiKey: profile.apiKey,
|
|
78
|
+
apiSecret: profile.apiSecret,
|
|
79
|
+
host: profile.url,
|
|
80
|
+
metadata: {
|
|
81
|
+
environment: 'cli',
|
|
82
|
+
replayFrom: fixtureUUID
|
|
83
83
|
}
|
|
84
84
|
})
|
|
85
85
|
|
|
86
86
|
console.log('===============================================')
|
|
87
|
-
console.log('
|
|
88
|
-
|
|
87
|
+
console.log('Sending replay log to Hive...')
|
|
88
|
+
const result = await client.sendLog(record)
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
if (result === 'success' || (typeof result === 'object' && 'uuid' in result)) {
|
|
91
|
+
console.log('✅ Log sent to API...', profile.name, profile.url)
|
|
92
|
+
console.log('Replay from fixture UUID:', fixtureUUID)
|
|
93
|
+
console.log('===============================================')
|
|
94
|
+
|
|
95
|
+
if (typeof result === 'object' && result && 'uuid' in result) {
|
|
96
|
+
return { success: true, logUuid: result.uuid }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { success: true }
|
|
100
|
+
} else {
|
|
101
|
+
console.error('❌ Failed to send log to Hive')
|
|
102
|
+
console.log('===============================================')
|
|
103
|
+
return { success: false }
|
|
104
|
+
}
|
|
91
105
|
} catch (e) {
|
|
92
106
|
const error = e as Error
|
|
93
|
-
console.error('Failed to send log to API:', error.message)
|
|
94
|
-
|
|
107
|
+
console.error('❌ Failed to send log to API:', error.message)
|
|
108
|
+
console.log('===============================================')
|
|
109
|
+
return { success: false }
|
|
95
110
|
}
|
|
96
111
|
}
|
|
97
112
|
}
|
|
@@ -107,7 +122,6 @@ export const replay = createTask({
|
|
|
107
122
|
// Load forge configuration
|
|
108
123
|
const forge: ForgeConf = await loadConf({})
|
|
109
124
|
const taskDescriptor = forge.tasks[descriptorName as keyof typeof forge.tasks]
|
|
110
|
-
const projectName = forge.project.name
|
|
111
125
|
|
|
112
126
|
if (taskDescriptor === undefined) {
|
|
113
127
|
throw new Error(`Task ${descriptorName} is not defined in forge.json`)
|
|
@@ -203,7 +217,7 @@ export const replay = createTask({
|
|
|
203
217
|
// Send the log to API if profile is available
|
|
204
218
|
if (profile) {
|
|
205
219
|
try {
|
|
206
|
-
await sendLogToAPI(profile,
|
|
220
|
+
await sendLogToAPI(profile, record, fixture.fixtureUUID)
|
|
207
221
|
} catch (e) {
|
|
208
222
|
console.error('Failed to send log to API:', e)
|
|
209
223
|
}
|
package/src/tasks/task/run.ts
CHANGED
|
@@ -10,7 +10,7 @@ import os from 'os'
|
|
|
10
10
|
import { createTask } from '@forgehive/task'
|
|
11
11
|
import { Schema } from '@forgehive/schema'
|
|
12
12
|
import { RecordTape } from '@forgehive/record-tape'
|
|
13
|
-
import {
|
|
13
|
+
import { createClientFromForgeConf, type ExecutionRecord } from '@forgehive/hive-sdk'
|
|
14
14
|
|
|
15
15
|
import { create as bundleCreate } from '../bundle/create'
|
|
16
16
|
import { load as bundleLoad } from '../bundle/load'
|
|
@@ -21,8 +21,8 @@ import { type ForgeConf, type Profile } from '../types'
|
|
|
21
21
|
// For now, we'll use a simple schema without the record type
|
|
22
22
|
// TODO: Use Schema.record once it's properly built and available
|
|
23
23
|
const schema = new Schema({
|
|
24
|
-
descriptorName: Schema.string(),
|
|
25
|
-
args: Schema.mixedRecord()
|
|
24
|
+
descriptorName: Schema.string().describe('The task descriptor name to run (e.g. domain:taskName)'),
|
|
25
|
+
args: Schema.mixedRecord().describe('Arguments to pass to the task')
|
|
26
26
|
// args will be passed directly without schema validation for now
|
|
27
27
|
})
|
|
28
28
|
|
|
@@ -51,46 +51,37 @@ const boundaries = {
|
|
|
51
51
|
},
|
|
52
52
|
sendLogToAPI: async (
|
|
53
53
|
profile: Profile,
|
|
54
|
-
projectName: string,
|
|
55
54
|
record: ExecutionRecord,
|
|
56
|
-
taskUuid?: string
|
|
57
|
-
projectUuid?: string
|
|
55
|
+
taskUuid?: string
|
|
58
56
|
): Promise<{ success: boolean; logUuid?: string; taskUuid?: string; skipRemoteLog?: boolean }> => {
|
|
59
|
-
// Check if we have required
|
|
60
|
-
if (!
|
|
57
|
+
// Check if we have required UUID for the task
|
|
58
|
+
if (!taskUuid) {
|
|
61
59
|
console.log('===============================================')
|
|
62
|
-
console.log('⚠️ Remote logging skipped - missing
|
|
60
|
+
console.log('⚠️ Remote logging skipped - missing task UUID')
|
|
63
61
|
console.log('')
|
|
64
62
|
console.log('To enable remote logging with enhanced features:')
|
|
65
|
-
|
|
66
|
-
console.log('• Use "forge project:create" to create a new project, or')
|
|
67
|
-
console.log('• Use "forge project:link" to connect to an existing project')
|
|
68
|
-
}
|
|
69
|
-
if (!taskUuid) {
|
|
70
|
-
console.log('• Use "forge project:sync" to get the task to have UUID')
|
|
71
|
-
}
|
|
63
|
+
console.log('• Use "forge project:sync" to sync tasks and get UUIDs')
|
|
72
64
|
console.log('===============================================')
|
|
73
65
|
return { success: true, skipRemoteLog: true }
|
|
74
66
|
}
|
|
75
67
|
|
|
76
68
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
projectUuid,
|
|
69
|
+
// Use createClientFromForgeConf to automatically load forge.json with task UUIDs
|
|
70
|
+
const client = createClientFromForgeConf('./forge.json', {
|
|
80
71
|
apiKey: profile.apiKey,
|
|
81
72
|
apiSecret: profile.apiSecret,
|
|
82
73
|
host: profile.url,
|
|
83
74
|
metadata: {
|
|
84
75
|
environment: 'cli'
|
|
85
76
|
}
|
|
86
|
-
}
|
|
77
|
+
})
|
|
87
78
|
|
|
88
|
-
|
|
79
|
+
console.log('===============================================')
|
|
89
80
|
console.log('Sending execution log to Hive...')
|
|
90
|
-
const result = await client.
|
|
81
|
+
const result = await client.sendLog(record)
|
|
91
82
|
|
|
92
83
|
if (result === 'success' || (typeof result === 'object' && 'uuid' in result)) {
|
|
93
|
-
console.log('
|
|
84
|
+
console.log('')
|
|
94
85
|
console.log('✅ Log sent to Hive successfully')
|
|
95
86
|
console.log(` Profile: ${profile.name}`)
|
|
96
87
|
console.log(` Host: ${profile.url}`)
|
|
@@ -102,13 +93,17 @@ const boundaries = {
|
|
|
102
93
|
|
|
103
94
|
return { success: true, taskUuid }
|
|
104
95
|
} else {
|
|
96
|
+
console.log('')
|
|
105
97
|
console.error('❌ Failed to send log to Hive:', profile.url)
|
|
98
|
+
console.log('===============================================')
|
|
106
99
|
return { success: false }
|
|
107
100
|
}
|
|
108
101
|
} catch (e) {
|
|
102
|
+
console.log('===============================================')
|
|
109
103
|
console.error('❌ Failed to send log to Hive:', profile.url)
|
|
110
104
|
const error = e as Error
|
|
111
105
|
console.error('Error:', error.message)
|
|
106
|
+
console.log('===============================================')
|
|
112
107
|
return { success: false }
|
|
113
108
|
}
|
|
114
109
|
}
|
|
@@ -129,8 +124,6 @@ export const run = createTask({
|
|
|
129
124
|
// Load forge configuration
|
|
130
125
|
const forge: ForgeConf = await loadConf({})
|
|
131
126
|
const taskDescriptor = forge.tasks[descriptorName as keyof typeof forge.tasks]
|
|
132
|
-
const projectName = forge.project.name
|
|
133
|
-
const projectUuid = forge.project.uuid
|
|
134
127
|
const taskUuid = taskDescriptor?.uuid
|
|
135
128
|
|
|
136
129
|
if (taskDescriptor === undefined) {
|
|
@@ -204,7 +197,7 @@ export const run = createTask({
|
|
|
204
197
|
|
|
205
198
|
if (profile) {
|
|
206
199
|
try {
|
|
207
|
-
const logResult = await sendLogToAPI(profile,
|
|
200
|
+
const logResult = await sendLogToAPI(profile, logItem, taskUuid)
|
|
208
201
|
|
|
209
202
|
if (logResult.success && !logResult.skipRemoteLog && taskUuid) {
|
|
210
203
|
console.log(`🔗 View execution logs: ${profile.url}/tasks/${taskUuid}?tab=logs`)
|
package/src/tasks/types.ts
CHANGED
|
@@ -16,8 +16,9 @@ const name = 'sample:newTask'
|
|
|
16
16
|
const description = 'Add task description here'
|
|
17
17
|
|
|
18
18
|
const schema = new Schema({
|
|
19
|
-
// Add your schema definitions here
|
|
20
|
-
//
|
|
19
|
+
// Add your schema definitions here.
|
|
20
|
+
// Use .describe() so the field shows up in \`forge sample:newTask --help\`.
|
|
21
|
+
// example: myParam: Schema.string().describe('What this parameter is for')
|
|
21
22
|
})
|
|
22
23
|
|
|
23
24
|
const boundaries = {
|
|
@@ -9,14 +9,22 @@ interface TaskLocation {
|
|
|
9
9
|
interface SchemaProperty {
|
|
10
10
|
name?: string
|
|
11
11
|
type: string
|
|
12
|
+
format?: string
|
|
13
|
+
description?: string
|
|
14
|
+
// Internal marker used while building the schema; stripped in favour of the
|
|
15
|
+
// object-level `required` array (JSON Schema semantics) before output.
|
|
12
16
|
optional?: boolean
|
|
13
17
|
default?: string
|
|
14
18
|
properties?: Record<string, SchemaProperty>
|
|
19
|
+
additionalProperties?: SchemaProperty | { anyOf: SchemaProperty[] }
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
// JSON Schema (draft 2020-12) representation of the task input, matching what
|
|
23
|
+
// `Schema.describe()` produces at runtime.
|
|
17
24
|
interface InputSchema {
|
|
18
25
|
type: string
|
|
19
26
|
properties: Record<string, SchemaProperty>
|
|
27
|
+
required?: string[]
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
interface OutputType {
|
|
@@ -558,69 +566,110 @@ function analyzeSchemaArg(node: ts.Expression, sourceFile: ts.SourceFile): Input
|
|
|
558
566
|
const arg = node.arguments?.[0]
|
|
559
567
|
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
560
568
|
const properties: Record<string, SchemaProperty> = {}
|
|
569
|
+
const required: string[] = []
|
|
561
570
|
arg.properties.forEach(prop => {
|
|
562
571
|
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
563
572
|
const propName = prop.name.text
|
|
564
573
|
const propValue = analyzeSchemaProp(prop.initializer, sourceFile)
|
|
574
|
+
// Optionality is expressed by absence from `required` (JSON Schema)
|
|
575
|
+
const isOptional = propValue.optional === true
|
|
576
|
+
delete propValue.optional
|
|
565
577
|
properties[propName] = propValue
|
|
578
|
+
if (!isOptional) {
|
|
579
|
+
required.push(propName)
|
|
580
|
+
}
|
|
566
581
|
}
|
|
567
582
|
})
|
|
568
|
-
|
|
583
|
+
const result: InputSchema = { type: 'object', properties }
|
|
584
|
+
if (required.length > 0) {
|
|
585
|
+
result.required = required
|
|
586
|
+
}
|
|
587
|
+
return result
|
|
569
588
|
}
|
|
570
589
|
}
|
|
571
590
|
return { type: 'object', properties: {} }
|
|
572
591
|
}
|
|
573
592
|
|
|
574
|
-
//
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
593
|
+
// Analyze a single schema field, unwrapping the call chain
|
|
594
|
+
// (e.g. Schema.string().describe('...').optional()) into a JSON Schema property.
|
|
595
|
+
function analyzeSchemaProp(node: ts.Expression, _sourceFile: ts.SourceFile): SchemaProperty {
|
|
596
|
+
let optional = false
|
|
597
|
+
let description: string | undefined
|
|
598
|
+
let defaultValue: string | undefined
|
|
599
|
+
|
|
600
|
+
let current: ts.Expression = node
|
|
601
|
+
while (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
|
|
602
|
+
const methodName = current.expression.name.text
|
|
603
|
+
const inner = current.expression.expression
|
|
604
|
+
|
|
605
|
+
// Base call: Schema.<type>(...)
|
|
606
|
+
if (ts.isIdentifier(inner) && inner.text === 'Schema') {
|
|
607
|
+
const prop = mapSchemaMethod(methodName)
|
|
608
|
+
if (description !== undefined) {
|
|
609
|
+
prop.description = description
|
|
610
|
+
}
|
|
611
|
+
if (defaultValue !== undefined) {
|
|
612
|
+
prop.default = defaultValue
|
|
613
|
+
}
|
|
614
|
+
if (optional) {
|
|
615
|
+
prop.optional = true
|
|
616
|
+
}
|
|
617
|
+
return prop
|
|
586
618
|
}
|
|
587
|
-
}
|
|
588
619
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if (ts.isCallExpression(baseCall)) {
|
|
597
|
-
const baseType = analyzeSchemaProp(baseCall, sourceFile)
|
|
598
|
-
return { ...baseType, optional: true }
|
|
599
|
-
}
|
|
600
|
-
} else if (chainedMethod === 'default') {
|
|
601
|
-
// This is a .default() call, get the base type
|
|
602
|
-
const baseCall = node.expression.expression
|
|
603
|
-
if (ts.isCallExpression(baseCall)) {
|
|
604
|
-
const baseType = analyzeSchemaProp(baseCall, sourceFile)
|
|
605
|
-
const defaultValue = node.arguments[0]?.getText() || 'undefined'
|
|
606
|
-
return { ...baseType, default: defaultValue }
|
|
607
|
-
}
|
|
620
|
+
// Chained modifiers
|
|
621
|
+
if (methodName === 'optional') {
|
|
622
|
+
optional = true
|
|
623
|
+
} else if (methodName === 'describe') {
|
|
624
|
+
const arg = current.arguments[0]
|
|
625
|
+
if (arg && ts.isStringLiteral(arg)) {
|
|
626
|
+
description = arg.text
|
|
608
627
|
}
|
|
628
|
+
} else if (methodName === 'default') {
|
|
629
|
+
defaultValue = current.arguments[0]?.getText() ?? 'undefined'
|
|
609
630
|
}
|
|
631
|
+
// Other modifiers (min/max/regex/...) don't change the JSON Schema type, so
|
|
632
|
+
// we keep unwrapping until we reach the base Schema.<type>() call.
|
|
633
|
+
|
|
634
|
+
current = inner
|
|
610
635
|
}
|
|
611
636
|
|
|
612
637
|
return { type: 'unknown' }
|
|
613
638
|
}
|
|
614
639
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
640
|
+
// Map a Schema.* helper name to its JSON Schema representation, matching what
|
|
641
|
+
// Schema.describe() emits at runtime.
|
|
642
|
+
function mapSchemaMethod(methodName: string): SchemaProperty {
|
|
643
|
+
switch (methodName) {
|
|
644
|
+
case 'string':
|
|
645
|
+
return { type: 'string' }
|
|
646
|
+
case 'number':
|
|
647
|
+
return { type: 'number' }
|
|
648
|
+
case 'boolean':
|
|
649
|
+
return { type: 'boolean' }
|
|
650
|
+
case 'date':
|
|
651
|
+
return { type: 'string', format: 'date-time' }
|
|
652
|
+
case 'email':
|
|
653
|
+
return { type: 'string', format: 'email' }
|
|
654
|
+
case 'uuid':
|
|
655
|
+
return { type: 'string', format: 'uuid' }
|
|
656
|
+
case 'url':
|
|
657
|
+
return { type: 'string', format: 'uri' }
|
|
658
|
+
case 'array':
|
|
659
|
+
return { type: 'array' }
|
|
660
|
+
case 'object':
|
|
661
|
+
return { type: 'object' }
|
|
662
|
+
case 'stringRecord':
|
|
663
|
+
return { type: 'object', additionalProperties: { type: 'string' } }
|
|
664
|
+
case 'numberRecord':
|
|
665
|
+
return { type: 'object', additionalProperties: { type: 'number' } }
|
|
666
|
+
case 'booleanRecord':
|
|
667
|
+
return { type: 'object', additionalProperties: { type: 'boolean' } }
|
|
668
|
+
case 'mixedRecord':
|
|
669
|
+
return { type: 'object', additionalProperties: { anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }] } }
|
|
670
|
+
default:
|
|
671
|
+
return { type: 'unknown' }
|
|
622
672
|
}
|
|
623
|
-
return typeMap[methodName] || 'unknown'
|
|
624
673
|
}
|
|
625
674
|
|
|
626
675
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"input":{"filePath":"/Users/danielzavaladlvega/forgehive/forge-mono-repo/apps/sample-project/src/tasks/test/errors.ts"},"boundaries":{"getCwd":[],"loadConf":[],"readFile":[],"writeFile":[],"ensureFingerprintsFolder":[]},"metadata":{"environment":"cli"},"metrics":[],"type":"error","error":"Invalid input on: descriptorName: Required"}
|