@outputai/core 0.10.0 → 0.10.1-next.09ed166.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 (45) hide show
  1. package/package.json +1 -1
  2. package/src/bus.js +58 -20
  3. package/src/bus.spec.js +135 -77
  4. package/src/consts.js +2 -1
  5. package/src/helpers/object.js +48 -35
  6. package/src/helpers/object.spec.js +123 -75
  7. package/src/hooks/index.d.ts +48 -45
  8. package/src/hooks/index.js +49 -44
  9. package/src/hooks/index.spec.js +74 -102
  10. package/src/interface/workflow.js +92 -85
  11. package/src/interface/workflow.spec.js +135 -142
  12. package/src/sdk/helpers/objects.d.ts +24 -19
  13. package/src/sdk/runtime/events.d.ts +4 -6
  14. package/src/sdk/runtime/events.js +2 -15
  15. package/src/sdk/runtime/events.spec.js +14 -92
  16. package/src/worker/global_functions.js +2 -2
  17. package/src/worker/global_functions.spec.js +3 -3
  18. package/src/worker/index.js +3 -3
  19. package/src/worker/index.spec.js +6 -6
  20. package/src/worker/interceptors/activity.js +4 -4
  21. package/src/worker/interceptors/activity.spec.js +9 -9
  22. package/src/worker/interceptors/workflow.js +5 -5
  23. package/src/worker/interceptors/workflow.spec.js +7 -7
  24. package/src/worker/loader/activities.js +56 -40
  25. package/src/worker/loader/activities.spec.js +21 -22
  26. package/src/worker/loader/workflows.spec.js +1 -2
  27. package/src/worker/log_hooks.js +9 -9
  28. package/src/worker/log_hooks.spec.js +2 -2
  29. package/src/worker/sinks.js +8 -8
  30. package/src/worker/sinks.spec.js +9 -9
  31. package/src/worker/webpack_loaders/consts.js +6 -0
  32. package/src/worker/webpack_loaders/tools.js +1 -146
  33. package/src/worker/webpack_loaders/tools.spec.js +0 -23
  34. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +24 -32
  35. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +82 -279
  36. package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +6 -7
  37. package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +57 -119
  38. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.js +88 -0
  39. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.spec.js +165 -0
  40. package/src/worker/webpack_loaders/workflow_validator/index.mjs +96 -29
  41. package/src/worker/webpack_loaders/workflow_validator/index.spec.js +110 -583
  42. package/src/worker/webpack_loaders/{npm_workflow_export_resolve.js → workflow_validator/npm_workflow_export_resolve.js} +1 -1
  43. package/src/worker/webpack_loaders/{npm_workflow_export_resolve.spec.js → workflow_validator/npm_workflow_export_resolve.spec.js} +1 -1
  44. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +0 -193
  45. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +0 -123
@@ -4,11 +4,6 @@ import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
  import validatorLoader from './index.mjs';
6
6
 
