@chainlink/cre-sdk 1.0.2 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chainlink/cre-sdk",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -128,9 +128,23 @@ export async function main() {
128
128
  main()`
129
129
  const result = wrapWorkflowCode(input, 'test.ts')
130
130
 
131
- // Should have two main() calls - original and wrapper
132
- expect(result).toContain('main()')
133
- expect(result).toContain('main().catch(sendErrorResponse)')
131
+ // Should replace the original main() call
132
+ expect(result.match(/main\(\)\.catch\(sendErrorResponse\)/g)?.length).toBe(1)
133
+ expect(result.match(/(^|\n)\s*main\(\)\s*;?\s*$/g)?.length ?? 0).toBe(0)
134
+ })
135
+
136
+ test('replaces await main() with await main().catch()', () => {
137
+ const input = `import { Runner } from '@chainlink/cre-sdk'
138
+
139
+ export async function main() {
140
+ const runner = await Runner.newRunner()
141
+ }
142
+
143
+ await main()`
144
+ const result = wrapWorkflowCode(input, 'test.ts')
145
+
146
+ expect(result).toContain('await main().catch(sendErrorResponse)')
147
+ expect(result.match(/main\(\)\.catch\(sendErrorResponse\)/g)?.length).toBe(1)
134
148
  })
135
149
  })
136
150
 
@@ -7,7 +7,8 @@ import * as ts from 'typescript'
7
7
  * 2. Detects if `main()` function is exported.
8
8
  * 3. Detects if there's already a top-level `main()` call with `.catch()` handler.
9
9
  * 4. Adds `sendErrorResponse` to imports if missing.
10
- * 5. Appends `main().catch(sendErrorResponse)` only if no error handling exists.
10
+ * 5. Replaces top-level `main()` or `await main()` with `main().catch(sendErrorResponse)`.
11
+ * 6. Appends `main().catch(sendErrorResponse)` only if no error handling exists and no call exists.
11
12
  *
12
13
  * @param sourceCode - The TypeScript source code to wrap
13
14
  * @param filePath - The file path (used for source file creation)
@@ -17,10 +18,9 @@ export function wrapWorkflowCode(sourceCode: string, filePath: string): string {
17
18
  const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true)
18
19
 
19
20
  // Analysis state
20
- let hasSendErrorResponseImport = false
21
- let creSdkImportDeclaration: ts.ImportDeclaration | null = null
22
21
  let hasMainExport = false
23
22
  let hasExistingErrorHandling = false
23
+ const mainCallStatements: { start: number; end: number; useAwait: boolean }[] = []
24
24
 
25
25
  // Helper to check if a node is a main() call expression
26
26
  const isMainCall = (node: ts.Node): boolean => {
@@ -43,24 +43,18 @@ export function wrapWorkflowCode(sourceCode: string, filePath: string): string {
43
43
  return false
44
44
  }
45
45
 
46
- // First pass: analyze AST
47
- for (const statement of sourceFile.statements) {
48
- // Check for @chainlink/cre-sdk import
49
- if (ts.isImportDeclaration(statement)) {
50
- const moduleSpecifier = statement.moduleSpecifier
51
- if (ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === '@chainlink/cre-sdk') {
52
- creSdkImportDeclaration = statement
53
- if (
54
- statement.importClause?.namedBindings &&
55
- ts.isNamedImports(statement.importClause.namedBindings)
56
- ) {
57
- hasSendErrorResponseImport = statement.importClause.namedBindings.elements.some(
58
- (element) => element.name.text === 'sendErrorResponse',
59
- )
60
- }
61
- }
46
+ const getMainCallFromExpression = (expr: ts.Expression): { useAwait: boolean } | null => {
47
+ if (isMainCall(expr)) {
48
+ return { useAwait: false }
62
49
  }
50
+ if (ts.isAwaitExpression(expr) && isMainCall(expr.expression)) {
51
+ return { useAwait: true }
52
+ }
53
+ return null
54
+ }
63
55
 
56
+ // First pass: analyze AST
57
+ for (const statement of sourceFile.statements) {
64
58
  // Check for main() export
65
59
  if (ts.isFunctionDeclaration(statement) && statement.name?.text === 'main') {
66
60
  if (statement.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword)) {
@@ -71,16 +65,17 @@ export function wrapWorkflowCode(sourceCode: string, filePath: string): string {
71
65
  // Check for top-level main() call with error handling
72
66
  if (ts.isExpressionStatement(statement)) {
73
67
  const expr = statement.expression
68
+ const exprToCheck = ts.isAwaitExpression(expr) ? expr.expression : expr
74
69
 
75
70
  // Check for main().catch(...)
76
- if (isWrappedWithCatch(expr)) {
71
+ if (isWrappedWithCatch(exprToCheck)) {
77
72
  hasExistingErrorHandling = true
78
73
  }
79
74
 
80
75
  // Also check for: main().then(...).catch(...) or other chained patterns
81
- if (ts.isCallExpression(expr)) {
76
+ if (ts.isCallExpression(exprToCheck)) {
82
77
  // Walk the chain to find if there's a .catch anywhere
83
- let current: ts.Expression = expr
78
+ let current: ts.Expression = exprToCheck
84
79
  while (ts.isCallExpression(current)) {
85
80
  const propAccess = current.expression
86
81
  if (ts.isPropertyAccessExpression(propAccess)) {
@@ -105,13 +100,53 @@ export function wrapWorkflowCode(sourceCode: string, filePath: string): string {
105
100
  }
106
101
  }
107
102
  }
103
+
104
+ if (!hasExistingErrorHandling) {
105
+ const mainCall = getMainCallFromExpression(expr)
106
+ if (mainCall) {
107
+ mainCallStatements.push({
108
+ start: statement.getStart(sourceFile),
109
+ end: statement.end,
110
+ useAwait: mainCall.useAwait,
111
+ })
112
+ }
113
+ }
108
114
  }
109
115
  }
110
116
 
111
117
  // Build the transformed code
112
118
  let result = sourceCode
113
119
 
120
+ if (!hasExistingErrorHandling && mainCallStatements.length > 0) {
121
+ for (const statement of [...mainCallStatements].sort((a, b) => b.start - a.start)) {
122
+ const replacement = `${statement.useAwait ? 'await ' : ''}main().catch(sendErrorResponse)`
123
+ result = result.slice(0, statement.start) + replacement + result.slice(statement.end)
124
+ }
125
+ }
126
+
114
127
  // If we need to add sendErrorResponse import
128
+ const nextSourceFile = ts.createSourceFile(filePath, result, ts.ScriptTarget.Latest, true)
129
+ let hasSendErrorResponseImport = false
130
+ let creSdkImportDeclaration: ts.ImportDeclaration | null = null
131
+
132
+ for (const statement of nextSourceFile.statements) {
133
+ // Check for @chainlink/cre-sdk import
134
+ if (ts.isImportDeclaration(statement)) {
135
+ const moduleSpecifier = statement.moduleSpecifier
136
+ if (ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === '@chainlink/cre-sdk') {
137
+ creSdkImportDeclaration = statement
138
+ if (
139
+ statement.importClause?.namedBindings &&
140
+ ts.isNamedImports(statement.importClause.namedBindings)
141
+ ) {
142
+ hasSendErrorResponseImport = statement.importClause.namedBindings.elements.some(
143
+ (element) => element.name.text === 'sendErrorResponse',
144
+ )
145
+ }
146
+ }
147
+ }
148
+ }
149
+
115
150
  if (!hasSendErrorResponseImport) {
116
151
  if (creSdkImportDeclaration) {
117
152
  // Add to existing import
@@ -131,7 +166,7 @@ export function wrapWorkflowCode(sourceCode: string, filePath: string): string {
131
166
 
132
167
  // Find the last import declaration to insert after it
133
168
  let lastImportEnd = 0
134
- for (const statement of sourceFile.statements) {
169
+ for (const statement of nextSourceFile.statements) {
135
170
  if (ts.isImportDeclaration(statement)) {
136
171
  lastImportEnd = statement.end
137
172
  }
@@ -154,7 +189,7 @@ export function wrapWorkflowCode(sourceCode: string, filePath: string): string {
154
189
  }
155
190
 
156
191
  // Append main().catch(sendErrorResponse) if no error handling exists
157
- if (!hasExistingErrorHandling) {
192
+ if (!hasExistingErrorHandling && mainCallStatements.length === 0) {
158
193
  const trimmedResult = result.trimEnd()
159
194
  result = trimmedResult + '\n\nmain().catch(sendErrorResponse)\n'
160
195
  }