@forgehive/forge-cli 0.2.13 → 0.3.0
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 +15 -7
- package/dist/tasks/auth/add.js +23 -19
- package/dist/tasks/auth/list.js +20 -16
- package/dist/tasks/auth/load.js +19 -15
- package/dist/tasks/auth/loadCurrent.js +13 -9
- package/dist/tasks/auth/remove.js +30 -26
- package/dist/tasks/auth/switch.js +19 -15
- package/dist/tasks/bundle/create.js +16 -12
- package/dist/tasks/bundle/fingerprint.d.ts +36 -0
- package/dist/tasks/bundle/fingerprint.js +164 -0
- package/dist/tasks/bundle/load.js +9 -5
- package/dist/tasks/bundle/zip.js +49 -45
- package/dist/tasks/conf/info.js +23 -19
- package/dist/tasks/conf/load.js +8 -4
- package/dist/tasks/fixture/download.js +40 -36
- package/dist/tasks/init.js +35 -31
- package/dist/tasks/runner/bundle.js +34 -30
- package/dist/tasks/runner/create.js +28 -24
- package/dist/tasks/runner/remove.js +22 -18
- package/dist/tasks/task/createTask.js +35 -28
- package/dist/tasks/task/describe.d.ts +35 -0
- package/dist/tasks/task/describe.js +130 -0
- package/dist/tasks/task/download.js +63 -59
- package/dist/tasks/task/fingerprint.d.ts +26 -0
- package/dist/tasks/task/fingerprint.js +87 -0
- package/dist/tasks/task/list.d.ts +12 -0
- package/dist/tasks/task/list.js +46 -0
- package/dist/tasks/task/publish.js +72 -68
- package/dist/tasks/task/remove.js +24 -20
- package/dist/tasks/task/replay.js +94 -90
- package/dist/tasks/task/run.js +84 -79
- package/dist/test/setup.d.ts +0 -0
- package/dist/test/setup.js +14 -0
- package/dist/test/tasks/create.test.js +6 -5
- package/dist/utils/taskAnalysis.d.ts +21 -0
- package/dist/utils/taskAnalysis.js +380 -0
- package/forge.json +20 -0
- package/jest.config.js +2 -1
- package/logs/task:fingerprint.log +10 -0
- package/package.json +8 -8
- package/specs/fingerprint.md +380 -0
- package/src/runner.ts +14 -5
- package/src/tasks/README.md +13 -13
- package/src/tasks/auth/add.ts +3 -3
- package/src/tasks/auth/list.ts +3 -3
- package/src/tasks/auth/load.ts +3 -3
- package/src/tasks/auth/loadCurrent.ts +3 -3
- package/src/tasks/auth/remove.ts +3 -3
- package/src/tasks/auth/switch.ts +3 -3
- package/src/tasks/bundle/README.md +7 -7
- package/src/tasks/bundle/create.ts +4 -4
- package/src/tasks/bundle/fingerprint.ts +218 -0
- package/src/tasks/bundle/load.ts +4 -4
- package/src/tasks/bundle/zip.ts +3 -3
- package/src/tasks/conf/info.ts +3 -3
- package/src/tasks/conf/load.ts +3 -3
- package/src/tasks/fixture/download.ts +3 -3
- package/src/tasks/init.ts +3 -3
- package/src/tasks/runner/bundle.ts +3 -3
- package/src/tasks/runner/create.ts +3 -3
- package/src/tasks/runner/remove.ts +3 -3
- package/src/tasks/task/createTask.ts +10 -7
- package/src/tasks/task/describe.ts +148 -0
- package/src/tasks/task/download.ts +3 -3
- package/src/tasks/task/fingerprint.ts +107 -0
- package/src/tasks/task/list.ts +58 -0
- package/src/tasks/task/publish.ts +3 -3
- package/src/tasks/task/remove.ts +3 -3
- package/src/tasks/task/replay.ts +3 -3
- package/src/tasks/task/run.ts +5 -4
- package/src/test/setup.ts +14 -0
- package/src/test/tasks/create.test.ts +9 -9
- package/src/utils/taskAnalysis.ts +419 -0
- package/dist/taskAdapter.d.ts +0 -34
- package/dist/taskAdapter.js +0 -85
- package/dist/templates/README.md +0 -23
- package/dist/templates/task.hbs +0 -27
- package/dist/test/utils.d.ts +0 -2
- package/dist/test/utils.js +0 -17
- package/logs/auth:list.log +0 -4
- package/logs/auth:load.log +0 -2
- package/logs/auth:loadCurrent.log +0 -1
- package/logs/conf:info.log +0 -2
- package/logs/runner:create.log +0 -4
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
|
|
3
|
+
interface TaskLocation {
|
|
4
|
+
file: string
|
|
5
|
+
line: number
|
|
6
|
+
column: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface SchemaProperty {
|
|
10
|
+
type: string
|
|
11
|
+
optional?: boolean
|
|
12
|
+
default?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface InputSchema {
|
|
16
|
+
type: string
|
|
17
|
+
properties: Record<string, SchemaProperty>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface OutputType {
|
|
21
|
+
type: string
|
|
22
|
+
properties?: Record<string, SchemaProperty>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface TaskFingerprint {
|
|
26
|
+
name: string
|
|
27
|
+
description?: string
|
|
28
|
+
location: TaskLocation
|
|
29
|
+
inputSchema: InputSchema
|
|
30
|
+
outputType: OutputType
|
|
31
|
+
boundaries: string[]
|
|
32
|
+
hash: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Simplified interface for filesystem output (excludes name, location, hash)
|
|
36
|
+
export interface TaskFingerprintOutput {
|
|
37
|
+
description?: string
|
|
38
|
+
inputSchema: InputSchema
|
|
39
|
+
outputType: OutputType
|
|
40
|
+
boundaries: string[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Hash generation function
|
|
44
|
+
function generateHash(input: string): string {
|
|
45
|
+
let hash = 0
|
|
46
|
+
for (let i = 0; i < input.length; i++) {
|
|
47
|
+
const char = input.charCodeAt(i)
|
|
48
|
+
hash = ((hash << 5) - hash) + char
|
|
49
|
+
hash = hash & hash // Convert to 32-bit integer
|
|
50
|
+
}
|
|
51
|
+
return Math.abs(hash).toString(36)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// TypeScript AST analysis function
|
|
55
|
+
function extractTaskFingerprints(sourceCode: string, filePath: string): TaskFingerprint[] {
|
|
56
|
+
const sourceFile = ts.createSourceFile(
|
|
57
|
+
filePath,
|
|
58
|
+
sourceCode,
|
|
59
|
+
ts.ScriptTarget.Latest,
|
|
60
|
+
true
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
const fingerprints: TaskFingerprint[] = []
|
|
64
|
+
let schemaNode: ts.Expression | null = null
|
|
65
|
+
let boundariesNode: ts.Expression | null = null
|
|
66
|
+
|
|
67
|
+
// First pass: find schema and boundaries variable declarations
|
|
68
|
+
function findVariables(node: ts.Node): void {
|
|
69
|
+
if (ts.isVariableStatement(node)) {
|
|
70
|
+
node.declarationList.declarations.forEach(decl => {
|
|
71
|
+
if (ts.isIdentifier(decl.name)) {
|
|
72
|
+
if (decl.name.text === 'schema' && decl.initializer) {
|
|
73
|
+
schemaNode = decl.initializer
|
|
74
|
+
} else if (decl.name.text === 'boundaries' && decl.initializer) {
|
|
75
|
+
boundariesNode = decl.initializer
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
ts.forEachChild(node, findVariables)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Second pass: find createTask calls
|
|
84
|
+
function findCreateTask(node: ts.Node): void {
|
|
85
|
+
// Look for createTask calls
|
|
86
|
+
if (ts.isCallExpression(node) &&
|
|
87
|
+
ts.isIdentifier(node.expression) &&
|
|
88
|
+
node.expression.text === 'createTask') {
|
|
89
|
+
|
|
90
|
+
const taskName = extractTaskName(node, sourceFile)
|
|
91
|
+
if (taskName) {
|
|
92
|
+
const fingerprint = analyzeCreateTaskCall(node, sourceFile, filePath, taskName, schemaNode, boundariesNode)
|
|
93
|
+
if (fingerprint) {
|
|
94
|
+
fingerprints.push(fingerprint)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Look for exported createTask assignments
|
|
100
|
+
if (ts.isVariableStatement(node) && node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
101
|
+
node.declarationList.declarations.forEach(decl => {
|
|
102
|
+
if (ts.isVariableDeclaration(decl) &&
|
|
103
|
+
decl.initializer &&
|
|
104
|
+
ts.isCallExpression(decl.initializer) &&
|
|
105
|
+
ts.isIdentifier(decl.initializer.expression) &&
|
|
106
|
+
decl.initializer.expression.text === 'createTask') {
|
|
107
|
+
|
|
108
|
+
const taskName = ts.isIdentifier(decl.name) ? decl.name.text : 'unknown'
|
|
109
|
+
const fingerprint = analyzeCreateTaskCall(decl.initializer, sourceFile, filePath, taskName, schemaNode, boundariesNode)
|
|
110
|
+
if (fingerprint) {
|
|
111
|
+
fingerprints.push(fingerprint)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
ts.forEachChild(node, findCreateTask)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Execute both passes
|
|
121
|
+
findVariables(sourceFile)
|
|
122
|
+
findCreateTask(sourceFile)
|
|
123
|
+
|
|
124
|
+
return fingerprints
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function extractTaskName(node: ts.CallExpression, _sourceFile: ts.SourceFile): string | null {
|
|
128
|
+
// Try to find the task name from variable assignment or export
|
|
129
|
+
let parent = node.parent
|
|
130
|
+
while (parent) {
|
|
131
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
|
132
|
+
return parent.name.text
|
|
133
|
+
}
|
|
134
|
+
parent = parent.parent
|
|
135
|
+
}
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function analyzeCreateTaskCall(
|
|
140
|
+
node: ts.CallExpression,
|
|
141
|
+
sourceFile: ts.SourceFile,
|
|
142
|
+
filePath: string,
|
|
143
|
+
taskName: string,
|
|
144
|
+
schemaNode: ts.Expression | null = null,
|
|
145
|
+
boundariesNode: ts.Expression | null = null
|
|
146
|
+
): TaskFingerprint | null {
|
|
147
|
+
try {
|
|
148
|
+
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart())
|
|
149
|
+
const args = node.arguments
|
|
150
|
+
|
|
151
|
+
// Use pre-found schema or fall back to argument analysis
|
|
152
|
+
let inputSchema: InputSchema = { type: 'object', properties: {} }
|
|
153
|
+
if (schemaNode) {
|
|
154
|
+
inputSchema = analyzeSchemaArg(schemaNode, sourceFile)
|
|
155
|
+
} else if (args[0]) {
|
|
156
|
+
inputSchema = analyzeSchemaArg(args[0], sourceFile)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Use pre-found boundaries or fall back to argument analysis - simplified to just names
|
|
160
|
+
let boundaries: string[] = []
|
|
161
|
+
if (boundariesNode) {
|
|
162
|
+
boundaries = analyzeBoundariesArg(boundariesNode, sourceFile)
|
|
163
|
+
} else if (args[1]) {
|
|
164
|
+
boundaries = analyzeBoundariesArg(args[1], sourceFile)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Extract function output type with better detection
|
|
168
|
+
let outputType: OutputType = { type: 'unknown' }
|
|
169
|
+
const functionArg = args[2]
|
|
170
|
+
|
|
171
|
+
if (functionArg) {
|
|
172
|
+
if (ts.isFunctionExpression(functionArg) || ts.isArrowFunction(functionArg)) {
|
|
173
|
+
// Better return type extraction
|
|
174
|
+
if (functionArg.type) {
|
|
175
|
+
const typeString = cleanTypeString(functionArg.type.getText(sourceFile))
|
|
176
|
+
outputType = { type: typeString }
|
|
177
|
+
} else {
|
|
178
|
+
// Try to infer from return statements with better object analysis
|
|
179
|
+
outputType = inferDetailedReturnType(functionArg, sourceFile)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Generate hash from task signature
|
|
185
|
+
const hashInput = `${taskName}:${JSON.stringify(inputSchema)}:${JSON.stringify(boundaries)}`
|
|
186
|
+
const hash = generateHash(hashInput)
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
name: taskName,
|
|
190
|
+
location: {
|
|
191
|
+
file: filePath,
|
|
192
|
+
line: position.line + 1,
|
|
193
|
+
column: position.character + 1
|
|
194
|
+
},
|
|
195
|
+
inputSchema,
|
|
196
|
+
outputType,
|
|
197
|
+
boundaries,
|
|
198
|
+
hash
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.warn(`Failed to analyze createTask call for ${taskName}:`, error)
|
|
202
|
+
return null
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Enhanced return type inference with detailed object analysis
|
|
207
|
+
function inferDetailedReturnType(func: ts.FunctionExpression | ts.ArrowFunction, _sourceFile: ts.SourceFile): OutputType {
|
|
208
|
+
let returnType: OutputType = { type: 'unknown' }
|
|
209
|
+
|
|
210
|
+
function visitReturnStatements(node: ts.Node): void {
|
|
211
|
+
if (ts.isReturnStatement(node) && node.expression) {
|
|
212
|
+
if (ts.isObjectLiteralExpression(node.expression)) {
|
|
213
|
+
// Analyze object literal properties
|
|
214
|
+
const properties: Record<string, SchemaProperty> = {}
|
|
215
|
+
node.expression.properties.forEach(prop => {
|
|
216
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
217
|
+
// Handle explicit property assignments: { propName: value }
|
|
218
|
+
const propName = prop.name.text
|
|
219
|
+
let propType = 'unknown'
|
|
220
|
+
|
|
221
|
+
// Try to infer property type from the initializer
|
|
222
|
+
if (ts.isStringLiteral(prop.initializer)) {
|
|
223
|
+
propType = 'string'
|
|
224
|
+
} else if (ts.isNumericLiteral(prop.initializer)) {
|
|
225
|
+
propType = 'number'
|
|
226
|
+
} else if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
|
|
227
|
+
prop.initializer.kind === ts.SyntaxKind.FalseKeyword) {
|
|
228
|
+
propType = 'boolean'
|
|
229
|
+
} else if (ts.isIdentifier(prop.initializer)) {
|
|
230
|
+
// Variable reference - try to infer from name
|
|
231
|
+
const varName = prop.initializer.text
|
|
232
|
+
propType = inferTypeFromVariableName(varName)
|
|
233
|
+
} else if (ts.isPropertyAccessExpression(prop.initializer)) {
|
|
234
|
+
// Property access like response.handler
|
|
235
|
+
propType = 'unknown'
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
properties[propName] = { type: propType }
|
|
239
|
+
} else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
240
|
+
// Handle shorthand properties: { propName } (equivalent to { propName: propName })
|
|
241
|
+
const propName = prop.name.text
|
|
242
|
+
const propType = inferTypeFromVariableName(propName)
|
|
243
|
+
properties[propName] = { type: propType }
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
if (Object.keys(properties).length > 0) {
|
|
248
|
+
returnType = {
|
|
249
|
+
type: 'object',
|
|
250
|
+
properties
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
returnType = { type: 'object' }
|
|
254
|
+
}
|
|
255
|
+
} else if (ts.isStringLiteral(node.expression)) {
|
|
256
|
+
returnType = { type: 'string' }
|
|
257
|
+
} else if (ts.isNumericLiteral(node.expression)) {
|
|
258
|
+
returnType = { type: 'number' }
|
|
259
|
+
} else if (node.expression.kind === ts.SyntaxKind.TrueKeyword ||
|
|
260
|
+
node.expression.kind === ts.SyntaxKind.FalseKeyword) {
|
|
261
|
+
returnType = { type: 'boolean' }
|
|
262
|
+
} else if (ts.isIdentifier(node.expression)) {
|
|
263
|
+
// Single variable return
|
|
264
|
+
const varType = inferTypeFromVariableName(node.expression.text)
|
|
265
|
+
returnType = { type: varType }
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
ts.forEachChild(node, visitReturnStatements)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (func.body) {
|
|
272
|
+
visitReturnStatements(func.body)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return returnType
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Helper function to infer types from variable names
|
|
279
|
+
function inferTypeFromVariableName(varName: string): string {
|
|
280
|
+
// Common patterns for type inference based on variable names
|
|
281
|
+
if (varName.includes('path') || varName.includes('Path') ||
|
|
282
|
+
varName.includes('name') || varName.includes('Name') ||
|
|
283
|
+
varName.includes('descriptor') || varName.includes('Descriptor') ||
|
|
284
|
+
varName.includes('fileName') || varName.includes('handler') ||
|
|
285
|
+
varName.includes('url') || varName.includes('id') ||
|
|
286
|
+
varName.includes('uuid') || varName.includes('token')) {
|
|
287
|
+
return 'string'
|
|
288
|
+
} else if (varName.includes('count') || varName.includes('Count') ||
|
|
289
|
+
varName.includes('size') || varName.includes('Size') ||
|
|
290
|
+
varName.includes('length') || varName.includes('Length') ||
|
|
291
|
+
varName.includes('index') || varName.includes('Index')) {
|
|
292
|
+
return 'number'
|
|
293
|
+
} else if (varName.includes('is') || varName.includes('has') ||
|
|
294
|
+
varName.includes('can') || varName.includes('should') ||
|
|
295
|
+
varName.includes('enabled') || varName.includes('success')) {
|
|
296
|
+
return 'boolean'
|
|
297
|
+
} else if (varName.includes('config') || varName.includes('Config') ||
|
|
298
|
+
varName.includes('options') || varName.includes('Options') ||
|
|
299
|
+
varName.includes('data') || varName.includes('result') ||
|
|
300
|
+
varName.includes('response') || varName.includes('error')) {
|
|
301
|
+
return 'unknown'
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return 'unknown'
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Clean up type strings (remove Promise wrappers for boundaries)
|
|
308
|
+
function cleanTypeString(typeString: string): string {
|
|
309
|
+
// Remove Promise wrapper for boundary functions
|
|
310
|
+
const promiseMatch = typeString.match(/Promise<(.+)>/)
|
|
311
|
+
if (promiseMatch) {
|
|
312
|
+
return promiseMatch[1]
|
|
313
|
+
}
|
|
314
|
+
return typeString
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function analyzeSchemaArg(node: ts.Expression, sourceFile: ts.SourceFile): InputSchema {
|
|
318
|
+
// Handle variable references (e.g., when schema is defined as const schema = ...)
|
|
319
|
+
if (ts.isIdentifier(node) && node.text === 'schema') {
|
|
320
|
+
// This case is now handled by pre-finding the schema node
|
|
321
|
+
return { type: 'object', properties: {} }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Handle direct Schema constructor calls
|
|
325
|
+
if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'Schema') {
|
|
326
|
+
const arg = node.arguments?.[0]
|
|
327
|
+
if (arg && ts.isObjectLiteralExpression(arg)) {
|
|
328
|
+
const properties: Record<string, SchemaProperty> = {}
|
|
329
|
+
arg.properties.forEach(prop => {
|
|
330
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
331
|
+
const propName = prop.name.text
|
|
332
|
+
const propValue = analyzeSchemaProp(prop.initializer, sourceFile)
|
|
333
|
+
properties[propName] = propValue
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
return { type: 'object', properties }
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return { type: 'object', properties: {} }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Enhanced schema property analysis
|
|
343
|
+
function analyzeSchemaProp(node: ts.Expression, _sourceFile: ts.SourceFile): SchemaProperty {
|
|
344
|
+
// Analyze Schema.string(), Schema.number(), etc.
|
|
345
|
+
if (ts.isCallExpression(node)) {
|
|
346
|
+
if (ts.isPropertyAccessExpression(node.expression) &&
|
|
347
|
+
ts.isIdentifier(node.expression.expression) &&
|
|
348
|
+
node.expression.expression.text === 'Schema') {
|
|
349
|
+
|
|
350
|
+
const methodName = node.expression.name.text
|
|
351
|
+
let baseType: SchemaProperty = { type: getSchemaTypeFromMethod(methodName) }
|
|
352
|
+
|
|
353
|
+
// Check for chained methods like .optional() or .default()
|
|
354
|
+
let current = node
|
|
355
|
+
while (current.parent && ts.isCallExpression(current.parent)) {
|
|
356
|
+
current = current.parent
|
|
357
|
+
if (ts.isPropertyAccessExpression(current.expression)) {
|
|
358
|
+
const chainedMethod = current.expression.name.text
|
|
359
|
+
if (chainedMethod === 'optional') {
|
|
360
|
+
baseType = { ...baseType, optional: true }
|
|
361
|
+
} else if (chainedMethod === 'default' && current.arguments[0]) {
|
|
362
|
+
baseType = { ...baseType, default: current.arguments[0].getText() }
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return baseType
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return { type: 'unknown' }
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function getSchemaTypeFromMethod(methodName: string): string {
|
|
374
|
+
const typeMap: Record<string, string> = {
|
|
375
|
+
string: 'string',
|
|
376
|
+
number: 'number',
|
|
377
|
+
boolean: 'boolean',
|
|
378
|
+
array: 'array',
|
|
379
|
+
object: 'object'
|
|
380
|
+
}
|
|
381
|
+
return typeMap[methodName] || 'unknown'
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function analyzeBoundariesArg(node: ts.Expression, _sourceFile: ts.SourceFile): string[] {
|
|
385
|
+
const boundaries: string[] = []
|
|
386
|
+
|
|
387
|
+
// Handle variable references (e.g., when boundaries is defined as const boundaries = ...)
|
|
388
|
+
if (ts.isIdentifier(node) && node.text === 'boundaries') {
|
|
389
|
+
// This case is now handled by pre-finding the boundaries node
|
|
390
|
+
return []
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
394
|
+
node.properties.forEach(prop => {
|
|
395
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
396
|
+
const boundaryName = prop.name.text
|
|
397
|
+
boundaries.push(boundaryName)
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return boundaries
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Export the core analysis function for reuse
|
|
406
|
+
export function analyzeTaskFile(sourceCode: string, filePath: string, _expectedTaskName?: string): TaskFingerprintOutput | null {
|
|
407
|
+
const taskFingerprint = extractTaskFingerprints(sourceCode, filePath)[0]
|
|
408
|
+
if (!taskFingerprint) {
|
|
409
|
+
return null
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Return simplified output without name, location, hash
|
|
413
|
+
return {
|
|
414
|
+
description: taskFingerprint.description,
|
|
415
|
+
inputSchema: taskFingerprint.inputSchema,
|
|
416
|
+
outputType: taskFingerprint.outputType,
|
|
417
|
+
boundaries: taskFingerprint.boundaries
|
|
418
|
+
}
|
|
419
|
+
}
|
package/dist/taskAdapter.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { TaskInstanceType, BaseFunction, Boundaries, ExecutionRecord, ReplayConfig } from '@forgehive/task';
|
|
2
|
-
import { SchemaDescription } from '@forgehive/schema';
|
|
3
|
-
/**
|
|
4
|
-
* TaskAdapter wraps a TaskInstanceType to make it compatible with Runner
|
|
5
|
-
* by handling boundary type issues
|
|
6
|
-
*/
|
|
7
|
-
export declare class TaskAdapter<F extends BaseFunction, B extends Boundaries> {
|
|
8
|
-
private task;
|
|
9
|
-
constructor(task: TaskInstanceType<F, B>);
|
|
10
|
-
get version(): string;
|
|
11
|
-
getDescription(): string | undefined;
|
|
12
|
-
describe(): SchemaDescription;
|
|
13
|
-
run(argv?: Parameters<F>[0]): Promise<ReturnType<F>>;
|
|
14
|
-
safeReplay(executionLog: ExecutionRecord<any, any, any>, config: ReplayConfig<any>): Promise<[ReturnType<F> | null, Error | null, ExecutionRecord<Parameters<F>[0], ReturnType<F>, B>]>;
|
|
15
|
-
getMode(): import("@forgehive/task").Mode;
|
|
16
|
-
setMode(mode: string): void;
|
|
17
|
-
validate(argv?: any): any;
|
|
18
|
-
isValid(argv?: any): boolean;
|
|
19
|
-
addListener(fn: any): void;
|
|
20
|
-
removeListener(): void;
|
|
21
|
-
emit(data: any): void;
|
|
22
|
-
asBoundary(): (args: Parameters<F>[0]) => Promise<ReturnType<F>>;
|
|
23
|
-
getBoundaries(): import("@forgehive/task").WrappedBoundaries<B>;
|
|
24
|
-
setBoundariesData(data: any): void;
|
|
25
|
-
getBondariesData(): Record<string, unknown>;
|
|
26
|
-
mockBoundary(name: any, mockFn: any): any;
|
|
27
|
-
resetMock(name: any): any;
|
|
28
|
-
resetMocks(): void;
|
|
29
|
-
safeRun(argv?: Parameters<F>[0]): Promise<[ReturnType<F> | null, Error | null, ExecutionRecord<Parameters<F>[0], ReturnType<F>, B>]>;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Helper function to adapt a task instance for Runner compatibility
|
|
33
|
-
*/
|
|
34
|
-
export declare function adaptTask<F extends BaseFunction, B extends Boundaries>(task: TaskInstanceType<F, B>): TaskInstanceType;
|
package/dist/taskAdapter.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TaskAdapter = void 0;
|
|
4
|
-
exports.adaptTask = adaptTask;
|
|
5
|
-
/**
|
|
6
|
-
* TaskAdapter wraps a TaskInstanceType to make it compatible with Runner
|
|
7
|
-
* by handling boundary type issues
|
|
8
|
-
*/
|
|
9
|
-
class TaskAdapter {
|
|
10
|
-
constructor(task) {
|
|
11
|
-
this.task = task;
|
|
12
|
-
}
|
|
13
|
-
// Forward all required methods to the underlying task
|
|
14
|
-
get version() {
|
|
15
|
-
return this.task.version;
|
|
16
|
-
}
|
|
17
|
-
getDescription() {
|
|
18
|
-
return this.task.getDescription();
|
|
19
|
-
}
|
|
20
|
-
describe() {
|
|
21
|
-
return this.task.describe();
|
|
22
|
-
}
|
|
23
|
-
async run(argv) {
|
|
24
|
-
return this.task.run(argv);
|
|
25
|
-
}
|
|
26
|
-
// This is the problematic method with boundary type incompatibilities
|
|
27
|
-
// Our implementation uses type assertions to make it compatible
|
|
28
|
-
async safeReplay(executionLog, config) {
|
|
29
|
-
// Type assertion to bridge the boundary type gap
|
|
30
|
-
return this.task.safeReplay(executionLog, config);
|
|
31
|
-
}
|
|
32
|
-
// Forward other methods as needed
|
|
33
|
-
getMode() {
|
|
34
|
-
return this.task.getMode();
|
|
35
|
-
}
|
|
36
|
-
setMode(mode) {
|
|
37
|
-
return this.task.setMode(mode);
|
|
38
|
-
}
|
|
39
|
-
validate(argv) {
|
|
40
|
-
return this.task.validate(argv);
|
|
41
|
-
}
|
|
42
|
-
isValid(argv) {
|
|
43
|
-
return this.task.isValid(argv);
|
|
44
|
-
}
|
|
45
|
-
addListener(fn) {
|
|
46
|
-
return this.task.addListener(fn);
|
|
47
|
-
}
|
|
48
|
-
removeListener() {
|
|
49
|
-
return this.task.removeListener();
|
|
50
|
-
}
|
|
51
|
-
emit(data) {
|
|
52
|
-
return this.task.emit(data);
|
|
53
|
-
}
|
|
54
|
-
asBoundary() {
|
|
55
|
-
return this.task.asBoundary();
|
|
56
|
-
}
|
|
57
|
-
getBoundaries() {
|
|
58
|
-
return this.task.getBoundaries();
|
|
59
|
-
}
|
|
60
|
-
setBoundariesData(data) {
|
|
61
|
-
return this.task.setBoundariesData(data);
|
|
62
|
-
}
|
|
63
|
-
getBondariesData() {
|
|
64
|
-
return this.task.getBondariesData();
|
|
65
|
-
}
|
|
66
|
-
mockBoundary(name, mockFn) {
|
|
67
|
-
return this.task.mockBoundary(name, mockFn);
|
|
68
|
-
}
|
|
69
|
-
resetMock(name) {
|
|
70
|
-
return this.task.resetMock(name);
|
|
71
|
-
}
|
|
72
|
-
resetMocks() {
|
|
73
|
-
return this.task.resetMocks();
|
|
74
|
-
}
|
|
75
|
-
async safeRun(argv) {
|
|
76
|
-
return this.task.safeRun(argv);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
exports.TaskAdapter = TaskAdapter;
|
|
80
|
-
/**
|
|
81
|
-
* Helper function to adapt a task instance for Runner compatibility
|
|
82
|
-
*/
|
|
83
|
-
function adaptTask(task) {
|
|
84
|
-
return new TaskAdapter(task);
|
|
85
|
-
}
|
package/dist/templates/README.md
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# Templates Directory
|
|
2
|
-
|
|
3
|
-
This directory contains Handlebars templates used by the CLI to generate files.
|
|
4
|
-
|
|
5
|
-
## Templates
|
|
6
|
-
|
|
7
|
-
- `task.hbs`: Template for generating new tasks
|
|
8
|
-
|
|
9
|
-
## Important Note
|
|
10
|
-
|
|
11
|
-
These templates are copied to the `dist/templates` directory during the build process.
|
|
12
|
-
This ensures that the templates are available when the CLI is installed and used in production.
|
|
13
|
-
|
|
14
|
-
The build script in `package.json` includes a step to copy these templates:
|
|
15
|
-
|
|
16
|
-
```json
|
|
17
|
-
"scripts": {
|
|
18
|
-
"build": "tsc && npm run copy-templates",
|
|
19
|
-
"copy-templates": "mkdir -p dist/templates && cp -r src/templates/* dist/templates/"
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
If you add new templates, make sure they are properly copied to the dist folder.
|
package/dist/templates/task.hbs
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// TASK: {{ taskName }}
|
|
2
|
-
// Run this task with:
|
|
3
|
-
// shadow-cli {{ taskDescriptor }}
|
|
4
|
-
|
|
5
|
-
import { createTask } from '@shadow/task'
|
|
6
|
-
import { Schema } from '@shadow/schema'
|
|
7
|
-
|
|
8
|
-
const schema = new Schema({
|
|
9
|
-
// Add your schema definitions here
|
|
10
|
-
// example: myParam: Schema.string()
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
const boundaries = {
|
|
14
|
-
// Add your boundary functions here
|
|
15
|
-
// example: readFile: async (path: string) => fs.readFile(path, 'utf-8')
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const {{ taskName }} = createTask(
|
|
19
|
-
schema,
|
|
20
|
-
boundaries,
|
|
21
|
-
async function (argv, boundaryFns) {
|
|
22
|
-
// Your task implementation goes here
|
|
23
|
-
const status = { status: 'Ok' }
|
|
24
|
-
|
|
25
|
-
return status
|
|
26
|
-
}
|
|
27
|
-
)
|
package/dist/test/utils.d.ts
DELETED
package/dist/test/utils.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createBoundaryMock = void 0;
|
|
4
|
-
const createBoundaryMock = () => {
|
|
5
|
-
const mockFn = jest.fn().mockResolvedValue(undefined);
|
|
6
|
-
const boundaryMock = mockFn;
|
|
7
|
-
// Add required methods to satisfy the interface
|
|
8
|
-
boundaryMock.getTape = jest.fn().mockReturnValue([]);
|
|
9
|
-
boundaryMock.setTape = jest.fn();
|
|
10
|
-
boundaryMock.getMode = jest.fn().mockReturnValue('proxy');
|
|
11
|
-
boundaryMock.setMode = jest.fn();
|
|
12
|
-
boundaryMock.startRun = jest.fn();
|
|
13
|
-
boundaryMock.stopRun = jest.fn();
|
|
14
|
-
boundaryMock.getRunData = jest.fn().mockReturnValue([]);
|
|
15
|
-
return boundaryMock;
|
|
16
|
-
};
|
|
17
|
-
exports.createBoundaryMock = createBoundaryMock;
|
package/logs/auth:list.log
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
{"name":"auth:list","type":"success","input":{},"output":{"status":"Ok","profiles":[{"name":"local","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:4000"},{"name":"localNext","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:3000"}],"default":"localNext"},"boundaries":{"loadProfiles":[{"input":[{}],"output":{"default":"localNext","profiles":[{"name":"local","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:4000"},{"name":"localNext","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:3000"}]}},{"input":[{}],"output":{"default":"localNext","profiles":[{"name":"local","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:4000"},{"name":"localNext","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:3000"}]}}]}}
|
|
2
|
-
{"name":"auth:list","type":"success","input":{},"output":{"status":"Ok","default":"localNext"},"boundaries":{"loadProfiles":[{"input":[{}],"output":{"default":"localNext","profiles":[{"name":"local","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:4000"},{"name":"localNext","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:3000"}]}}]}}
|
|
3
|
-
{"name":"auth:list","type":"success","input":{},"output":{"default":"localNext"},"boundaries":{"loadProfiles":[{"input":[{}],"output":{"default":"localNext","profiles":[{"name":"local","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:4000"},{"name":"localNext","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:3000"}]}}]}}
|
|
4
|
-
{"name":"auth:list","type":"success","input":{},"output":{"default":"localNext"},"boundaries":{"loadProfiles":[{"input":[{}],"output":{"default":"localNext","profiles":[{"name":"local","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:4000"},{"name":"localNext","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:3000"}]}}]}}
|
package/logs/auth:load.log
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
{"name":"auth:load","type":"error","input":{},"error":"ENOENT: no such file or directory, open '/Users/danielzavaladlvega/.forge/profiles.json'","boundaries":{"ensureBuildsFolder":[{"input":[],"output":"/Users/danielzavaladlvega/.forge"},{"input":[],"output":"/Users/danielzavaladlvega/.forge"}]}}
|
|
2
|
-
{"name":"auth:load","type":"success","input":{},"output":{"profiles":[]},"boundaries":{"ensureBuildsFolder":[{"input":[],"output":"/Users/danielzavaladlvega/.forge"}]}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"name":"auth:loadCurrent","type":"success","input":{},"output":{"name":"local","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:4000"},"boundaries":{"loadProfiles":[{"input":[{}],"output":{"default":"local","profiles":[{"name":"local","apiKey":"b4b5a766fcd7dc2d059e8f96a57c8edd","apiSecret":"2900246cb8bebcbeaadbe6348477592f42d62788d13fd4067588438bc11bf116","url":"http://localhost:4000"}]}}]}}
|
package/logs/conf:info.log
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
{"name":"conf:info","type":"error","input":{},"error":"ENOENT: no such file or directory, open '/Users/package.json'","boundaries":{"readFile":[{"input":["/Users/package.json"],"error":"ENOENT: no such file or directory, open '/Users/package.json'"},{"input":["/Users/package.json"],"error":"ENOENT: no such file or directory, open '/Users/package.json'"}],"loadCurrentProfile":[]}}
|
|
2
|
-
{"name":"conf:info","type":"error","input":{"name":"foobar"},"error":"ENOENT: no such file or directory, open '/Users/package.json'","boundaries":{"readFile":[{"input":["/Users/package.json"],"error":"ENOENT: no such file or directory, open '/Users/package.json'"}],"loadCurrentProfile":[]}}
|
package/logs/runner:create.log
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
{"name":"runner:create","type":"success","input":{},"output":{"status":"Ok"},"boundaries":{}}
|
|
2
|
-
{"name":"runner:create","type":"success","input":{"runnerName":"inventory"},"output":{},"boundaries":{"loadConf":[{"input":[{}],"output":{"project":{"name":"forge-cli"},"paths":{"logs":"logs/","tasks":"src/tasks/","runners":"src/runners/","fixtures":"src/tests/fixtures","tests":"src/tests/"},"infra":{"region":"us-west-2","bucket":""},"tasks":{"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"}},"runners":{}}},{"input":[{}],"output":{"project":{"name":"forge-cli"},"paths":{"logs":"logs/","tasks":"src/tasks/","runners":"src/runners/","fixtures":"src/tests/fixtures","tests":"src/tests/"},"infra":{"region":"us-west-2","bucket":""},"tasks":{"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"}},"runners":{}}}],"getCwd":[{"input":[],"output":"/Users/danielzavaladlvega/forgehive/mono/apps/cli"},{"input":[],"output":"/Users/danielzavaladlvega/forgehive/mono/apps/cli"}],"persistRunner":[],"persistConf":[]}}
|
|
3
|
-
{"name":"runner:create","type":"success","input":{"runnerName":"inventory"},"output":{},"boundaries":{"loadConf":[{"input":[{}],"output":{"project":{"name":"forge-cli"},"paths":{"logs":"logs/","tasks":"src/tasks/","runners":"src/runners/","fixtures":"src/tests/fixtures","tests":"src/tests/"},"infra":{"region":"us-west-2","bucket":""},"tasks":{"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"}},"runners":{}}}],"getCwd":[{"input":[],"output":"/Users/danielzavaladlvega/forgehive/mono/apps/cli"}],"persistRunner":[],"persistConf":[]}}
|
|
4
|
-
{"name":"runner:create","type":"error","input":{},"error":"Invalid input on: runnerName: Required","boundaries":{"loadConf":[],"getCwd":[],"persistRunner":[],"persistConf":[]}}
|