7
- /**
8
- * Minimal published catalog under `dir/node_modules` so `require.resolve` / export following works.
9
- * @param {string} dir - Temp project root containing `steps.js` / `workflow.js` under test.
10
- * @param {string} workflowName - Declared workflow `name` in the leaf `workflow.js`.
11
- */
12
7
  const writeMockGrowthxlabsCatalog = ( dir, workflowName ) => {
13
8
  const pkgRoot = join( dir, 'node_modules', '@growthxlabs', 'workflows_catalog' );
14
9
  const srcDir = join( pkgRoot, 'src' );
@@ -41,635 +36,167 @@ function runLoader( filename, source ) {
41
36
  }
42
37
 
43
38
  describe( 'workflow_validator loader', () => {
44
- it( 'workflow.js: allows imports from steps/evaluators/workflow', async () => {
45
- const dir = mkdtempSync( join( tmpdir(), 'wf-allow-' ) );
39
+ it( 'allows supported workflow imports and arbitrary non-component imports', async () => {
40
+ const dir = mkdtempSync( join( tmpdir(), 'wf-imports-allow-' ) );
46
41
  writeFileSync( join( dir, 'steps.js' ), 'export const S = step({ name: "s" })\n' );
47
42
  writeFileSync( join( dir, 'evaluators.js' ), 'export const E = evaluator({ name: "e" })\n' );
48
43
  writeFileSync( join( dir, 'workflow.js' ), 'export const W = workflow({ name: "w" })\n' );
44
+ mkdirSync( join( dir, 'utils' ) );
45
+ writeFileSync( join( dir, 'utils', 'helper.js' ), 'export const helper = () => 1;\n' );
49
46
 
50
- const src = [
47
+ const source = [
51
48
  'import { S } from "./steps.js";',
52
49
  'import { E } from "./evaluators.js";',
53
50
  'import { W } from "./workflow.js";',
51
+ 'import { helper } from "./utils/helper.js";',
54
52
  'const x = 1;'
55
53
  ].join( '\n' );
56
54
 
57
- await expect( runLoader( join( dir, 'workflow.js' ), src ) ).resolves.toBeTruthy();
55
+ await expect( runLoader( join( dir, 'workflow.js' ), source ) ).resolves.toBeTruthy();
58
56
  rmSync( dir, { recursive: true, force: true } );
59
57
  } );
60
58
 
61
- it( 'workflow.js: allows imports from any non-component file', async () => {
62
- const dir = mkdtempSync( join( tmpdir(), 'wf-any-allow-' ) );
63
- const src = 'import x from "./foo.js";';
64
- await expect( runLoader( join( dir, 'workflow.js' ), src ) ).resolves.toBeTruthy();
65
- rmSync( dir, { recursive: true, force: true } );
66
- } );
67
-
68
- it( 'workflow.js: allows imports from @outputai/core', async () => {
69
- const dir = mkdtempSync( join( tmpdir(), 'wf-allow-external-' ) );
70
- const src = [
71
- 'import a from "@outputai/core";',
72
- 'const z = 1;'
73
- ].join( '\n' );
74
- await expect( runLoader( join( dir, 'workflow.js' ), src ) ).resolves.toBeTruthy();
75
- rmSync( dir, { recursive: true, force: true } );
76
- } );
77
-
78
- it( 'steps.js: allows importing steps/evaluators/workflow (no import restrictions)', async () => {
79
- const dir = mkdtempSync( join( tmpdir(), 'steps-allow-import-' ) );
80
- const src = 'import { S } from "./steps.js";';
81
- const result = await runLoader( join( dir, 'steps.js' ), src );
82
- expect( result.warnings ).toHaveLength( 0 );
83
- rmSync( dir, { recursive: true, force: true } );
84
- } );
59
+ it( 'allows cross-component imports and requires in activity files', async () => {
60
+ const dir = mkdtempSync( join( tmpdir(), 'activity-cross-imports-' ) );
61
+ const stepsResult = await runLoader( join( dir, 'steps.js' ), [
62
+ 'import { E } from "./evaluators.js";',
63
+ 'const W = require("./workflow.js");'
64
+ ].join( '\n' ) );
65
+ const evaluatorResult = await runLoader( join( dir, 'evaluators.js' ), [
66
+ 'import { S } from "./steps.js";',
67
+ 'const util = require("./util.js");'
68
+ ].join( '\n' ) );
85
69
 
86
- it( 'steps.js: allows other imports', async () => {
87
- const dir = mkdtempSync( join( tmpdir(), 'steps-allow-' ) );
88
- const src = 'import x from "./util.js";\nconst obj = { fn: () => 1 };';
89
- await expect( runLoader( join( dir, 'steps.js' ), src ) ).resolves.toBeTruthy();
70
+ expect( stepsResult.warnings ).toHaveLength( 0 );
71
+ expect( evaluatorResult.warnings ).toHaveLength( 0 );
90
72
  rmSync( dir, { recursive: true, force: true } );
91
73
  } );
92
74
 
93
- it( 'evaluators.js: allows importing evaluators/steps/workflow (no import restrictions)', async () => {
94
- const dir = mkdtempSync( join( tmpdir(), 'evals-allow-import-' ) );
95
- const src = 'import { E } from "./evaluators.js";';
96
- const result = await runLoader( join( dir, 'evaluators.js' ), src );
97
- expect( result.warnings ).toHaveLength( 0 );
98
- rmSync( dir, { recursive: true, force: true } );
99
- } );
75
+ it( 'rejects default and namespace imports from activity files', async () => {
76
+ const dir = mkdtempSync( join( tmpdir(), 'activity-import-shape-' ) );
100
77
 
101
- it( 'steps.js: warns when calling imported catalog workflow inside fn', async () => {
102
- const dir = mkdtempSync( join( tmpdir(), 'steps-catalog-warn-' ) );
103
- writeMockGrowthxlabsCatalog( dir, 'cat.warn' );
104
- const src = [
105
- 'import { sumNumbers } from "@growthxlabs/workflows_catalog";',
106
- 'const A = step({ name: "a", fn: async () => ({}) });',
107
- 'const obj = { fn: function() { sumNumbers(); } };'
108
- ].join( '\n' );
109
- const result = await runLoader( join( dir, 'steps.js' ), src );
110
- expect( result.warnings ).toHaveLength( 1 );
111
- expect( result.warnings[0].message ).toMatch( /Invalid call in .*steps\.js fn: calling a workflow/ );
112
- rmSync( dir, { recursive: true, force: true } );
113
- } );
78
+ await expect( runLoader( join( dir, 'workflow.js' ), 'import StepA from "./steps.js";' ) )
79
+ .rejects.toThrow( /Invalid activity import.*named imports only/ );
80
+ await expect( runLoader( join( dir, 'workflow.js' ), 'import * as steps from "./steps.js";' ) )
81
+ .rejects.toThrow( /Invalid activity import.*named imports only/ );
82
+ await expect( runLoader( join( dir, 'workflow.js' ), 'import EvalA, { EvalB } from "./evaluators.js";' ) )
83
+ .rejects.toThrow( /Invalid activity import.*named imports only/ );
114
84
 
115
- it( 'evaluators.js: warns when calling catalog workflow inside fn (require)', async () => {
116
- const dir = mkdtempSync( join( tmpdir(), 'evals-catalog-warn-' ) );
117
- writeMockGrowthxlabsCatalog( dir, 'cat.warn.req' );
118
- const src = [
119
- 'const { sumNumbers } = require("@growthxlabs/workflows_catalog");',
120
- 'const E = evaluator({ name: "e", fn: async () => ({ value: 1 }) });',
121
- 'const obj = { fn: function() { sumNumbers(); } };'
122
- ].join( '\n' );
123
- const result = await runLoader( join( dir, 'evaluators.js' ), src );
124
- expect( result.warnings ).toHaveLength( 1 );
125
- expect( result.warnings[0].message ).toMatch( /Invalid call in .*evaluators\.js fn: calling a workflow/ );
126
85
  rmSync( dir, { recursive: true, force: true } );
127
86
  } );
128
87
 
129
- it( 'workflow.js: allows import from @growthxlabs/workflows_catalog', async () => {
130
- const dir = mkdtempSync( join( tmpdir(), 'wf-catalog-allow-' ) );
131
- writeMockGrowthxlabsCatalog( dir, 'cat.allow' );
132
- const src = [
133
- 'import { sumNumbers } from "@growthxlabs/workflows_catalog";',
134
- 'const x = 1;'
135
- ].join( '\n' );
136
- const result = await runLoader( join( dir, 'workflow.js' ), src );
137
- expect( result.warnings ).toHaveLength( 0 );
138
- rmSync( dir, { recursive: true, force: true } );
139
- } );
88
+ it( 'rejects non-destructured requires from activity files', async () => {
89
+ const dir = mkdtempSync( join( tmpdir(), 'activity-require-shape-' ) );
140
90
 
141
- it( 'steps.js: warns when calling another step inside fn', async () => {
142
- const dir = mkdtempSync( join( tmpdir(), 'steps-call-reject-' ) );
143
- // Can only test same-type components since cross-type declarations are now blocked by instantiation validation
144
- const src = [
145
- 'const A = step({ name: "a" });',
146
- 'const B = step({ name: "b" });',
147
- 'const obj = { fn: function() { B(); } };'
148
- ].join( '\n' );
149
- const result = await runLoader( join( dir, 'steps.js' ), src );
150
- expect( result.warnings ).toHaveLength( 1 );
151
- expect( result.warnings[0].message ).toMatch( /Invalid call in .*\.js fn/ );
152
- rmSync( dir, { recursive: true, force: true } );
153
- } );
91
+ await expect( runLoader( join( dir, 'workflow.js' ), 'const steps = require("./steps.js");' ) )
92
+ .rejects.toThrow( /Invalid activity require.*destructured requires only/ );
93
+ await expect( runLoader( join( dir, 'workflow.js' ), 'const evaluators = require("./evaluators.js");' ) )
94
+ .rejects.toThrow( /Invalid activity require.*destructured requires only/ );
154
95
 
155
- it( 'evaluators.js: warns when calling another evaluator inside fn', async () => {
156
- const dir = mkdtempSync( join( tmpdir(), 'evals-call-reject-' ) );
157
- // Can only test same-type components since cross-type declarations are now blocked by instantiation validation
158
- const src = [
159
- 'const E1 = evaluator({ name: "e1" });',
160
- 'const E2 = evaluator({ name: "e2" });',
161
- 'const obj = { fn: function() { E2(); } };'
162
- ].join( '\n' );
163
- const result = await runLoader( join( dir, 'evaluators.js' ), src );
164
- expect( result.warnings ).toHaveLength( 1 );
165
- expect( result.warnings[0].message ).toMatch( /Invalid call in .*\.js fn/ );
166
96
  rmSync( dir, { recursive: true, force: true } );
167
97
  } );
168
98
 
169
- it( 'steps.js/evaluators.js: allows calling unrelated local functions in fn', async () => {
170
- const dir = mkdtempSync( join( tmpdir(), 'fn-allow-' ) );
171
- const stepsSrc = [
172
- 'function helper() { return 1; }',
173
- 'const obj = { fn: function() { helper(); } };'
174
- ].join( '\n' );
175
- await expect( runLoader( join( dir, 'steps.js' ), stepsSrc ) ).resolves.toBeTruthy();
99
+ it( 'rejects non-named exports from activity files', async () => {
100
+ const dir = mkdtempSync( join( tmpdir(), 'activity-export-shape-' ) );
176
101
 
177
- const evalsSrc = [
178
- 'function helper() { return 1; }',
179
- 'const obj = { fn: () => { helper(); } };'
180
- ].join( '\n' );
181
- await expect( runLoader( join( dir, 'evaluators.js' ), evalsSrc ) ).resolves.toBeTruthy();
182
- rmSync( dir, { recursive: true, force: true } );
183
- } );
102
+ await expect( runLoader( join( dir, 'steps.js' ), 'export default step({ name: "a" });' ) )
103
+ .rejects.toThrow( /Invalid activity export.*named exports only/ );
104
+ await expect( runLoader( join( dir, 'evaluators.js' ), 'export * from "./other_evaluators.js";' ) )
105
+ .rejects.toThrow( /Invalid activity export.*named exports only/ );
106
+ await expect( runLoader( join( dir, 'steps.js' ), 'export const A = step({ name: "a" });' ) )
107
+ .resolves.toBeTruthy();
184
108
 
185
- it( 'workflow.js: allows require from steps/evaluators/workflow; allows other require', async () => {
186
- const dir = mkdtempSync( join( tmpdir(), 'wf-req-' ) );
187
- writeFileSync( join( dir, 'steps.js' ), 'export const S = step({ name: "s" })\n' );
188
- writeFileSync( join( dir, 'evaluators.js' ), 'export const E = evaluator({ name: "e" })\n' );
189
- writeFileSync( join( dir, 'workflow.js' ), 'export default workflow({ name: "w" })\n' );
190
- const ok = [
191
- 'const { S } = require("./steps.js");',
192
- 'const { E } = require("./evaluators.js");',
193
- 'const W = require("./workflow.js");'
194
- ].join( '\n' );
195
- await expect( runLoader( join( dir, 'workflow.js' ), ok ) ).resolves.toBeTruthy();
196
- // Also allow random files (not rejected anymore)
197
- const ok2 = 'const X = require("./random_file.js");';
198
- await expect( runLoader( join( dir, 'workflow.js' ), ok2 ) ).resolves.toBeTruthy();
199
109
  rmSync( dir, { recursive: true, force: true } );
200
110
  } );
201
111
 
202
- it( 'steps.js: allows importing evaluators/workflow variants (no import restrictions)', async () => {
203
- const dir = mkdtempSync( join( tmpdir(), 'steps-allow-import2-' ) );
204
- const result1 = await runLoader( join( dir, 'steps.js' ), 'import { E } from "./evaluators.js";' );
205
- expect( result1.warnings ).toHaveLength( 0 );
206
- const result2 = await runLoader( join( dir, 'steps.js' ), 'import WF from "./workflow.js";' );
207
- expect( result2.warnings ).toHaveLength( 0 );
208
- rmSync( dir, { recursive: true, force: true } );
209
- } );
112
+ it( 'rejects top-level activity calls but allows calls inside helper functions', async () => {
113
+ const dir = mkdtempSync( join( tmpdir(), 'top-level-activity-calls-' ) );
210
114
 
211
- it( 'evaluators.js: allows importing steps/workflow variants (no import restrictions)', async () => {
212
- const dir = mkdtempSync( join( tmpdir(), 'evals-allow-import2-' ) );
213
- const result1 = await runLoader( join( dir, 'evaluators.js' ), 'import { S } from "./steps.js";' );
214
- expect( result1.warnings ).toHaveLength( 0 );
215
- const result2 = await runLoader( join( dir, 'evaluators.js' ), 'import WF from "./workflow.js";' );
216
- expect( result2.warnings ).toHaveLength( 0 );
217
- rmSync( dir, { recursive: true, force: true } );
218
- } );
115
+ await expect( runLoader( join( dir, 'steps.js' ), 'const A = step({ name: "a" });\nA();' ) )
116
+ .rejects.toThrow( /Invalid top-level step call 'A'/ );
117
+ await expect( runLoader( join( dir, 'evaluators.js' ), 'const E = evaluator({ name: "e" });\nE();' ) )
118
+ .rejects.toThrow( /Invalid top-level evaluator call 'E'/ );
119
+ await expect( runLoader( join( dir, 'steps.js' ), 'const A = step({ name: "a" });\nfunction helper() { return A(); }' ) )
120
+ .resolves.toBeTruthy();
219
121
 
220
- it( 'top-level calls outside fn are allowed in steps.js and evaluators.js', async () => {
221
- const dir = mkdtempSync( join( tmpdir(), 'toplevel-allowed-' ) );
222
- const stepsTop = [ 'const A = step({ name: "a" });', 'A();' ].join( '\n' );
223
- await expect( runLoader( join( dir, 'steps.js' ), stepsTop ) ).resolves.toBeTruthy();
224
- const evaluatorsTop = [ 'const E = evaluator({ name: "e" });', 'E();' ].join( '\n' );
225
- await expect( runLoader( join( dir, 'evaluators.js' ), evaluatorsTop ) ).resolves.toBeTruthy();
226
122
  rmSync( dir, { recursive: true, force: true } );
227
123
  } );
228
124
 
229
- it( 'workflow.js: allows importing ./types.js and bare types', async () => {
230
- const dir = mkdtempSync( join( tmpdir(), 'wf-types-allow-' ) );
231
- writeFileSync( join( dir, 'types.js' ), 'export const T = {}\n' );
232
- const src1 = 'import { T } from "./types.js";';
233
- await expect( runLoader( join( dir, 'workflow.js' ), src1 ) ).resolves.toBeTruthy();
234
- rmSync( dir, { recursive: true, force: true } );
235
- } );
236
-
237
- it( 'workflow.js: allows importing any file (consts/constants/vars/variables/random)', async () => {
238
- const dir = mkdtempSync( join( tmpdir(), 'wf-extra-allow-' ) );
239
- const bases = [ 'consts', 'constants', 'vars', 'variables', 'random', 'anything' ];
240
- for ( const base of bases ) {
241
- writeFileSync( join( dir, `${base}.js` ), 'export const X = 1\n' );
242
- const src = `import x from "./${base}.js";`;
243
- await expect( runLoader( join( dir, 'workflow.js' ), src ) ).resolves.toBeTruthy();
244
- }
245
- rmSync( dir, { recursive: true, force: true } );
246
- } );
125
+ it( 'warns when activity fn bodies call activities or workflows', async () => {
126
+ const dir = mkdtempSync( join( tmpdir(), 'activity-fn-call-warnings-' ) );
127
+ writeMockGrowthxlabsCatalog( dir, 'cat.warn' );
247
128
 
248
- it( 'steps.js: allows require of steps/evaluators/workflow (no import restrictions)', async () => {
249
- const dir = mkdtempSync( join( tmpdir(), 'steps-require-allow-' ) );
250
- const result1 = await runLoader( join( dir, 'steps.js' ), 'const { S } = require("./steps.js");' );
251
- expect( result1.warnings ).toHaveLength( 0 );
252
- const result2 = await runLoader( join( dir, 'steps.js' ), 'const { E } = require("./evaluators.js");' );
253
- expect( result2.warnings ).toHaveLength( 0 );
254
- const result3 = await runLoader( join( dir, 'steps.js' ), 'const W = require("./workflow.js");' );
255
- expect( result3.warnings ).toHaveLength( 0 );
256
- const ok = 'const util = require("./util.js");';
257
- const resultOk = await runLoader( join( dir, 'steps.js' ), ok );
258
- expect( resultOk.warnings ).toHaveLength( 0 );
259
- rmSync( dir, { recursive: true, force: true } );
260
- } );
129
+ const stepResult = await runLoader( join( dir, 'steps.js' ), [
130
+ 'const A = step({ name: "a" });',
131
+ 'const B = step({ name: "b" });',
132
+ 'const obj = { fn: function() { B(); } };'
133
+ ].join( '\n' ) );
134
+ const evaluatorResult = await runLoader( join( dir, 'evaluators.js' ), [
135
+ 'import { sumNumbers } from "@growthxlabs/workflows_catalog";',
136
+ 'const E = evaluator({ name: "e", fn: async () => ({ value: 1 }) });',
137
+ 'const obj = { fn: function() { sumNumbers(); } };'
138
+ ].join( '\n' ) );
261
139
 
262
- it( 'evaluators.js: allows require of steps/workflow (no import restrictions)', async () => {
263
- const dir = mkdtempSync( join( tmpdir(), 'evals-require-allow-' ) );
264
- const result1 = await runLoader( join( dir, 'evaluators.js' ), 'const { S } = require("./steps.js");' );
265
- expect( result1.warnings ).toHaveLength( 0 );
266
- const result2 = await runLoader( join( dir, 'evaluators.js' ), 'const W = require("./workflow.js");' );
267
- expect( result2.warnings ).toHaveLength( 0 );
268
- const ok = 'const util = require("./util.js");';
269
- const resultOk = await runLoader( join( dir, 'evaluators.js' ), ok );
270
- expect( resultOk.warnings ).toHaveLength( 0 );
140
+ expect( stepResult.warnings ).toHaveLength( 1 );
141
+ expect( stepResult.warnings[0].message ).toMatch( /Invalid call in .*steps\.js fn: calling a step/ );
142
+ expect( evaluatorResult.warnings ).toHaveLength( 1 );
143
+ expect( evaluatorResult.warnings[0].message ).toMatch( /Invalid call in .*evaluators\.js fn: calling a workflow/ );
271
144
  rmSync( dir, { recursive: true, force: true } );
272
145
  } );
273
146
 
274
- // =====================================================
275
- // Folder-based utilities and shared directory
276
- // =====================================================
277
-
278
- describe( 'folder-based utilities', () => {
279
- it( 'workflow.js: allows imports from ./utils/helper.js (folder-based utils)', async () => {
280
- const dir = mkdtempSync( join( tmpdir(), 'wf-folder-utils-' ) );
281
- mkdirSync( join( dir, 'utils' ) );
282
- writeFileSync( join( dir, 'utils', 'helper.js' ), 'export const helper = () => 1;\n' );
283
- const src = 'import { helper } from "./utils/helper.js";';
284
- await expect( runLoader( join( dir, 'workflow.js' ), src ) ).resolves.toBeTruthy();
285
- rmSync( dir, { recursive: true, force: true } );
286
- } );
287
-
288
- it( 'workflow.js: allows imports from ./clients/redis.js (folder-based clients)', async () => {
289
- const dir = mkdtempSync( join( tmpdir(), 'wf-folder-clients-' ) );
290
- mkdirSync( join( dir, 'clients' ) );
291
- writeFileSync( join( dir, 'clients', 'redis.js' ), 'export const client = {};\n' );
292
- const src = 'import { client } from "./clients/redis.js";';
293
- await expect( runLoader( join( dir, 'workflow.js' ), src ) ).resolves.toBeTruthy();
294
- rmSync( dir, { recursive: true, force: true } );
295
- } );
296
-
297
- it( 'steps/fetch_data.js: allows imports from ../utils/http.js', async () => {
298
- const dir = mkdtempSync( join( tmpdir(), 'steps-folder-utils-' ) );
299
- mkdirSync( join( dir, 'steps' ) );
300
- mkdirSync( join( dir, 'utils' ) );
301
- writeFileSync( join( dir, 'utils', 'http.js' ), 'export const get = () => {};\n' );
302
- const src = 'import { get } from "../utils/http.js";';
303
- await expect( runLoader( join( dir, 'steps', 'fetch_data.js' ), src ) ).resolves.toBeTruthy();
304
- rmSync( dir, { recursive: true, force: true } );
305
- } );
306
-
307
- it( 'evaluators/quality.js: allows imports from ../utils/metrics.js', async () => {
308
- const dir = mkdtempSync( join( tmpdir(), 'evals-folder-utils-' ) );
309
- mkdirSync( join( dir, 'evaluators' ) );
310
- mkdirSync( join( dir, 'utils' ) );
311
- writeFileSync( join( dir, 'utils', 'metrics.js' ), 'export const compute = () => {};\n' );
312
- const src = 'import { compute } from "../utils/metrics.js";';
313
- await expect( runLoader( join( dir, 'evaluators', 'quality.js' ), src ) ).resolves.toBeTruthy();
314
- rmSync( dir, { recursive: true, force: true } );
315
- } );
316
- } );
317
-
318
- describe( 'shared directory imports', () => {
319
- it( 'workflow.js: allows imports from ../../shared/utils/keys.js', async () => {
320
- const dir = mkdtempSync( join( tmpdir(), 'wf-shared-utils-' ) );
321
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
322
- mkdirSync( join( dir, 'shared', 'utils' ), { recursive: true } );
323
- writeFileSync( join( dir, 'shared', 'utils', 'keys.js' ), 'export const KEY = "test";\n' );
324
- const src = 'import { KEY } from "../../shared/utils/keys.js";';
325
- await expect( runLoader( join( dir, 'workflows', 'my_workflow', 'workflow.js' ), src ) ).resolves.toBeTruthy();
326
- rmSync( dir, { recursive: true, force: true } );
327
- } );
328
-
329
- it( 'workflow.js: allows imports from ../../shared/steps/common.js', async () => {
330
- const dir = mkdtempSync( join( tmpdir(), 'wf-shared-steps-' ) );
331
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
332
- mkdirSync( join( dir, 'shared', 'steps' ), { recursive: true } );
333
- writeFileSync( join( dir, 'shared', 'steps', 'common.js' ), 'export const commonStep = step({ name: "common" });\n' );
334
- const src = 'import { commonStep } from "../../shared/steps/common.js";';
335
- await expect( runLoader( join( dir, 'workflows', 'my_workflow', 'workflow.js' ), src ) ).resolves.toBeTruthy();
336
- rmSync( dir, { recursive: true, force: true } );
337
- } );
338
-
339
- it( 'workflow.js: allows imports from ../../shared/evaluators/quality.js', async () => {
340
- const dir = mkdtempSync( join( tmpdir(), 'wf-shared-evals-' ) );
341
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
342
- mkdirSync( join( dir, 'shared', 'evaluators' ), { recursive: true } );
343
- writeFileSync( join( dir, 'shared', 'evaluators', 'quality.js' ), 'export const qualityEval = evaluator({ name: "quality" });\n' );
344
- const src = 'import { qualityEval } from "../../shared/evaluators/quality.js";';
345
- await expect( runLoader( join( dir, 'workflows', 'my_workflow', 'workflow.js' ), src ) ).resolves.toBeTruthy();
346
- rmSync( dir, { recursive: true, force: true } );
347
- } );
348
-
349
- it( 'workflow.js: allows imports from ../../clients/pokeapi.js', async () => {
350
- const dir = mkdtempSync( join( tmpdir(), 'wf-clients-' ) );
351
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
352
- mkdirSync( join( dir, 'clients' ), { recursive: true } );
353
- writeFileSync( join( dir, 'clients', 'pokeapi.js' ), 'export const getPokemon = () => {};\n' );
354
- const src = 'import { getPokemon } from "../../clients/pokeapi.js";';
355
- await expect( runLoader( join( dir, 'workflows', 'my_workflow', 'workflow.js' ), src ) ).resolves.toBeTruthy();
356
- rmSync( dir, { recursive: true, force: true } );
357
- } );
358
-
359
- it( 'steps.ts: allows imports from ../../shared/utils/keys.js', async () => {
360
- const dir = mkdtempSync( join( tmpdir(), 'steps-shared-utils-' ) );
361
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
362
- mkdirSync( join( dir, 'shared', 'utils' ), { recursive: true } );
363
- writeFileSync( join( dir, 'shared', 'utils', 'keys.js' ), 'export const KEY = "test";\n' );
364
- const src = 'import { KEY } from "../../shared/utils/keys.js";';
365
- await expect( runLoader( join( dir, 'workflows', 'my_workflow', 'steps.js' ), src ) ).resolves.toBeTruthy();
366
- rmSync( dir, { recursive: true, force: true } );
367
- } );
368
-
369
- it( 'steps.ts: allows imports from ../../clients/redis.js', async () => {
370
- const dir = mkdtempSync( join( tmpdir(), 'steps-clients-' ) );
371
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
372
- mkdirSync( join( dir, 'clients' ), { recursive: true } );
373
- writeFileSync( join( dir, 'clients', 'redis.js' ), 'export const client = {};\n' );
374
- const src = 'import { client } from "../../clients/redis.js";';
375
- await expect( runLoader( join( dir, 'workflows', 'my_workflow', 'steps.js' ), src ) ).resolves.toBeTruthy();
376
- rmSync( dir, { recursive: true, force: true } );
377
- } );
378
-
379
- it( 'evaluators.ts: allows imports from ../../shared/utils/helpers.js', async () => {
380
- const dir = mkdtempSync( join( tmpdir(), 'evals-shared-utils-' ) );
381
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
382
- mkdirSync( join( dir, 'shared', 'utils' ), { recursive: true } );
383
- writeFileSync( join( dir, 'shared', 'utils', 'helpers.js' ), 'export const helper = () => 1;\n' );
384
- const src = 'import { helper } from "../../shared/utils/helpers.js";';
385
- await expect( runLoader( join( dir, 'workflows', 'my_workflow', 'evaluators.js' ), src ) ).resolves.toBeTruthy();
386
- rmSync( dir, { recursive: true, force: true } );
387
- } );
388
- } );
389
-
390
- describe( 'cross-component imports - now allowed (no import restrictions)', () => {
391
- it( 'steps.ts: allows imports from ../../shared/steps/common.js', async () => {
392
- const dir = mkdtempSync( join( tmpdir(), 'steps-shared-steps-allow-' ) );
393
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
394
- mkdirSync( join( dir, 'shared', 'steps' ), { recursive: true } );
395
- writeFileSync( join( dir, 'shared', 'steps', 'common.js' ), 'export const commonStep = step({ name: "common" });\n' );
396
- const src = 'import { commonStep } from "../../shared/steps/common.js";';
397
- const result = await runLoader( join( dir, 'workflows', 'my_workflow', 'steps.js' ), src );
398
- expect( result.warnings ).toHaveLength( 0 );
399
- rmSync( dir, { recursive: true, force: true } );
400
- } );
401
-
402
- it( 'steps.ts: allows imports from ../../shared/evaluators/quality.js', async () => {
403
- const dir = mkdtempSync( join( tmpdir(), 'steps-shared-evals-allow-' ) );
404
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
405
- mkdirSync( join( dir, 'shared', 'evaluators' ), { recursive: true } );
406
- writeFileSync( join( dir, 'shared', 'evaluators', 'quality.js' ), 'export const qualityEval = evaluator({ name: "quality" });\n' );
407
- const src = 'import { qualityEval } from "../../shared/evaluators/quality.js";';
408
- const result = await runLoader( join( dir, 'workflows', 'my_workflow', 'steps.js' ), src );
409
- expect( result.warnings ).toHaveLength( 0 );
410
- rmSync( dir, { recursive: true, force: true } );
411
- } );
147
+ it( 'allows component instantiation in matching files', async () => {
148
+ const dir = mkdtempSync( join( tmpdir(), 'instantiation-allowed-' ) );
149
+ mkdirSync( join( dir, 'steps' ) );
150
+ mkdirSync( join( dir, 'evaluators' ) );
412
151
 
413
- it( 'evaluators.ts: allows imports from ../../shared/steps/common.js', async () => {
414
- const dir = mkdtempSync( join( tmpdir(), 'evals-shared-steps-allow-' ) );
415
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
416
- mkdirSync( join( dir, 'shared', 'steps' ), { recursive: true } );
417
- writeFileSync( join( dir, 'shared', 'steps', 'common.js' ), 'export const commonStep = step({ name: "common" });\n' );
418
- const src = 'import { commonStep } from "../../shared/steps/common.js";';
419
- const result = await runLoader( join( dir, 'workflows', 'my_workflow', 'evaluators.js' ), src );
420
- expect( result.warnings ).toHaveLength( 0 );
421
- rmSync( dir, { recursive: true, force: true } );
422
- } );
152
+ await expect( runLoader(
153
+ join( dir, 'steps', 'fetch_data.js' ),
154
+ 'export const fetchData = step({ name: "fetch_data", fn: async () => ({}) });'
155
+ ) ).resolves.toBeTruthy();
156
+ await expect( runLoader(
157
+ join( dir, 'evaluators', 'quality.js' ),
158
+ 'export const quality = evaluator({ name: "quality", fn: async () => ({ value: 1 }) });'
159
+ ) ).resolves.toBeTruthy();
160
+ await expect( runLoader(
161
+ join( dir, 'workflow.js' ),
162
+ 'export default workflow({ name: "wf", fn: async () => ({}) });'
163
+ ) ).resolves.toBeTruthy();
423
164
 
424
- it( 'evaluators.ts: allows imports from ../../shared/evaluators/other.js', async () => {
425
- const dir = mkdtempSync( join( tmpdir(), 'evals-shared-evals-allow-' ) );
426
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
427
- mkdirSync( join( dir, 'shared', 'evaluators' ), { recursive: true } );
428
- writeFileSync( join( dir, 'shared', 'evaluators', 'other.js' ), 'export const otherEval = evaluator({ name: "other" });\n' );
429
- const src = 'import { otherEval } from "../../shared/evaluators/other.js";';
430
- const result = await runLoader( join( dir, 'workflows', 'my_workflow', 'evaluators.js' ), src );
431
- expect( result.warnings ).toHaveLength( 0 );
432
- rmSync( dir, { recursive: true, force: true } );
433
- } );
434
- } );
435
-
436
- describe( 'workflow import scope', () => {
437
- it( 'workflow.js: allows imports from other workflow directories (cross-workflow)', async () => {
438
- const dir = mkdtempSync( join( tmpdir(), 'wf-cross-allow-' ) );
439
- mkdirSync( join( dir, 'workflows', 'workflow_a' ), { recursive: true } );
440
- mkdirSync( join( dir, 'workflows', 'workflow_b' ), { recursive: true } );
441
- writeFileSync( join( dir, 'workflows', 'workflow_b', 'steps.js' ), 'export const S = step({ name: "s" });\n' );
442
- // Cross-workflow imports are allowed for workflows (they can import steps from other workflows)
443
- const src = 'import { S } from "../workflow_b/steps.js";';
444
- await expect( runLoader( join( dir, 'workflows', 'workflow_a', 'workflow.js' ), src ) ).resolves.toBeTruthy();
445
- rmSync( dir, { recursive: true, force: true } );
446
- } );
447
-
448
- it( 'workflow.js: allows imports from utils with relative parent paths', async () => {
449
- // The validator does not enforce strict scope boundaries, it validates by file type patterns
450
- const dir = mkdtempSync( join( tmpdir(), 'wf-parent-utils-' ) );
451
- mkdirSync( join( dir, 'src', 'workflows', 'my_workflow' ), { recursive: true } );
452
- mkdirSync( join( dir, 'utils' ), { recursive: true } );
453
- writeFileSync( join( dir, 'utils', 'helpers.js' ), 'export const helper = () => 1;\n' );
454
- const src = 'import { helper } from "../../../utils/helpers.js";';
455
- // This should pass because it's not a component file
456
- await expect( runLoader( join( dir, 'src', 'workflows', 'my_workflow', 'workflow.js' ), src ) ).resolves.toBeTruthy();
457
- rmSync( dir, { recursive: true, force: true } );
458
- } );
459
- } );
460
-
461
- // =====================================================
462
- // Arbitrary path imports (proving any folder name works)
463
- // =====================================================
464
-
465
- describe( 'arbitrary path imports', () => {
466
- it( 'workflow.js: allows imports from ./foobar.js', async () => {
467
- const dir = mkdtempSync( join( tmpdir(), 'wf-arbitrary-1-' ) );
468
- const src = 'import { foo } from "./foobar.js";';
469
- await expect( runLoader( join( dir, 'workflow.js' ), src ) ).resolves.toBeTruthy();
470
- rmSync( dir, { recursive: true, force: true } );
471
- } );
472
-
473
- it( 'workflow.js: allows imports from ./foobar/baz.js', async () => {
474
- const dir = mkdtempSync( join( tmpdir(), 'wf-arbitrary-2-' ) );
475
- mkdirSync( join( dir, 'foobar' ) );
476
- writeFileSync( join( dir, 'foobar', 'baz.js' ), 'export const baz = 1;\n' );
477
- const src = 'import { baz } from "./foobar/baz.js";';
478
- await expect( runLoader( join( dir, 'workflow.js' ), src ) ).resolves.toBeTruthy();
479
- rmSync( dir, { recursive: true, force: true } );
480
- } );
481
-
482
- it( 'workflow.js: allows imports from ../../shared/foobar.js', async () => {
483
- const dir = mkdtempSync( join( tmpdir(), 'wf-arbitrary-3-' ) );
484
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
485
- mkdirSync( join( dir, 'shared' ), { recursive: true } );
486
- writeFileSync( join( dir, 'shared', 'foobar.js' ), 'export const foobar = 1;\n' );
487
- const src = 'import { foobar } from "../../shared/foobar.js";';
488
- await expect( runLoader( join( dir, 'workflows', 'my_workflow', 'workflow.js' ), src ) ).resolves.toBeTruthy();
489
- rmSync( dir, { recursive: true, force: true } );
490
- } );
491
-
492
- it( 'workflow.js: allows imports from ../../shared/foobar/baz.js', async () => {
493
- const dir = mkdtempSync( join( tmpdir(), 'wf-arbitrary-4-' ) );
494
- mkdirSync( join( dir, 'workflows', 'my_workflow' ), { recursive: true } );
495
- mkdirSync( join( dir, 'shared', 'foobar' ), { recursive: true } );
496
- writeFileSync( join( dir, 'shared', 'foobar', 'baz.js' ), 'export const baz = 1;\n' );
497
- const src = 'import { baz } from "../../shared/foobar/baz.js";';
498
- await expect( runLoader( join( dir, 'workflows', 'my_workflow', 'workflow.js' ), src ) ).resolves.toBeTruthy();
499
- rmSync( dir, { recursive: true, force: true } );
500
- } );
501
-
502
- it( 'steps.js: allows imports from ./helpers/anything.js', async () => {
503
- const dir = mkdtempSync( join( tmpdir(), 'steps-arbitrary-1-' ) );
504
- mkdirSync( join( dir, 'helpers' ) );
505
- writeFileSync( join( dir, 'helpers', 'anything.js' ), 'export const anything = 1;\n' );
506
- const src = 'import { anything } from "./helpers/anything.js";';
507
- await expect( runLoader( join( dir, 'steps.js' ), src ) ).resolves.toBeTruthy();
508
- rmSync( dir, { recursive: true, force: true } );
509
- } );
510
-
511
- it( 'evaluators.js: allows imports from ./my_custom_lib/tools.js', async () => {
512
- const dir = mkdtempSync( join( tmpdir(), 'evals-arbitrary-1-' ) );
513
- mkdirSync( join( dir, 'my_custom_lib' ) );
514
- writeFileSync( join( dir, 'my_custom_lib', 'tools.js' ), 'export const tools = {};\n' );
515
- const src = 'import { tools } from "./my_custom_lib/tools.js";';
516
- await expect( runLoader( join( dir, 'evaluators.js' ), src ) ).resolves.toBeTruthy();
517
- rmSync( dir, { recursive: true, force: true } );
518
- } );
165
+ rmSync( dir, { recursive: true, force: true } );
519
166
  } );
520
167
 
521
- // =====================================================
522
- // Instantiation location validation
523
- // =====================================================
168
+ it( 'rejects component instantiation in mismatched files', async () => {
169
+ const dir = mkdtempSync( join( tmpdir(), 'instantiation-rejected-' ) );
170
+ mkdirSync( join( dir, 'shared' ) );
524
171
 
525
- describe( 'instantiation location tests - correct locations', () => {
526
- it( 'step() called in steps/fetch_data.js is allowed', async () => {
527
- const dir = mkdtempSync( join( tmpdir(), 'step-folder-allowed-' ) );
528
- mkdirSync( join( dir, 'steps' ) );
529
- const src = [
530
- 'import { step } from "@outputai/core";',
531
- 'export const fetchData = step({ name: "fetch_data", fn: async () => ({}) });'
532
- ].join( '\n' );
533
- await expect( runLoader( join( dir, 'steps', 'fetch_data.js' ), src ) ).resolves.toBeTruthy();
534
- rmSync( dir, { recursive: true, force: true } );
535
- } );
172
+ await expect( runLoader(
173
+ join( dir, 'utils.js' ),
174
+ 'export const badStep = step({ name: "bad", fn: async () => ({}) });'
175
+ ) ).rejects.toThrow( /Invalid instantiation location.*step\(\).*steps/ );
176
+ await expect( runLoader(
177
+ join( dir, 'steps.js' ),
178
+ 'export const badEval = evaluator({ name: "bad", fn: async () => ({ value: 1 }) });'
179
+ ) ).rejects.toThrow( /Invalid instantiation location.*evaluator\(\).*evaluators/ );
180
+ await expect( runLoader(
181
+ join( dir, 'shared', 'common.js' ),
182
+ 'export const badWf = workflow({ name: "bad", fn: async () => ({}) });'
183
+ ) ).rejects.toThrow( /Invalid instantiation location.*workflow\(\).*workflow/ );
536
184
 
537
- it( 'step() called in src/shared/steps/common.js is allowed', async () => {
538
- const dir = mkdtempSync( join( tmpdir(), 'step-shared-allowed-' ) );
539
- mkdirSync( join( dir, 'src', 'shared', 'steps' ), { recursive: true } );
540
- const src = [
541
- 'import { step } from "@outputai/core";',
542
- 'export const commonStep = step({ name: "common_step", fn: async () => ({}) });'
543
- ].join( '\n' );
544
- await expect( runLoader( join( dir, 'src', 'shared', 'steps', 'common.js' ), src ) ).resolves.toBeTruthy();
545
- rmSync( dir, { recursive: true, force: true } );
546
- } );
547
-
548
- it( 'evaluator() called in evaluators/quality.js is allowed', async () => {
549
- const dir = mkdtempSync( join( tmpdir(), 'eval-folder-allowed-' ) );
550
- mkdirSync( join( dir, 'evaluators' ) );
551
- const src = [
552
- 'import { evaluator } from "@outputai/core";',
553
- 'export const quality = evaluator({ name: "quality", fn: async () => ({ value: 1 }) });'
554
- ].join( '\n' );
555
- await expect( runLoader( join( dir, 'evaluators', 'quality.js' ), src ) ).resolves.toBeTruthy();
556
- rmSync( dir, { recursive: true, force: true } );
557
- } );
558
-
559
- it( 'evaluator() called in src/shared/evaluators/metrics.js is allowed', async () => {
560
- const dir = mkdtempSync( join( tmpdir(), 'eval-shared-allowed-' ) );
561
- mkdirSync( join( dir, 'src', 'shared', 'evaluators' ), { recursive: true } );
562
- const src = [
563
- 'import { evaluator } from "@outputai/core";',
564
- 'export const metrics = evaluator({ name: "metrics", fn: async () => ({ value: 1 }) });'
565
- ].join( '\n' );
566
- await expect( runLoader( join( dir, 'src', 'shared', 'evaluators', 'metrics.js' ), src ) ).resolves.toBeTruthy();
567
- rmSync( dir, { recursive: true, force: true } );
568
- } );
185
+ rmSync( dir, { recursive: true, force: true } );
569
186
  } );
570
187
 
571
- describe( 'instantiation location tests - wrong locations', () => {
572
- it( 'step() called in utils.js is blocked by validator', async () => {
573
- const dir = mkdtempSync( join( tmpdir(), 'step-utils-fail-' ) );
574
- const src = [
575
- 'import { step } from "@outputai/core";',
576
- 'export const badStep = step({ name: "bad", fn: async () => ({}) });'
577
- ].join( '\n' );
578
- await expect( runLoader( join( dir, 'utils.js' ), src ) )
579
- .rejects.toThrow( /Invalid instantiation location.*step\(\).*steps/ );
580
- rmSync( dir, { recursive: true, force: true } );
581
- } );
582
-
583
- it( 'step() called in clients/api.js is blocked by validator', async () => {
584
- const dir = mkdtempSync( join( tmpdir(), 'step-clients-fail-' ) );
585
- mkdirSync( join( dir, 'clients' ) );
586
- const src = [
587
- 'import { step } from "@outputai/core";',
588
- 'export const badStep = step({ name: "bad", fn: async () => ({}) });'
589
- ].join( '\n' );
590
- await expect( runLoader( join( dir, 'clients', 'api.js' ), src ) )
591
- .rejects.toThrow( /Invalid instantiation location.*step\(\).*steps/ );
592
- rmSync( dir, { recursive: true, force: true } );
593
- } );
594
-
595
- it( 'evaluator() called in utils.js is blocked by validator', async () => {
596
- const dir = mkdtempSync( join( tmpdir(), 'eval-utils-fail-' ) );
597
- const src = [
598
- 'import { evaluator } from "@outputai/core";',
599
- 'export const badEval = evaluator({ name: "bad", fn: async () => ({ value: 1 }) });'
600
- ].join( '\n' );
601
- await expect( runLoader( join( dir, 'utils.js' ), src ) )
602
- .rejects.toThrow( /Invalid instantiation location.*evaluator\(\).*evaluators/ );
603
- rmSync( dir, { recursive: true, force: true } );
604
- } );
605
-
606
- it( 'evaluator() called in clients/api.js is blocked by validator', async () => {
607
- const dir = mkdtempSync( join( tmpdir(), 'eval-clients-fail-' ) );
608
- mkdirSync( join( dir, 'clients' ) );
609
- const src = [
610
- 'import { evaluator } from "@outputai/core";',
611
- 'export const badEval = evaluator({ name: "bad", fn: async () => ({ value: 1 }) });'
612
- ].join( '\n' );
613
- await expect( runLoader( join( dir, 'clients', 'api.js' ), src ) )
614
- .rejects.toThrow( /Invalid instantiation location.*evaluator\(\).*evaluators/ );
615
- rmSync( dir, { recursive: true, force: true } );
616
- } );
617
-
618
- it( 'evaluator() called in helpers.js is blocked by validator', async () => {
619
- const dir = mkdtempSync( join( tmpdir(), 'eval-helpers-fail-' ) );
620
- const src = [
621
- 'import { evaluator } from "@outputai/core";',
622
- 'export const badEval = evaluator({ name: "bad", fn: async () => ({ value: 1 }) });'
623
- ].join( '\n' );
624
- await expect( runLoader( join( dir, 'helpers.js' ), src ) )
625
- .rejects.toThrow( /Invalid instantiation location.*evaluator\(\).*evaluators/ );
626
- rmSync( dir, { recursive: true, force: true } );
627
- } );
628
-
629
- it( 'step() called in evaluators.js is blocked by validator', async () => {
630
- const dir = mkdtempSync( join( tmpdir(), 'step-evals-fail-' ) );
631
- const src = [
632
- 'import { step } from "@outputai/core";',
633
- 'export const badStep = step({ name: "bad", fn: async () => ({}) });'
634
- ].join( '\n' );
635
- await expect( runLoader( join( dir, 'evaluators.js' ), src ) )
636
- .rejects.toThrow( /Invalid instantiation location.*step\(\).*steps/ );
637
- rmSync( dir, { recursive: true, force: true } );
638
- } );
639
-
640
- it( 'evaluator() called in steps.js is blocked by validator', async () => {
641
- const dir = mkdtempSync( join( tmpdir(), 'eval-steps-fail-' ) );
642
- const src = [
643
- 'import { evaluator } from "@outputai/core";',
644
- 'export const badEval = evaluator({ name: "bad", fn: async () => ({ value: 1 }) });'
645
- ].join( '\n' );
646
- await expect( runLoader( join( dir, 'steps.js' ), src ) )
647
- .rejects.toThrow( /Invalid instantiation location.*evaluator\(\).*evaluators/ );
648
- rmSync( dir, { recursive: true, force: true } );
649
- } );
188
+ it( 'resolves bare npm workflows for require warnings in activity fn bodies', async () => {
189
+ const dir = mkdtempSync( join( tmpdir(), 'bare-require-workflow-warning-' ) );
190
+ writeMockGrowthxlabsCatalog( dir, 'cat.warn.req' );
650
191
 
651
- it( 'workflow() called in shared/common.js is blocked by validator', async () => {
652
- const dir = mkdtempSync( join( tmpdir(), 'wf-shared-fail-' ) );
653
- mkdirSync( join( dir, 'shared' ) );
654
- const src = [
655
- 'import { workflow } from "@outputai/core";',
656
- 'export const badWf = workflow({ name: "bad", fn: async () => ({}) });'
657
- ].join( '\n' );
658
- await expect( runLoader( join( dir, 'shared', 'common.js' ), src ) )
659
- .rejects.toThrow( /Invalid instantiation location.*workflow\(\).*workflow/ );
660
- rmSync( dir, { recursive: true, force: true } );
661
- } );
192
+ const result = await runLoader( join( dir, 'evaluators.js' ), [
193
+ 'const { sumNumbers } = require("@growthxlabs/workflows_catalog");',
194
+ 'const E = evaluator({ name: "e", fn: async () => ({ value: 1 }) });',
195
+ 'const obj = { fn: function() { sumNumbers(); } };'
196
+ ].join( '\n' ) );
662
197
 
663
- it( 'workflow() called in utils/index.js is blocked by validator', async () => {
664
- const dir = mkdtempSync( join( tmpdir(), 'wf-utils-fail-' ) );
665
- mkdirSync( join( dir, 'utils' ) );
666
- const src = [
667
- 'import { workflow } from "@outputai/core";',
668
- 'export const badWf = workflow({ name: "bad", fn: async () => ({}) });'
669
- ].join( '\n' );
670
- await expect( runLoader( join( dir, 'utils', 'index.js' ), src ) )
671
- .rejects.toThrow( /Invalid instantiation location.*workflow\(\).*workflow/ );
672
- rmSync( dir, { recursive: true, force: true } );
673
- } );
198
+ expect( result.warnings ).toHaveLength( 1 );
199
+ expect( result.warnings[0].message ).toMatch( /Invalid call in .*evaluators\.js fn: calling a workflow/ );
200
+ rmSync( dir, { recursive: true, force: true } );
674
201
  } );
675
202
  } );