Make plugin system fully dynamic with auto-generated registries#78
Make plugin system fully dynamic with auto-generated registries#78ctate merged 2 commits intovercel-labs:mainfrom
Conversation
This refactors the codebase to use the plugin registry as the single source of truth, eliminating hardcoded integration/action data throughout. New plugins now automatically work everywhere without manual updates to core files. Key changes: **Auto-generated files (via discover-plugins.ts):** - lib/types/integration.ts - IntegrationType union and IntegrationConfig types - lib/step-registry.ts - Step importers for workflow execution (statically analyzable) - plugins/index.ts - Plugin imports and registry exports - README.md - Auto-updated plugins list **Dynamic plugin registry usage:** - workflow-executor.workflow.ts - Uses step-registry for dynamic step imports - workflow-codegen-shared.ts - getStepInfo() uses findActionById() - workflow-codegen-sdk.ts - loadStepImplementation() uses plugin codegenTemplates - workflow-codegen.ts - Helper functions use getStepInfo() consistently - credential-fetcher.ts - Uses plugin credentialMapping functions - code-generators.ts - Uses getAllActions() from registry - action-node.tsx - Uses getAllActions() for action categories - integration-form-dialog.tsx - Uses plugin formFields dynamically - integrations-manager.tsx - Uses getAllIntegrations() **Removed hardcoding:** - ~200 lines of hardcoded step mappings in workflow-codegen-sdk.ts - ~70 lines of credential mappers in credential-fetcher.ts - Hardcoded action type dispatchers in workflow-executor.workflow.ts - Hardcoded IntegrationType unions (now auto-generated) **discover-plugins.ts improvements:** - Converted from .mjs to .ts with proper typing - Generates lib/types/integration.ts with dynamic types - Generates lib/step-registry.ts with static imports for bundler compatibility - Updates README.md with current plugin list
|
@bensabic is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
| `return (${transformedExpression});` | ||
| ); | ||
| const result = evalFunc(...varValues); | ||
| evaluatedCondition = Boolean(result); | ||
| } catch (error) { | ||
| console.error("[Condition] Failed to evaluate condition:", error); | ||
| console.error("[Condition] Expression was:", conditionExpression); | ||
| // If evaluation fails, treat as false to be safe | ||
| evaluatedCondition = false; | ||
| } | ||
| } else { | ||
| // Coerce to boolean for other types |
There was a problem hiding this comment.
System action step functions are called without checking if they exist in the imported module, which will throw a TypeError if the export is missing. Plugin actions have proper defensive checks, but system actions don't.
View Details
📝 Patch Details
diff --git a/lib/workflow-executor.workflow.ts b/lib/workflow-executor.workflow.ts
index 6df2a66..82cc91f 100644
--- a/lib/workflow-executor.workflow.ts
+++ b/lib/workflow-executor.workflow.ts
@@ -175,13 +175,21 @@ async function executeActionStep(input: {
if (actionType === "Condition") {
const systemAction = SYSTEM_ACTIONS.Condition;
const module = await systemAction.importer();
+ const stepFunction = module[systemAction.stepFunction];
+ if (!stepFunction) {
+ return {
+ success: false,
+ error: `Step function "${systemAction.stepFunction}" not found in module`,
+ };
+ }
+
const evaluatedCondition = evaluateConditionExpression(
stepInput.condition,
outputs
);
console.log("[Condition] Final result:", evaluatedCondition);
- return await module[systemAction.stepFunction]({
+ return await stepFunction({
condition: evaluatedCondition,
_context: context,
});
@@ -192,6 +200,12 @@ async function executeActionStep(input: {
if (systemAction) {
const module = await systemAction.importer();
const stepFunction = module[systemAction.stepFunction];
+ if (!stepFunction) {
+ return {
+ success: false,
+ error: `Step function "${systemAction.stepFunction}" not found in module`,
+ };
+ }
return await stepFunction(stepInput);
}
Analysis
Missing null/undefined checks for system action step functions cause inconsistent error handling
What fails: executeActionStep() in lib/workflow-executor.workflow.ts throws a TypeError when a system action's step function is not found in the imported module, while plugin actions gracefully return an error object.
How to reproduce: Modify the SYSTEM_ACTIONS mapping in lib/workflow-executor.workflow.ts to use an incorrect step function name:
"Database Query": {
importer: () => import("./steps/database-query") as Promise<any>,
stepFunction: "wrongFunctionName", // Changed from "databaseQueryStep"
},Then execute a workflow with a Database Query action. The code at line 194 will try to call module.wrongFunctionName(), which is undefined, resulting in:
TypeError: stepFunction is not a function
Expected behavior: Should return a graceful error object like plugin actions do:
{
success: false,
error: `Step function "wrongFunctionName" not found in module`
}Root cause:
- Line 184 (Condition action): Calls
module[systemAction.stepFunction]without checking if it exists - Lines 194-195 (Database Query/HTTP Request): Calls
module[systemAction.stepFunction]without checking if it exists - Lines 202-210 (Plugin actions): Already has proper defensive check with
if (stepFunction) { ... }
Fix applied: Added null/undefined checks for system action step functions at both locations (Condition action and general system actions), matching the pattern used for plugin actions. This ensures consistent error handling across all action types.
|
@ctate - I feel like I'm doing something wrong haha. Not seeing anything on my end with that PR Check error, can't seem to replicate it |
|
THANK YOU. This is so good. I'm AFK atm but will take a look first thing in the morning |
Node 20 + tsx has an issue where side-effect imports in dynamically imported modules don't execute properly. This caused discover-plugins to fail to register plugins, resulting in only database being generated in IntegrationType. Node 22 handles this correctly.
Should be thanking you! You set up a great foundation with introducing the plugin system. Also FYI was able to fix that issue by just bumping up the Node verison to 22 |
* Make plugin system fully dynamic with auto-generated registries This refactors the codebase to use the plugin registry as the single source of truth, eliminating hardcoded integration/action data throughout. New plugins now automatically work everywhere without manual updates to core files. Key changes: **Auto-generated files (via discover-plugins.ts):** - lib/types/integration.ts - IntegrationType union and IntegrationConfig types - lib/step-registry.ts - Step importers for workflow execution (statically analyzable) - plugins/index.ts - Plugin imports and registry exports - README.md - Auto-updated plugins list **Dynamic plugin registry usage:** - workflow-executor.workflow.ts - Uses step-registry for dynamic step imports - workflow-codegen-shared.ts - getStepInfo() uses findActionById() - workflow-codegen-sdk.ts - loadStepImplementation() uses plugin codegenTemplates - workflow-codegen.ts - Helper functions use getStepInfo() consistently - credential-fetcher.ts - Uses plugin credentialMapping functions - code-generators.ts - Uses getAllActions() from registry - action-node.tsx - Uses getAllActions() for action categories - integration-form-dialog.tsx - Uses plugin formFields dynamically - integrations-manager.tsx - Uses getAllIntegrations() **Removed hardcoding:** - ~200 lines of hardcoded step mappings in workflow-codegen-sdk.ts - ~70 lines of credential mappers in credential-fetcher.ts - Hardcoded action type dispatchers in workflow-executor.workflow.ts - Hardcoded IntegrationType unions (now auto-generated) **discover-plugins.ts improvements:** - Converted from .mjs to .ts with proper typing - Generates lib/types/integration.ts with dynamic types - Generates lib/step-registry.ts with static imports for bundler compatibility - Updates README.md with current plugin list * fix: use Node 22 in CI for tsx compatibility Node 20 + tsx has an issue where side-effect imports in dynamically imported modules don't execute properly. This caused discover-plugins to fail to register plugins, resulting in only database being generated in IntegrationType. Node 22 handles this correctly. --------- Co-authored-by: Benjamin Sabic <bensabic@users.noreply.github.com>
This refactors the codebase to use the plugin registry as the single source of truth,
eliminating hardcoded integration/action data throughout. New plugins now automatically
work everywhere without manual updates to core files.
Key changes:
Auto-generated files (via discover-plugins.ts):
Dynamic plugin registry usage:
Removed hardcoding:
discover-plugins.ts improvements: