@outputai/core 0.14.0 → 0.14.1-next.2947341.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/dist/credentials/credentials.d.ts +6 -0
- package/dist/credentials/credentials.js +87 -0
- package/dist/credentials/encrypted_yaml_provider.d.ts +2 -0
- package/dist/credentials/encrypted_yaml_provider.js +77 -0
- package/dist/credentials/encryption.d.ts +3 -0
- package/dist/credentials/encryption.js +6 -0
- package/dist/credentials/errors.d.ts +13 -0
- package/dist/credentials/errors.js +39 -0
- package/dist/credentials/index.d.ts +6 -0
- package/dist/credentials/index.js +5 -0
- package/dist/credentials/init.d.ts +1 -0
- package/dist/credentials/init.js +7 -0
- package/dist/credentials/paths.d.ts +7 -0
- package/dist/credentials/paths.js +14 -0
- package/dist/credentials/provider_registry.d.ts +3 -0
- package/dist/credentials/provider_registry.js +6 -0
- package/dist/credentials/types.d.ts +12 -0
- package/dist/credentials/types.js +1 -0
- package/package.json +25 -14
- package/src/async_storage.d.ts +4 -0
- package/src/consts.js +0 -2
- package/src/helpers/object.d.ts +2 -0
- package/src/hooks/index.d.ts +0 -8
- package/src/hooks/index.js +0 -3
- package/src/logger/index.d.ts +4 -0
- package/src/worker/index.js +3 -1
- package/bin/healthcheck.spec.js +0 -90
- package/src/bus.spec.js +0 -196
- package/src/errors.spec.js +0 -19
- package/src/helpers/activity_options.spec.js +0 -55
- package/src/helpers/component.spec.js +0 -54
- package/src/helpers/error_serializer.spec.js +0 -479
- package/src/helpers/errors.spec.js +0 -189
- package/src/helpers/fetch.spec.js +0 -292
- package/src/helpers/function.spec.js +0 -24
- package/src/helpers/object.spec.js +0 -425
- package/src/helpers/promise.spec.js +0 -62
- package/src/helpers/redact.spec.js +0 -66
- package/src/helpers/string.spec.js +0 -99
- package/src/helpers/temporal_context.spec.ts +0 -83
- package/src/helpers/trace_info.spec.js +0 -45
- package/src/helpers/workflow_context.spec.js +0 -46
- package/src/hooks/index.spec.js +0 -265
- package/src/hooks/pending_hooks.spec.js +0 -62
- package/src/interface/evaluator.spec.js +0 -685
- package/src/interface/logger.spec.js +0 -172
- package/src/interface/step.spec.js +0 -128
- package/src/interface/validations/index.spec.js +0 -197
- package/src/interface/validations/schemas.spec.js +0 -229
- package/src/interface/webhook.spec.js +0 -170
- package/src/interface/workflow.spec.js +0 -710
- package/src/interface/workflow_utils.spec.js +0 -271
- package/src/interface/zod_integration.spec.js +0 -646
- package/src/internal_activities/index.spec.js +0 -186
- package/src/logger/development.spec.js +0 -87
- package/src/logger/index.spec.js +0 -27
- package/src/logger/production.spec.js +0 -71
- package/src/sdk/helpers/component_metadata.spec.js +0 -30
- package/src/sdk/helpers/objects.spec.js +0 -16
- package/src/sdk/helpers/path.spec.js +0 -102
- package/src/sdk/runtime/context.spec.js +0 -37
- package/src/sdk/runtime/events.spec.js +0 -40
- package/src/temporal/client.spec.js +0 -124
- package/src/tracing/processors/local/index.spec.js +0 -204
- package/src/tracing/processors/s3/configs.spec.js +0 -64
- package/src/tracing/processors/s3/index.spec.js +0 -185
- package/src/tracing/processors/s3/redis_client.spec.js +0 -179
- package/src/tracing/processors/s3/s3_client.spec.js +0 -68
- package/src/tracing/tools/build_trace_tree.spec.js +0 -216
- package/src/tracing/trace_engine.spec.js +0 -299
- package/src/worker/bundle.spec.js +0 -53
- package/src/worker/bundler_options.spec.js +0 -62
- package/src/worker/catalog_workflow/catalog_publisher.spec.js +0 -317
- package/src/worker/catalog_workflow/index.spec.js +0 -222
- package/src/worker/configs.spec.js +0 -213
- package/src/worker/configs_tuner_schema.spec.js +0 -127
- package/src/worker/connection_monitor.spec.js +0 -328
- package/src/worker/global_functions.spec.js +0 -55
- package/src/worker/index.spec.js +0 -526
- package/src/worker/interceptors/activity.spec.js +0 -369
- package/src/worker/interceptors/headers.spec.js +0 -73
- package/src/worker/interceptors/workflow.spec.js +0 -357
- package/src/worker/interruption.spec.js +0 -177
- package/src/worker/loader/activities.spec.js +0 -212
- package/src/worker/loader/hooks.spec.js +0 -64
- package/src/worker/loader/matchers.spec.js +0 -140
- package/src/worker/loader/tools.spec.js +0 -595
- package/src/worker/loader/workflows.spec.js +0 -287
- package/src/worker/log_hooks.spec.js +0 -303
- package/src/worker/shutdown.spec.js +0 -197
- package/src/worker/sinks.spec.js +0 -220
- package/src/worker/telemetry.spec.js +0 -80
- package/src/worker/temporal_logger.spec.js +0 -90
- package/src/worker/webpack_loaders/tools.spec.js +0 -314
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +0 -134
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +0 -154
- package/src/worker/webpack_loaders/workflow_rewriter/rewrite_activity_calls.spec.js +0 -165
- package/src/worker/webpack_loaders/workflow_validator/index.spec.js +0 -202
- package/src/worker/webpack_loaders/workflow_validator/npm_workflow_export_resolve.spec.js +0 -374
- package/src/worker/worker_runner.spec.js +0 -220
|
@@ -1,595 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach, beforeEach, vi } from 'vitest';
|
|
2
|
-
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { dirname, join, sep } from 'node:path';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { pathToFileURL } from 'node:url';
|
|
6
|
-
|
|
7
|
-
const { workflowFileMatcherMock, sharedStepsDirMatcherMock, sharedEvaluatorsDirMatcherMock } = vi.hoisted( () => ( {
|
|
8
|
-
workflowFileMatcherMock: vi.fn(),
|
|
9
|
-
sharedStepsDirMatcherMock: vi.fn(),
|
|
10
|
-
sharedEvaluatorsDirMatcherMock: vi.fn()
|
|
11
|
-
} ) );
|
|
12
|
-
|
|
13
|
-
vi.mock( './matchers.js', () => ( {
|
|
14
|
-
staticMatchers: {
|
|
15
|
-
workflowFile: workflowFileMatcherMock,
|
|
16
|
-
sharedStepsDir: sharedStepsDirMatcherMock,
|
|
17
|
-
sharedEvaluatorsDir: sharedEvaluatorsDirMatcherMock
|
|
18
|
-
}
|
|
19
|
-
} ) );
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
matchFiles,
|
|
23
|
-
findWorkflowsInNodeModules,
|
|
24
|
-
findWorkflowsInPackages,
|
|
25
|
-
findSharedActivitiesFromWorkflows,
|
|
26
|
-
importComponents,
|
|
27
|
-
findPackageRoot,
|
|
28
|
-
hashSourceCode,
|
|
29
|
-
isPackageRoot,
|
|
30
|
-
isPathDescendentFromNodeModules,
|
|
31
|
-
resolveNodeModulesPath,
|
|
32
|
-
resolveSymlink,
|
|
33
|
-
packageExposesWorkflows
|
|
34
|
-
} from './tools.js';
|
|
35
|
-
|
|
36
|
-
const TEMP_BASE = join( process.cwd(), 'sdk/core/temp_test_modules' );
|
|
37
|
-
|
|
38
|
-
beforeEach( () => {
|
|
39
|
-
vi.clearAllMocks();
|
|
40
|
-
workflowFileMatcherMock.mockImplementation( path => path.endsWith( `${sep}workflow.js` ) );
|
|
41
|
-
sharedStepsDirMatcherMock.mockImplementation( path => path.includes( `${sep}shared${sep}steps${sep}` ) && path.endsWith( '.js' ) );
|
|
42
|
-
sharedEvaluatorsDirMatcherMock.mockImplementation( path => path.includes( `${sep}shared${sep}evaluators${sep}` ) && path.endsWith( '.js' ) );
|
|
43
|
-
} );
|
|
44
|
-
|
|
45
|
-
afterEach( () => {
|
|
46
|
-
rmSync( TEMP_BASE, { recursive: true, force: true } );
|
|
47
|
-
} );
|
|
48
|
-
|
|
49
|
-
const fileEntry = path => ( { path, url: pathToFileURL( path ).href } );
|
|
50
|
-
|
|
51
|
-
describe( 'resolveSymlink', () => {
|
|
52
|
-
it( 'returns the canonical path for a directory', () => {
|
|
53
|
-
const dir = join( TEMP_BASE, `rs-dir-${Date.now()}` );
|
|
54
|
-
mkdirSync( dir, { recursive: true } );
|
|
55
|
-
expect( resolveSymlink( dir ) ).toBe( dir );
|
|
56
|
-
} );
|
|
57
|
-
|
|
58
|
-
it( 'returns the canonical path for a regular file', () => {
|
|
59
|
-
const dir = join( TEMP_BASE, `rs-file-${Date.now()}` );
|
|
60
|
-
mkdirSync( dir, { recursive: true } );
|
|
61
|
-
const file = join( dir, 'a.txt' );
|
|
62
|
-
writeFileSync( file, 'x' );
|
|
63
|
-
expect( resolveSymlink( file ) ).toBe( file );
|
|
64
|
-
} );
|
|
65
|
-
|
|
66
|
-
it( 'returns null for a broken symlink', () => {
|
|
67
|
-
const dir = join( TEMP_BASE, `rs-broken-${Date.now()}` );
|
|
68
|
-
mkdirSync( dir, { recursive: true } );
|
|
69
|
-
const link = join( dir, 'broken' );
|
|
70
|
-
symlinkSync( 'missing-target', link, 'file' );
|
|
71
|
-
expect( resolveSymlink( link ) ).toBe( null );
|
|
72
|
-
} );
|
|
73
|
-
} );
|
|
74
|
-
|
|
75
|
-
describe( 'resolveNodeModulesPath', () => {
|
|
76
|
-
it( 'returns node_modules when passed that directory', () => {
|
|
77
|
-
const root = join( TEMP_BASE, `rnm-direct-${Date.now()}` );
|
|
78
|
-
const nm = join( root, 'node_modules' );
|
|
79
|
-
mkdirSync( nm, { recursive: true } );
|
|
80
|
-
expect( resolveNodeModulesPath( nm ) ).toBe( nm );
|
|
81
|
-
} );
|
|
82
|
-
|
|
83
|
-
it( 'finds node_modules from a project root directory', () => {
|
|
84
|
-
const root = join( TEMP_BASE, `rnm-root-${Date.now()}` );
|
|
85
|
-
const nm = join( root, 'node_modules' );
|
|
86
|
-
mkdirSync( nm, { recursive: true } );
|
|
87
|
-
expect( resolveNodeModulesPath( root ) ).toBe( nm );
|
|
88
|
-
} );
|
|
89
|
-
|
|
90
|
-
it( 'walks upward from a nested path until node_modules is found', () => {
|
|
91
|
-
const root = join( TEMP_BASE, `rnm-walk-${Date.now()}` );
|
|
92
|
-
const nm = join( root, 'node_modules' );
|
|
93
|
-
const nested = join( root, 'src', 'deep' );
|
|
94
|
-
mkdirSync( nested, { recursive: true } );
|
|
95
|
-
mkdirSync( nm, { recursive: true } );
|
|
96
|
-
expect( resolveNodeModulesPath( nested ) ).toBe( nm );
|
|
97
|
-
} );
|
|
98
|
-
|
|
99
|
-
it( 'uses dirname when targetPath is a file', () => {
|
|
100
|
-
const root = join( TEMP_BASE, `rnm-file-${Date.now()}` );
|
|
101
|
-
const nm = join( root, 'node_modules' );
|
|
102
|
-
const file = join( root, 'index.js' );
|
|
103
|
-
mkdirSync( nm, { recursive: true } );
|
|
104
|
-
writeFileSync( file, '' );
|
|
105
|
-
expect( resolveNodeModulesPath( file ) ).toBe( nm );
|
|
106
|
-
} );
|
|
107
|
-
|
|
108
|
-
it( 'returns null when targetPath does not exist', () => {
|
|
109
|
-
expect( resolveNodeModulesPath( join( TEMP_BASE, 'nope', 'missing' ) ) ).toBe( null );
|
|
110
|
-
} );
|
|
111
|
-
|
|
112
|
-
it( 'returns null when no ancestor has node_modules', () => {
|
|
113
|
-
const isolated = mkdtempSync( join( tmpdir(), 'loader-tools-rnm-' ) );
|
|
114
|
-
try {
|
|
115
|
-
const orphan = join( isolated, 'nested', 'sub' );
|
|
116
|
-
mkdirSync( orphan, { recursive: true } );
|
|
117
|
-
expect( resolveNodeModulesPath( orphan ) ).toBe( null );
|
|
118
|
-
} finally {
|
|
119
|
-
rmSync( isolated, { recursive: true, force: true } );
|
|
120
|
-
}
|
|
121
|
-
} );
|
|
122
|
-
|
|
123
|
-
it( 'returns the canonical directory when node_modules is a symlink', () => {
|
|
124
|
-
const root = join( TEMP_BASE, `rnm-symlink-${Date.now()}` );
|
|
125
|
-
const realNm = join( root, 'real_node_modules' );
|
|
126
|
-
const nmLink = join( root, 'node_modules' );
|
|
127
|
-
mkdirSync( realNm, { recursive: true } );
|
|
128
|
-
symlinkSync( 'real_node_modules', nmLink, 'dir' );
|
|
129
|
-
expect( resolveNodeModulesPath( root ) ).toBe( realNm );
|
|
130
|
-
} );
|
|
131
|
-
|
|
132
|
-
it( 'walks up when node_modules is a broken symlink', () => {
|
|
133
|
-
const grandparent = join( TEMP_BASE, `rnm-walkup-${Date.now()}` );
|
|
134
|
-
const root = join( grandparent, 'proj' );
|
|
135
|
-
const nmBroken = join( root, 'node_modules' );
|
|
136
|
-
const nmUp = join( grandparent, 'node_modules' );
|
|
137
|
-
mkdirSync( nmUp, { recursive: true } );
|
|
138
|
-
mkdirSync( root, { recursive: true } );
|
|
139
|
-
symlinkSync( 'does-not-exist', nmBroken, 'dir' );
|
|
140
|
-
expect( resolveNodeModulesPath( root ) ).toBe( nmUp );
|
|
141
|
-
} );
|
|
142
|
-
} );
|
|
143
|
-
|
|
144
|
-
describe( 'node_modules package resource helpers', () => {
|
|
145
|
-
it( 'detects paths inside node_modules', () => {
|
|
146
|
-
expect( isPathDescendentFromNodeModules( `${sep}app${sep}node_modules${sep}pkg${sep}index.js` ) ).toBe( true );
|
|
147
|
-
expect( isPathDescendentFromNodeModules( 'C:\\app\\node_modules\\pkg\\index.js' ) ).toBe( true );
|
|
148
|
-
expect( isPathDescendentFromNodeModules( `${sep}app${sep}src${sep}index.js` ) ).toBe( false );
|
|
149
|
-
} );
|
|
150
|
-
|
|
151
|
-
it( 'detects installed package roots', () => {
|
|
152
|
-
expect( isPackageRoot( `${sep}app${sep}node_modules${sep}pkg` ) ).toBe( true );
|
|
153
|
-
expect( isPackageRoot( `${sep}app${sep}node_modules${sep}@scope${sep}pkg` ) ).toBe( true );
|
|
154
|
-
expect( isPackageRoot( `${sep}app${sep}node_modules${sep}@scope` ) ).toBe( false );
|
|
155
|
-
expect( isPackageRoot( `${sep}app${sep}node_modules${sep}pkg${sep}lib` ) ).toBe( false );
|
|
156
|
-
} );
|
|
157
|
-
|
|
158
|
-
it( 'finds the closest installed package root for a file', () => {
|
|
159
|
-
const root = join( TEMP_BASE, `pkgroot-${Date.now()}` );
|
|
160
|
-
const pkgRoot = join( root, 'node_modules', '@acme', 'wf_pkg' );
|
|
161
|
-
const deepFile = join( pkgRoot, 'lib', 'nested', 'workflow.js' );
|
|
162
|
-
mkdirSync( join( pkgRoot, 'lib', 'nested' ), { recursive: true } );
|
|
163
|
-
writeFileSync( join( pkgRoot, 'package.json' ), JSON.stringify( {
|
|
164
|
-
name: '@acme/wf_pkg',
|
|
165
|
-
dependencies: { '@outputai/core': '1.0.0' }
|
|
166
|
-
} ) );
|
|
167
|
-
writeFileSync( deepFile, 'export default {};\n' );
|
|
168
|
-
|
|
169
|
-
expect( findPackageRoot( deepFile ) ).toBe( pkgRoot );
|
|
170
|
-
} );
|
|
171
|
-
|
|
172
|
-
it( 'returns null when no installed package root is found', () => {
|
|
173
|
-
const root = join( TEMP_BASE, `pkgroot-missing-${Date.now()}` );
|
|
174
|
-
const deepFile = join( root, 'node_modules', 'plain_lib', 'index.js' );
|
|
175
|
-
mkdirSync( dirname( deepFile ), { recursive: true } );
|
|
176
|
-
writeFileSync( deepFile, 'export const x = 1;\n' );
|
|
177
|
-
|
|
178
|
-
expect( findPackageRoot( deepFile ) ).toBe( null );
|
|
179
|
-
} );
|
|
180
|
-
} );
|
|
181
|
-
|
|
182
|
-
describe( 'matchFiles', () => {
|
|
183
|
-
it( 'collects files matching matchers', () => {
|
|
184
|
-
const root = join( TEMP_BASE, `fbnr-files-${Date.now()}` );
|
|
185
|
-
mkdirSync( root, { recursive: true } );
|
|
186
|
-
writeFileSync( join( root, 'a.txt' ), '' );
|
|
187
|
-
writeFileSync( join( root, 'b.txt' ), '' );
|
|
188
|
-
const found = matchFiles( root, [ p => p.endsWith( 'a.txt' ) ] );
|
|
189
|
-
expect( found ).toHaveLength( 1 );
|
|
190
|
-
expect( found[0].path ).toBe( join( root, 'a.txt' ) );
|
|
191
|
-
} );
|
|
192
|
-
|
|
193
|
-
it( 'skips broken symlinks without throwing', () => {
|
|
194
|
-
const root = join( TEMP_BASE, `fbnr-broken-${Date.now()}` );
|
|
195
|
-
mkdirSync( root, { recursive: true } );
|
|
196
|
-
symlinkSync( 'nowhere', join( root, 'bad' ), 'file' );
|
|
197
|
-
writeFileSync( join( root, 'ok.txt' ), '' );
|
|
198
|
-
const found = matchFiles( root, [ p => p.endsWith( '.txt' ) ] );
|
|
199
|
-
expect( found.map( f => f.path ) ).toEqual( [ join( root, 'ok.txt' ) ] );
|
|
200
|
-
} );
|
|
201
|
-
|
|
202
|
-
it( 'follows symlinks to directories', () => {
|
|
203
|
-
const base = join( TEMP_BASE, `fbnr-symlink-dir-${Date.now()}` );
|
|
204
|
-
const targetDir = join( base, 'target' );
|
|
205
|
-
const scanRoot = join( base, 'root' );
|
|
206
|
-
mkdirSync( targetDir, { recursive: true } );
|
|
207
|
-
mkdirSync( scanRoot, { recursive: true } );
|
|
208
|
-
writeFileSync( join( targetDir, 'x.txt' ), '' );
|
|
209
|
-
symlinkSync( join( '..', 'target' ), join( scanRoot, 'link' ), 'dir' );
|
|
210
|
-
const found = matchFiles( scanRoot, [ p => p.endsWith( 'x.txt' ) ] );
|
|
211
|
-
expect( found ).toHaveLength( 1 );
|
|
212
|
-
expect( found[0].path ).toBe( join( targetDir, 'x.txt' ) );
|
|
213
|
-
} );
|
|
214
|
-
} );
|
|
215
|
-
|
|
216
|
-
describe( 'findWorkflowsInPackages', () => {
|
|
217
|
-
it( 'collects workflow.js from exposed workflow packages under node_modules', () => {
|
|
218
|
-
const root = join( TEMP_BASE, `fwp-${Date.now()}` );
|
|
219
|
-
const nm = join( root, 'node_modules' );
|
|
220
|
-
const pkg = join( nm, 'wf_pkg' );
|
|
221
|
-
mkdirSync( join( pkg, 'lib' ), { recursive: true } );
|
|
222
|
-
writeFileSync( join( pkg, 'package.json' ), JSON.stringify( {
|
|
223
|
-
name: 'wf_pkg',
|
|
224
|
-
outputai: { workflows: { expose: true } }
|
|
225
|
-
} ) );
|
|
226
|
-
const wf = join( pkg, 'lib', 'workflow.js' );
|
|
227
|
-
writeFileSync( wf, 'export default {};\n' );
|
|
228
|
-
|
|
229
|
-
const found = findWorkflowsInPackages( nm );
|
|
230
|
-
expect( found ).toHaveLength( 1 );
|
|
231
|
-
expect( found[0].path ).toBe( wf );
|
|
232
|
-
} );
|
|
233
|
-
|
|
234
|
-
it( 'lists the same workflow twice when package scan sees a canonical package and symlink alias', () => {
|
|
235
|
-
const root = join( TEMP_BASE, `fwp-symlink-${Date.now()}` );
|
|
236
|
-
const nm = join( root, 'node_modules' );
|
|
237
|
-
const realPkg = join( nm, 'real_pkg' );
|
|
238
|
-
const linkPkg = join( nm, 'link_pkg' );
|
|
239
|
-
mkdirSync( join( realPkg, 'w' ), { recursive: true } );
|
|
240
|
-
const wf = join( realPkg, 'w', 'workflow.js' );
|
|
241
|
-
writeFileSync( join( realPkg, 'package.json' ), JSON.stringify( {
|
|
242
|
-
name: 'real_pkg',
|
|
243
|
-
outputai: { workflows: { expose: true } }
|
|
244
|
-
} ) );
|
|
245
|
-
writeFileSync( wf, 'export default {};\n' );
|
|
246
|
-
symlinkSync( 'real_pkg', linkPkg, 'dir' );
|
|
247
|
-
|
|
248
|
-
const found = findWorkflowsInPackages( nm );
|
|
249
|
-
expect( found ).toHaveLength( 2 );
|
|
250
|
-
expect( new Set( found.map( f => realpathSync( f.path ) ) ).size ).toBe( 1 );
|
|
251
|
-
} );
|
|
252
|
-
} );
|
|
253
|
-
|
|
254
|
-
describe( 'importComponents', () => {
|
|
255
|
-
it( 'imports modules and yields metadata from exports tagged with METADATA_ACCESS_SYMBOL', async () => {
|
|
256
|
-
const root = join( TEMP_BASE, `meta-${Date.now()}` );
|
|
257
|
-
mkdirSync( root, { recursive: true } );
|
|
258
|
-
const file = join( root, 'meta.module.js' );
|
|
259
|
-
writeFileSync( file, [
|
|
260
|
-
'import { METADATA_ACCESS_SYMBOL } from "#consts";',
|
|
261
|
-
'export const StepA = () => {};',
|
|
262
|
-
'StepA[METADATA_ACCESS_SYMBOL] = { kind: "step", name: "a" };',
|
|
263
|
-
'export const FlowB = () => {};',
|
|
264
|
-
'FlowB[METADATA_ACCESS_SYMBOL] = { kind: "workflow", name: "b" };'
|
|
265
|
-
].join( '\n' ) );
|
|
266
|
-
|
|
267
|
-
const collected = [];
|
|
268
|
-
for await ( const m of importComponents( [ fileEntry( file ) ] ) ) {
|
|
269
|
-
collected.push( m );
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
expect( collected.length ).toBe( 2 );
|
|
273
|
-
expect( collected.map( m => m.metadata.name ).sort() ).toEqual( [ 'a', 'b' ] );
|
|
274
|
-
expect( collected.map( m => m.metadata.kind ).sort() ).toEqual( [ 'step', 'workflow' ] );
|
|
275
|
-
for ( const m of collected ) {
|
|
276
|
-
expect( m.path ).toBe( file );
|
|
277
|
-
expect( typeof m.fn ).toBe( 'function' );
|
|
278
|
-
}
|
|
279
|
-
} );
|
|
280
|
-
|
|
281
|
-
it( 'ignores exports without metadata symbol', async () => {
|
|
282
|
-
const root = join( TEMP_BASE, `meta-${Date.now()}-nometa` );
|
|
283
|
-
mkdirSync( root, { recursive: true } );
|
|
284
|
-
const file = join( root, 'meta.module.js' );
|
|
285
|
-
writeFileSync( file, [
|
|
286
|
-
'export const Plain = () => {};',
|
|
287
|
-
'export const AlsoPlain = {}'
|
|
288
|
-
].join( '\n' ) );
|
|
289
|
-
|
|
290
|
-
const collected = [];
|
|
291
|
-
for await ( const m of importComponents( [ fileEntry( file ) ] ) ) {
|
|
292
|
-
collected.push( m );
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
expect( collected.length ).toBe( 0 );
|
|
296
|
-
} );
|
|
297
|
-
|
|
298
|
-
it( 'skips files inside ignored directories (node_modules, vendor)', async () => {
|
|
299
|
-
const root = join( TEMP_BASE, `meta-${Date.now()}-ignoredirs` );
|
|
300
|
-
const okDir = join( root, 'ok' );
|
|
301
|
-
const nmDir = join( root, 'node_modules' );
|
|
302
|
-
const vendorDir = join( root, 'vendor' );
|
|
303
|
-
mkdirSync( okDir, { recursive: true } );
|
|
304
|
-
mkdirSync( nmDir, { recursive: true } );
|
|
305
|
-
mkdirSync( vendorDir, { recursive: true } );
|
|
306
|
-
|
|
307
|
-
const okFile = join( okDir, 'meta.module.js' );
|
|
308
|
-
const nmFile = join( nmDir, 'meta.module.js' );
|
|
309
|
-
const vendorFile = join( vendorDir, 'meta.module.js' );
|
|
310
|
-
|
|
311
|
-
const fileContents = [
|
|
312
|
-
'import { METADATA_ACCESS_SYMBOL } from "#consts";',
|
|
313
|
-
'export const C = () => {};',
|
|
314
|
-
'C[METADATA_ACCESS_SYMBOL] = { kind: "step", name: "c" };'
|
|
315
|
-
].join( '\n' );
|
|
316
|
-
writeFileSync( okFile, fileContents );
|
|
317
|
-
writeFileSync( nmFile, fileContents );
|
|
318
|
-
writeFileSync( vendorFile, fileContents );
|
|
319
|
-
|
|
320
|
-
const collected = [];
|
|
321
|
-
for await ( const m of importComponents( matchFiles( root, [ v => v.endsWith( 'meta.module.js' ) ] ) ) ) {
|
|
322
|
-
collected.push( m );
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
expect( collected.length ).toBe( 1 );
|
|
326
|
-
expect( collected[0].path ).toBe( okFile );
|
|
327
|
-
} );
|
|
328
|
-
|
|
329
|
-
it( 'supports partial matching by folder name', async () => {
|
|
330
|
-
const root = join( TEMP_BASE, `meta-${Date.now()}-foldermatch` );
|
|
331
|
-
const okDir = join( root, 'features', 'ok' );
|
|
332
|
-
const otherDir = join( root, 'features', 'other' );
|
|
333
|
-
mkdirSync( okDir, { recursive: true } );
|
|
334
|
-
mkdirSync( otherDir, { recursive: true } );
|
|
335
|
-
|
|
336
|
-
const okFile = join( okDir, 'alpha.js' );
|
|
337
|
-
const otherFile = join( otherDir, 'beta.js' );
|
|
338
|
-
const src = [
|
|
339
|
-
'import { METADATA_ACCESS_SYMBOL } from "#consts";',
|
|
340
|
-
'export const X = () => {};',
|
|
341
|
-
'X[METADATA_ACCESS_SYMBOL] = { kind: "step", name: "x" };'
|
|
342
|
-
].join( '\n' );
|
|
343
|
-
writeFileSync( okFile, src );
|
|
344
|
-
writeFileSync( otherFile, src );
|
|
345
|
-
|
|
346
|
-
const matcher = v => v.includes( `${join( 'features', 'ok' )}${sep}` );
|
|
347
|
-
const collected = [];
|
|
348
|
-
for await ( const m of importComponents( matchFiles( root, [ matcher ] ) ) ) {
|
|
349
|
-
collected.push( m );
|
|
350
|
-
}
|
|
351
|
-
expect( collected.length ).toBe( 1 );
|
|
352
|
-
expect( collected[0].path ).toBe( okFile );
|
|
353
|
-
} );
|
|
354
|
-
|
|
355
|
-
it( 'follows symlinks to directories and collects matching files inside the target', async () => {
|
|
356
|
-
const base = join( TEMP_BASE, `symlink-dir-${Date.now()}` );
|
|
357
|
-
const targetDir = join( base, 'target' );
|
|
358
|
-
const scanRoot = join( base, 'root' );
|
|
359
|
-
mkdirSync( targetDir, { recursive: true } );
|
|
360
|
-
mkdirSync( scanRoot, { recursive: true } );
|
|
361
|
-
const moduleFile = join( targetDir, 'meta.module.js' );
|
|
362
|
-
writeFileSync( moduleFile, [
|
|
363
|
-
'import { METADATA_ACCESS_SYMBOL } from "#consts";',
|
|
364
|
-
'export const ViaLink = () => {};',
|
|
365
|
-
'ViaLink[METADATA_ACCESS_SYMBOL] = { kind: "step", name: "via_link" };'
|
|
366
|
-
].join( '\n' ) );
|
|
367
|
-
|
|
368
|
-
symlinkSync( join( '..', 'target' ), join( scanRoot, 'pkg' ), 'dir' );
|
|
369
|
-
|
|
370
|
-
const collected = [];
|
|
371
|
-
for await ( const m of importComponents( matchFiles( scanRoot, [ v => v.endsWith( 'meta.module.js' ) ] ) ) ) {
|
|
372
|
-
collected.push( m );
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
expect( collected.length ).toBe( 1 );
|
|
376
|
-
expect( collected[0].path ).toBe( moduleFile );
|
|
377
|
-
expect( collected[0].metadata.name ).toBe( 'via_link' );
|
|
378
|
-
} );
|
|
379
|
-
|
|
380
|
-
it( 'collects a symlinked file when the matcher matches the realpath target (canonical url)', async () => {
|
|
381
|
-
const root = join( TEMP_BASE, `symlink-file-${Date.now()}` );
|
|
382
|
-
mkdirSync( root, { recursive: true } );
|
|
383
|
-
const realFile = join( root, 'real.module.js' );
|
|
384
|
-
writeFileSync( realFile, [
|
|
385
|
-
'import { METADATA_ACCESS_SYMBOL } from "#consts";',
|
|
386
|
-
'export const R = () => {};',
|
|
387
|
-
'R[METADATA_ACCESS_SYMBOL] = { kind: "step", name: "r" };'
|
|
388
|
-
].join( '\n' ) );
|
|
389
|
-
const linkPath = join( root, 'alias.module.js' );
|
|
390
|
-
symlinkSync( 'real.module.js', linkPath, 'file' );
|
|
391
|
-
|
|
392
|
-
const collected = [];
|
|
393
|
-
for await ( const m of importComponents( matchFiles( root, [ v => v.endsWith( '.module.js' ) ] ) ) ) {
|
|
394
|
-
collected.push( m );
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
expect( collected.length ).toBe( 2 );
|
|
398
|
-
expect( collected.every( m => m.metadata.name === 'r' ) ).toBe( true );
|
|
399
|
-
expect( collected[0].fn ).toBe( collected[1].fn );
|
|
400
|
-
} );
|
|
401
|
-
} );
|
|
402
|
-
|
|
403
|
-
describe( 'packageExposesWorkflows', () => {
|
|
404
|
-
it( 'returns true when outputai.workflows.expose is true', () => {
|
|
405
|
-
const dir = join( TEMP_BASE, `wf-proj-expose-${Date.now()}` );
|
|
406
|
-
mkdirSync( dir, { recursive: true } );
|
|
407
|
-
const pkg = join( dir, 'package.json' );
|
|
408
|
-
writeFileSync( pkg, JSON.stringify( { outputai: { workflows: { expose: true } } } ) );
|
|
409
|
-
expect( packageExposesWorkflows( pkg ) ).toBe( true );
|
|
410
|
-
} );
|
|
411
|
-
|
|
412
|
-
it( 'returns false when outputai.workflows.expose is false', () => {
|
|
413
|
-
const dir = join( TEMP_BASE, `wf-proj-no-expose-${Date.now()}` );
|
|
414
|
-
mkdirSync( dir, { recursive: true } );
|
|
415
|
-
const pkg = join( dir, 'package.json' );
|
|
416
|
-
writeFileSync( pkg, JSON.stringify( { outputai: { workflows: { expose: false } } } ) );
|
|
417
|
-
expect( packageExposesWorkflows( pkg ) ).toBe( false );
|
|
418
|
-
} );
|
|
419
|
-
|
|
420
|
-
it( 'returns false for the legacy output.workflows.expose field', () => {
|
|
421
|
-
const dir = join( TEMP_BASE, `wf-proj-legacy-expose-${Date.now()}` );
|
|
422
|
-
mkdirSync( dir, { recursive: true } );
|
|
423
|
-
const pkg = join( dir, 'package.json' );
|
|
424
|
-
writeFileSync( pkg, JSON.stringify( { output: { workflows: { expose: true } } } ) );
|
|
425
|
-
expect( packageExposesWorkflows( pkg ) ).toBe( false );
|
|
426
|
-
} );
|
|
427
|
-
|
|
428
|
-
it( 'returns false when only OutputAI dependencies are present', () => {
|
|
429
|
-
const dir = join( TEMP_BASE, `wf-proj-dep-only-${Date.now()}` );
|
|
430
|
-
mkdirSync( dir, { recursive: true } );
|
|
431
|
-
const pkg = join( dir, 'package.json' );
|
|
432
|
-
writeFileSync( pkg, JSON.stringify( { dependencies: { '@outputai/core': '1.0.0' } } ) );
|
|
433
|
-
expect( packageExposesWorkflows( pkg ) ).toBe( false );
|
|
434
|
-
} );
|
|
435
|
-
|
|
436
|
-
it( 'returns false when package.json is missing', () => {
|
|
437
|
-
expect( packageExposesWorkflows( join( TEMP_BASE, 'missing-package-json', 'package.json' ) ) ).toBe( false );
|
|
438
|
-
} );
|
|
439
|
-
|
|
440
|
-
it( 'returns false when package.json is not valid JSON', () => {
|
|
441
|
-
const dir = join( TEMP_BASE, `wf-proj-badjson-${Date.now()}` );
|
|
442
|
-
mkdirSync( dir, { recursive: true } );
|
|
443
|
-
const pkg = join( dir, 'package.json' );
|
|
444
|
-
writeFileSync( pkg, '{ not json' );
|
|
445
|
-
expect( packageExposesWorkflows( pkg ) ).toBe( false );
|
|
446
|
-
} );
|
|
447
|
-
} );
|
|
448
|
-
|
|
449
|
-
describe( 'findWorkflowsInNodeModules', () => {
|
|
450
|
-
it( 'resolves node_modules from project root and finds workflows', () => {
|
|
451
|
-
const root = join( TEMP_BASE, `nm-from-root-${Date.now()}` );
|
|
452
|
-
const nm = join( root, 'node_modules' );
|
|
453
|
-
const pkg = join( nm, 'pkg_a' );
|
|
454
|
-
mkdirSync( join( pkg, 'w' ), { recursive: true } );
|
|
455
|
-
writeFileSync( join( pkg, 'package.json' ), JSON.stringify( {
|
|
456
|
-
name: 'pkg_a',
|
|
457
|
-
outputai: { workflows: { expose: true } }
|
|
458
|
-
} ) );
|
|
459
|
-
const wf = join( pkg, 'w', 'workflow.js' );
|
|
460
|
-
writeFileSync( wf, 'export default {};\n' );
|
|
461
|
-
|
|
462
|
-
const found = findWorkflowsInNodeModules( root );
|
|
463
|
-
expect( found.length ).toBe( 1 );
|
|
464
|
-
expect( found[0].path ).toBe( wf );
|
|
465
|
-
} );
|
|
466
|
-
|
|
467
|
-
it( 'finds workflow.js under an unscoped package with exposed workflows', () => {
|
|
468
|
-
const root = join( TEMP_BASE, `nm-unscoped-${Date.now()}` );
|
|
469
|
-
const nm = join( root, 'node_modules' );
|
|
470
|
-
const pkg = join( nm, 'catalog_pkg' );
|
|
471
|
-
mkdirSync( join( pkg, 'workflows', 'a' ), { recursive: true } );
|
|
472
|
-
writeFileSync( join( pkg, 'package.json' ), JSON.stringify( {
|
|
473
|
-
name: 'catalog_pkg',
|
|
474
|
-
outputai: { workflows: { expose: true } }
|
|
475
|
-
} ) );
|
|
476
|
-
writeFileSync( join( pkg, 'workflows', 'a', 'workflow.js' ), 'export default {};\n' );
|
|
477
|
-
|
|
478
|
-
const found = findWorkflowsInNodeModules( nm );
|
|
479
|
-
expect( found.length ).toBe( 1 );
|
|
480
|
-
expect( found[0].path ).toBe( join( pkg, 'workflows', 'a', 'workflow.js' ) );
|
|
481
|
-
expect( found[0].url ).toBe( pathToFileURL( join( pkg, 'workflows', 'a', 'workflow.js' ) ).href );
|
|
482
|
-
} );
|
|
483
|
-
|
|
484
|
-
it( 'finds workflow.js under a scoped package', () => {
|
|
485
|
-
const root = join( TEMP_BASE, `nm-scoped-${Date.now()}` );
|
|
486
|
-
const nm = join( root, 'node_modules' );
|
|
487
|
-
const pkg = join( nm, '@acme', 'wf_pkg' );
|
|
488
|
-
mkdirSync( join( pkg, 'lib' ), { recursive: true } );
|
|
489
|
-
writeFileSync( join( pkg, 'package.json' ), JSON.stringify( {
|
|
490
|
-
name: '@acme/wf_pkg',
|
|
491
|
-
outputai: { workflows: { expose: true } }
|
|
492
|
-
} ) );
|
|
493
|
-
writeFileSync( join( pkg, 'lib', 'workflow.js' ), 'export default {};\n' );
|
|
494
|
-
|
|
495
|
-
const found = findWorkflowsInNodeModules( nm );
|
|
496
|
-
expect( found.length ).toBe( 1 );
|
|
497
|
-
expect( found[0].path ).toBe( join( pkg, 'lib', 'workflow.js' ) );
|
|
498
|
-
} );
|
|
499
|
-
|
|
500
|
-
it( 'skips packages that do not expose workflows', () => {
|
|
501
|
-
const root = join( TEMP_BASE, `nm-skip-${Date.now()}` );
|
|
502
|
-
const nm = join( root, 'node_modules' );
|
|
503
|
-
const pkg = join( nm, 'plain_lib' );
|
|
504
|
-
mkdirSync( pkg, { recursive: true } );
|
|
505
|
-
writeFileSync( join( pkg, 'package.json' ), JSON.stringify( { name: 'plain_lib' } ) );
|
|
506
|
-
writeFileSync( join( pkg, 'workflow.js' ), 'export default {};\n' );
|
|
507
|
-
|
|
508
|
-
expect( findWorkflowsInNodeModules( nm ) ).toEqual( [] );
|
|
509
|
-
} );
|
|
510
|
-
|
|
511
|
-
it( 'deduplicates the same workflow when reachable via symlink alias and canonical package', () => {
|
|
512
|
-
const root = join( TEMP_BASE, `nm-dedupe-${Date.now()}` );
|
|
513
|
-
const nm = join( root, 'node_modules' );
|
|
514
|
-
const realPkg = join( nm, 'real_pkg' );
|
|
515
|
-
const linkPkg = join( nm, 'link_pkg' );
|
|
516
|
-
mkdirSync( join( realPkg, 'w' ), { recursive: true } );
|
|
517
|
-
const wf = join( realPkg, 'w', 'workflow.js' );
|
|
518
|
-
writeFileSync( join( realPkg, 'package.json' ), JSON.stringify( {
|
|
519
|
-
name: 'real_pkg',
|
|
520
|
-
outputai: { workflows: { expose: true } }
|
|
521
|
-
} ) );
|
|
522
|
-
writeFileSync( wf, 'export default {};\n' );
|
|
523
|
-
symlinkSync( 'real_pkg', linkPkg, 'dir' );
|
|
524
|
-
|
|
525
|
-
const found = findWorkflowsInNodeModules( nm );
|
|
526
|
-
expect( found ).toHaveLength( 1 );
|
|
527
|
-
expect( realpathSync( found[0].path ) ).toBe( wf );
|
|
528
|
-
} );
|
|
529
|
-
} );
|
|
530
|
-
|
|
531
|
-
describe( 'findSharedActivitiesFromWorkflows', () => {
|
|
532
|
-
it( 'finds shared steps and evaluators from external workflow package roots', () => {
|
|
533
|
-
const root = join( TEMP_BASE, `external-shared-${Date.now()}` );
|
|
534
|
-
const pkg = join( root, 'node_modules', '@acme', 'workflow_pkg' );
|
|
535
|
-
const workflowA = join( pkg, 'workflows', 'a', 'workflow.js' );
|
|
536
|
-
const workflowB = join( pkg, 'workflows', 'b', 'workflow.js' );
|
|
537
|
-
const sharedStep = join( pkg, 'shared', 'steps', 'prepare.js' );
|
|
538
|
-
const sharedEvaluator = join( pkg, 'shared', 'evaluators', 'quality.js' );
|
|
539
|
-
mkdirSync( dirname( workflowA ), { recursive: true } );
|
|
540
|
-
mkdirSync( dirname( workflowB ), { recursive: true } );
|
|
541
|
-
mkdirSync( dirname( sharedStep ), { recursive: true } );
|
|
542
|
-
mkdirSync( dirname( sharedEvaluator ), { recursive: true } );
|
|
543
|
-
writeFileSync( join( pkg, 'package.json' ), JSON.stringify( {
|
|
544
|
-
name: '@acme/workflow_pkg',
|
|
545
|
-
dependencies: { '@outputai/core': '1.0.0' }
|
|
546
|
-
} ) );
|
|
547
|
-
writeFileSync( workflowA, 'export default {};\n' );
|
|
548
|
-
writeFileSync( workflowB, 'export default {};\n' );
|
|
549
|
-
writeFileSync( sharedStep, 'export const Prepare = step({ name: "prepare" });\n' );
|
|
550
|
-
writeFileSync( sharedEvaluator, 'export const Quality = evaluator({ name: "quality" });\n' );
|
|
551
|
-
writeFileSync( join( pkg, 'shared', 'readme.md' ), '# ignored\n' );
|
|
552
|
-
|
|
553
|
-
const found = findSharedActivitiesFromWorkflows( [
|
|
554
|
-
{ path: workflowA },
|
|
555
|
-
{ path: workflowB },
|
|
556
|
-
{ path: join( root, 'local', 'workflow.js' ) }
|
|
557
|
-
] );
|
|
558
|
-
expect( found.map( f => f.path ).sort() ).toEqual( [ sharedEvaluator, sharedStep ].sort() );
|
|
559
|
-
} );
|
|
560
|
-
} );
|
|
561
|
-
|
|
562
|
-
describe( 'hashSourceCode', () => {
|
|
563
|
-
const writeSource = root => {
|
|
564
|
-
mkdirSync( join( root, 'src' ), { recursive: true } );
|
|
565
|
-
writeFileSync( join( root, 'package.json' ), JSON.stringify( { name: 'proj' } ) );
|
|
566
|
-
writeFileSync( join( root, 'src', 'workflow.js' ), 'export default {};\n' );
|
|
567
|
-
};
|
|
568
|
-
|
|
569
|
-
it( 'ignores excluded folders so accumulated run logs do not change the hash', async () => {
|
|
570
|
-
const baseline = join( TEMP_BASE, `hash-baseline-${Date.now()}` );
|
|
571
|
-
const withCruft = join( TEMP_BASE, `hash-cruft-${Date.now()}` );
|
|
572
|
-
writeSource( baseline );
|
|
573
|
-
writeSource( withCruft );
|
|
574
|
-
|
|
575
|
-
// The cruft tree is identical source plus large excluded artifacts that
|
|
576
|
-
// boot must not walk: local trace dumps under logs/ and build output under dist/.
|
|
577
|
-
for ( const excluded of [ 'logs', 'logs/runs', 'node_modules' ] ) {
|
|
578
|
-
const dir = join( withCruft, excluded );
|
|
579
|
-
mkdirSync( dir, { recursive: true } );
|
|
580
|
-
writeFileSync( join( dir, 'dump.json' ), JSON.stringify( { blob: 'x'.repeat( 50_000 ) } ) );
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
expect( await hashSourceCode( withCruft ) ).toBe( await hashSourceCode( baseline ) );
|
|
584
|
-
} );
|
|
585
|
-
|
|
586
|
-
it( 'changes the hash when actual source changes', async () => {
|
|
587
|
-
const before = join( TEMP_BASE, `hash-src-before-${Date.now()}` );
|
|
588
|
-
const after = join( TEMP_BASE, `hash-src-after-${Date.now()}` );
|
|
589
|
-
writeSource( before );
|
|
590
|
-
writeSource( after );
|
|
591
|
-
writeFileSync( join( after, 'src', 'workflow.js' ), 'export default { changed: true };\n' );
|
|
592
|
-
|
|
593
|
-
expect( await hashSourceCode( after ) ).not.toBe( await hashSourceCode( before ) );
|
|
594
|
-
} );
|
|
595
|
-
} );
|