@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.
Files changed (84) hide show
  1. package/dist/runner.js +15 -7
  2. package/dist/tasks/auth/add.js +23 -19
  3. package/dist/tasks/auth/list.js +20 -16
  4. package/dist/tasks/auth/load.js +19 -15
  5. package/dist/tasks/auth/loadCurrent.js +13 -9
  6. package/dist/tasks/auth/remove.js +30 -26
  7. package/dist/tasks/auth/switch.js +19 -15
  8. package/dist/tasks/bundle/create.js +16 -12
  9. package/dist/tasks/bundle/fingerprint.d.ts +36 -0
  10. package/dist/tasks/bundle/fingerprint.js +164 -0
  11. package/dist/tasks/bundle/load.js +9 -5
  12. package/dist/tasks/bundle/zip.js +49 -45
  13. package/dist/tasks/conf/info.js +23 -19
  14. package/dist/tasks/conf/load.js +8 -4
  15. package/dist/tasks/fixture/download.js +40 -36
  16. package/dist/tasks/init.js +35 -31
  17. package/dist/tasks/runner/bundle.js +34 -30
  18. package/dist/tasks/runner/create.js +28 -24
  19. package/dist/tasks/runner/remove.js +22 -18
  20. package/dist/tasks/task/createTask.js +35 -28
  21. package/dist/tasks/task/describe.d.ts +35 -0
  22. package/dist/tasks/task/describe.js +130 -0
  23. package/dist/tasks/task/download.js +63 -59
  24. package/dist/tasks/task/fingerprint.d.ts +26 -0
  25. package/dist/tasks/task/fingerprint.js +87 -0
  26. package/dist/tasks/task/list.d.ts +12 -0
  27. package/dist/tasks/task/list.js +46 -0
  28. package/dist/tasks/task/publish.js +72 -68
  29. package/dist/tasks/task/remove.js +24 -20
  30. package/dist/tasks/task/replay.js +94 -90
  31. package/dist/tasks/task/run.js +84 -79
  32. package/dist/test/setup.d.ts +0 -0
  33. package/dist/test/setup.js +14 -0
  34. package/dist/test/tasks/create.test.js +6 -5
  35. package/dist/utils/taskAnalysis.d.ts +21 -0
  36. package/dist/utils/taskAnalysis.js +380 -0
  37. package/forge.json +20 -0
  38. package/jest.config.js +2 -1
  39. package/logs/task:fingerprint.log +10 -0
  40. package/package.json +8 -8
  41. package/specs/fingerprint.md +380 -0
  42. package/src/runner.ts +14 -5
  43. package/src/tasks/README.md +13 -13
  44. package/src/tasks/auth/add.ts +3 -3
  45. package/src/tasks/auth/list.ts +3 -3
  46. package/src/tasks/auth/load.ts +3 -3
  47. package/src/tasks/auth/loadCurrent.ts +3 -3
  48. package/src/tasks/auth/remove.ts +3 -3
  49. package/src/tasks/auth/switch.ts +3 -3
  50. package/src/tasks/bundle/README.md +7 -7
  51. package/src/tasks/bundle/create.ts +4 -4
  52. package/src/tasks/bundle/fingerprint.ts +218 -0
  53. package/src/tasks/bundle/load.ts +4 -4
  54. package/src/tasks/bundle/zip.ts +3 -3
  55. package/src/tasks/conf/info.ts +3 -3
  56. package/src/tasks/conf/load.ts +3 -3
  57. package/src/tasks/fixture/download.ts +3 -3
  58. package/src/tasks/init.ts +3 -3
  59. package/src/tasks/runner/bundle.ts +3 -3
  60. package/src/tasks/runner/create.ts +3 -3
  61. package/src/tasks/runner/remove.ts +3 -3
  62. package/src/tasks/task/createTask.ts +10 -7
  63. package/src/tasks/task/describe.ts +148 -0
  64. package/src/tasks/task/download.ts +3 -3
  65. package/src/tasks/task/fingerprint.ts +107 -0
  66. package/src/tasks/task/list.ts +58 -0
  67. package/src/tasks/task/publish.ts +3 -3
  68. package/src/tasks/task/remove.ts +3 -3
  69. package/src/tasks/task/replay.ts +3 -3
  70. package/src/tasks/task/run.ts +5 -4
  71. package/src/test/setup.ts +14 -0
  72. package/src/test/tasks/create.test.ts +9 -9
  73. package/src/utils/taskAnalysis.ts +419 -0
  74. package/dist/taskAdapter.d.ts +0 -34
  75. package/dist/taskAdapter.js +0 -85
  76. package/dist/templates/README.md +0 -23
  77. package/dist/templates/task.hbs +0 -27
  78. package/dist/test/utils.d.ts +0 -2
  79. package/dist/test/utils.js +0 -17
  80. package/logs/auth:list.log +0 -4
  81. package/logs/auth:load.log +0 -2
  82. package/logs/auth:loadCurrent.log +0 -1
  83. package/logs/conf:info.log +0 -2
  84. package/logs/runner:create.log +0 -4
