@outputai/core 0.3.3-next.e8eff63.0 → 0.4.1-dev.06c2b50.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/package.json +10 -2
- package/src/activity_integration/tracing.d.ts +1 -0
- package/src/activity_integration/tracing.js +2 -1
- package/src/interface/workflow.d.ts +2 -2
- package/src/tracing/tools/aggregate_trace_attributes.js +118 -0
- package/src/tracing/tools/aggregate_trace_attributes.spec.js +231 -0
- package/src/tracing/tools/index.js +7 -0
- package/src/worker/bundler_options.js +33 -4
- package/src/worker/bundler_options.spec.js +62 -0
- package/src/worker/loader.js +62 -50
- package/src/worker/loader.spec.js +285 -82
- package/src/worker/loader_tools.js +232 -60
- package/src/worker/loader_tools.spec.js +496 -25
- package/src/worker/webpack_loaders/npm_workflow_export_resolve.js +474 -0
- package/src/worker/webpack_loaders/npm_workflow_export_resolve.spec.js +374 -0
- package/src/worker/webpack_loaders/tools.js +9 -0
- package/src/worker/webpack_loaders/tools.spec.js +7 -0
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +80 -11
- package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +67 -0
- package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +2 -1
- package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +73 -1
- package/src/worker/webpack_loaders/workflow_validator/index.mjs +66 -1
- package/src/worker/webpack_loaders/workflow_validator/index.spec.js +62 -0
package/src/worker/loader.js
CHANGED
|
@@ -3,7 +3,14 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
|
3
3
|
import { EOL } from 'node:os';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { getTraceDestinations, sendHttpRequest } from '#internal_activities';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
activityMatchersBuilder,
|
|
8
|
+
findSharedActivitiesFromWorkflows,
|
|
9
|
+
findWorkflowsInNodeModules,
|
|
10
|
+
importComponents,
|
|
11
|
+
matchFiles,
|
|
12
|
+
staticMatchers
|
|
13
|
+
} from './loader_tools.js';
|
|
7
14
|
import {
|
|
8
15
|
ACTIVITY_SEND_HTTP_REQUEST,
|
|
9
16
|
ACTIVITY_OPTIONS_FILENAME,
|
|
@@ -13,6 +20,7 @@ import {
|
|
|
13
20
|
ACTIVITY_GET_TRACE_DESTINATIONS
|
|
14
21
|
} from '#consts';
|
|
15
22
|
import { createChildLogger } from '#logger';
|
|
23
|
+
import { ValidationError } from '#errors';
|
|
16
24
|
|
|
17
25
|
const log = createChildLogger( 'Scanner' );
|
|
18
26
|
|
|
@@ -64,23 +72,34 @@ export async function loadActivities( rootDir, workflows ) {
|
|
|
64
72
|
const activityOptionsMap = {};
|
|
65
73
|
|
|
66
74
|
// Load workflow based activities
|
|
67
|
-
for ( const { path: workflowPath, name: workflowName } of workflows ) {
|
|
75
|
+
for ( const { path: workflowPath, name: workflowName, external } of workflows ) {
|
|
68
76
|
const dir = dirname( workflowPath );
|
|
69
|
-
for await ( const { fn, metadata, path } of importComponents( dir, Object.values( activityMatchersBuilder( dir ) ) ) ) {
|
|
70
|
-
log.info( 'Component loaded', { type: metadata.type, name: metadata.name, path, workflow: workflowName } );
|
|
77
|
+
for await ( const { fn, metadata, path } of importComponents( matchFiles( dir, Object.values( activityMatchersBuilder( dir ) ) ) ) ) {
|
|
78
|
+
log.info( 'Component loaded', { type: metadata.type, name: metadata.name, path, workflow: workflowName, ...( external && { external } ) } );
|
|
71
79
|
// Activities loaded from a workflow path will use the workflow name as a namespace, which is unique across the platform, avoiding collision
|
|
72
80
|
const activityKey = generateActivityKey( { namespace: workflowName, activityName: metadata.name } );
|
|
81
|
+
if ( activities[activityKey] ) {
|
|
82
|
+
throw new ValidationError( `Activity "${metadata.name}" in workflow "${workflowName}" conflicts with another \
|
|
83
|
+
activity in the same workflow. Activity names must be unique within a workflow.` );
|
|
84
|
+
}
|
|
73
85
|
activities[activityKey] = fn;
|
|
74
86
|
// propagate the custom options set on the step()/evaluator() constructor
|
|
75
87
|
activityOptionsMap[activityKey] = metadata.options?.activityOptions ?? undefined;
|
|
76
88
|
}
|
|
77
89
|
}
|
|
78
90
|
|
|
79
|
-
// Load shared activities/evaluators
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
// Load shared activities/evaluators from local and external npm modules
|
|
92
|
+
const localSharedActivities = matchFiles( rootDir, [ staticMatchers.sharedStepsDir, staticMatchers.sharedEvaluatorsDir ] );
|
|
93
|
+
const externalSharedActivities = findSharedActivitiesFromWorkflows( workflows.filter( w => w.external ) );
|
|
94
|
+
for await ( const { fn, metadata, path } of importComponents( [ ...localSharedActivities, ...externalSharedActivities ] ) ) {
|
|
95
|
+
const external = externalSharedActivities.some( a => a.path === path );
|
|
96
|
+
log.info( 'Shared component loaded', { type: metadata.type, name: metadata.name, path, ...( external && { external } ) } );
|
|
97
|
+
// Reuses the same global namespace for shared activities
|
|
83
98
|
const activityKey = generateActivityKey( { namespace: SHARED_STEP_PREFIX, activityName: metadata.name } );
|
|
99
|
+
if ( activities[activityKey] ) {
|
|
100
|
+
throw new ValidationError( `Shared activity "${metadata.name}" conflicts with another shared activity. \
|
|
101
|
+
Shared activity names must be unique.` );
|
|
102
|
+
}
|
|
84
103
|
activities[activityKey] = fn;
|
|
85
104
|
activityOptionsMap[activityKey] = metadata.options?.activityOptions ?? undefined;
|
|
86
105
|
}
|
|
@@ -103,19 +122,43 @@ export async function loadActivities( rootDir, workflows ) {
|
|
|
103
122
|
* @returns {object[]}
|
|
104
123
|
*/
|
|
105
124
|
export async function loadWorkflows( rootDir ) {
|
|
125
|
+
const workflowNames = new Set();
|
|
106
126
|
const workflows = [];
|
|
107
|
-
|
|
127
|
+
const localWorkflows = matchFiles( rootDir, [ staticMatchers.workflowFile ] );
|
|
128
|
+
const externalWorkflows = findWorkflowsInNodeModules( rootDir );
|
|
129
|
+
for await ( const { metadata, path } of importComponents( [ ...localWorkflows, ...externalWorkflows ] ) ) {
|
|
130
|
+
const external = externalWorkflows.some( a => a.path === path );
|
|
108
131
|
if ( staticMatchers.workflowPathHasShared( path ) ) {
|
|
109
|
-
throw new
|
|
132
|
+
throw new ValidationError( 'Workflow directory can\'t be named "shared"' );
|
|
133
|
+
}
|
|
134
|
+
const { name, aliases } = metadata;
|
|
135
|
+
if ( workflowNames.has( name ) ) {
|
|
136
|
+
throw new ValidationError( `Workflow name "${name}" conflicts with another workflow or alias. \
|
|
137
|
+
Workflow names and aliases must be unique.` );
|
|
138
|
+
}
|
|
139
|
+
if ( WORKFLOW_CATALOG === name ) {
|
|
140
|
+
throw new ValidationError( `Workflow name "${name}" is reserved for the internal catalog workflow.` );
|
|
141
|
+
}
|
|
142
|
+
workflowNames.add( name );
|
|
143
|
+
for ( const alias of aliases ?? [] ) {
|
|
144
|
+
if ( workflowNames.has( alias ) ) {
|
|
145
|
+
throw new ValidationError( `Workflow "${name}" alias "${alias}" conflicts with another workflow or alias. \
|
|
146
|
+
Workflow names and aliases must be unique.` );
|
|
147
|
+
}
|
|
148
|
+
if ( WORKFLOW_CATALOG === alias ) {
|
|
149
|
+
throw new ValidationError( `Workflow "${name}" alias "${alias}" is reserved for the internal catalog workflow.` );
|
|
150
|
+
}
|
|
151
|
+
workflowNames.add( alias );
|
|
110
152
|
}
|
|
111
|
-
|
|
112
|
-
|
|
153
|
+
|
|
154
|
+
log.info( 'Workflow loaded', { name, path, aliases, ...( external && { external } ) } );
|
|
155
|
+
workflows.push( { ...metadata, path, external } );
|
|
113
156
|
}
|
|
114
157
|
return workflows;
|
|
115
158
|
};
|
|
116
159
|
|
|
117
160
|
/**
|
|
118
|
-
* Loads the hook files from package.json's
|
|
161
|
+
* Loads the hook files from package.json's "outputai" section.
|
|
119
162
|
*
|
|
120
163
|
* @param {string} rootDir
|
|
121
164
|
* @returns {void}
|
|
@@ -124,7 +167,12 @@ export async function loadHooks( rootDir ) {
|
|
|
124
167
|
const packageFile = join( rootDir, 'package.json' );
|
|
125
168
|
if ( existsSync( packageFile ) ) {
|
|
126
169
|
const pkg = await import( packageFile, { with: { type: 'json' } } );
|
|
127
|
-
|
|
170
|
+
const content = pkg.default;
|
|
171
|
+
const hooks = [];
|
|
172
|
+
// @DEPRECATED: "output" is the legacy namespace for configs, can be removed after couple version (this is being added in 0.3.x)
|
|
173
|
+
hooks.push( ...( content['output']?.hookFiles ?? [] ) );
|
|
174
|
+
hooks.push( ...( content['outputai']?.hookFiles ?? [] ) );
|
|
175
|
+
for ( const path of hooks ) {
|
|
128
176
|
const hookFile = join( rootDir, path );
|
|
129
177
|
await import( hookFile );
|
|
130
178
|
log.info( 'Hook file loaded', { path } );
|
|
@@ -132,40 +180,6 @@ export async function loadHooks( rootDir ) {
|
|
|
132
180
|
}
|
|
133
181
|
};
|
|
134
182
|
|
|
135
|
-
/**
|
|
136
|
-
* Validates that all workflow names and aliases are unique across the project.
|
|
137
|
-
*
|
|
138
|
-
* @param {object[]} workflows
|
|
139
|
-
* @throws {Error} If any alias conflicts with a workflow name or another alias
|
|
140
|
-
*/
|
|
141
|
-
function validateWorkflowNames( workflows ) {
|
|
142
|
-
const allNames = new Map();
|
|
143
|
-
|
|
144
|
-
// Register primary names (case-insensitive to prevent confusing collisions)
|
|
145
|
-
for ( const { name } of workflows ) {
|
|
146
|
-
allNames.set( name.toLowerCase(), `workflow "${name}"` );
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Check the reserved catalog name
|
|
150
|
-
allNames.set( WORKFLOW_CATALOG.toLowerCase(), 'system workflow "$catalog"' );
|
|
151
|
-
|
|
152
|
-
// Check aliases against all names
|
|
153
|
-
for ( const { name, aliases = [] } of workflows ) {
|
|
154
|
-
const lowerCaseName = name.toLowerCase();
|
|
155
|
-
for ( const alias of aliases ) {
|
|
156
|
-
const lowerAliasName = alias.toLowerCase();
|
|
157
|
-
if ( lowerAliasName === lowerCaseName ) {
|
|
158
|
-
throw new Error( `Workflow "${name}" has an alias identical to its own name` );
|
|
159
|
-
}
|
|
160
|
-
const conflict = allNames.get( lowerAliasName );
|
|
161
|
-
if ( conflict ) {
|
|
162
|
-
throw new Error( `Alias "${alias}" on workflow "${name}" conflicts with ${conflict}` );
|
|
163
|
-
}
|
|
164
|
-
allNames.set( lowerAliasName, `alias "${alias}" on workflow "${name}"` );
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
183
|
/**
|
|
170
184
|
* Creates a temporary index file importing all workflows for Temporal.
|
|
171
185
|
*
|
|
@@ -173,8 +187,6 @@ function validateWorkflowNames( workflows ) {
|
|
|
173
187
|
* @returns
|
|
174
188
|
*/
|
|
175
189
|
export function createWorkflowsEntryPoint( workflows ) {
|
|
176
|
-
validateWorkflowNames( workflows );
|
|
177
|
-
|
|
178
190
|
const path = join( __dirname, 'temp', WORKFLOWS_INDEX_FILENAME );
|
|
179
191
|
|
|
180
192
|
// default system catalog workflow
|