@output.ai/core 0.1.6 → 0.1.8-dev.pr156.05c9aa2
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/README.md +22 -0
- package/package.json +1 -1
- package/src/index.d.ts +1 -1
- package/src/tracing/processors/local/index.js +16 -5
- package/src/tracing/processors/local/index.spec.js +16 -1
- package/src/worker/webpack_loaders/consts.js +13 -4
- package/src/worker/webpack_loaders/tools.js +5 -3
- package/src/worker/webpack_loaders/tools.spec.js +44 -0
- package/src/worker/webpack_loaders/workflow_validator/index.mjs +3 -3
- package/src/worker/webpack_loaders/workflow_validator/index.spec.js +12 -1
package/README.md
CHANGED
|
@@ -48,6 +48,28 @@ export default workflow( {
|
|
|
48
48
|
})
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
Workflows can only import the following files
|
|
52
|
+
|
|
53
|
+
#### Components
|
|
54
|
+
- `evaluators.js`
|
|
55
|
+
- `shared_steps.js`
|
|
56
|
+
- `steps.js`
|
|
57
|
+
- `workflow.js`
|
|
58
|
+
|
|
59
|
+
#### Core library
|
|
60
|
+
- `@output.ai.core`
|
|
61
|
+
|
|
62
|
+
#### Whitelisted files
|
|
63
|
+
- `types.js`
|
|
64
|
+
- `consts.js`
|
|
65
|
+
- `constants.js`
|
|
66
|
+
- `vars.js`
|
|
67
|
+
- `variables.js`
|
|
68
|
+
- `utils.js`
|
|
69
|
+
- `tools.js`
|
|
70
|
+
- `functions.js`
|
|
71
|
+
- `shared.js`
|
|
72
|
+
|
|
51
73
|
### Step
|
|
52
74
|
|
|
53
75
|
Re-usable units of work that can contain IO, used by the workflow.
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -144,7 +144,7 @@ export async function step( options: {
|
|
|
144
144
|
*/
|
|
145
145
|
|
|
146
146
|
/**
|
|
147
|
-
* Creates a workflow orchestrator defined schema for both input and output.
|
|
147
|
+
* Creates a workflow orchestrator with defined schema for both input and output.
|
|
148
148
|
*
|
|
149
149
|
* @param {object} options - Workflow options
|
|
150
150
|
* @param {string} options.name - Unique workflow name
|
|
@@ -27,7 +27,17 @@ export const init = () => {
|
|
|
27
27
|
cleanupOldTempFiles();
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Get the base path for trace file storage
|
|
32
|
+
* @returns {string} The base path where traces should be written
|
|
33
|
+
*/
|
|
34
|
+
const getBasePath = () => {
|
|
35
|
+
return process.env.HOST_TRACE_PATH || join( process.cwd(), 'logs' );
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const getOutputDir = workflowName => {
|
|
39
|
+
return join( getBasePath(), 'runs', workflowName );
|
|
40
|
+
};
|
|
31
41
|
|
|
32
42
|
const buildOutputFileName = ( { startTime, workflowId } ) => {
|
|
33
43
|
const timestamp = new Date( startTime ).toISOString().replace( /[:T.]/g, '-' );
|
|
@@ -56,12 +66,13 @@ export const exec = ( { entry, executionContext } ) => {
|
|
|
56
66
|
};
|
|
57
67
|
|
|
58
68
|
/**
|
|
59
|
-
* Returns where the trace is saved
|
|
69
|
+
* Returns where the trace is saved as an absolute path
|
|
60
70
|
* @param {object} args
|
|
61
71
|
* @param {string} args.startTime - The start time of the workflow
|
|
62
72
|
* @param {string} args.workflowId - The id of the workflow execution
|
|
63
73
|
* @param {string} args.workflowName - The name of the workflow
|
|
64
|
-
* @returns {string} The path where the trace will be saved
|
|
74
|
+
* @returns {string} The absolute path where the trace will be saved
|
|
65
75
|
*/
|
|
66
|
-
export const getDestination = ( { startTime, workflowId, workflowName } ) =>
|
|
67
|
-
join( getOutputDir( workflowName ), buildOutputFileName( { workflowId, startTime } ) );
|
|
76
|
+
export const getDestination = ( { startTime, workflowId, workflowName } ) => {
|
|
77
|
+
return join( getOutputDir( workflowName ), buildOutputFileName( { workflowId, startTime } ) );
|
|
78
|
+
};
|
|
@@ -60,8 +60,23 @@ describe( 'tracing/processors/local', () => {
|
|
|
60
60
|
|
|
61
61
|
expect( writeFileSyncMock ).toHaveBeenCalledTimes( 3 );
|
|
62
62
|
const [ writtenPath, content ] = writeFileSyncMock.mock.calls.at( -1 );
|
|
63
|
-
|
|
63
|
+
// Changed: Now uses process.cwd() + '/logs' fallback when HOST_TRACE_PATH not set
|
|
64
|
+
expect( writtenPath ).toMatch( /\/runs\/WF\// );
|
|
64
65
|
expect( JSON.parse( content.trim() ).count ).toBe( 3 );
|
|
65
66
|
} );
|
|
67
|
+
|
|
68
|
+
it( 'getDestination(): returns absolute path', async () => {
|
|
69
|
+
const { getDestination } = await import( './index.js' );
|
|
70
|
+
|
|
71
|
+
const startTime = Date.parse( '2020-01-02T03:04:05.678Z' );
|
|
72
|
+
const workflowId = 'workflow-id-123';
|
|
73
|
+
const workflowName = 'test-workflow';
|
|
74
|
+
|
|
75
|
+
const destination = getDestination( { startTime, workflowId, workflowName } );
|
|
76
|
+
|
|
77
|
+
// Should return an absolute path
|
|
78
|
+
expect( destination ).toMatch( /^\/|^[A-Z]:\\/i ); // Starting with / or Windows drive letter
|
|
79
|
+
expect( destination ).toContain( '/logs/runs/test-workflow/2020-01-02-03-04-05-678Z_workflow-id-123.json' );
|
|
80
|
+
} );
|
|
66
81
|
} );
|
|
67
82
|
|
|
@@ -3,15 +3,24 @@ export const NodeType = {
|
|
|
3
3
|
};
|
|
4
4
|
|
|
5
5
|
export const ComponentFile = {
|
|
6
|
+
EVALUATORS: 'evaluators',
|
|
6
7
|
STEPS: 'steps',
|
|
7
8
|
SHARED_STEPS: 'shared_steps',
|
|
8
|
-
EVALUATORS: 'evaluators',
|
|
9
9
|
WORKFLOW: 'workflow'
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
export const
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
export const EXTRANEOUS_FILE = 'extraneous';
|
|
13
|
+
export const ExtraneousFileList = [
|
|
14
|
+
'types',
|
|
15
|
+
'consts',
|
|
16
|
+
'constants',
|
|
17
|
+
'vars',
|
|
18
|
+
'variables',
|
|
19
|
+
'utils',
|
|
20
|
+
'tools',
|
|
21
|
+
'functions',
|
|
22
|
+
'shared'
|
|
23
|
+
];
|
|
15
24
|
|
|
16
25
|
export const CoreModule = {
|
|
17
26
|
LOCAL: 'local_core',
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
thisExpression,
|
|
21
21
|
isExportDefaultDeclaration
|
|
22
22
|
} from '@babel/types';
|
|
23
|
-
import { ComponentFile,
|
|
23
|
+
import { ComponentFile, EXTRANEOUS_FILE, ExtraneousFileList, NodeType } from './consts.js';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Resolve a relative module specifier against a base directory.
|
|
@@ -134,6 +134,8 @@ export const isWorkflowPath = value => /(^|\/)workflow\.js$/.test( value );
|
|
|
134
134
|
*/
|
|
135
135
|
export const isTypesPath = value => /(^|\/)types\.js$/.test( value );
|
|
136
136
|
|
|
137
|
+
export const isExtraneousFile = value => ExtraneousFileList.map( t => new RegExp( `(^|\/)${t}\\.js$` ) ).find( rx => rx.test( value ) );
|
|
138
|
+
|
|
137
139
|
/**
|
|
138
140
|
* Determine file kind based on its path.
|
|
139
141
|
* @param {string} filename
|
|
@@ -152,8 +154,8 @@ export const getFileKind = path => {
|
|
|
152
154
|
if ( isWorkflowPath( path ) ) {
|
|
153
155
|
return ComponentFile.WORKFLOW;
|
|
154
156
|
}
|
|
155
|
-
if (
|
|
156
|
-
return
|
|
157
|
+
if ( isExtraneousFile( path ) ) {
|
|
158
|
+
return EXTRANEOUS_FILE;
|
|
157
159
|
}
|
|
158
160
|
return null;
|
|
159
161
|
};
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
buildSharedStepsNameMap,
|
|
21
21
|
buildWorkflowNameMap,
|
|
22
22
|
buildEvaluatorsNameMap,
|
|
23
|
+
isExtraneousFile,
|
|
23
24
|
getFileKind
|
|
24
25
|
} from './tools.js';
|
|
25
26
|
|
|
@@ -169,6 +170,49 @@ describe( 'workflow_rewriter tools', () => {
|
|
|
169
170
|
expect( isEvaluatorsPath( 'steps.js' ) ).toBe( false );
|
|
170
171
|
} );
|
|
171
172
|
|
|
173
|
+
it( 'isExtraneousFile: returns truthy for extraneous files (types/consts/constants/vars/variables)', () => {
|
|
174
|
+
const ok = [
|
|
175
|
+
'types.js',
|
|
176
|
+
'./types.js',
|
|
177
|
+
'/a/b/types.js',
|
|
178
|
+
'consts.js',
|
|
179
|
+
'./consts.js',
|
|
180
|
+
'/a/b/consts.js',
|
|
181
|
+
'constants.js',
|
|
182
|
+
'./constants.js',
|
|
183
|
+
'/a/b/constants.js',
|
|
184
|
+
'vars.js',
|
|
185
|
+
'./vars.js',
|
|
186
|
+
'/a/b/vars.js',
|
|
187
|
+
'variables.js',
|
|
188
|
+
'./variables.js',
|
|
189
|
+
'/a/b/variables.js'
|
|
190
|
+
];
|
|
191
|
+
for ( const p of ok ) {
|
|
192
|
+
expect( Boolean( isExtraneousFile( p ) ) ).toBe( true );
|
|
193
|
+
}
|
|
194
|
+
} );
|
|
195
|
+
|
|
196
|
+
it( 'isExtraneousFile: returns falsy for non-extraneous or non-.js files', () => {
|
|
197
|
+
const bad = [
|
|
198
|
+
'types.ts',
|
|
199
|
+
'/a/b/types.ts',
|
|
200
|
+
'types.mjs',
|
|
201
|
+
'variables.jsx',
|
|
202
|
+
'variables.mjs',
|
|
203
|
+
'myconstants.js',
|
|
204
|
+
'steps.js',
|
|
205
|
+
'evaluators.js',
|
|
206
|
+
'shared_steps.js',
|
|
207
|
+
'workflow.js',
|
|
208
|
+
'typess.js',
|
|
209
|
+
'/a/b/c/variables.json'
|
|
210
|
+
];
|
|
211
|
+
for ( const p of bad ) {
|
|
212
|
+
expect( isExtraneousFile( p ) ).toBeFalsy();
|
|
213
|
+
}
|
|
214
|
+
} );
|
|
215
|
+
|
|
172
216
|
it( 'createThisMethodCall: builds this.method(\'name\', ...args) call', () => {
|
|
173
217
|
const call = createThisMethodCall( 'invoke', 'n', [ t.numericLiteral( 1 ), t.identifier( 'x' ) ] );
|
|
174
218
|
expect( t.isCallExpression( call ) ).toBe( true );
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import traverseModule from '@babel/traverse';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { parse, toAbsolutePath, getFileKind } from '../tools.js';
|
|
4
|
-
import { ComponentFile, CoreModule,
|
|
4
|
+
import { ComponentFile, CoreModule, EXTRANEOUS_FILE, ExtraneousFileList } from '../consts.js';
|
|
5
5
|
import {
|
|
6
6
|
isCallExpression,
|
|
7
7
|
isFunctionExpression,
|
|
@@ -23,10 +23,10 @@ const traverse = traverseModule.default ?? traverseModule;
|
|
|
23
23
|
const validateWorkflowImports = ( { specifier, filename } ) => {
|
|
24
24
|
const isCore = Object.values( CoreModule ).includes( specifier );
|
|
25
25
|
const isComponent = Object.values( ComponentFile ).includes( getFileKind( specifier ) );
|
|
26
|
-
const isAllowedExtraneous = getFileKind( specifier ) ===
|
|
26
|
+
const isAllowedExtraneous = getFileKind( specifier ) === EXTRANEOUS_FILE;
|
|
27
27
|
if ( !isCore && !isComponent && !isAllowedExtraneous ) {
|
|
28
28
|
throw new Error( `Invalid dependency in workflow.js: '${specifier}'. \
|
|
29
|
-
Only
|
|
29
|
+
Only components (${Object.values( ComponentFile ) } ), @output.ai/core, or whitelisted (${ExtraneousFileList}) imports are allowed in ${filename}` );
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
32
|
|
|
@@ -38,7 +38,7 @@ describe( 'workflow_validator loader', () => {
|
|
|
38
38
|
|
|
39
39
|
it( 'workflow.js: rejects external dependencies', async () => {
|
|
40
40
|
const dir = mkdtempSync( join( tmpdir(), 'wf-reject-' ) );
|
|
41
|
-
const src = 'import x from "./
|
|
41
|
+
const src = 'import x from "./foo.js";';
|
|
42
42
|
await expect( runLoader( join( dir, 'workflow.js' ), src ) ).rejects.toThrow( /Invalid (import|dependency) in workflow\.js/ );
|
|
43
43
|
rmSync( dir, { recursive: true, force: true } );
|
|
44
44
|
} );
|
|
@@ -218,6 +218,17 @@ describe( 'workflow_validator loader', () => {
|
|
|
218
218
|
rmSync( dir, { recursive: true, force: true } );
|
|
219
219
|
} );
|
|
220
220
|
|
|
221
|
+
it( 'workflow.js: allows importing extraneous files (consts/constants/vars/variables)', async () => {
|
|
222
|
+
const dir = mkdtempSync( join( tmpdir(), 'wf-extra-allow-' ) );
|
|
223
|
+
const bases = [ 'consts', 'constants', 'vars', 'variables' ];
|
|
224
|
+
for ( const base of bases ) {
|
|
225
|
+
writeFileSync( join( dir, `${base}.js` ), 'export const X = 1\n' );
|
|
226
|
+
const src = `import x from "./${base}.js";`;
|
|
227
|
+
await expect( runLoader( join( dir, 'workflow.js' ), src ) ).resolves.toBeTruthy();
|
|
228
|
+
}
|
|
229
|
+
rmSync( dir, { recursive: true, force: true } );
|
|
230
|
+
} );
|
|
231
|
+
|
|
221
232
|
it( 'steps.js: rejects require of steps/shared_steps/evaluators/workflow; allows other require', async () => {
|
|
222
233
|
const dir = mkdtempSync( join( tmpdir(), 'steps-require-' ) );
|
|
223
234
|
await expect( runLoader( join( dir, 'steps.js' ), 'const { S } = require("./steps.js");' ) )
|