@@ -0,0 +1,380 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.analyzeTaskFile = analyzeTaskFile;
37
+ const ts = __importStar(require("typescript"));
38
+ // Hash generation function
39
+ function generateHash(input) {
40
+ let hash = 0;
41
+ for (let i = 0; i < input.length; i++) {
42
+ const char = input.charCodeAt(i);
43
+ hash = ((hash << 5) - hash) + char;
44
+ hash = hash & hash; // Convert to 32-bit integer
45
+ }
46
+ return Math.abs(hash).toString(36);
47
+ }
48
+ // TypeScript AST analysis function
49
+ function extractTaskFingerprints(sourceCode, filePath) {
50
+ const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true);
51
+ const fingerprints = [];
52
+ let schemaNode = null;
53
+ let boundariesNode = null;
54
+ // First pass: find schema and boundaries variable declarations
55
+ function findVariables(node) {
56
+ if (ts.isVariableStatement(node)) {
57
+ node.declarationList.declarations.forEach(decl => {
58
+ if (ts.isIdentifier(decl.name)) {
59
+ if (decl.name.text === 'schema' && decl.initializer) {
60
+ schemaNode = decl.initializer;
61
+ }
62
+ else if (decl.name.text === 'boundaries' && decl.initializer) {
63
+ boundariesNode = decl.initializer;
64
+ }
65
+ }
66
+ });
67
+ }
68
+ ts.forEachChild(node, findVariables);
69
+ }
70
+ // Second pass: find createTask calls
71
+ function findCreateTask(node) {
72
+ // Look for createTask calls
73
+ if (ts.isCallExpression(node) &&
74
+ ts.isIdentifier(node.expression) &&
75
+ node.expression.text === 'createTask') {
76
+ const taskName = extractTaskName(node, sourceFile);
77
+ if (taskName) {
78
+ const fingerprint = analyzeCreateTaskCall(node, sourceFile, filePath, taskName, schemaNode, boundariesNode);
79
+ if (fingerprint) {
80
+ fingerprints.push(fingerprint);
81
+ }
82
+ }
83
+ }
84
+ // Look for exported createTask assignments
85
+ if (ts.isVariableStatement(node) && node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
86
+ node.declarationList.declarations.forEach(decl => {
87
+ if (ts.isVariableDeclaration(decl) &&
88
+ decl.initializer &&
89
+ ts.isCallExpression(decl.initializer) &&
90
+ ts.isIdentifier(decl.initializer.expression) &&
91
+ decl.initializer.expression.text === 'createTask') {
92
+ const taskName = ts.isIdentifier(decl.name) ? decl.name.text : 'unknown';
93
+ const fingerprint = analyzeCreateTaskCall(decl.initializer, sourceFile, filePath, taskName, schemaNode, boundariesNode);
94
+ if (fingerprint) {
95
+ fingerprints.push(fingerprint);
96
+ }
97
+ }
98
+ });
99
+ }
100
+ ts.forEachChild(node, findCreateTask);
101
+ }
102
+ // Execute both passes
103
+ findVariables(sourceFile);
104
+ findCreateTask(sourceFile);
105
+ return fingerprints;
106
+ }
107
+ function extractTaskName(node, _sourceFile) {
108
+ // Try to find the task name from variable assignment or export
109
+ let parent = node.parent;
110
+ while (parent) {
111
+ if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
112
+ return parent.name.text;
113
+ }
114
+ parent = parent.parent;
115
+ }
116
+ return null;
117
+ }
118
+ function analyzeCreateTaskCall(node, sourceFile, filePath, taskName, schemaNode = null, boundariesNode = null) {
119
+ try {
120
+ const position = sourceFile.getLineAndCharacterOfPosition(node.getStart());
121
+ const args = node.arguments;
122
+ // Use pre-found schema or fall back to argument analysis
123
+ let inputSchema = { type: 'object', properties: {} };
124
+ if (schemaNode) {
125
+ inputSchema = analyzeSchemaArg(schemaNode, sourceFile);
126
+ }
127
+ else if (args[0]) {
128
+ inputSchema = analyzeSchemaArg(args[0], sourceFile);
129
+ }
130
+ // Use pre-found boundaries or fall back to argument analysis - simplified to just names
131
+ let boundaries = [];
132
+ if (boundariesNode) {
133
+ boundaries = analyzeBoundariesArg(boundariesNode, sourceFile);
134
+ }
135
+ else if (args[1]) {
136
+ boundaries = analyzeBoundariesArg(args[1], sourceFile);
137
+ }
138
+ // Extract function output type with better detection
139
+ let outputType = { type: 'unknown' };
140
+ const functionArg = args[2];
141
+ if (functionArg) {
142
+ if (ts.isFunctionExpression(functionArg) || ts.isArrowFunction(functionArg)) {
143
+ // Better return type extraction
144
+ if (functionArg.type) {
145
+ const typeString = cleanTypeString(functionArg.type.getText(sourceFile));
146
+ outputType = { type: typeString };
147
+ }
148
+ else {
149
+ // Try to infer from return statements with better object analysis
150
+ outputType = inferDetailedReturnType(functionArg, sourceFile);
151
+ }
152
+ }
153
+ }
154
+ // Generate hash from task signature
155
+ const hashInput = `${taskName}:${JSON.stringify(inputSchema)}:${JSON.stringify(boundaries)}`;
156
+ const hash = generateHash(hashInput);
157
+ return {
158
+ name: taskName,
159
+ location: {
160
+ file: filePath,
161
+ line: position.line + 1,
162
+ column: position.character + 1
163
+ },
164
+ inputSchema,
165
+ outputType,
166
+ boundaries,
167
+ hash
168
+ };
169
+ }
170
+ catch (error) {
171
+ console.warn(`Failed to analyze createTask call for ${taskName}:`, error);
172
+ return null;
173
+ }
174
+ }
175
+ // Enhanced return type inference with detailed object analysis
176
+ function inferDetailedReturnType(func, _sourceFile) {
177
+ let returnType = { type: 'unknown' };
178
+ function visitReturnStatements(node) {
179
+ if (ts.isReturnStatement(node) && node.expression) {
180
+ if (ts.isObjectLiteralExpression(node.expression)) {
181
+ // Analyze object literal properties
182
+ const properties = {};
183
+ node.expression.properties.forEach(prop => {
184
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
185
+ // Handle explicit property assignments: { propName: value }
186
+ const propName = prop.name.text;
187
+ let propType = 'unknown';
188
+ // Try to infer property type from the initializer
189
+ if (ts.isStringLiteral(prop.initializer)) {
190
+ propType = 'string';
191
+ }
192
+ else if (ts.isNumericLiteral(prop.initializer)) {
193
+ propType = 'number';
194
+ }
195
+ else if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
196
+ prop.initializer.kind === ts.SyntaxKind.FalseKeyword) {
197
+ propType = 'boolean';
198
+ }
199
+ else if (ts.isIdentifier(prop.initializer)) {
200
+ // Variable reference - try to infer from name
201
+ const varName = prop.initializer.text;
202
+ propType = inferTypeFromVariableName(varName);
203
+ }
204
+ else if (ts.isPropertyAccessExpression(prop.initializer)) {
205
+ // Property access like response.handler
206
+ propType = 'unknown';
207
+ }
208
+ properties[propName] = { type: propType };
209
+ }
210
+ else if (ts.isShorthandPropertyAssignment(prop)) {
211
+ // Handle shorthand properties: { propName } (equivalent to { propName: propName })
212
+ const propName = prop.name.text;
213
+ const propType = inferTypeFromVariableName(propName);
214
+ properties[propName] = { type: propType };
215
+ }
216
+ });
217
+ if (Object.keys(properties).length > 0) {
218
+ returnType = {
219
+ type: 'object',
220
+ properties
221
+ };
222
+ }
223
+ else {
224
+ returnType = { type: 'object' };
225
+ }
226
+ }
227
+ else if (ts.isStringLiteral(node.expression)) {
228
+ returnType = { type: 'string' };
229
+ }
230
+ else if (ts.isNumericLiteral(node.expression)) {
231
+ returnType = { type: 'number' };
232
+ }
233
+ else if (node.expression.kind === ts.SyntaxKind.TrueKeyword ||
234
+ node.expression.kind === ts.SyntaxKind.FalseKeyword) {
235
+ returnType = { type: 'boolean' };
236
+ }
237
+ else if (ts.isIdentifier(node.expression)) {
238
+ // Single variable return
239
+ const varType = inferTypeFromVariableName(node.expression.text);
240
+ returnType = { type: varType };
241
+ }
242
+ }
243
+ ts.forEachChild(node, visitReturnStatements);
244
+ }
245
+ if (func.body) {
246
+ visitReturnStatements(func.body);
247
+ }
248
+ return returnType;
249
+ }
250
+ // Helper function to infer types from variable names
251
+ function inferTypeFromVariableName(varName) {
252
+ // Common patterns for type inference based on variable names
253
+ if (varName.includes('path') || varName.includes('Path') ||
254
+ varName.includes('name') || varName.includes('Name') ||
255
+ varName.includes('descriptor') || varName.includes('Descriptor') ||
256
+ varName.includes('fileName') || varName.includes('handler') ||
257
+ varName.includes('url') || varName.includes('id') ||
258
+ varName.includes('uuid') || varName.includes('token')) {
259
+ return 'string';
260
+ }
261
+ else if (varName.includes('count') || varName.includes('Count') ||
262
+ varName.includes('size') || varName.includes('Size') ||
263
+ varName.includes('length') || varName.includes('Length') ||
264
+ varName.includes('index') || varName.includes('Index')) {
265
+ return 'number';
266
+ }
267
+ else if (varName.includes('is') || varName.includes('has') ||
268
+ varName.includes('can') || varName.includes('should') ||
269
+ varName.includes('enabled') || varName.includes('success')) {
270
+ return 'boolean';
271
+ }
272
+ else if (varName.includes('config') || varName.includes('Config') ||
273
+ varName.includes('options') || varName.includes('Options') ||
274
+ varName.includes('data') || varName.includes('result') ||
275
+ varName.includes('response') || varName.includes('error')) {
276
+ return 'unknown';
277
+ }
278
+ return 'unknown';
279
+ }
280
+ // Clean up type strings (remove Promise wrappers for boundaries)
281
+ function cleanTypeString(typeString) {
282
+ // Remove Promise wrapper for boundary functions
283
+ const promiseMatch = typeString.match(/Promise<(.+)>/);
284
+ if (promiseMatch) {
285
+ return promiseMatch[1];
286
+ }
287
+ return typeString;
288
+ }
289
+ function analyzeSchemaArg(node, sourceFile) {
290
+ // Handle variable references (e.g., when schema is defined as const schema = ...)
291
+ if (ts.isIdentifier(node) && node.text === 'schema') {
292
+ // This case is now handled by pre-finding the schema node
293
+ return { type: 'object', properties: {} };
294
+ }
295
+ // Handle direct Schema constructor calls
296
+ if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'Schema') {
297
+ const arg = node.arguments?.[0];
298
+ if (arg && ts.isObjectLiteralExpression(arg)) {
299
+ const properties = {};
300
+ arg.properties.forEach(prop => {
301
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
302
+ const propName = prop.name.text;
303
+ const propValue = analyzeSchemaProp(prop.initializer, sourceFile);
304
+ properties[propName] = propValue;
305
+ }
306
+ });
307
+ return { type: 'object', properties };
308
+ }
309
+ }
310
+ return { type: 'object', properties: {} };
311
+ }
312
+ // Enhanced schema property analysis
313
+ function analyzeSchemaProp(node, _sourceFile) {
314
+ // Analyze Schema.string(), Schema.number(), etc.
315
+ if (ts.isCallExpression(node)) {
316
+ if (ts.isPropertyAccessExpression(node.expression) &&
317
+ ts.isIdentifier(node.expression.expression) &&
318
+ node.expression.expression.text === 'Schema') {
319
+ const methodName = node.expression.name.text;
320
+ let baseType = { type: getSchemaTypeFromMethod(methodName) };
321
+ // Check for chained methods like .optional() or .default()
322
+ let current = node;
323
+ while (current.parent && ts.isCallExpression(current.parent)) {
324
+ current = current.parent;
325
+ if (ts.isPropertyAccessExpression(current.expression)) {
326
+ const chainedMethod = current.expression.name.text;
327
+ if (chainedMethod === 'optional') {
328
+ baseType = { ...baseType, optional: true };
329
+ }
330
+ else if (chainedMethod === 'default' && current.arguments[0]) {
331
+ baseType = { ...baseType, default: current.arguments[0].getText() };
332
+ }
333
+ }
334
+ }
335
+ return baseType;
336
+ }
337
+ }
338
+ return { type: 'unknown' };
339
+ }
340
+ function getSchemaTypeFromMethod(methodName) {
341
+ const typeMap = {
342
+ string: 'string',
343
+ number: 'number',
344
+ boolean: 'boolean',
345
+ array: 'array',
346
+ object: 'object'
347
+ };
348
+ return typeMap[methodName] || 'unknown';
349
+ }
350
+ function analyzeBoundariesArg(node, _sourceFile) {
351
+ const boundaries = [];
352
+ // Handle variable references (e.g., when boundaries is defined as const boundaries = ...)
353
+ if (ts.isIdentifier(node) && node.text === 'boundaries') {
354
+ // This case is now handled by pre-finding the boundaries node
355
+ return [];
356
+ }
357
+ if (ts.isObjectLiteralExpression(node)) {
358
+ node.properties.forEach(prop => {
359
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
360
+ const boundaryName = prop.name.text;
361
+ boundaries.push(boundaryName);
362
+ }
363
+ });
364
+ }
365
+ return boundaries;
366
+ }
367
+ // Export the core analysis function for reuse
368
+ function analyzeTaskFile(sourceCode, filePath, _expectedTaskName) {
369
+ const taskFingerprint = extractTaskFingerprints(sourceCode, filePath)[0];
370
+ if (!taskFingerprint) {
371
+ return null;
372
+ }
373
+ // Return simplified output without name, location, hash
374
+ return {
375
+ description: taskFingerprint.description,
376
+ inputSchema: taskFingerprint.inputSchema,
377
+ outputType: taskFingerprint.outputType,
378
+ boundaries: taskFingerprint.boundaries
379
+ };
380
+ }
package/forge.json CHANGED
@@ -14,6 +14,10 @@
14
14
  "bucket": ""
15
15
  },
16
16
  "tasks": {
17
+ "task:createTask": {
18
+ "path": "src/tasks/task/createTask.ts",
19
+ "handler": "createTask"
20
+ },
17
21
  "bundle:create": {
18
22
  "path": "src/tasks/bundle/create.ts",
19
23
  "handler": "create"
@@ -89,6 +93,22 @@
89
93
  "bundle:zip": {
90
94
  "path": "src/tasks/bundle/zip.ts",
91
95
  "handler": "zip"
96
+ },
97
+ "task:list": {
98
+ "path": "src/tasks/task/list.ts",
99
+ "handler": "list"
100
+ },
101
+ "task:describe": {
102
+ "path": "src/tasks/task/describe.ts",
103
+ "handler": "describe"
104
+ },
105
+ "task:fingerprint": {
106
+ "path": "src/tasks/task/fingerprint.ts",
107
+ "handler": "fingerprint"
108
+ },
109
+ "bundle:fingerprint": {
110
+ "path": "src/tasks/bundle/fingerprint.ts",
111
+ "handler": "fingerprint"
92
112
  }
93
113
  },
94
114
  "runners": {}
package/jest.config.js CHANGED
@@ -3,7 +3,8 @@ module.exports = {
3
3
  preset: 'ts-jest',
4
4
  testEnvironment: 'node',
5
5
  testMatch: ['**/*.test.ts'],
6
+ setupFilesAfterEnv: ['<rootDir>/src/test/setup.ts'],
6
7
  transform: {
7
8
  '^.+\\.tsx?$': 'ts-jest',
8
9
  },
9
- };
10
+ };