@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,336 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { parse } from '../tools.js';
|
|
6
|
+
import collectTargetImports from './collect_target_imports.js';
|
|
7
|
+
|
|
8
|
+
function makeAst( source, filename ) {
|
|
9
|
+
return parse( source, filename );
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe( 'collect_target_imports', () => {
|
|
13
|
+
it( 'collects ESM imports for steps and workflows and flags changes', () => {
|
|
14
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-esm-' ) );
|
|
15
|
+
writeFileSync( join( dir, 'steps.js' ), `
|
|
16
|
+
export const StepA = step({ name: 'step.a' });
|
|
17
|
+
export const StepB = step({ name: 'step.b' });` );
|
|
18
|
+
writeFileSync( join( dir, 'evaluators.js' ), 'export const EvalA = evaluator({ name: \'eval.a\' });' );
|
|
19
|
+
writeFileSync( join( dir, 'workflow.js' ), `
|
|
20
|
+
export const FlowA = workflow({ name: 'flow.a' });
|
|
21
|
+
export default workflow({ name: 'flow.def' });` );
|
|
22
|
+
|
|
23
|
+
const source = `
|
|
24
|
+
import { StepA } from './steps.js';
|
|
25
|
+
import { EvalA } from './evaluators.js';
|
|
26
|
+
import WF, { FlowA } from './workflow.js';
|
|
27
|
+
const x = 1;`;
|
|
28
|
+
|
|
29
|
+
const ast = makeAst( source, join( dir, 'file.js' ) );
|
|
30
|
+
const { stepImports, evaluatorImports, flowImports } = collectTargetImports(
|
|
31
|
+
ast,
|
|
32
|
+
dir,
|
|
33
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
34
|
+
);
|
|
35
|
+
expect( evaluatorImports ).toEqual( [ { localName: 'EvalA', evaluatorName: 'eval.a' } ] );
|
|
36
|
+
|
|
37
|
+
expect( stepImports ).toEqual( [ { localName: 'StepA', stepName: 'step.a' } ] );
|
|
38
|
+
expect( flowImports ).toEqual( [
|
|
39
|
+
{ localName: 'WF', workflowName: 'flow.def' },
|
|
40
|
+
{ localName: 'FlowA', workflowName: 'flow.a' }
|
|
41
|
+
] );
|
|
42
|
+
// Import declarations should have been removed
|
|
43
|
+
expect( ast.program.body.find( n => n.type === 'ImportDeclaration' ) ).toBeUndefined();
|
|
44
|
+
|
|
45
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
46
|
+
} );
|
|
47
|
+
|
|
48
|
+
it( 'collects CJS requires and removes declarators (steps + default workflow)', () => {
|
|
49
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-' ) );
|
|
50
|
+
writeFileSync( join( dir, 'steps.js' ), 'export const StepB = step({ name: \'step.b\' })' );
|
|
51
|
+
writeFileSync( join( dir, 'evaluators.js' ), 'export const EvalB = evaluator({ name: \'eval.b\' })' );
|
|
52
|
+
writeFileSync( join( dir, 'workflow.js' ), 'export default workflow({ name: \'flow.c\' })' );
|
|
53
|
+
|
|
54
|
+
const source = `
|
|
55
|
+
const { StepB } = require( './steps.js' );
|
|
56
|
+
const { EvalB } = require( './evaluators.js' );
|
|
57
|
+
const WF = require( './workflow.js' );
|
|
58
|
+
const obj = {};`;
|
|
59
|
+
|
|
60
|
+
const ast = makeAst( source, join( dir, 'file.js' ) );
|
|
61
|
+
const { stepImports, evaluatorImports, flowImports } = collectTargetImports(
|
|
62
|
+
ast,
|
|
63
|
+
dir,
|
|
64
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
65
|
+
);
|
|
66
|
+
expect( evaluatorImports ).toEqual( [ { localName: 'EvalB', evaluatorName: 'eval.b' } ] );
|
|
67
|
+
|
|
68
|
+
expect( stepImports ).toEqual( [ { localName: 'StepB', stepName: 'step.b' } ] );
|
|
69
|
+
expect( flowImports ).toEqual( [ { localName: 'WF', workflowName: 'flow.c' } ] );
|
|
70
|
+
// All require-based declarators should have been removed (only non-require decls may remain)
|
|
71
|
+
const hasRequireDecl = ast.program.body.some( n =>
|
|
72
|
+
n.type === 'VariableDeclaration' && n.declarations.some( d => d.init && d.init.type === 'CallExpression' )
|
|
73
|
+
);
|
|
74
|
+
expect( hasRequireDecl ).toBe( false );
|
|
75
|
+
|
|
76
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
77
|
+
} );
|
|
78
|
+
|
|
79
|
+
it( 'resolves ESM import from evaluators.js regardless of callee name', () => {
|
|
80
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-esm-mismatch-eval-' ) );
|
|
81
|
+
writeFileSync( join( dir, 'evaluators.js' ), 'export const MyExport = step({ name: \'bad\' });' );
|
|
82
|
+
|
|
83
|
+
const source = 'import { MyExport } from \'./evaluators.js\';';
|
|
84
|
+
const ast = makeAst( source, join( dir, 'file.js' ) );
|
|
85
|
+
|
|
86
|
+
const { evaluatorImports } = collectTargetImports(
|
|
87
|
+
ast,
|
|
88
|
+
dir,
|
|
89
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
90
|
+
);
|
|
91
|
+
expect( evaluatorImports ).toEqual( [ { localName: 'MyExport', evaluatorName: 'bad' } ] );
|
|
92
|
+
|
|
93
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
94
|
+
} );
|
|
95
|
+
|
|
96
|
+
it( 'resolves ESM import from steps.js regardless of callee name', () => {
|
|
97
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-esm-mismatch-step-' ) );
|
|
98
|
+
writeFileSync( join( dir, 'steps.js' ), 'export const MyExport = evaluator({ name: \'bad\' });' );
|
|
99
|
+
|
|
100
|
+
const source = 'import { MyExport } from \'./steps.js\';';
|
|
101
|
+
const ast = makeAst( source, join( dir, 'file.js' ) );
|
|
102
|
+
|
|
103
|
+
const { stepImports } = collectTargetImports(
|
|
104
|
+
ast,
|
|
105
|
+
dir,
|
|
106
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
107
|
+
);
|
|
108
|
+
expect( stepImports ).toEqual( [ { localName: 'MyExport', stepName: 'bad' } ] );
|
|
109
|
+
|
|
110
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
111
|
+
} );
|
|
112
|
+
|
|
113
|
+
it( 'resolves CJS require from evaluators.js regardless of callee name', () => {
|
|
114
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-mismatch-eval-' ) );
|
|
115
|
+
writeFileSync( join( dir, 'evaluators.js' ), 'export const MyExport = step({ name: \'bad\' });' );
|
|
116
|
+
|
|
117
|
+
const source = 'const { MyExport } = require( \'./evaluators.js\' );';
|
|
118
|
+
const ast = makeAst( source, join( dir, 'file.js' ) );
|
|
119
|
+
|
|
120
|
+
const { evaluatorImports } = collectTargetImports(
|
|
121
|
+
ast,
|
|
122
|
+
dir,
|
|
123
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
124
|
+
);
|
|
125
|
+
expect( evaluatorImports ).toEqual( [ { localName: 'MyExport', evaluatorName: 'bad' } ] );
|
|
126
|
+
|
|
127
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
128
|
+
} );
|
|
129
|
+
|
|
130
|
+
it( 'resolves CJS require from steps.js regardless of callee name', () => {
|
|
131
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-mismatch-step-' ) );
|
|
132
|
+
writeFileSync( join( dir, 'steps.js' ), 'export const MyExport = evaluator({ name: \'bad\' });' );
|
|
133
|
+
|
|
134
|
+
const source = 'const { MyExport } = require( \'./steps.js\' );';
|
|
135
|
+
const ast = makeAst( source, join( dir, 'file.js' ) );
|
|
136
|
+
|
|
137
|
+
const { stepImports } = collectTargetImports(
|
|
138
|
+
ast,
|
|
139
|
+
dir,
|
|
140
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
141
|
+
);
|
|
142
|
+
expect( stepImports ).toEqual( [ { localName: 'MyExport', stepName: 'bad' } ] );
|
|
143
|
+
|
|
144
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
145
|
+
} );
|
|
146
|
+
|
|
147
|
+
it( 'throws when ESM import from workflow.js has non-workflow export', () => {
|
|
148
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-esm-mismatch-wf-' ) );
|
|
149
|
+
writeFileSync( join( dir, 'workflow.js' ), 'export const helper = () => 42;' );
|
|
150
|
+
|
|
151
|
+
const source = 'import { helper } from \'./workflow.js\';';
|
|
152
|
+
const ast = makeAst( source, join( dir, 'file.js' ) );
|
|
153
|
+
|
|
154
|
+
expect( () => collectTargetImports(
|
|
155
|
+
ast,
|
|
156
|
+
dir,
|
|
157
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
158
|
+
) ).toThrow( /Unresolved import 'helper' from workflow file/ );
|
|
159
|
+
|
|
160
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
161
|
+
} );
|
|
162
|
+
|
|
163
|
+
it( 'throws when CJS destructured require from workflow.js has non-workflow export', () => {
|
|
164
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-mismatch-wf-' ) );
|
|
165
|
+
writeFileSync( join( dir, 'workflow.js' ), 'export const helper = () => 42;' );
|
|
166
|
+
|
|
167
|
+
const source = 'const { helper } = require( \'./workflow.js\' );';
|
|
168
|
+
const ast = makeAst( source, join( dir, 'file.js' ) );
|
|
169
|
+
|
|
170
|
+
expect( () => collectTargetImports(
|
|
171
|
+
ast,
|
|
172
|
+
dir,
|
|
173
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
174
|
+
) ).toThrow( /Unresolved import 'helper' from workflow file/ );
|
|
175
|
+
|
|
176
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
177
|
+
} );
|
|
178
|
+
|
|
179
|
+
it( 'collects CJS destructured require from workflow.js', () => {
|
|
180
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-wf-destruct-' ) );
|
|
181
|
+
writeFileSync( join( dir, 'workflow.js' ), 'export const FlowX = workflow({ name: \'flow.x\' });' );
|
|
182
|
+
|
|
183
|
+
const source = 'const { FlowX } = require( \'./workflow.js\' );\nconst obj = {};';
|
|
184
|
+
const ast = makeAst( source, join( dir, 'file.js' ) );
|
|
185
|
+
|
|
186
|
+
const { flowImports } = collectTargetImports(
|
|
187
|
+
ast,
|
|
188
|
+
dir,
|
|
189
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
190
|
+
);
|
|
191
|
+
expect( flowImports ).toEqual( [ { localName: 'FlowX', workflowName: 'flow.x' } ] );
|
|
192
|
+
|
|
193
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
194
|
+
} );
|
|
195
|
+
|
|
196
|
+
it( 'collects ESM shared evaluator imports', () => {
|
|
197
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-esm-shared-eval-' ) );
|
|
198
|
+
mkdirSync( join( dir, 'shared', 'evaluators' ), { recursive: true } );
|
|
199
|
+
mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
|
|
200
|
+
writeFileSync(
|
|
201
|
+
join( dir, 'shared', 'evaluators', 'common.js' ),
|
|
202
|
+
'export const SharedEval = evaluator({ name: \'shared.eval\' });'
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const source = 'import { SharedEval } from \'../../shared/evaluators/common.js\';';
|
|
206
|
+
const ast = makeAst( source, join( dir, 'workflows', 'my_workflow', 'workflow.js' ) );
|
|
207
|
+
|
|
208
|
+
const { sharedEvaluatorImports } = collectTargetImports(
|
|
209
|
+
ast,
|
|
210
|
+
join( dir, 'workflows', 'my_workflow' ),
|
|
211
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
212
|
+
);
|
|
213
|
+
expect( sharedEvaluatorImports ).toEqual( [ { localName: 'SharedEval', evaluatorName: 'shared.eval' } ] );
|
|
214
|
+
|
|
215
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
216
|
+
} );
|
|
217
|
+
|
|
218
|
+
it( 'collects CJS shared evaluator requires', () => {
|
|
219
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-shared-eval-' ) );
|
|
220
|
+
mkdirSync( join( dir, 'shared', 'evaluators' ), { recursive: true } );
|
|
221
|
+
mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
|
|
222
|
+
writeFileSync(
|
|
223
|
+
join( dir, 'shared', 'evaluators', 'common.js' ),
|
|
224
|
+
'export const SharedEval = evaluator({ name: \'shared.eval\' });'
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const source = 'const { SharedEval } = require( \'../../shared/evaluators/common.js\' );';
|
|
228
|
+
const ast = makeAst( source, join( dir, 'workflows', 'my_workflow', 'workflow.js' ) );
|
|
229
|
+
|
|
230
|
+
const { sharedEvaluatorImports } = collectTargetImports(
|
|
231
|
+
ast,
|
|
232
|
+
join( dir, 'workflows', 'my_workflow' ),
|
|
233
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
234
|
+
);
|
|
235
|
+
expect( sharedEvaluatorImports ).toEqual( [ { localName: 'SharedEval', evaluatorName: 'shared.eval' } ] );
|
|
236
|
+
|
|
237
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
238
|
+
} );
|
|
239
|
+
|
|
240
|
+
it( 'collects CJS shared steps requires', () => {
|
|
241
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-shared-step-' ) );
|
|
242
|
+
mkdirSync( join( dir, 'shared', 'steps' ), { recursive: true } );
|
|
243
|
+
mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
|
|
244
|
+
writeFileSync(
|
|
245
|
+
join( dir, 'shared', 'steps', 'common.js' ),
|
|
246
|
+
'export const SharedA = step({ name: \'shared.a\' });'
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const source = 'const { SharedA } = require( \'../../shared/steps/common.js\' );';
|
|
250
|
+
const ast = makeAst( source, join( dir, 'workflows', 'my_workflow', 'workflow.js' ) );
|
|
251
|
+
|
|
252
|
+
const { sharedStepImports } = collectTargetImports(
|
|
253
|
+
ast,
|
|
254
|
+
join( dir, 'workflows', 'my_workflow' ),
|
|
255
|
+
{
|
|
256
|
+
stepsNameCache: new Map(), sharedStepsNameCache: new Map(),
|
|
257
|
+
evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map(),
|
|
258
|
+
workflowNameCache: new Map()
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
expect( sharedStepImports ).toEqual( [ { localName: 'SharedA', stepName: 'shared.a' } ] );
|
|
262
|
+
|
|
263
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
264
|
+
} );
|
|
265
|
+
|
|
266
|
+
it( 'resolves CJS shared steps require regardless of callee name', () => {
|
|
267
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-shared-step-mismatch-' ) );
|
|
268
|
+
mkdirSync( join( dir, 'shared', 'steps' ), { recursive: true } );
|
|
269
|
+
mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
|
|
270
|
+
writeFileSync(
|
|
271
|
+
join( dir, 'shared', 'steps', 'common.js' ),
|
|
272
|
+
'export const MyExport = evaluator({ name: \'bad\' });'
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const source = 'const { MyExport } = require( \'../../shared/steps/common.js\' );';
|
|
276
|
+
const ast = makeAst( source, join( dir, 'workflows', 'my_workflow', 'workflow.js' ) );
|
|
277
|
+
|
|
278
|
+
const { sharedStepImports } = collectTargetImports(
|
|
279
|
+
ast,
|
|
280
|
+
join( dir, 'workflows', 'my_workflow' ),
|
|
281
|
+
{
|
|
282
|
+
stepsNameCache: new Map(), sharedStepsNameCache: new Map(),
|
|
283
|
+
evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map(),
|
|
284
|
+
workflowNameCache: new Map()
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
expect( sharedStepImports ).toEqual( [ { localName: 'MyExport', stepName: 'bad' } ] );
|
|
288
|
+
|
|
289
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
290
|
+
} );
|
|
291
|
+
|
|
292
|
+
it( 'resolves CJS shared evaluator require regardless of callee name', () => {
|
|
293
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-cjs-shared-eval-mismatch-' ) );
|
|
294
|
+
mkdirSync( join( dir, 'shared', 'evaluators' ), { recursive: true } );
|
|
295
|
+
mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
|
|
296
|
+
writeFileSync(
|
|
297
|
+
join( dir, 'shared', 'evaluators', 'common.js' ),
|
|
298
|
+
'export const MyExport = step({ name: \'bad\' });'
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const source = 'const { MyExport } = require( \'../../shared/evaluators/common.js\' );';
|
|
302
|
+
const ast = makeAst( source, join( dir, 'workflows', 'my_workflow', 'workflow.js' ) );
|
|
303
|
+
|
|
304
|
+
const { sharedEvaluatorImports } = collectTargetImports(
|
|
305
|
+
ast,
|
|
306
|
+
join( dir, 'workflows', 'my_workflow' ),
|
|
307
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
308
|
+
);
|
|
309
|
+
expect( sharedEvaluatorImports ).toEqual( [ { localName: 'MyExport', evaluatorName: 'bad' } ] );
|
|
310
|
+
|
|
311
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
312
|
+
} );
|
|
313
|
+
|
|
314
|
+
it( 'resolves ESM shared evaluator import regardless of callee name', () => {
|
|
315
|
+
const dir = mkdtempSync( join( tmpdir(), 'collect-esm-shared-eval-mismatch-' ) );
|
|
316
|
+
mkdirSync( join( dir, 'shared', 'evaluators' ), { recursive: true } );
|
|
317
|
+
mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
|
|
318
|
+
writeFileSync(
|
|
319
|
+
join( dir, 'shared', 'evaluators', 'common.js' ),
|
|
320
|
+
'export const MyExport = step({ name: \'bad\' });'
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const source = 'import { MyExport } from \'../../shared/evaluators/common.js\';';
|
|
324
|
+
const ast = makeAst( source, join( dir, 'workflows', 'my_workflow', 'workflow.js' ) );
|
|
325
|
+
|
|
326
|
+
const { sharedEvaluatorImports } = collectTargetImports(
|
|
327
|
+
ast,
|
|
328
|
+
join( dir, 'workflows', 'my_workflow' ),
|
|
329
|
+
{ stepsNameCache: new Map(), evaluatorsNameCache: new Map(), sharedEvaluatorsNameCache: new Map(), workflowNameCache: new Map() }
|
|
330
|
+
);
|
|
331
|
+
expect( sharedEvaluatorImports ).toEqual( [ { localName: 'MyExport', evaluatorName: 'bad' } ] );
|
|
332
|
+
|
|
333
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
334
|
+
} );
|
|
335
|
+
} );
|
|
336
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
|
+
import generatorModule from '@babel/generator';
|
|
3
|
+
import { parse } from '../tools.js';
|
|
4
|
+
|
|
5
|
+
import rewriteFnBodies from './rewrite_fn_bodies.js';
|
|
6
|
+
import collectTargetImports from './collect_target_imports.js';
|
|
7
|
+
|
|
8
|
+
// Handle CJS/ESM interop for Babel packages when executed as a webpack loader
|
|
9
|
+
const generate = generatorModule.default ?? generatorModule;
|
|
10
|
+
|
|
11
|
+
// Caches to avoid re-reading files during a build
|
|
12
|
+
const stepsNameCache = new Map(); // path -> Map<exported, stepName>
|
|
13
|
+
const sharedStepsNameCache = new Map(); // path -> Map<exported, stepName> (shared)
|
|
14
|
+
const evaluatorsNameCache = new Map(); // path -> Map<exported, evaluatorName>
|
|
15
|
+
const sharedEvaluatorsNameCache = new Map(); // path -> Map<exported, evaluatorName> (shared)
|
|
16
|
+
const workflowNameCache = new Map(); // path -> { default?: name, named: Map<exported, flowName> }
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Webpack loader that rewrites step/workflow calls by reading names from
|
|
20
|
+
* the respective modules and transforming `fn` bodies accordingly.
|
|
21
|
+
* Preserves sourcemaps.
|
|
22
|
+
*
|
|
23
|
+
* @param {string|Buffer} source - Module source code.
|
|
24
|
+
* @param {any} inputMap - Incoming source map.
|
|
25
|
+
* @this {import('webpack').LoaderContext<{}>}
|
|
26
|
+
* @returns {void}
|
|
27
|
+
*/
|
|
28
|
+
export default function stepImportRewriterAstLoader( source, inputMap ) {
|
|
29
|
+
this.cacheable?.( true );
|
|
30
|
+
const callback = this.async?.() ?? this.callback;
|
|
31
|
+
const cache = { stepsNameCache, sharedStepsNameCache, evaluatorsNameCache, sharedEvaluatorsNameCache, workflowNameCache };
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const filename = this.resourcePath;
|
|
35
|
+
const ast = parse( String( source ), filename );
|
|
36
|
+
const fileDir = dirname( filename );
|
|
37
|
+
const { stepImports, sharedStepImports, evaluatorImports, sharedEvaluatorImports, flowImports } = collectTargetImports( ast, fileDir, cache );
|
|
38
|
+
|
|
39
|
+
// No imports
|
|
40
|
+
if ( [].concat( stepImports, sharedStepImports, evaluatorImports, sharedEvaluatorImports, flowImports ).length === 0 ) {
|
|
41
|
+
return callback( null, source, inputMap );
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const rewrote = rewriteFnBodies( { ast, stepImports, sharedStepImports, evaluatorImports, sharedEvaluatorImports, flowImports } );
|
|
45
|
+
// No edits performed
|
|
46
|
+
if ( !rewrote ) {
|
|
47
|
+
return callback( null, source, inputMap );
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { code, map } = generate( ast, {
|
|
51
|
+
sourceMaps: true,
|
|
52
|
+
sourceFileName: filename,
|
|
53
|
+
quotes: 'single',
|
|
54
|
+
jsescOption: { quotes: 'single' }
|
|
55
|
+
}, String( source ) );
|
|
56
|
+
return callback( null, code, map ?? inputMap );
|
|
57
|
+
} catch ( err ) {
|
|
58
|
+
// Fail gracefully as loader error
|
|
59
|
+
return callback( err );
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -0,0 +1,216 @@
|
|
|
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 } from 'node:path';
|
|
5
|
+
import loader from './index.mjs';
|
|
6
|
+
|
|
7
|
+
function runLoader( source, resourcePath ) {
|
|
8
|
+
return new Promise( ( resolve, reject ) => {
|
|
9
|
+
const ctx = {
|
|
10
|
+
resourcePath,
|
|
11
|
+
cacheable: () => {},
|
|
12
|
+
async: () => ( err, code, map ) => ( err ? reject( err ) : resolve( { code, map } ) ),
|
|
13
|
+
callback: ( err, code, map ) => ( err ? reject( err ) : resolve( { code, map } ) )
|
|
14
|
+
};
|
|
15
|
+
loader.call( ctx, source, null );
|
|
16
|
+
} );
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe( 'workflows_rewriter Webpack loader spec', () => {
|
|
20
|
+
it( 'rewrites ESM imports and converts fn arrow to function', async () => {
|
|
21
|
+
const dir = mkdtempSync( join( tmpdir(), 'ast-loader-esm-' ) );
|
|
22
|
+
writeFileSync( join( dir, 'steps.js' ), 'export const StepA = step({ name: \'step.a\' });' );
|
|
23
|
+
writeFileSync( join( dir, 'workflow.js' ), `
|
|
24
|
+
export const FlowA = workflow({ name: 'flow.a' });
|
|
25
|
+
export default workflow({ name: 'flow.def' });` );
|
|
26
|
+
|
|
27
|
+
const source = `
|
|
28
|
+
import { StepA } from './steps.js';
|
|
29
|
+
import FlowDef, { FlowA } from './workflow.js';
|
|
30
|
+
|
|
31
|
+
const obj = {
|
|
32
|
+
fn: async (x) => {
|
|
33
|
+
StepA(1);
|
|
34
|
+
FlowA(2);
|
|
35
|
+
FlowDef(3);
|
|
36
|
+
}
|
|
37
|
+
}`;
|
|
38
|
+
|
|
39
|
+
const { code } = await runLoader( source, join( dir, 'file.js' ) );
|
|
40
|
+
|
|
41
|
+
expect( code ).not.toMatch( /from '\.\/steps\.js'/ );
|
|
42
|
+
expect( code ).not.toMatch( /from '\.\/workflow\.js'/ );
|
|
43
|
+
expect( code ).toMatch( /fn:\s*async function \(x\)/ );
|
|
44
|
+
expect( code ).toMatch( /this\.invokeStep\('step\.a',\s*1\)/ );
|
|
45
|
+
expect( code ).toMatch( /this\.startWorkflow\('flow\.a',\s*2\)/ );
|
|
46
|
+
expect( code ).toMatch( /this\.startWorkflow\('flow\.def',\s*3\)/ );
|
|
47
|
+
|
|
48
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
49
|
+
} );
|
|
50
|
+
|
|
51
|
+
it( 'rewrites ESM shared steps imports to invokeSharedStep', async () => {
|
|
52
|
+
// Create directory structure: shared/steps/common.js
|
|
53
|
+
const dir = mkdtempSync( join( tmpdir(), 'ast-loader-esm-shared-' ) );
|
|
54
|
+
mkdirSync( join( dir, 'shared', 'steps' ), { recursive: true } );
|
|
55
|
+
mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
|
|
56
|
+
writeFileSync( join( dir, 'shared', 'steps', 'common.js' ), 'export const SharedA = step({ name: \'shared.a\' });' );
|
|
57
|
+
|
|
58
|
+
const source = `
|
|
59
|
+
import { SharedA } from '../../shared/steps/common.js';
|
|
60
|
+
|
|
61
|
+
const obj = {
|
|
62
|
+
fn: async (x) => {
|
|
63
|
+
SharedA(1);
|
|
64
|
+
}
|
|
65
|
+
}`;
|
|
66
|
+
|
|
67
|
+
const { code } = await runLoader( source, join( dir, 'workflows', 'my_workflow', 'workflow.js' ) );
|
|
68
|
+
|
|
69
|
+
expect( code ).not.toMatch( /from '\.\.\/\.\.\/shared\/steps\/common\.js'/ );
|
|
70
|
+
expect( code ).toMatch( /fn:\s*async function \(x\)/ );
|
|
71
|
+
expect( code ).toMatch( /this\.invokeSharedStep\('shared\.a',\s*1\)/ );
|
|
72
|
+
|
|
73
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
74
|
+
} );
|
|
75
|
+
|
|
76
|
+
it( 'rewrites CJS shared steps requires to invokeSharedStep', async () => {
|
|
77
|
+
// Create directory structure: shared/steps/common.js
|
|
78
|
+
const dir = mkdtempSync( join( tmpdir(), 'ast-loader-cjs-shared-' ) );
|
|
79
|
+
mkdirSync( join( dir, 'shared', 'steps' ), { recursive: true } );
|
|
80
|
+
mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
|
|
81
|
+
writeFileSync( join( dir, 'shared', 'steps', 'common.js' ), 'export const SharedB = step({ name: \'shared.b\' });' );
|
|
82
|
+
|
|
83
|
+
const source = `
|
|
84
|
+
const { SharedB } = require( '../../shared/steps/common.js' );
|
|
85
|
+
|
|
86
|
+
const obj = {
|
|
87
|
+
fn: async (y) => {
|
|
88
|
+
SharedB();
|
|
89
|
+
}
|
|
90
|
+
}`;
|
|
91
|
+
|
|
92
|
+
const { code } = await runLoader( source, join( dir, 'workflows', 'my_workflow', 'workflow.js' ) );
|
|
93
|
+
|
|
94
|
+
expect( code ).not.toMatch( /require\('\.\.\/\.\.\/shared\/steps\/common\.js'\)/ );
|
|
95
|
+
expect( code ).toMatch( /fn:\s*async function \(y\)/ );
|
|
96
|
+
expect( code ).toMatch( /this\.invokeSharedStep\('shared\.b'\)/ );
|
|
97
|
+
|
|
98
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
99
|
+
} );
|
|
100
|
+
|
|
101
|
+
it( 'rewrites CJS requires and converts fn arrow to function', async () => {
|
|
102
|
+
const dir = mkdtempSync( join( tmpdir(), 'ast-loader-cjs-' ) );
|
|
103
|
+
writeFileSync( join( dir, 'steps.js' ), 'export const StepB = step({ name: \'step.b\' });' );
|
|
104
|
+
writeFileSync( join( dir, 'workflow.js' ), 'export default workflow({ name: \'flow.c\' });' );
|
|
105
|
+
|
|
106
|
+
const source = `
|
|
107
|
+
const { StepB } = require( './steps.js' );
|
|
108
|
+
const FlowDefault = require( './workflow.js' );
|
|
109
|
+
|
|
110
|
+
const obj = {
|
|
111
|
+
fn: async (y) => {
|
|
112
|
+
StepB();
|
|
113
|
+
FlowDefault();
|
|
114
|
+
}
|
|
115
|
+
}`;
|
|
116
|
+
|
|
117
|
+
const { code } = await runLoader( source, join( dir, 'file.js' ) );
|
|
118
|
+
|
|
119
|
+
expect( code ).not.toMatch( /require\('\.\/steps\.js'\)/ );
|
|
120
|
+
expect( code ).not.toMatch( /require\('\.\/workflow\.js'\)/ );
|
|
121
|
+
expect( code ).toMatch( /fn:\s*async function \(y\)/ );
|
|
122
|
+
expect( code ).toMatch( /this\.invokeStep\('step\.b'\)/ );
|
|
123
|
+
expect( code ).toMatch( /this\.startWorkflow\('flow\.c'\)/ );
|
|
124
|
+
|
|
125
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
126
|
+
} );
|
|
127
|
+
|
|
128
|
+
it( 'resolves top-level const name variables', async () => {
|
|
129
|
+
const dir = mkdtempSync( join( tmpdir(), 'ast-loader-const-' ) );
|
|
130
|
+
writeFileSync( join( dir, 'steps.js' ), `
|
|
131
|
+
const NAME = 'step.const';
|
|
132
|
+
export const StepC = step({ name: NAME });` );
|
|
133
|
+
writeFileSync( join( dir, 'workflow.js' ), `
|
|
134
|
+
const WF = 'wf.const';
|
|
135
|
+
export const FlowC = workflow({ name: WF });
|
|
136
|
+
const D = 'wf.def';
|
|
137
|
+
export default workflow({ name: D });` );
|
|
138
|
+
|
|
139
|
+
const source = `
|
|
140
|
+
import { StepC } from './steps.js';
|
|
141
|
+
import FlowDef, { FlowC } from './workflow.js';
|
|
142
|
+
const obj = { fn: async () => { StepC(); FlowC(); FlowDef(); } }`;
|
|
143
|
+
|
|
144
|
+
const { code } = await runLoader( source, join( dir, 'file.js' ) );
|
|
145
|
+
expect( code ).toMatch( /this\.invokeStep\('step\.const'\)/ );
|
|
146
|
+
expect( code ).toMatch( /this\.startWorkflow\('wf\.const'\)/ );
|
|
147
|
+
expect( code ).toMatch( /this\.startWorkflow\('wf\.def'\)/ );
|
|
148
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
149
|
+
} );
|
|
150
|
+
|
|
151
|
+
it( 'rewrites ESM shared evaluator imports to invokeSharedEvaluator', async () => {
|
|
152
|
+
const dir = mkdtempSync( join( tmpdir(), 'ast-loader-esm-shared-eval-' ) );
|
|
153
|
+
mkdirSync( join( dir, 'shared', 'evaluators' ), { recursive: true } );
|
|
154
|
+
mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
|
|
155
|
+
writeFileSync( join( dir, 'shared', 'evaluators', 'common.js' ), 'export const SharedEval = evaluator({ name: \'shared.eval\' });' );
|
|
156
|
+
|
|
157
|
+
const source = `
|
|
158
|
+
import { SharedEval } from '../../shared/evaluators/common.js';
|
|
159
|
+
|
|
160
|
+
const obj = {
|
|
161
|
+
fn: async (x) => {
|
|
162
|
+
SharedEval(1);
|
|
163
|
+
}
|
|
164
|
+
}`;
|
|
165
|
+
|
|
166
|
+
const { code } = await runLoader( source, join( dir, 'workflows', 'my_workflow', 'workflow.js' ) );
|
|
167
|
+
|
|
168
|
+
expect( code ).not.toMatch( /from '\.\.\/\.\.\/shared\/evaluators\/common\.js'/ );
|
|
169
|
+
expect( code ).toMatch( /fn:\s*async function \(x\)/ );
|
|
170
|
+
expect( code ).toMatch( /this\.invokeSharedEvaluator\('shared\.eval',\s*1\)/ );
|
|
171
|
+
|
|
172
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
173
|
+
} );
|
|
174
|
+
|
|
175
|
+
it( 'rewrites CJS shared evaluator requires to invokeSharedEvaluator', async () => {
|
|
176
|
+
const dir = mkdtempSync( join( tmpdir(), 'ast-loader-cjs-shared-eval-' ) );
|
|
177
|
+
mkdirSync( join( dir, 'shared', 'evaluators' ), { recursive: true } );
|
|
178
|
+
mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
|
|
179
|
+
writeFileSync( join( dir, 'shared', 'evaluators', 'common.js' ), 'export const SharedEval = evaluator({ name: \'shared.eval\' });' );
|
|
180
|
+
|
|
181
|
+
const source = `
|
|
182
|
+
const { SharedEval } = require( '../../shared/evaluators/common.js' );
|
|
183
|
+
|
|
184
|
+
const obj = {
|
|
185
|
+
fn: async (y) => {
|
|
186
|
+
SharedEval();
|
|
187
|
+
}
|
|
188
|
+
}`;
|
|
189
|
+
|
|
190
|
+
const { code } = await runLoader( source, join( dir, 'workflows', 'my_workflow', 'workflow.js' ) );
|
|
191
|
+
|
|
192
|
+
expect( code ).not.toMatch( /require\('\.\.\/\.\.\/shared\/evaluators\/common\.js'\)/ );
|
|
193
|
+
expect( code ).toMatch( /fn:\s*async function \(y\)/ );
|
|
194
|
+
expect( code ).toMatch( /this\.invokeSharedEvaluator\('shared\.eval'\)/ );
|
|
195
|
+
|
|
196
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
197
|
+
} );
|
|
198
|
+
|
|
199
|
+
it( 'throws on non-static name', async () => {
|
|
200
|
+
const dir = mkdtempSync( join( tmpdir(), 'ast-loader-error-' ) );
|
|
201
|
+
writeFileSync( join( dir, 'steps.js' ), `
|
|
202
|
+
function n() { return 'x'; }
|
|
203
|
+
export const StepX = step({ name: n() });` );
|
|
204
|
+
writeFileSync( join( dir, 'workflow.js' ), `
|
|
205
|
+
const base = 'a';
|
|
206
|
+
export default workflow({ name: \`\${base}-b\` });` );
|
|
207
|
+
|
|
208
|
+
const source = `
|
|
209
|
+
import { StepX } from './steps.js';
|
|
210
|
+
import WF from './workflow.js';
|
|
211
|
+
const obj = { fn: async () => { StepX(); WF(); } }`;
|
|
212
|
+
|
|
213
|
+
await expect( runLoader( source, join( dir, 'file.js' ) ) ).rejects.toThrow( /Invalid (step|default workflow) name/ );
|
|
214
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
215
|
+
} );
|
|
216
|
+
} );
|