@outputai/core 0.1.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/LICENSE +201 -0
- package/README.md +11 -0
- package/bin/healthcheck.mjs +36 -0
- package/bin/healthcheck.spec.js +90 -0
- package/bin/worker.sh +26 -0
- package/package.json +67 -0
- package/src/activity_integration/context.d.ts +27 -0
- package/src/activity_integration/context.js +17 -0
- package/src/activity_integration/context.spec.js +42 -0
- package/src/activity_integration/events.d.ts +7 -0
- package/src/activity_integration/events.js +10 -0
- package/src/activity_integration/index.d.ts +9 -0
- package/src/activity_integration/index.js +3 -0
- package/src/activity_integration/tracing.d.ts +32 -0
- package/src/activity_integration/tracing.js +37 -0
- package/src/async_storage.js +19 -0
- package/src/bus.js +3 -0
- package/src/consts.js +32 -0
- package/src/errors.d.ts +15 -0
- package/src/errors.js +14 -0
- package/src/hooks/index.d.ts +28 -0
- package/src/hooks/index.js +32 -0
- package/src/index.d.ts +49 -0
- package/src/index.js +4 -0
- package/src/interface/evaluation_result.d.ts +173 -0
- package/src/interface/evaluation_result.js +215 -0
- package/src/interface/evaluator.d.ts +70 -0
- package/src/interface/evaluator.js +34 -0
- package/src/interface/evaluator.spec.js +565 -0
- package/src/interface/index.d.ts +9 -0
- package/src/interface/index.js +26 -0
- package/src/interface/step.d.ts +138 -0
- package/src/interface/step.js +22 -0
- package/src/interface/types.d.ts +27 -0
- package/src/interface/validations/runtime.js +20 -0
- package/src/interface/validations/runtime.spec.js +29 -0
- package/src/interface/validations/schema_utils.js +8 -0
- package/src/interface/validations/schema_utils.spec.js +67 -0
- package/src/interface/validations/static.js +136 -0
- package/src/interface/validations/static.spec.js +366 -0
- package/src/interface/webhook.d.ts +84 -0
- package/src/interface/webhook.js +64 -0
- package/src/interface/webhook.spec.js +122 -0
- package/src/interface/workflow.d.ts +273 -0
- package/src/interface/workflow.js +128 -0
- package/src/interface/workflow.spec.js +467 -0
- package/src/interface/workflow_context.js +31 -0
- package/src/interface/workflow_utils.d.ts +76 -0
- package/src/interface/workflow_utils.js +50 -0
- package/src/interface/workflow_utils.spec.js +190 -0
- package/src/interface/zod_integration.spec.js +646 -0
- package/src/internal_activities/index.js +66 -0
- package/src/internal_activities/index.spec.js +102 -0
- package/src/logger.js +73 -0
- package/src/tracing/internal_interface.js +71 -0
- package/src/tracing/processors/local/index.js +111 -0
- package/src/tracing/processors/local/index.spec.js +149 -0
- package/src/tracing/processors/s3/configs.js +31 -0
- package/src/tracing/processors/s3/configs.spec.js +64 -0
- package/src/tracing/processors/s3/index.js +114 -0
- package/src/tracing/processors/s3/index.spec.js +153 -0
- package/src/tracing/processors/s3/redis_client.js +62 -0
- package/src/tracing/processors/s3/redis_client.spec.js +185 -0
- package/src/tracing/processors/s3/s3_client.js +27 -0
- package/src/tracing/processors/s3/s3_client.spec.js +62 -0
- package/src/tracing/tools/build_trace_tree.js +83 -0
- package/src/tracing/tools/build_trace_tree.spec.js +135 -0
- package/src/tracing/tools/utils.js +21 -0
- package/src/tracing/tools/utils.spec.js +14 -0
- package/src/tracing/trace_engine.js +97 -0
- package/src/tracing/trace_engine.spec.js +199 -0
- package/src/utils/index.d.ts +134 -0
- package/src/utils/index.js +2 -0
- package/src/utils/resolve_invocation_dir.js +34 -0
- package/src/utils/resolve_invocation_dir.spec.js +102 -0
- package/src/utils/utils.js +211 -0
- package/src/utils/utils.spec.js +448 -0
- package/src/worker/bundler_options.js +43 -0
- package/src/worker/catalog_workflow/catalog.js +114 -0
- package/src/worker/catalog_workflow/index.js +54 -0
- package/src/worker/catalog_workflow/index.spec.js +196 -0
- package/src/worker/catalog_workflow/workflow.js +24 -0
- package/src/worker/configs.js +49 -0
- package/src/worker/configs.spec.js +130 -0
- package/src/worker/index.js +89 -0
- package/src/worker/index.spec.js +177 -0
- package/src/worker/interceptors/activity.js +62 -0
- package/src/worker/interceptors/activity.spec.js +212 -0
- package/src/worker/interceptors/workflow.js +70 -0
- package/src/worker/interceptors/workflow.spec.js +167 -0
- package/src/worker/interceptors.js +10 -0
- package/src/worker/loader.js +151 -0
- package/src/worker/loader.spec.js +236 -0
- package/src/worker/loader_tools.js +132 -0
- package/src/worker/loader_tools.spec.js +156 -0
- package/src/worker/log_hooks.js +95 -0
- package/src/worker/log_hooks.spec.js +217 -0
- package/src/worker/sandboxed_utils.js +18 -0
- package/src/worker/shutdown.js +26 -0
- package/src/worker/shutdown.spec.js +82 -0
- package/src/worker/sinks.js +74 -0
- package/src/worker/start_catalog.js +36 -0
- package/src/worker/start_catalog.spec.js +118 -0
- package/src/worker/webpack_loaders/consts.js +9 -0
- package/src/worker/webpack_loaders/tools.js +548 -0
- package/src/worker/webpack_loaders/tools.spec.js +330 -0
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +221 -0
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +336 -0
- package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +61 -0
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +216 -0
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +196 -0
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +123 -0
- package/src/worker/webpack_loaders/workflow_validator/index.mjs +205 -0
- package/src/worker/webpack_loaders/workflow_validator/index.spec.js +613 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mkdtempSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join, resolve as resolvePath } from 'node:path';
|
|
5
|
+
import * as t from '@babel/types';
|
|
6
|
+
import {
|
|
7
|
+
toAbsolutePath,
|
|
8
|
+
parse,
|
|
9
|
+
extractTopLevelStringConsts,
|
|
10
|
+
getObjectKeyName,
|
|
11
|
+
getLocalNameFromDestructuredProperty,
|
|
12
|
+
toFunctionExpression,
|
|
13
|
+
isStepsPath,
|
|
14
|
+
isSharedStepsPath,
|
|
15
|
+
isAnyStepsPath,
|
|
16
|
+
isEvaluatorsPath,
|
|
17
|
+
isSharedEvaluatorsPath,
|
|
18
|
+
isWorkflowPath,
|
|
19
|
+
createThisMethodCall,
|
|
20
|
+
resolveNameFromArg,
|
|
21
|
+
resolveNameFromOptions,
|
|
22
|
+
buildStepsNameMap,
|
|
23
|
+
buildSharedStepsNameMap,
|
|
24
|
+
buildWorkflowNameMap,
|
|
25
|
+
buildEvaluatorsNameMap,
|
|
26
|
+
getFileKind
|
|
27
|
+
} from './tools.js';
|
|
28
|
+
|
|
29
|
+
describe( 'workflow_rewriter tools', () => {
|
|
30
|
+
it( 'parse: parses JS with JSX plugin enabled', () => {
|
|
31
|
+
const ast = parse( 'const A = 1; const C = () => <div />', 'file.js' );
|
|
32
|
+
expect( ast?.type ).toBe( 'File' );
|
|
33
|
+
expect( ast.program.body.length ).toBeGreaterThan( 0 );
|
|
34
|
+
} );
|
|
35
|
+
|
|
36
|
+
it( 'toAbsolutePath: resolves relative path against base directory', () => {
|
|
37
|
+
expect( toAbsolutePath( '/base/dir', './file.js' ) ).toBe( resolvePath( '/base/dir', './file.js' ) );
|
|
38
|
+
} );
|
|
39
|
+
|
|
40
|
+
it( 'extractTopLevelStringConsts: returns only const string bindings', () => {
|
|
41
|
+
const ast = parse( [
|
|
42
|
+
'const A = \"a\"; let B = \"b\"; const C = 3;',
|
|
43
|
+
'const D = `d`; const E = \"e\"'
|
|
44
|
+
].join( '\n' ), 'file.js' );
|
|
45
|
+
const map = extractTopLevelStringConsts( ast );
|
|
46
|
+
expect( map.get( 'A' ) ).toBe( 'a' );
|
|
47
|
+
expect( map.has( 'B' ) ).toBe( false );
|
|
48
|
+
expect( map.has( 'C' ) ).toBe( false );
|
|
49
|
+
// Template literal is not a StringLiteral
|
|
50
|
+
expect( map.has( 'D' ) ).toBe( false );
|
|
51
|
+
expect( map.get( 'E' ) ).toBe( 'e' );
|
|
52
|
+
} );
|
|
53
|
+
|
|
54
|
+
it( 'resolveNameFromOptions: returns literal name from options object', () => {
|
|
55
|
+
const opts = t.objectExpression( [ t.objectProperty( t.identifier( 'name' ), t.stringLiteral( 'literal.name' ) ) ] );
|
|
56
|
+
const out = resolveNameFromOptions( opts, new Map(), 'X' );
|
|
57
|
+
expect( out ).toBe( 'literal.name' );
|
|
58
|
+
} );
|
|
59
|
+
|
|
60
|
+
it( 'getObjectKeyName: resolves from Identifier and StringLiteral', () => {
|
|
61
|
+
expect( getObjectKeyName( t.identifier( 'name' ) ) ).toBe( 'name' );
|
|
62
|
+
expect( getObjectKeyName( t.stringLiteral( 'x' ) ) ).toBe( 'x' );
|
|
63
|
+
expect( getObjectKeyName( t.numericLiteral( 1 ) ) ).toBeNull();
|
|
64
|
+
} );
|
|
65
|
+
|
|
66
|
+
it( 'buildStepsNameMap: reads names from steps module and caches result', () => {
|
|
67
|
+
const dir = mkdtempSync( join( tmpdir(), 'tools-steps-' ) );
|
|
68
|
+
const stepsPath = join( dir, 'steps.js' );
|
|
69
|
+
writeFileSync( stepsPath, [
|
|
70
|
+
'export const StepA = step({ name: "step.a" })',
|
|
71
|
+
'export const StepB = step({ name: "step.b" })'
|
|
72
|
+
].join( '\n' ) );
|
|
73
|
+
const cache = new Map();
|
|
74
|
+
const map1 = buildStepsNameMap( stepsPath, cache );
|
|
75
|
+
expect( map1.get( 'StepA' ) ).toBe( 'step.a' );
|
|
76
|
+
expect( map1.get( 'StepB' ) ).toBe( 'step.b' );
|
|
77
|
+
expect( cache.get( stepsPath ) ).toBe( map1 );
|
|
78
|
+
const map2 = buildStepsNameMap( stepsPath, cache );
|
|
79
|
+
expect( map2 ).toBe( map1 );
|
|
80
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
81
|
+
} );
|
|
82
|
+
|
|
83
|
+
it( 'buildEvaluatorsNameMap: reads names from evaluators module and caches result', () => {
|
|
84
|
+
const dir = mkdtempSync( join( tmpdir(), 'tools-evals-' ) );
|
|
85
|
+
const evalsPath = join( dir, 'evaluators.js' );
|
|
86
|
+
writeFileSync( evalsPath, [
|
|
87
|
+
'export const EvalA = evaluator({ name: "eval.a" })',
|
|
88
|
+
'export const EvalB = evaluator({ name: "eval.b" })'
|
|
89
|
+
].join( '\n' ) );
|
|
90
|
+
const cache = new Map();
|
|
91
|
+
const map1 = buildEvaluatorsNameMap( evalsPath, cache );
|
|
92
|
+
expect( map1.get( 'EvalA' ) ).toBe( 'eval.a' );
|
|
93
|
+
expect( map1.get( 'EvalB' ) ).toBe( 'eval.b' );
|
|
94
|
+
expect( cache.get( evalsPath ) ).toBe( map1 );
|
|
95
|
+
const map2 = buildEvaluatorsNameMap( evalsPath, cache );
|
|
96
|
+
expect( map2 ).toBe( map1 );
|
|
97
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
98
|
+
} );
|
|
99
|
+
|
|
100
|
+
it( 'getLocalNameFromDestructuredProperty: handles { a }, { a: b }, { a: b = 1 }', () => {
|
|
101
|
+
// { a }
|
|
102
|
+
const p1 = t.objectProperty( t.identifier( 'a' ), t.identifier( 'a' ), false, true );
|
|
103
|
+
expect( getLocalNameFromDestructuredProperty( p1 ) ).toBe( 'a' );
|
|
104
|
+
// { a: b }
|
|
105
|
+
const p2 = t.objectProperty( t.identifier( 'a' ), t.identifier( 'b' ) );
|
|
106
|
+
expect( getLocalNameFromDestructuredProperty( p2 ) ).toBe( 'b' );
|
|
107
|
+
// { a: b = 1 }
|
|
108
|
+
const p3 = t.objectProperty( t.identifier( 'a' ), t.assignmentPattern( t.identifier( 'b' ), t.numericLiteral( 1 ) ) );
|
|
109
|
+
expect( getLocalNameFromDestructuredProperty( p3 ) ).toBe( 'b' );
|
|
110
|
+
// Unsupported shape
|
|
111
|
+
const p4 = t.objectProperty( t.identifier( 'a' ), t.arrayExpression( [] ) );
|
|
112
|
+
expect( getLocalNameFromDestructuredProperty( p4 ) ).toBeNull();
|
|
113
|
+
} );
|
|
114
|
+
|
|
115
|
+
it( 'buildWorkflowNameMap: reads named and default workflow names and caches', () => {
|
|
116
|
+
const dir = mkdtempSync( join( tmpdir(), 'tools-output-' ) );
|
|
117
|
+
const wfPath = join( dir, 'workflow.js' );
|
|
118
|
+
writeFileSync( wfPath, [
|
|
119
|
+
'export const FlowA = workflow({ name: "flow.a" })',
|
|
120
|
+
'export default workflow({ name: "flow.def" })'
|
|
121
|
+
].join( '\n' ) );
|
|
122
|
+
const cache = new Map();
|
|
123
|
+
const res1 = buildWorkflowNameMap( wfPath, cache );
|
|
124
|
+
expect( res1.named.get( 'FlowA' ) ).toBe( 'flow.a' );
|
|
125
|
+
expect( res1.default ).toBe( 'flow.def' );
|
|
126
|
+
expect( cache.get( wfPath ) ).toBe( res1 );
|
|
127
|
+
const res2 = buildWorkflowNameMap( wfPath, cache );
|
|
128
|
+
expect( res2 ).toBe( res1 );
|
|
129
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
130
|
+
} );
|
|
131
|
+
|
|
132
|
+
it( 'toFunctionExpression: converts arrow, wraps expression bodies', () => {
|
|
133
|
+
const arrowExprBody = t.arrowFunctionExpression( [ t.identifier( 'x' ) ], t.identifier( 'x' ) );
|
|
134
|
+
const arrowBlockBody = t.arrowFunctionExpression( [], t.blockStatement( [ t.returnStatement( t.numericLiteral( 1 ) ) ] ) );
|
|
135
|
+
const fn1 = toFunctionExpression( arrowExprBody );
|
|
136
|
+
const fn2 = toFunctionExpression( arrowBlockBody );
|
|
137
|
+
expect( t.isFunctionExpression( fn1 ) ).toBe( true );
|
|
138
|
+
expect( t.isBlockStatement( fn1.body ) ).toBe( true );
|
|
139
|
+
expect( t.isReturnStatement( fn1.body.body[0] ) ).toBe( true );
|
|
140
|
+
expect( t.isFunctionExpression( fn2 ) ).toBe( true );
|
|
141
|
+
} );
|
|
142
|
+
|
|
143
|
+
it( 'isStepsPath: matches LOCAL steps.js (no path traversal)', () => {
|
|
144
|
+
// Local steps (without ../ or /shared/)
|
|
145
|
+
expect( isStepsPath( 'steps.js' ) ).toBe( true );
|
|
146
|
+
expect( isStepsPath( './steps.js' ) ).toBe( true );
|
|
147
|
+
expect( isStepsPath( '/a/b/steps.js' ) ).toBe( true );
|
|
148
|
+
expect( isStepsPath( './steps/fetch.js' ) ).toBe( true );
|
|
149
|
+
// Shared steps (with ../ or /shared/) should NOT match isStepsPath
|
|
150
|
+
expect( isStepsPath( '../steps.js' ) ).toBe( false );
|
|
151
|
+
expect( isStepsPath( '../../shared/steps/common.js' ) ).toBe( false );
|
|
152
|
+
// Non-steps
|
|
153
|
+
expect( isStepsPath( 'steps.ts' ) ).toBe( false );
|
|
154
|
+
expect( isStepsPath( 'workflow.js' ) ).toBe( false );
|
|
155
|
+
} );
|
|
156
|
+
|
|
157
|
+
it( 'isSharedStepsPath: matches steps imported from outside workflow directory', () => {
|
|
158
|
+
// Shared steps: must have steps pattern AND have path traversal or /shared/
|
|
159
|
+
expect( isSharedStepsPath( '../steps.js' ) ).toBe( true );
|
|
160
|
+
expect( isSharedStepsPath( '../../steps.js' ) ).toBe( true );
|
|
161
|
+
expect( isSharedStepsPath( '../../shared/steps/common.js' ) ).toBe( true );
|
|
162
|
+
expect( isSharedStepsPath( '../other_workflow/steps.js' ) ).toBe( true );
|
|
163
|
+
expect( isSharedStepsPath( '/src/shared/steps/common.js' ) ).toBe( true );
|
|
164
|
+
// Local steps (no traversal, no /shared/) should NOT match
|
|
165
|
+
expect( isSharedStepsPath( './steps.js' ) ).toBe( false );
|
|
166
|
+
expect( isSharedStepsPath( 'steps.js' ) ).toBe( false );
|
|
167
|
+
expect( isSharedStepsPath( './steps/fetch.js' ) ).toBe( false );
|
|
168
|
+
// Non-steps should NOT match
|
|
169
|
+
expect( isSharedStepsPath( '../utils.js' ) ).toBe( false );
|
|
170
|
+
expect( isSharedStepsPath( 'evaluators.js' ) ).toBe( false );
|
|
171
|
+
} );
|
|
172
|
+
|
|
173
|
+
it( 'isAnyStepsPath: matches any steps pattern (local or shared)', () => {
|
|
174
|
+
// Local steps
|
|
175
|
+
expect( isAnyStepsPath( 'steps.js' ) ).toBe( true );
|
|
176
|
+
expect( isAnyStepsPath( './steps.js' ) ).toBe( true );
|
|
177
|
+
expect( isAnyStepsPath( './steps/fetch.js' ) ).toBe( true );
|
|
178
|
+
// Shared steps
|
|
179
|
+
expect( isAnyStepsPath( '../steps.js' ) ).toBe( true );
|
|
180
|
+
expect( isAnyStepsPath( '../../shared/steps/common.js' ) ).toBe( true );
|
|
181
|
+
// Non-steps
|
|
182
|
+
expect( isAnyStepsPath( 'workflow.js' ) ).toBe( false );
|
|
183
|
+
expect( isAnyStepsPath( 'utils.js' ) ).toBe( false );
|
|
184
|
+
} );
|
|
185
|
+
|
|
186
|
+
it( 'isWorkflowPath: matches workflow.js at root or subpath', () => {
|
|
187
|
+
expect( isWorkflowPath( 'workflow.js' ) ).toBe( true );
|
|
188
|
+
expect( isWorkflowPath( './workflow.js' ) ).toBe( true );
|
|
189
|
+
expect( isWorkflowPath( '/a/b/workflow.js' ) ).toBe( true );
|
|
190
|
+
expect( isWorkflowPath( 'workflow.ts' ) ).toBe( false );
|
|
191
|
+
expect( isWorkflowPath( 'steps.js' ) ).toBe( false );
|
|
192
|
+
} );
|
|
193
|
+
|
|
194
|
+
it( 'isEvaluatorsPath: matches local evaluators.js but excludes shared', () => {
|
|
195
|
+
expect( isEvaluatorsPath( 'evaluators.js' ) ).toBe( true );
|
|
196
|
+
expect( isEvaluatorsPath( './evaluators.js' ) ).toBe( true );
|
|
197
|
+
expect( isEvaluatorsPath( '/a/b/evaluators.js' ) ).toBe( true );
|
|
198
|
+
expect( isEvaluatorsPath( './evaluators/quality.js' ) ).toBe( true );
|
|
199
|
+
// Shared evaluators should NOT match (path traversal or /shared/)
|
|
200
|
+
expect( isEvaluatorsPath( '../evaluators.js' ) ).toBe( false );
|
|
201
|
+
expect( isEvaluatorsPath( '../../shared/evaluators/common.js' ) ).toBe( false );
|
|
202
|
+
expect( isEvaluatorsPath( 'evaluators.ts' ) ).toBe( false );
|
|
203
|
+
expect( isEvaluatorsPath( 'steps.js' ) ).toBe( false );
|
|
204
|
+
} );
|
|
205
|
+
|
|
206
|
+
it( 'isSharedEvaluatorsPath: matches evaluators imported from outside workflow directory', () => {
|
|
207
|
+
// Shared evaluators: must have evaluators pattern AND have path traversal or /shared/
|
|
208
|
+
expect( isSharedEvaluatorsPath( '../evaluators.js' ) ).toBe( true );
|
|
209
|
+
expect( isSharedEvaluatorsPath( '../../evaluators.js' ) ).toBe( true );
|
|
210
|
+
expect( isSharedEvaluatorsPath( '../../shared/evaluators/quality.js' ) ).toBe( true );
|
|
211
|
+
expect( isSharedEvaluatorsPath( '../other_workflow/evaluators.js' ) ).toBe( true );
|
|
212
|
+
expect( isSharedEvaluatorsPath( '/src/shared/evaluators/quality.js' ) ).toBe( true );
|
|
213
|
+
// Local evaluators (no traversal, no /shared/) should NOT match
|
|
214
|
+
expect( isSharedEvaluatorsPath( './evaluators.js' ) ).toBe( false );
|
|
215
|
+
expect( isSharedEvaluatorsPath( 'evaluators.js' ) ).toBe( false );
|
|
216
|
+
expect( isSharedEvaluatorsPath( './evaluators/quality.js' ) ).toBe( false );
|
|
217
|
+
// Non-evaluators should NOT match
|
|
218
|
+
expect( isSharedEvaluatorsPath( '../utils.js' ) ).toBe( false );
|
|
219
|
+
expect( isSharedEvaluatorsPath( 'steps.js' ) ).toBe( false );
|
|
220
|
+
} );
|
|
221
|
+
|
|
222
|
+
it( 'createThisMethodCall: builds this.method(\'name\', ...args) call', () => {
|
|
223
|
+
const call = createThisMethodCall( 'invoke', 'n', [ t.numericLiteral( 1 ), t.identifier( 'x' ) ] );
|
|
224
|
+
expect( t.isCallExpression( call ) ).toBe( true );
|
|
225
|
+
expect( t.isMemberExpression( call.callee ) ).toBe( true );
|
|
226
|
+
expect( t.isThisExpression( call.callee.object ) ).toBe( true );
|
|
227
|
+
expect( t.isIdentifier( call.callee.property, { name: 'invoke' } ) ).toBe( true );
|
|
228
|
+
expect( t.isStringLiteral( call.arguments[0], { value: 'n' } ) ).toBe( true );
|
|
229
|
+
expect( call.arguments.length ).toBe( 3 );
|
|
230
|
+
} );
|
|
231
|
+
|
|
232
|
+
it( 'buildSharedStepsNameMap: reads names from shared steps module and caches result', () => {
|
|
233
|
+
const dir = mkdtempSync( join( tmpdir(), 'tools-shared-steps-' ) );
|
|
234
|
+
mkdirSync( join( dir, 'shared', 'steps' ), { recursive: true } );
|
|
235
|
+
const stepsPath = join( dir, 'shared', 'steps', 'common.js' );
|
|
236
|
+
writeFileSync( stepsPath, [
|
|
237
|
+
'export const StepA = step({ name: "shared.step.a" })',
|
|
238
|
+
'export const StepB = step({ name: "shared.step.b" })'
|
|
239
|
+
].join( '\n' ) );
|
|
240
|
+
const cache = new Map();
|
|
241
|
+
const map1 = buildSharedStepsNameMap( stepsPath, cache );
|
|
242
|
+
expect( map1.get( 'StepA' ) ).toBe( 'shared.step.a' );
|
|
243
|
+
expect( map1.get( 'StepB' ) ).toBe( 'shared.step.b' );
|
|
244
|
+
expect( cache.get( stepsPath ) ).toBe( map1 );
|
|
245
|
+
const map2 = buildSharedStepsNameMap( stepsPath, cache );
|
|
246
|
+
expect( map2 ).toBe( map1 );
|
|
247
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
248
|
+
} );
|
|
249
|
+
|
|
250
|
+
it( 'getFileKind: classifies file by its path', () => {
|
|
251
|
+
expect( getFileKind( '/p/workflow.js' ) ).toBe( 'workflow' );
|
|
252
|
+
expect( getFileKind( '/p/steps.js' ) ).toBe( 'steps' );
|
|
253
|
+
// Files in steps folder are steps
|
|
254
|
+
expect( getFileKind( '/p/steps/fetch.js' ) ).toBe( 'steps' );
|
|
255
|
+
expect( getFileKind( '/p/shared/steps/common.js' ) ).toBe( 'steps' );
|
|
256
|
+
expect( getFileKind( '/p/evaluators.js' ) ).toBe( 'evaluators' );
|
|
257
|
+
expect( getFileKind( '/p/evaluators/quality.js' ) ).toBe( 'evaluators' );
|
|
258
|
+
expect( getFileKind( '/p/other.js' ) ).toBe( null );
|
|
259
|
+
expect( getFileKind( '/p/utils.js' ) ).toBe( null );
|
|
260
|
+
expect( getFileKind( '/p/clients/api.js' ) ).toBe( null );
|
|
261
|
+
} );
|
|
262
|
+
|
|
263
|
+
it( 'resolveNameFromArg: resolves string literal directly', () => {
|
|
264
|
+
expect( resolveNameFromArg( t.stringLiteral( 'my_name' ), new Map(), 'X' ) ).toBe( 'my_name' );
|
|
265
|
+
} );
|
|
266
|
+
|
|
267
|
+
it( 'resolveNameFromArg: resolves identifier from consts', () => {
|
|
268
|
+
const consts = new Map( [ [ 'MY_NAME', 'resolved_name' ] ] );
|
|
269
|
+
expect( resolveNameFromArg( t.identifier( 'MY_NAME' ), consts, 'X' ) ).toBe( 'resolved_name' );
|
|
270
|
+
} );
|
|
271
|
+
|
|
272
|
+
it( 'resolveNameFromArg: falls back to resolveNameFromOptions for objects', () => {
|
|
273
|
+
const opts = t.objectExpression( [ t.objectProperty( t.identifier( 'name' ), t.stringLiteral( 'obj_name' ) ) ] );
|
|
274
|
+
expect( resolveNameFromArg( opts, new Map(), 'X' ) ).toBe( 'obj_name' );
|
|
275
|
+
} );
|
|
276
|
+
|
|
277
|
+
it( 'buildEvaluatorsNameMap: reads names from string-arg factory pattern', () => {
|
|
278
|
+
const dir = mkdtempSync( join( tmpdir(), 'tools-verify-evals-' ) );
|
|
279
|
+
const evalsPath = join( dir, 'evaluators.js' );
|
|
280
|
+
writeFileSync( evalsPath, 'export const EvalA = verify( \'eval_a\', async () => {} )' );
|
|
281
|
+
const cache = new Map();
|
|
282
|
+
const map = buildEvaluatorsNameMap( evalsPath, cache );
|
|
283
|
+
expect( map.get( 'EvalA' ) ).toBe( 'eval_a' );
|
|
284
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
285
|
+
} );
|
|
286
|
+
|
|
287
|
+
it( 'buildEvaluatorsNameMap: reads names from object-arg verify pattern', () => {
|
|
288
|
+
const dir = mkdtempSync( join( tmpdir(), 'tools-verify-obj-evals-' ) );
|
|
289
|
+
const evalsPath = join( dir, 'evaluators.js' );
|
|
290
|
+
writeFileSync( evalsPath, 'export const EvalA = verify( { name: \'eval_a\' }, async () => {} )' );
|
|
291
|
+
const cache = new Map();
|
|
292
|
+
const map = buildEvaluatorsNameMap( evalsPath, cache );
|
|
293
|
+
expect( map.get( 'EvalA' ) ).toBe( 'eval_a' );
|
|
294
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
295
|
+
} );
|
|
296
|
+
|
|
297
|
+
it( 'buildEvaluatorsNameMap: reads names from mixed factory patterns', () => {
|
|
298
|
+
const dir = mkdtempSync( join( tmpdir(), 'tools-mixed-evals-' ) );
|
|
299
|
+
const evalsPath = join( dir, 'evaluators.js' );
|
|
300
|
+
writeFileSync( evalsPath, [
|
|
301
|
+
'export const EvalA = verify( { name: \'eval_a\' }, async () => {} )',
|
|
302
|
+
'export const EvalB = evaluator( { name: \'eval_b\' } )'
|
|
303
|
+
].join( '\n' ) );
|
|
304
|
+
const cache = new Map();
|
|
305
|
+
const map = buildEvaluatorsNameMap( evalsPath, cache );
|
|
306
|
+
expect( map.get( 'EvalA' ) ).toBe( 'eval_a' );
|
|
307
|
+
expect( map.get( 'EvalB' ) ).toBe( 'eval_b' );
|
|
308
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
309
|
+
} );
|
|
310
|
+
|
|
311
|
+
it( 'buildStepsNameMap: reads names from string-arg factory pattern', () => {
|
|
312
|
+
const dir = mkdtempSync( join( tmpdir(), 'tools-verify-steps-' ) );
|
|
313
|
+
const stepsPath = join( dir, 'steps.js' );
|
|
314
|
+
writeFileSync( stepsPath, 'export const StepA = myStepHelper( \'step_a\', async () => {} )' );
|
|
315
|
+
const cache = new Map();
|
|
316
|
+
const map = buildStepsNameMap( stepsPath, cache );
|
|
317
|
+
expect( map.get( 'StepA' ) ).toBe( 'step_a' );
|
|
318
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
319
|
+
} );
|
|
320
|
+
|
|
321
|
+
it( 'buildWorkflowNameMap: reads names from string-arg factory pattern', () => {
|
|
322
|
+
const dir = mkdtempSync( join( tmpdir(), 'tools-verify-workflow-' ) );
|
|
323
|
+
const wfPath = join( dir, 'workflow.js' );
|
|
324
|
+
writeFileSync( wfPath, 'export default myWorkflowHelper( { name: \'my_flow\' } )' );
|
|
325
|
+
const cache = new Map();
|
|
326
|
+
const res = buildWorkflowNameMap( wfPath, cache );
|
|
327
|
+
expect( res.default ).toBe( 'my_flow' );
|
|
328
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
329
|
+
} );
|
|
330
|
+
} );
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import traverseModule from '@babel/traverse';
|
|
2
|
+
import {
|
|
3
|
+
buildWorkflowNameMap,
|
|
4
|
+
getLocalNameFromDestructuredProperty,
|
|
5
|
+
isEvaluatorsPath,
|
|
6
|
+
isSharedEvaluatorsPath,
|
|
7
|
+
isSharedStepsPath,
|
|
8
|
+
isStepsPath,
|
|
9
|
+
isWorkflowPath,
|
|
10
|
+
buildStepsNameMap,
|
|
11
|
+
buildSharedStepsNameMap,
|
|
12
|
+
buildEvaluatorsNameMap,
|
|
13
|
+
buildSharedEvaluatorsNameMap,
|
|
14
|
+
toAbsolutePath
|
|
15
|
+
} from '../tools.js';
|
|
16
|
+
import {
|
|
17
|
+
isCallExpression,
|
|
18
|
+
isIdentifier,
|
|
19
|
+
isImportDefaultSpecifier,
|
|
20
|
+
isImportSpecifier,
|
|
21
|
+
isObjectPattern,
|
|
22
|
+
isObjectProperty,
|
|
23
|
+
isStringLiteral,
|
|
24
|
+
isVariableDeclaration
|
|
25
|
+
} from '@babel/types';
|
|
26
|
+
|
|
27
|
+
// Handle CJS/ESM interop for Babel packages when executed as a webpack loader
|
|
28
|
+
const traverse = traverseModule.default ?? traverseModule;
|
|
29
|
+
|
|
30
|
+
const unresolvedImportError = ( name, fileLabel, filePath ) =>
|
|
31
|
+
new Error(
|
|
32
|
+
`Unresolved import '${name}' from ${fileLabel} file '${filePath}'. ` +
|
|
33
|
+
'This export may have been defined with the wrong component type. ' +
|
|
34
|
+
'Use the matching factory function for the file ' +
|
|
35
|
+
'(e.g. step() in steps files, evaluator() in evaluators files, workflow() in workflow files).'
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const removeRequireDeclarator = path => {
|
|
39
|
+
if ( isVariableDeclaration( path.parent ) && path.parent.declarations.length === 1 ) {
|
|
40
|
+
path.parentPath.remove();
|
|
41
|
+
} else {
|
|
42
|
+
path.remove();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const collectDestructuredRequires = ( path, absolutePath, req, descriptors ) => {
|
|
47
|
+
const propFilter = p => isObjectProperty( p ) && isIdentifier( p.key );
|
|
48
|
+
for ( const { match, buildMap, cache, target, valueKey, label } of descriptors ) {
|
|
49
|
+
if ( !match( req ) ) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const nameMap = buildMap( absolutePath, cache );
|
|
53
|
+
for ( const prop of path.node.id.properties.filter( propFilter ) ) {
|
|
54
|
+
const importedName = prop.key.name;
|
|
55
|
+
const localName = getLocalNameFromDestructuredProperty( prop );
|
|
56
|
+
if ( localName ) {
|
|
57
|
+
const resolved = nameMap.get( importedName );
|
|
58
|
+
if ( resolved ) {
|
|
59
|
+
target.push( { localName, [valueKey]: resolved } );
|
|
60
|
+
} else {
|
|
61
|
+
throw unresolvedImportError( importedName, label, absolutePath );
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
removeRequireDeclarator( path );
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Collect and strip target imports and requires from an AST, producing
|
|
72
|
+
* step/workflow import mappings for later rewrites.
|
|
73
|
+
*
|
|
74
|
+
* Mutates the AST by removing matching import declarations and require declarators.
|
|
75
|
+
*
|
|
76
|
+
* @param {import('@babel/types').File} ast - Parsed file AST.
|
|
77
|
+
* @param {string} fileDir - Absolute directory of the file represented by `ast`.
|
|
78
|
+
* @param {{ stepsNameCache: Map<string,Map<string,string>>, workflowNameCache: Map<string,{default:(string|null),named:Map<string,string>}> }} caches
|
|
79
|
+
* Resolved-name caches to avoid re-reading same modules.
|
|
80
|
+
* @returns {{ stepImports: Array<{localName:string,stepName:string}>,
|
|
81
|
+
* flowImports: Array<{localName:string,workflowName:string}> }} Collected info mappings.
|
|
82
|
+
*/
|
|
83
|
+
export default function collectTargetImports(
|
|
84
|
+
ast, fileDir,
|
|
85
|
+
{ stepsNameCache, workflowNameCache, evaluatorsNameCache, sharedStepsNameCache, sharedEvaluatorsNameCache }
|
|
86
|
+
) {
|
|
87
|
+
const stepImports = [];
|
|
88
|
+
const sharedStepImports = [];
|
|
89
|
+
const flowImports = [];
|
|
90
|
+
const evaluatorImports = [];
|
|
91
|
+
const sharedEvaluatorImports = [];
|
|
92
|
+
|
|
93
|
+
traverse( ast, {
|
|
94
|
+
ImportDeclaration: path => {
|
|
95
|
+
const src = path.node.source.value;
|
|
96
|
+
// Ignore other imports
|
|
97
|
+
const isTargetImport = isStepsPath( src ) || isSharedStepsPath( src ) ||
|
|
98
|
+
isWorkflowPath( src ) || isEvaluatorsPath( src ) || isSharedEvaluatorsPath( src );
|
|
99
|
+
if ( !isTargetImport ) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const absolutePath = toAbsolutePath( fileDir, src );
|
|
104
|
+
const collectNamedImports = ( match, buildMapFn, cache, targetArr, valueKey, fileLabel ) => {
|
|
105
|
+
if ( !match ) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const nameMap = buildMapFn( absolutePath, cache );
|
|
109
|
+
for ( const s of path.node.specifiers.filter( s => isImportSpecifier( s ) ) ) {
|
|
110
|
+
const importedName = s.imported.name;
|
|
111
|
+
const localName = s.local.name;
|
|
112
|
+
const value = nameMap.get( importedName );
|
|
113
|
+
if ( value ) {
|
|
114
|
+
const entry = { localName };
|
|
115
|
+
entry[valueKey] = value;
|
|
116
|
+
targetArr.push( entry );
|
|
117
|
+
} else {
|
|
118
|
+
throw unresolvedImportError( importedName, fileLabel, absolutePath );
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
collectNamedImports( isStepsPath( src ), buildStepsNameMap, stepsNameCache, stepImports, 'stepName', 'steps' );
|
|
124
|
+
collectNamedImports( isSharedStepsPath( src ), buildSharedStepsNameMap, sharedStepsNameCache, sharedStepImports, 'stepName', 'shared steps' );
|
|
125
|
+
collectNamedImports( isEvaluatorsPath( src ), buildEvaluatorsNameMap, evaluatorsNameCache, evaluatorImports, 'evaluatorName', 'evaluators' );
|
|
126
|
+
collectNamedImports(
|
|
127
|
+
isSharedEvaluatorsPath( src ), buildSharedEvaluatorsNameMap,
|
|
128
|
+
sharedEvaluatorsNameCache, sharedEvaluatorImports, 'evaluatorName', 'shared evaluators'
|
|
129
|
+
);
|
|
130
|
+
if ( isWorkflowPath( src ) ) {
|
|
131
|
+
const { named, default: defName } = buildWorkflowNameMap( absolutePath, workflowNameCache );
|
|
132
|
+
for ( const s of path.node.specifiers ) {
|
|
133
|
+
if ( isImportDefaultSpecifier( s ) ) {
|
|
134
|
+
const localName = s.local.name;
|
|
135
|
+
flowImports.push( { localName, workflowName: defName ?? localName } );
|
|
136
|
+
} else if ( isImportSpecifier( s ) ) {
|
|
137
|
+
const importedName = s.imported.name;
|
|
138
|
+
const localName = s.local.name;
|
|
139
|
+
const workflowName = named.get( importedName );
|
|
140
|
+
if ( workflowName ) {
|
|
141
|
+
flowImports.push( { localName, workflowName } );
|
|
142
|
+
} else {
|
|
143
|
+
throw unresolvedImportError( importedName, 'workflow', absolutePath );
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
path.remove();
|
|
149
|
+
},
|
|
150
|
+
VariableDeclarator: path => {
|
|
151
|
+
const init = path.node.init;
|
|
152
|
+
if ( !isCallExpression( init ) ) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if ( !isIdentifier( init.callee, { name: 'require' } ) ) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const firstArgument = init.arguments[0];
|
|
159
|
+
if ( !isStringLiteral( firstArgument ) ) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const req = firstArgument.value;
|
|
164
|
+
const isTargetRequire = isStepsPath( req ) || isSharedStepsPath( req ) ||
|
|
165
|
+
isWorkflowPath( req ) || isEvaluatorsPath( req ) || isSharedEvaluatorsPath( req );
|
|
166
|
+
if ( !isTargetRequire ) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const absolutePath = toAbsolutePath( fileDir, req );
|
|
171
|
+
|
|
172
|
+
// Destructured requires: const { X } = require('./steps.js')
|
|
173
|
+
if ( isObjectPattern( path.node.id ) ) {
|
|
174
|
+
const cjsDescriptors = [
|
|
175
|
+
{
|
|
176
|
+
match: isStepsPath, buildMap: buildStepsNameMap,
|
|
177
|
+
cache: stepsNameCache, target: stepImports,
|
|
178
|
+
valueKey: 'stepName', label: 'steps'
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
match: isSharedStepsPath, buildMap: buildSharedStepsNameMap,
|
|
182
|
+
cache: sharedStepsNameCache ?? stepsNameCache,
|
|
183
|
+
target: sharedStepImports,
|
|
184
|
+
valueKey: 'stepName', label: 'shared steps'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
match: isEvaluatorsPath, buildMap: buildEvaluatorsNameMap,
|
|
188
|
+
cache: evaluatorsNameCache, target: evaluatorImports,
|
|
189
|
+
valueKey: 'evaluatorName', label: 'evaluators'
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
match: isSharedEvaluatorsPath, buildMap: buildSharedEvaluatorsNameMap,
|
|
193
|
+
cache: sharedEvaluatorsNameCache ?? evaluatorsNameCache,
|
|
194
|
+
target: sharedEvaluatorImports,
|
|
195
|
+
valueKey: 'evaluatorName', label: 'shared evaluators'
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
match: isWorkflowPath,
|
|
199
|
+
buildMap: ( p, c ) => buildWorkflowNameMap( p, c ).named,
|
|
200
|
+
cache: workflowNameCache, target: flowImports,
|
|
201
|
+
valueKey: 'workflowName', label: 'workflow'
|
|
202
|
+
}
|
|
203
|
+
];
|
|
204
|
+
collectDestructuredRequires(
|
|
205
|
+
path, absolutePath, req, cjsDescriptors
|
|
206
|
+
);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Default workflow require: const WF = require('./workflow.js')
|
|
211
|
+
if ( isWorkflowPath( req ) && isIdentifier( path.node.id ) ) {
|
|
212
|
+
const { default: defName } = buildWorkflowNameMap( absolutePath, workflowNameCache );
|
|
213
|
+
const localName = path.node.id.name;
|
|
214
|
+
flowImports.push( { localName, workflowName: defName ?? localName } );
|
|
215
|
+
removeRequireDeclarator( path );
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} );
|
|
219
|
+
|
|
220
|
+
return { stepImports, sharedStepImports, evaluatorImports, sharedEvaluatorImports, flowImports };
|
|
221
|
+
};
|