@synergenius/flow-weaver 0.2.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/LICENSE +122 -0
- package/README.md +315 -0
- package/dist/annotation-generator.d.ts +45 -0
- package/dist/annotation-generator.js +557 -0
- package/dist/api/builder.d.ts +223 -0
- package/dist/api/builder.js +345 -0
- package/dist/api/compile.d.ts +92 -0
- package/dist/api/compile.js +149 -0
- package/dist/api/extract-types.d.ts +29 -0
- package/dist/api/extract-types.js +57 -0
- package/dist/api/generate-in-place.d.ts +73 -0
- package/dist/api/generate-in-place.js +1353 -0
- package/dist/api/generate.d.ts +83 -0
- package/dist/api/generate.js +510 -0
- package/dist/api/helpers.d.ts +248 -0
- package/dist/api/helpers.js +285 -0
- package/dist/api/index.d.ts +46 -0
- package/dist/api/index.js +45 -0
- package/dist/api/inline-runtime.d.ts +27 -0
- package/dist/api/inline-runtime.js +551 -0
- package/dist/api/manipulation/connections.d.ts +79 -0
- package/dist/api/manipulation/connections.js +151 -0
- package/dist/api/manipulation/index.d.ts +34 -0
- package/dist/api/manipulation/index.js +41 -0
- package/dist/api/manipulation/node-types.d.ts +123 -0
- package/dist/api/manipulation/node-types.js +200 -0
- package/dist/api/manipulation/nodes.d.ts +144 -0
- package/dist/api/manipulation/nodes.js +333 -0
- package/dist/api/manipulation/ports.d.ts +59 -0
- package/dist/api/manipulation/ports.js +228 -0
- package/dist/api/manipulation/scopes.d.ts +52 -0
- package/dist/api/manipulation/scopes.js +156 -0
- package/dist/api/manipulation/validation.d.ts +6 -0
- package/dist/api/manipulation/validation.js +6 -0
- package/dist/api/manipulation/workflow.d.ts +81 -0
- package/dist/api/manipulation/workflow.js +116 -0
- package/dist/api/manipulation.d.ts +8 -0
- package/dist/api/manipulation.js +8 -0
- package/dist/api/parse.d.ts +48 -0
- package/dist/api/parse.js +110 -0
- package/dist/api/patterns.d.ts +112 -0
- package/dist/api/patterns.js +306 -0
- package/dist/api/query.d.ts +429 -0
- package/dist/api/query.js +816 -0
- package/dist/api/templates.d.ts +98 -0
- package/dist/api/templates.js +117 -0
- package/dist/api/transform.d.ts +31 -0
- package/dist/api/transform.js +40 -0
- package/dist/api/validate.d.ts +25 -0
- package/dist/api/validate.js +39 -0
- package/dist/api/workflow-file-operations.d.ts +29 -0
- package/dist/api/workflow-file-operations.js +180 -0
- package/dist/ast/builder.d.ts +210 -0
- package/dist/ast/builder.js +395 -0
- package/dist/ast/index.d.ts +5 -0
- package/dist/ast/index.js +5 -0
- package/dist/ast/serialization-node.d.ts +6 -0
- package/dist/ast/serialization-node.js +30 -0
- package/dist/ast/serialization.d.ts +43 -0
- package/dist/ast/serialization.js +134 -0
- package/dist/ast/types.d.ts +852 -0
- package/dist/ast/types.js +2 -0
- package/dist/ast/workflow-utils.d.ts +54 -0
- package/dist/ast/workflow-utils.js +114 -0
- package/dist/body-generator.d.ts +31 -0
- package/dist/body-generator.js +35 -0
- package/dist/built-in-nodes/delay.d.ts +11 -0
- package/dist/built-in-nodes/delay.js +29 -0
- package/dist/built-in-nodes/index.d.ts +5 -0
- package/dist/built-in-nodes/index.js +4 -0
- package/dist/built-in-nodes/invoke-workflow.d.ts +13 -0
- package/dist/built-in-nodes/invoke-workflow.js +25 -0
- package/dist/built-in-nodes/mock-types.d.ts +18 -0
- package/dist/built-in-nodes/mock-types.js +12 -0
- package/dist/built-in-nodes/wait-for-event.d.ts +13 -0
- package/dist/built-in-nodes/wait-for-event.js +25 -0
- package/dist/chevrotain-parser/connect-parser.d.ts +24 -0
- package/dist/chevrotain-parser/connect-parser.js +98 -0
- package/dist/chevrotain-parser/grammar-diagrams.d.ts +29 -0
- package/dist/chevrotain-parser/grammar-diagrams.js +264 -0
- package/dist/chevrotain-parser/index.d.ts +25 -0
- package/dist/chevrotain-parser/index.js +27 -0
- package/dist/chevrotain-parser/map-parser.d.ts +33 -0
- package/dist/chevrotain-parser/map-parser.js +130 -0
- package/dist/chevrotain-parser/node-parser.d.ts +36 -0
- package/dist/chevrotain-parser/node-parser.js +466 -0
- package/dist/chevrotain-parser/path-parser.d.ts +28 -0
- package/dist/chevrotain-parser/path-parser.js +118 -0
- package/dist/chevrotain-parser/port-parser.d.ts +36 -0
- package/dist/chevrotain-parser/port-parser.js +442 -0
- package/dist/chevrotain-parser/position-parser.d.ts +20 -0
- package/dist/chevrotain-parser/position-parser.js +83 -0
- package/dist/chevrotain-parser/scope-parser.d.ts +19 -0
- package/dist/chevrotain-parser/scope-parser.js +104 -0
- package/dist/chevrotain-parser/tokens.d.ts +78 -0
- package/dist/chevrotain-parser/tokens.js +384 -0
- package/dist/chevrotain-parser/trigger-cancel-parser.d.ts +50 -0
- package/dist/chevrotain-parser/trigger-cancel-parser.js +282 -0
- package/dist/cli/commands/changelog.d.ts +13 -0
- package/dist/cli/commands/changelog.js +135 -0
- package/dist/cli/commands/compile.d.ts +64 -0
- package/dist/cli/commands/compile.js +278 -0
- package/dist/cli/commands/create.d.ts +33 -0
- package/dist/cli/commands/create.js +147 -0
- package/dist/cli/commands/describe.d.ts +68 -0
- package/dist/cli/commands/describe.js +377 -0
- package/dist/cli/commands/dev.d.ts +32 -0
- package/dist/cli/commands/dev.js +384 -0
- package/dist/cli/commands/diagram.d.ts +13 -0
- package/dist/cli/commands/diagram.js +33 -0
- package/dist/cli/commands/diff.d.ts +11 -0
- package/dist/cli/commands/diff.js +59 -0
- package/dist/cli/commands/doctor.d.ts +57 -0
- package/dist/cli/commands/doctor.js +719 -0
- package/dist/cli/commands/export.d.ts +57 -0
- package/dist/cli/commands/export.js +163 -0
- package/dist/cli/commands/grammar.d.ts +9 -0
- package/dist/cli/commands/grammar.js +39 -0
- package/dist/cli/commands/init.d.ts +59 -0
- package/dist/cli/commands/init.js +435 -0
- package/dist/cli/commands/listen.d.ts +16 -0
- package/dist/cli/commands/listen.js +39 -0
- package/dist/cli/commands/market.d.ts +52 -0
- package/dist/cli/commands/market.js +436 -0
- package/dist/cli/commands/migrate.d.ts +13 -0
- package/dist/cli/commands/migrate.js +89 -0
- package/dist/cli/commands/openapi.d.ts +37 -0
- package/dist/cli/commands/openapi.js +67 -0
- package/dist/cli/commands/pattern.d.ts +34 -0
- package/dist/cli/commands/pattern.js +185 -0
- package/dist/cli/commands/plugin.d.ts +16 -0
- package/dist/cli/commands/plugin.js +176 -0
- package/dist/cli/commands/run.d.ts +49 -0
- package/dist/cli/commands/run.js +191 -0
- package/dist/cli/commands/serve.d.ts +45 -0
- package/dist/cli/commands/serve.js +81 -0
- package/dist/cli/commands/templates.d.ts +8 -0
- package/dist/cli/commands/templates.js +54 -0
- package/dist/cli/commands/ui.d.ts +16 -0
- package/dist/cli/commands/ui.js +130 -0
- package/dist/cli/commands/validate.d.ts +12 -0
- package/dist/cli/commands/validate.js +247 -0
- package/dist/cli/commands/watch.d.ts +9 -0
- package/dist/cli/commands/watch.js +70 -0
- package/dist/cli/flow-weaver.mjs +92924 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +742 -0
- package/dist/cli/templates/ai/mock-provider.d.ts +7 -0
- package/dist/cli/templates/ai/mock-provider.js +64 -0
- package/dist/cli/templates/ai/types.d.ts +47 -0
- package/dist/cli/templates/ai/types.js +5 -0
- package/dist/cli/templates/approvals/index.d.ts +15 -0
- package/dist/cli/templates/approvals/index.js +241 -0
- package/dist/cli/templates/index.d.ts +102 -0
- package/dist/cli/templates/index.js +101 -0
- package/dist/cli/templates/nodes/agent-router.d.ts +3 -0
- package/dist/cli/templates/nodes/agent-router.js +114 -0
- package/dist/cli/templates/nodes/aggregator.d.ts +7 -0
- package/dist/cli/templates/nodes/aggregator.js +63 -0
- package/dist/cli/templates/nodes/conversation-memory.d.ts +3 -0
- package/dist/cli/templates/nodes/conversation-memory.js +85 -0
- package/dist/cli/templates/nodes/http.d.ts +7 -0
- package/dist/cli/templates/nodes/http.js +80 -0
- package/dist/cli/templates/nodes/human-approval.d.ts +3 -0
- package/dist/cli/templates/nodes/human-approval.js +110 -0
- package/dist/cli/templates/nodes/json-extractor.d.ts +3 -0
- package/dist/cli/templates/nodes/json-extractor.js +119 -0
- package/dist/cli/templates/nodes/llm-call.d.ts +3 -0
- package/dist/cli/templates/nodes/llm-call.js +106 -0
- package/dist/cli/templates/nodes/prompt-template.d.ts +3 -0
- package/dist/cli/templates/nodes/prompt-template.js +52 -0
- package/dist/cli/templates/nodes/rag-retriever.d.ts +3 -0
- package/dist/cli/templates/nodes/rag-retriever.js +128 -0
- package/dist/cli/templates/nodes/tool-executor.d.ts +3 -0
- package/dist/cli/templates/nodes/tool-executor.js +108 -0
- package/dist/cli/templates/nodes/transformer.d.ts +7 -0
- package/dist/cli/templates/nodes/transformer.js +68 -0
- package/dist/cli/templates/nodes/validator.d.ts +7 -0
- package/dist/cli/templates/nodes/validator.js +62 -0
- package/dist/cli/templates/providers/index.d.ts +14 -0
- package/dist/cli/templates/providers/index.js +239 -0
- package/dist/cli/templates/shared/approval-types.d.ts +9 -0
- package/dist/cli/templates/shared/approval-types.js +31 -0
- package/dist/cli/templates/shared/llm-types.d.ts +15 -0
- package/dist/cli/templates/shared/llm-types.js +104 -0
- package/dist/cli/templates/workflows/aggregator.d.ts +7 -0
- package/dist/cli/templates/workflows/aggregator.js +104 -0
- package/dist/cli/templates/workflows/ai-agent-durable.d.ts +8 -0
- package/dist/cli/templates/workflows/ai-agent-durable.js +338 -0
- package/dist/cli/templates/workflows/ai-agent.d.ts +31 -0
- package/dist/cli/templates/workflows/ai-agent.js +326 -0
- package/dist/cli/templates/workflows/ai-chat.d.ts +7 -0
- package/dist/cli/templates/workflows/ai-chat.js +169 -0
- package/dist/cli/templates/workflows/ai-pipeline-durable.d.ts +8 -0
- package/dist/cli/templates/workflows/ai-pipeline-durable.js +330 -0
- package/dist/cli/templates/workflows/ai-rag.d.ts +7 -0
- package/dist/cli/templates/workflows/ai-rag.js +186 -0
- package/dist/cli/templates/workflows/ai-react.d.ts +7 -0
- package/dist/cli/templates/workflows/ai-react.js +294 -0
- package/dist/cli/templates/workflows/conditional.d.ts +12 -0
- package/dist/cli/templates/workflows/conditional.js +142 -0
- package/dist/cli/templates/workflows/error-handler.d.ts +7 -0
- package/dist/cli/templates/workflows/error-handler.js +147 -0
- package/dist/cli/templates/workflows/foreach.d.ts +7 -0
- package/dist/cli/templates/workflows/foreach.js +143 -0
- package/dist/cli/templates/workflows/sequential.d.ts +7 -0
- package/dist/cli/templates/workflows/sequential.js +198 -0
- package/dist/cli/templates/workflows/webhook.d.ts +7 -0
- package/dist/cli/templates/workflows/webhook.js +161 -0
- package/dist/cli/utils/logger.d.ts +15 -0
- package/dist/cli/utils/logger.js +46 -0
- package/dist/constants.d.ts +100 -0
- package/dist/constants.js +125 -0
- package/dist/defaults.d.ts +3 -0
- package/dist/defaults.js +3 -0
- package/dist/deployment/config/defaults.d.ts +29 -0
- package/dist/deployment/config/defaults.js +98 -0
- package/dist/deployment/config/loader.d.ts +24 -0
- package/dist/deployment/config/loader.js +236 -0
- package/dist/deployment/config/types.d.ts +117 -0
- package/dist/deployment/config/types.js +5 -0
- package/dist/deployment/core/adapters.d.ts +90 -0
- package/dist/deployment/core/adapters.js +251 -0
- package/dist/deployment/core/executor.d.ts +62 -0
- package/dist/deployment/core/executor.js +197 -0
- package/dist/deployment/core/formatters.d.ts +57 -0
- package/dist/deployment/core/formatters.js +170 -0
- package/dist/deployment/index.d.ts +31 -0
- package/dist/deployment/index.js +48 -0
- package/dist/deployment/openapi/generator.d.ts +146 -0
- package/dist/deployment/openapi/generator.js +347 -0
- package/dist/deployment/openapi/schema-converter.d.ts +49 -0
- package/dist/deployment/openapi/schema-converter.js +192 -0
- package/dist/deployment/targets/base.d.ts +316 -0
- package/dist/deployment/targets/base.js +823 -0
- package/dist/deployment/targets/cloudflare.d.ts +23 -0
- package/dist/deployment/targets/cloudflare.js +1125 -0
- package/dist/deployment/targets/inngest.d.ts +38 -0
- package/dist/deployment/targets/inngest.js +926 -0
- package/dist/deployment/targets/lambda.d.ts +23 -0
- package/dist/deployment/targets/lambda.js +1289 -0
- package/dist/deployment/targets/vercel.d.ts +23 -0
- package/dist/deployment/targets/vercel.js +886 -0
- package/dist/deployment/types.d.ts +183 -0
- package/dist/deployment/types.js +8 -0
- package/dist/diagram/geometry.d.ts +26 -0
- package/dist/diagram/geometry.js +850 -0
- package/dist/diagram/index.d.ts +16 -0
- package/dist/diagram/index.js +42 -0
- package/dist/diagram/layout.d.ts +11 -0
- package/dist/diagram/layout.js +143 -0
- package/dist/diagram/orthogonal-router.d.ts +79 -0
- package/dist/diagram/orthogonal-router.js +568 -0
- package/dist/diagram/renderer.d.ts +3 -0
- package/dist/diagram/renderer.js +207 -0
- package/dist/diagram/theme.d.ts +20 -0
- package/dist/diagram/theme.js +189 -0
- package/dist/diagram/types.d.ts +70 -0
- package/dist/diagram/types.js +2 -0
- package/dist/diff/WorkflowDiffer.d.ts +13 -0
- package/dist/diff/WorkflowDiffer.js +429 -0
- package/dist/diff/formatDiff.d.ts +10 -0
- package/dist/diff/formatDiff.js +220 -0
- package/dist/diff/impact.d.ts +29 -0
- package/dist/diff/impact.js +119 -0
- package/dist/diff/index.d.ts +10 -0
- package/dist/diff/index.js +9 -0
- package/dist/diff/types.d.ts +138 -0
- package/dist/diff/types.js +35 -0
- package/dist/doc-metadata/extractors/annotations.d.ts +56 -0
- package/dist/doc-metadata/extractors/annotations.js +337 -0
- package/dist/doc-metadata/extractors/cli-commands.d.ts +17 -0
- package/dist/doc-metadata/extractors/cli-commands.js +355 -0
- package/dist/doc-metadata/extractors/mcp-tools.d.ts +16 -0
- package/dist/doc-metadata/extractors/mcp-tools.js +689 -0
- package/dist/doc-metadata/extractors/plugin-api.d.ts +19 -0
- package/dist/doc-metadata/extractors/plugin-api.js +279 -0
- package/dist/doc-metadata/index.d.ts +5 -0
- package/dist/doc-metadata/index.js +4 -0
- package/dist/doc-metadata/types.d.ts +120 -0
- package/dist/doc-metadata/types.js +5 -0
- package/dist/editor-completions/annotationValues.d.ts +12 -0
- package/dist/editor-completions/annotationValues.js +138 -0
- package/dist/editor-completions/contextParser.d.ts +40 -0
- package/dist/editor-completions/contextParser.js +410 -0
- package/dist/editor-completions/dataTypes.d.ts +16 -0
- package/dist/editor-completions/dataTypes.js +95 -0
- package/dist/editor-completions/goToDefinition.d.ts +27 -0
- package/dist/editor-completions/goToDefinition.js +112 -0
- package/dist/editor-completions/index.d.ts +39 -0
- package/dist/editor-completions/index.js +181 -0
- package/dist/editor-completions/jsDocAnnotations.d.ts +29 -0
- package/dist/editor-completions/jsDocAnnotations.js +357 -0
- package/dist/editor-completions/modifierCompletions.d.ts +17 -0
- package/dist/editor-completions/modifierCompletions.js +197 -0
- package/dist/editor-completions/types.d.ts +119 -0
- package/dist/editor-completions/types.js +8 -0
- package/dist/export/index.d.ts +68 -0
- package/dist/export/index.js +1074 -0
- package/dist/export/templates.d.ts +24 -0
- package/dist/export/templates.js +186 -0
- package/dist/friendly-errors.d.ts +35 -0
- package/dist/friendly-errors.js +375 -0
- package/dist/function-like.d.ts +38 -0
- package/dist/function-like.js +83 -0
- package/dist/generated-branding.d.ts +16 -0
- package/dist/generated-branding.js +22 -0
- package/dist/generator/async-detection.d.ts +27 -0
- package/dist/generator/async-detection.js +56 -0
- package/dist/generator/code-utils.d.ts +76 -0
- package/dist/generator/code-utils.js +410 -0
- package/dist/generator/control-flow.d.ts +54 -0
- package/dist/generator/control-flow.js +284 -0
- package/dist/generator/inngest.d.ts +53 -0
- package/dist/generator/inngest.js +1126 -0
- package/dist/generator/scope-function-generator.d.ts +78 -0
- package/dist/generator/scope-function-generator.js +360 -0
- package/dist/generator/unified.d.ts +42 -0
- package/dist/generator/unified.js +1504 -0
- package/dist/generator.d.ts +54 -0
- package/dist/generator.js +100 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +89 -0
- package/dist/jsdoc-parser.d.ts +308 -0
- package/dist/jsdoc-parser.js +923 -0
- package/dist/jsdoc-port-sync/constants.d.ts +41 -0
- package/dist/jsdoc-port-sync/constants.js +103 -0
- package/dist/jsdoc-port-sync/diff.d.ts +76 -0
- package/dist/jsdoc-port-sync/diff.js +319 -0
- package/dist/jsdoc-port-sync/index.d.ts +42 -0
- package/dist/jsdoc-port-sync/index.js +45 -0
- package/dist/jsdoc-port-sync/port-parser.d.ts +68 -0
- package/dist/jsdoc-port-sync/port-parser.js +579 -0
- package/dist/jsdoc-port-sync/rename.d.ts +21 -0
- package/dist/jsdoc-port-sync/rename.js +256 -0
- package/dist/jsdoc-port-sync/signature-parser.d.ts +104 -0
- package/dist/jsdoc-port-sync/signature-parser.js +559 -0
- package/dist/jsdoc-port-sync/sync.d.ts +36 -0
- package/dist/jsdoc-port-sync/sync.js +644 -0
- package/dist/jsdoc-port-sync.d.ts +10 -0
- package/dist/jsdoc-port-sync.js +10 -0
- package/dist/marketplace/index.d.ts +11 -0
- package/dist/marketplace/index.js +10 -0
- package/dist/marketplace/manifest.d.ts +32 -0
- package/dist/marketplace/manifest.js +176 -0
- package/dist/marketplace/registry.d.ts +30 -0
- package/dist/marketplace/registry.js +100 -0
- package/dist/marketplace/types.d.ts +154 -0
- package/dist/marketplace/types.js +9 -0
- package/dist/marketplace/validator.d.ts +13 -0
- package/dist/marketplace/validator.js +131 -0
- package/dist/mcp/auto-registration.d.ts +3 -0
- package/dist/mcp/auto-registration.js +62 -0
- package/dist/mcp/editor-connection.d.ts +50 -0
- package/dist/mcp/editor-connection.js +125 -0
- package/dist/mcp/event-buffer.d.ts +62 -0
- package/dist/mcp/event-buffer.js +150 -0
- package/dist/mcp/index.d.ts +12 -0
- package/dist/mcp/index.js +11 -0
- package/dist/mcp/resources.d.ts +14 -0
- package/dist/mcp/resources.js +55 -0
- package/dist/mcp/response-utils.d.ts +63 -0
- package/dist/mcp/response-utils.js +89 -0
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.js +99 -0
- package/dist/mcp/tools-diagram.d.ts +8 -0
- package/dist/mcp/tools-diagram.js +53 -0
- package/dist/mcp/tools-editor.d.ts +5 -0
- package/dist/mcp/tools-editor.js +190 -0
- package/dist/mcp/tools-export.d.ts +9 -0
- package/dist/mcp/tools-export.js +180 -0
- package/dist/mcp/tools-marketplace.d.ts +9 -0
- package/dist/mcp/tools-marketplace.js +132 -0
- package/dist/mcp/tools-pattern.d.ts +3 -0
- package/dist/mcp/tools-pattern.js +783 -0
- package/dist/mcp/tools-query.d.ts +3 -0
- package/dist/mcp/tools-query.js +364 -0
- package/dist/mcp/tools-template.d.ts +10 -0
- package/dist/mcp/tools-template.js +119 -0
- package/dist/mcp/types.d.ts +70 -0
- package/dist/mcp/types.js +8 -0
- package/dist/mcp/workflow-executor.d.ts +47 -0
- package/dist/mcp/workflow-executor.js +133 -0
- package/dist/migration/registry.d.ts +30 -0
- package/dist/migration/registry.js +29 -0
- package/dist/node-types-generator.d.ts +49 -0
- package/dist/node-types-generator.js +139 -0
- package/dist/npm-packages.d.ts +56 -0
- package/dist/npm-packages.js +255 -0
- package/dist/parser.d.ts +204 -0
- package/dist/parser.js +2100 -0
- package/dist/plugin/PluginPanel.d.ts +12 -0
- package/dist/plugin/PluginPanel.js +5 -0
- package/dist/plugin/index.d.ts +13 -0
- package/dist/plugin/index.js +14 -0
- package/dist/plugin/types.d.ts +75 -0
- package/dist/plugin/types.js +8 -0
- package/dist/resolve-package-types.d.ts +17 -0
- package/dist/resolve-package-types.js +123 -0
- package/dist/runtime/CancellationError.d.ts +11 -0
- package/dist/runtime/CancellationError.js +20 -0
- package/dist/runtime/ExecutionContext.d.ts +146 -0
- package/dist/runtime/ExecutionContext.js +235 -0
- package/dist/runtime/builtin-functions.d.ts +8 -0
- package/dist/runtime/builtin-functions.js +549 -0
- package/dist/runtime/events.d.ts +50 -0
- package/dist/runtime/events.js +2 -0
- package/dist/runtime/function-registry.d.ts +59 -0
- package/dist/runtime/function-registry.js +66 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/parameter-resolver.d.ts +62 -0
- package/dist/runtime/parameter-resolver.js +113 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.js +6 -0
- package/dist/server/types.d.ts +93 -0
- package/dist/server/types.js +5 -0
- package/dist/server/webhook-server.d.ts +50 -0
- package/dist/server/webhook-server.js +269 -0
- package/dist/server/workflow-registry.d.ts +61 -0
- package/dist/server/workflow-registry.js +202 -0
- package/dist/shared-project.d.ts +9 -0
- package/dist/shared-project.js +28 -0
- package/dist/sugar-optimizer.d.ts +40 -0
- package/dist/sugar-optimizer.js +387 -0
- package/dist/testing/assertions.d.ts +51 -0
- package/dist/testing/assertions.js +127 -0
- package/dist/testing/index.d.ts +30 -0
- package/dist/testing/index.js +24 -0
- package/dist/testing/mock-approval.d.ts +81 -0
- package/dist/testing/mock-approval.js +98 -0
- package/dist/testing/mock-llm.d.ts +124 -0
- package/dist/testing/mock-llm.js +119 -0
- package/dist/testing/recorder.d.ts +72 -0
- package/dist/testing/recorder.js +70 -0
- package/dist/testing/replayer.d.ts +56 -0
- package/dist/testing/replayer.js +143 -0
- package/dist/testing/token-tracker.d.ts +71 -0
- package/dist/testing/token-tracker.js +94 -0
- package/dist/type-checker.d.ts +42 -0
- package/dist/type-checker.js +190 -0
- package/dist/type-mappings.d.ts +29 -0
- package/dist/type-mappings.js +125 -0
- package/dist/types/branded-ports.d.ts +151 -0
- package/dist/types/branded-ports.js +121 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +5 -0
- package/dist/types.d.ts +139 -0
- package/dist/types.js +15 -0
- package/dist/utils/error-utils.d.ts +15 -0
- package/dist/utils/error-utils.js +27 -0
- package/dist/utils/lru-cache.d.ts +15 -0
- package/dist/utils/lru-cache.js +40 -0
- package/dist/utils/port-ordering.d.ts +26 -0
- package/dist/utils/port-ordering.js +88 -0
- package/dist/utils/port-tag-utils.d.ts +23 -0
- package/dist/utils/port-tag-utils.js +41 -0
- package/dist/utils/string-distance.d.ts +14 -0
- package/dist/utils/string-distance.js +56 -0
- package/dist/validation/agent-detection.d.ts +33 -0
- package/dist/validation/agent-detection.js +115 -0
- package/dist/validation/agent-rules.d.ts +48 -0
- package/dist/validation/agent-rules.js +262 -0
- package/dist/validator.d.ts +92 -0
- package/dist/validator.js +970 -0
- package/package.json +109 -0
|
@@ -0,0 +1,1353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Place Code Generation
|
|
3
|
+
*
|
|
4
|
+
* Generates executable code directly into the source file while preserving
|
|
5
|
+
* user code (node functions). Updates:
|
|
6
|
+
* - Runtime section (between markers)
|
|
7
|
+
* - Workflow JSDoc annotations (when AST changes)
|
|
8
|
+
* - Function body (between markers)
|
|
9
|
+
*/
|
|
10
|
+
import { bodyGenerator } from '../body-generator.js';
|
|
11
|
+
import { generateInlineRuntime, generateInlineDebugClient } from './inline-runtime.js';
|
|
12
|
+
import { isExecutePort, isSuccessPort, isFailurePort, isControlFlowPort } from '../constants.js';
|
|
13
|
+
import { generateJSDocPortTag, assignPortOrders, generateNodeInstanceTag, } from '../annotation-generator.js';
|
|
14
|
+
import { shouldWorkflowBeAsync } from '../generator/async-detection.js';
|
|
15
|
+
import { detectSugarPatterns, filterStaleMacros } from '../sugar-optimizer.js';
|
|
16
|
+
import * as ts from 'typescript';
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
import * as fs from 'fs';
|
|
19
|
+
// Marker constants
|
|
20
|
+
export const MARKERS = {
|
|
21
|
+
RUNTIME_START: '// @flow-weaver-runtime-start',
|
|
22
|
+
RUNTIME_END: '// @flow-weaver-runtime-end',
|
|
23
|
+
BODY_START: '// @flow-weaver-body-start',
|
|
24
|
+
BODY_END: '// @flow-weaver-body-end',
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Check if `@synergenius/flow-weaver` is available as an npm package
|
|
28
|
+
* by walking up from the given directory looking for node_modules.
|
|
29
|
+
*/
|
|
30
|
+
function isFlowWeaverPackageInstalled(startDir) {
|
|
31
|
+
let dir = startDir;
|
|
32
|
+
const root = path.parse(dir).root;
|
|
33
|
+
while (dir !== root) {
|
|
34
|
+
const candidate = path.join(dir, 'node_modules', '@synergenius', 'flow-weaver');
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(candidate)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Permission error or similar — skip and keep walking
|
|
42
|
+
}
|
|
43
|
+
const parent = path.dirname(dir);
|
|
44
|
+
if (parent === dir)
|
|
45
|
+
break;
|
|
46
|
+
dir = parent;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generate executable code in-place, preserving user code.
|
|
52
|
+
*
|
|
53
|
+
* @param sourceCode - The original source code
|
|
54
|
+
* @param ast - The parsed workflow AST
|
|
55
|
+
* @param options - Generation options
|
|
56
|
+
* @returns The updated source code with generated sections
|
|
57
|
+
*/
|
|
58
|
+
export function generateInPlace(sourceCode, ast, options = {}) {
|
|
59
|
+
const { production = false, allWorkflows, moduleFormat = 'esm', inlineRuntime = false, sourceFile, skipParamReturns = false } = options;
|
|
60
|
+
let result = sourceCode;
|
|
61
|
+
let hasChanges = false;
|
|
62
|
+
// Step 1: Update JSDoc annotations for node type functions
|
|
63
|
+
// Skip sibling workflows (variant IMPORTED_WORKFLOW/WORKFLOW) — their JSDoc should not be rewritten
|
|
64
|
+
for (const nodeType of ast.nodeTypes) {
|
|
65
|
+
if (nodeType.variant === 'IMPORTED_WORKFLOW' || nodeType.variant === 'WORKFLOW' || nodeType.variant === 'MAP_ITERATOR') {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// Skip node types imported from other files — the import statement handles them.
|
|
69
|
+
// Inlining would create duplicate declarations (TS2440) and duplicate node type names.
|
|
70
|
+
if (nodeType.sourceLocation?.file &&
|
|
71
|
+
path.resolve(nodeType.sourceLocation.file) !== path.resolve(ast.sourceFile)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const nodeTypeResult = replaceNodeTypeJSDoc(result, nodeType);
|
|
75
|
+
if (nodeTypeResult.changed) {
|
|
76
|
+
result = nodeTypeResult.code;
|
|
77
|
+
hasChanges = true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Step 1.5: Remove orphaned nodeType functions (functions that don't match any AST nodeType)
|
|
81
|
+
// When multi-workflow, consider ALL workflows' node types to avoid deleting types used by siblings
|
|
82
|
+
const cleanupResult = removeOrphanedNodeTypeFunctions(result, ast, allWorkflows);
|
|
83
|
+
if (cleanupResult.changed) {
|
|
84
|
+
result = cleanupResult.code;
|
|
85
|
+
hasChanges = true;
|
|
86
|
+
}
|
|
87
|
+
// Step 2: Update JSDoc annotations for workflow function
|
|
88
|
+
const jsdocResult = replaceWorkflowJSDoc(result, ast, { skipParamReturns });
|
|
89
|
+
if (jsdocResult.changed) {
|
|
90
|
+
result = jsdocResult.code;
|
|
91
|
+
hasChanges = true;
|
|
92
|
+
}
|
|
93
|
+
// Step 3: Generate and insert/replace runtime section
|
|
94
|
+
// Auto-detect external runtime unless --inline-runtime is forced
|
|
95
|
+
let useExternalRuntime = false;
|
|
96
|
+
if (!inlineRuntime) {
|
|
97
|
+
const lookupDir = sourceFile ? path.dirname(sourceFile) : (ast.sourceFile ? path.dirname(ast.sourceFile) : process.cwd());
|
|
98
|
+
useExternalRuntime = isFlowWeaverPackageInstalled(lookupDir);
|
|
99
|
+
}
|
|
100
|
+
const externalRuntimePath = useExternalRuntime ? '@synergenius/flow-weaver/runtime' : undefined;
|
|
101
|
+
const runtimeCode = generateRuntimeSection(ast.functionName, production, moduleFormat, externalRuntimePath);
|
|
102
|
+
const runtimeResult = replaceOrInsertSection(result, MARKERS.RUNTIME_START, MARKERS.RUNTIME_END, runtimeCode, 'top');
|
|
103
|
+
if (runtimeResult.changed) {
|
|
104
|
+
result = runtimeResult.code;
|
|
105
|
+
hasChanges = true;
|
|
106
|
+
}
|
|
107
|
+
// Step 4: Ensure function signature includes __abortSignal__ parameter
|
|
108
|
+
const signatureResult = ensureAbortSignalParameter(result, ast.functionName);
|
|
109
|
+
if (signatureResult.changed) {
|
|
110
|
+
result = signatureResult.code;
|
|
111
|
+
hasChanges = true;
|
|
112
|
+
}
|
|
113
|
+
// Step 5: Detect async from node composition + source signature
|
|
114
|
+
// If any node is async, force async (even if source isn't marked async)
|
|
115
|
+
const nodesRequireAsync = shouldWorkflowBeAsync(ast, ast.nodeTypes);
|
|
116
|
+
const sourceIsAsync = detectFunctionIsAsync(result, ast.functionName);
|
|
117
|
+
const isAsync = nodesRequireAsync || sourceIsAsync;
|
|
118
|
+
// Add async keyword to source if nodes require it but source doesn't have it
|
|
119
|
+
const asyncSigResult = ensureAsyncKeyword(result, ast.functionName, nodesRequireAsync);
|
|
120
|
+
if (asyncSigResult.changed) {
|
|
121
|
+
result = asyncSigResult.code;
|
|
122
|
+
hasChanges = true;
|
|
123
|
+
}
|
|
124
|
+
// Step 5b: Wrap return type in Promise<T> when async was added
|
|
125
|
+
const returnTypeResult = ensurePromiseReturnType(result, ast.functionName, nodesRequireAsync);
|
|
126
|
+
if (returnTypeResult.changed) {
|
|
127
|
+
result = returnTypeResult.code;
|
|
128
|
+
hasChanges = true;
|
|
129
|
+
}
|
|
130
|
+
const functionBody = generateFunctionBody(ast, production, isAsync);
|
|
131
|
+
const bodyResult = replaceWorkflowFunctionBody(result, ast.functionName, functionBody);
|
|
132
|
+
if (bodyResult.changed) {
|
|
133
|
+
result = bodyResult.code;
|
|
134
|
+
hasChanges = true;
|
|
135
|
+
}
|
|
136
|
+
// Final check: if the output equals the input, there were no real changes
|
|
137
|
+
// This catches cases where individual steps report changes but produce identical output
|
|
138
|
+
if (hasChanges && result === sourceCode) {
|
|
139
|
+
hasChanges = false;
|
|
140
|
+
}
|
|
141
|
+
return { code: result, hasChanges };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Generate the runtime section with proper markers.
|
|
145
|
+
* When externalRuntimePath is provided, generates import statements instead of inline code.
|
|
146
|
+
*/
|
|
147
|
+
function generateRuntimeSection(functionName, production, moduleFormat = 'esm', externalRuntimePath) {
|
|
148
|
+
const lines = [];
|
|
149
|
+
lines.push('// ============================================================================');
|
|
150
|
+
lines.push('// DO NOT EDIT - This section is auto-generated by Flow Weaver');
|
|
151
|
+
lines.push('// ============================================================================');
|
|
152
|
+
lines.push('');
|
|
153
|
+
if (externalRuntimePath) {
|
|
154
|
+
// External runtime: generate import statements instead of inline code
|
|
155
|
+
lines.push(`import { GeneratedExecutionContext, CancellationError } from '${externalRuntimePath}';`);
|
|
156
|
+
if (!production) {
|
|
157
|
+
lines.push(`import type { TDebugger } from '${externalRuntimePath}';`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// Inline runtime: embed all types and classes directly
|
|
162
|
+
lines.push(generateInlineRuntime(production));
|
|
163
|
+
// Add debug client (dev mode only)
|
|
164
|
+
if (!production) {
|
|
165
|
+
lines.push('');
|
|
166
|
+
lines.push(generateInlineDebugClient(moduleFormat));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return lines.join('\n');
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Detect if a workflow function is declared as async
|
|
173
|
+
*/
|
|
174
|
+
function detectFunctionIsAsync(source, functionName) {
|
|
175
|
+
const sourceFile = ts.createSourceFile('temp.ts', source, ts.ScriptTarget.Latest, true);
|
|
176
|
+
let isAsync = false;
|
|
177
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
178
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === functionName) {
|
|
179
|
+
isAsync = !!node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
return isAsync;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Ensure the workflow function has __abortSignal__ parameter.
|
|
186
|
+
* Adds it if not present.
|
|
187
|
+
*/
|
|
188
|
+
function ensureAbortSignalParameter(source, functionName) {
|
|
189
|
+
const sourceFile = ts.createSourceFile('temp.ts', source, ts.ScriptTarget.Latest, true);
|
|
190
|
+
let functionNode;
|
|
191
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
192
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === functionName) {
|
|
193
|
+
functionNode = node;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
if (!functionNode) {
|
|
197
|
+
return { code: source, changed: false };
|
|
198
|
+
}
|
|
199
|
+
// Check if __abortSignal__ parameter already exists
|
|
200
|
+
const hasAbortSignal = functionNode.parameters.some((param) => ts.isIdentifier(param.name) && param.name.text === '__abortSignal__');
|
|
201
|
+
if (hasAbortSignal) {
|
|
202
|
+
return { code: source, changed: false };
|
|
203
|
+
}
|
|
204
|
+
// Find the closing parenthesis of the parameter list
|
|
205
|
+
const lastParam = functionNode.parameters[functionNode.parameters.length - 1];
|
|
206
|
+
if (!lastParam) {
|
|
207
|
+
// No parameters - find the opening parenthesis and insert after it
|
|
208
|
+
const openParen = source.indexOf('(', functionNode.name?.end || 0);
|
|
209
|
+
if (openParen === -1) {
|
|
210
|
+
return { code: source, changed: false };
|
|
211
|
+
}
|
|
212
|
+
const before = source.slice(0, openParen + 1);
|
|
213
|
+
const after = source.slice(openParen + 1);
|
|
214
|
+
return {
|
|
215
|
+
code: before + '__abortSignal__?: AbortSignal' + after,
|
|
216
|
+
changed: true,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// Has parameters - insert after the last one with a comma
|
|
220
|
+
const lastParamEnd = lastParam.end;
|
|
221
|
+
const before = source.slice(0, lastParamEnd);
|
|
222
|
+
const after = source.slice(lastParamEnd);
|
|
223
|
+
return {
|
|
224
|
+
code: before + ', __abortSignal__?: AbortSignal' + after,
|
|
225
|
+
changed: true,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Ensure the workflow function has the correct async/non-async modifier.
|
|
230
|
+
* If shouldBeAsync is true and the function is not async, adds the `async` keyword.
|
|
231
|
+
*/
|
|
232
|
+
function ensureAsyncKeyword(source, functionName, shouldBeAsync) {
|
|
233
|
+
if (!shouldBeAsync) {
|
|
234
|
+
return { code: source, changed: false };
|
|
235
|
+
}
|
|
236
|
+
const sourceFile = ts.createSourceFile('temp.ts', source, ts.ScriptTarget.Latest, true);
|
|
237
|
+
let functionNode;
|
|
238
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
239
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === functionName) {
|
|
240
|
+
functionNode = node;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
if (!functionNode) {
|
|
244
|
+
return { code: source, changed: false };
|
|
245
|
+
}
|
|
246
|
+
const alreadyAsync = !!functionNode.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword);
|
|
247
|
+
if (alreadyAsync) {
|
|
248
|
+
return { code: source, changed: false };
|
|
249
|
+
}
|
|
250
|
+
// Find the 'function' keyword position and insert 'async ' before it
|
|
251
|
+
const funcStart = functionNode.getStart();
|
|
252
|
+
const textAfter = source.slice(funcStart);
|
|
253
|
+
// The text at funcStart starts with optional 'export' then 'function'
|
|
254
|
+
// Insert 'async ' right before 'function'
|
|
255
|
+
const functionKeywordOffset = textAfter.indexOf('function');
|
|
256
|
+
if (functionKeywordOffset === -1) {
|
|
257
|
+
return { code: source, changed: false };
|
|
258
|
+
}
|
|
259
|
+
const insertPos = funcStart + functionKeywordOffset;
|
|
260
|
+
const before = source.slice(0, insertPos);
|
|
261
|
+
const after = source.slice(insertPos);
|
|
262
|
+
return {
|
|
263
|
+
code: before + 'async ' + after,
|
|
264
|
+
changed: true,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Ensure the workflow function's return type is wrapped in Promise<T> when async is required.
|
|
269
|
+
* If shouldBeAsync is true and the return type is not already Promise<...>, wraps it.
|
|
270
|
+
*/
|
|
271
|
+
function ensurePromiseReturnType(source, functionName, shouldBeAsync) {
|
|
272
|
+
if (!shouldBeAsync) {
|
|
273
|
+
return { code: source, changed: false };
|
|
274
|
+
}
|
|
275
|
+
const sourceFile = ts.createSourceFile('temp.ts', source, ts.ScriptTarget.Latest, true);
|
|
276
|
+
let functionNode;
|
|
277
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
278
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === functionName) {
|
|
279
|
+
functionNode = node;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
if (!functionNode || !functionNode.type) {
|
|
283
|
+
return { code: source, changed: false };
|
|
284
|
+
}
|
|
285
|
+
const returnTypeText = source.slice(functionNode.type.pos, functionNode.type.end).trim();
|
|
286
|
+
// Already wrapped in Promise<...>
|
|
287
|
+
if (returnTypeText.startsWith('Promise<')) {
|
|
288
|
+
return { code: source, changed: false };
|
|
289
|
+
}
|
|
290
|
+
const before = source.slice(0, functionNode.type.pos);
|
|
291
|
+
const after = source.slice(functionNode.type.end);
|
|
292
|
+
return {
|
|
293
|
+
code: before + ' Promise<' + returnTypeText + '>' + after,
|
|
294
|
+
changed: true,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Generate the workflow function body
|
|
299
|
+
*/
|
|
300
|
+
function generateFunctionBody(ast, production, isAsync) {
|
|
301
|
+
const lines = [];
|
|
302
|
+
lines.push(' // ============================================================================');
|
|
303
|
+
lines.push(' // DO NOT EDIT - This section is auto-generated by Flow Weaver');
|
|
304
|
+
lines.push(' // Edit the @flowWeaver annotations above to modify workflow behavior');
|
|
305
|
+
lines.push(' // ============================================================================');
|
|
306
|
+
lines.push('');
|
|
307
|
+
// Get the generated body from existing body generator
|
|
308
|
+
const body = bodyGenerator.generateWithExecutionContext(ast, ast.nodeTypes, isAsync, // Respect original function's async/sync nature
|
|
309
|
+
production);
|
|
310
|
+
// Add proper indentation
|
|
311
|
+
const indentedBody = body
|
|
312
|
+
.split('\n')
|
|
313
|
+
.map((line) => (line.trim() ? ' ' + line : line))
|
|
314
|
+
.join('\n');
|
|
315
|
+
lines.push(indentedBody);
|
|
316
|
+
return lines.join('\n');
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Replace content between markers, or insert if markers don't exist
|
|
320
|
+
*/
|
|
321
|
+
function replaceOrInsertSection(source, startMarker, endMarker, newContent, insertPosition) {
|
|
322
|
+
const startIdx = source.indexOf(startMarker);
|
|
323
|
+
const endIdx = source.indexOf(endMarker);
|
|
324
|
+
// If both markers exist, replace content between them
|
|
325
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
326
|
+
const before = source.slice(0, startIdx + startMarker.length);
|
|
327
|
+
const after = source.slice(endIdx);
|
|
328
|
+
const newCode = before + '\n' + newContent + '\n' + after;
|
|
329
|
+
// Check if content actually changed
|
|
330
|
+
const originalContent = source.slice(startIdx + startMarker.length, endIdx);
|
|
331
|
+
const changed = originalContent.trim() !== newContent.trim();
|
|
332
|
+
return { code: newCode, changed };
|
|
333
|
+
}
|
|
334
|
+
// Markers don't exist - insert them
|
|
335
|
+
if (insertPosition === 'top') {
|
|
336
|
+
// Insert after imports (if any)
|
|
337
|
+
const insertIdx = findInsertPositionAfterImports(source);
|
|
338
|
+
const before = source.slice(0, insertIdx);
|
|
339
|
+
const after = source.slice(insertIdx);
|
|
340
|
+
const newSection = ['', startMarker, newContent, endMarker, ''].join('\n');
|
|
341
|
+
return {
|
|
342
|
+
code: before + newSection + after,
|
|
343
|
+
changed: true,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return { code: source, changed: false };
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Find the position after all imports in the source code
|
|
350
|
+
*/
|
|
351
|
+
function findInsertPositionAfterImports(source) {
|
|
352
|
+
const sourceFile = ts.createSourceFile('temp.ts', source, ts.ScriptTarget.Latest, true);
|
|
353
|
+
let lastImportEnd = 0;
|
|
354
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
355
|
+
if (ts.isImportDeclaration(node)) {
|
|
356
|
+
lastImportEnd = node.end;
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
// Find the next line after the last import
|
|
360
|
+
if (lastImportEnd > 0) {
|
|
361
|
+
const nextNewline = source.indexOf('\n', lastImportEnd);
|
|
362
|
+
return nextNewline !== -1 ? nextNewline + 1 : lastImportEnd;
|
|
363
|
+
}
|
|
364
|
+
// No imports - insert at the very beginning
|
|
365
|
+
return 0;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Replace the body of a workflow function with generated code
|
|
369
|
+
*/
|
|
370
|
+
function replaceWorkflowFunctionBody(source, functionName, newBody) {
|
|
371
|
+
const sourceFile = ts.createSourceFile('temp.ts', source, ts.ScriptTarget.Latest, true);
|
|
372
|
+
let functionNode;
|
|
373
|
+
// Find the workflow function
|
|
374
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
375
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === functionName) {
|
|
376
|
+
functionNode = node;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
if (!functionNode || !functionNode.body) {
|
|
380
|
+
return { code: source, changed: false };
|
|
381
|
+
}
|
|
382
|
+
// Check if body already has markers
|
|
383
|
+
const bodyText = source.slice(functionNode.body.pos, functionNode.body.end);
|
|
384
|
+
const bodyStartMarkerIdx = bodyText.indexOf(MARKERS.BODY_START);
|
|
385
|
+
const bodyEndMarkerIdx = bodyText.indexOf(MARKERS.BODY_END);
|
|
386
|
+
if (bodyStartMarkerIdx !== -1 && bodyEndMarkerIdx !== -1) {
|
|
387
|
+
// Replace content between body markers
|
|
388
|
+
const absoluteBodyStart = functionNode.body.pos + bodyStartMarkerIdx + MARKERS.BODY_START.length;
|
|
389
|
+
const absoluteBodyEnd = functionNode.body.pos + bodyEndMarkerIdx;
|
|
390
|
+
const before = source.slice(0, absoluteBodyStart);
|
|
391
|
+
const after = source.slice(absoluteBodyEnd);
|
|
392
|
+
const newCode = before + '\n' + newBody + '\n ' + after;
|
|
393
|
+
// Check if content changed
|
|
394
|
+
const originalBody = source.slice(absoluteBodyStart, absoluteBodyEnd);
|
|
395
|
+
const changed = originalBody.trim() !== newBody.trim();
|
|
396
|
+
return { code: newCode, changed };
|
|
397
|
+
}
|
|
398
|
+
// No markers in body - insert them
|
|
399
|
+
// Find the opening brace of the function body
|
|
400
|
+
const openBraceIdx = source.indexOf('{', functionNode.body.pos);
|
|
401
|
+
if (openBraceIdx === -1) {
|
|
402
|
+
return { code: source, changed: false };
|
|
403
|
+
}
|
|
404
|
+
// Find the closing brace
|
|
405
|
+
const closeBraceIdx = functionNode.body.end - 1;
|
|
406
|
+
const before = source.slice(0, openBraceIdx + 1);
|
|
407
|
+
const after = source.slice(closeBraceIdx);
|
|
408
|
+
const newBodyWithMarkers = [
|
|
409
|
+
'',
|
|
410
|
+
' ' + MARKERS.BODY_START,
|
|
411
|
+
newBody,
|
|
412
|
+
' ' + MARKERS.BODY_END,
|
|
413
|
+
'',
|
|
414
|
+
].join('\n');
|
|
415
|
+
return {
|
|
416
|
+
code: before + newBodyWithMarkers + after,
|
|
417
|
+
changed: true,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Rename a function in the provided code to a new name.
|
|
422
|
+
* Handles both regular functions and arrow functions.
|
|
423
|
+
*/
|
|
424
|
+
function renameFunctionInCode(code, newName) {
|
|
425
|
+
const codeSourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);
|
|
426
|
+
let functionName;
|
|
427
|
+
let functionNameStart;
|
|
428
|
+
let functionNameEnd;
|
|
429
|
+
// Find the function declaration
|
|
430
|
+
ts.forEachChild(codeSourceFile, (node) => {
|
|
431
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
432
|
+
functionName = node.name.text;
|
|
433
|
+
functionNameStart = node.name.getStart();
|
|
434
|
+
functionNameEnd = node.name.getEnd();
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
if (!functionName || functionNameStart === undefined || functionNameEnd === undefined) {
|
|
438
|
+
// No function found - return as-is
|
|
439
|
+
return code;
|
|
440
|
+
}
|
|
441
|
+
if (functionName === newName) {
|
|
442
|
+
// Already has the correct name
|
|
443
|
+
return code;
|
|
444
|
+
}
|
|
445
|
+
// Replace the function name
|
|
446
|
+
const before = code.slice(0, functionNameStart);
|
|
447
|
+
const after = code.slice(functionNameEnd);
|
|
448
|
+
return before + newName + after;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Insert a new node type function into the source code.
|
|
452
|
+
* Inserts before the workflow function (exported function).
|
|
453
|
+
* Renames the function to match nodeType.functionName if different.
|
|
454
|
+
*/
|
|
455
|
+
function insertNodeTypeFunction(source, nodeType, functionCode) {
|
|
456
|
+
const sourceFile = ts.createSourceFile('temp.ts', source, ts.ScriptTarget.Latest, true);
|
|
457
|
+
// Find position AFTER the runtime section end marker to avoid being overwritten
|
|
458
|
+
// when the runtime section is replaced
|
|
459
|
+
const runtimeEndMarker = '// @flow-weaver-runtime-end';
|
|
460
|
+
const runtimeEndPos = source.indexOf(runtimeEndMarker);
|
|
461
|
+
let insertPosition = -1;
|
|
462
|
+
if (runtimeEndPos !== -1) {
|
|
463
|
+
// Insert after the runtime-end marker line
|
|
464
|
+
const afterMarker = source.indexOf('\n', runtimeEndPos);
|
|
465
|
+
insertPosition = afterMarker !== -1 ? afterMarker + 1 : runtimeEndPos + runtimeEndMarker.length;
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
// No runtime marker - find the first exported function to insert before it
|
|
469
|
+
let foundExportedFunction = false;
|
|
470
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
471
|
+
if (!foundExportedFunction &&
|
|
472
|
+
ts.isFunctionDeclaration(node) &&
|
|
473
|
+
node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
474
|
+
insertPosition = node.getFullStart();
|
|
475
|
+
foundExportedFunction = true;
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
if (insertPosition === -1) {
|
|
479
|
+
insertPosition = source.length;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// Rename the function in the code to match nodeType.functionName
|
|
483
|
+
const renamedCode = renameFunctionInCode(functionCode, nodeType.functionName);
|
|
484
|
+
// The functionCode should already have JSDoc, but ensure it does
|
|
485
|
+
const hasJSDoc = renamedCode.trim().startsWith('/**');
|
|
486
|
+
let finalCode = renamedCode;
|
|
487
|
+
if (!hasJSDoc) {
|
|
488
|
+
// Generate JSDoc for the function
|
|
489
|
+
const jsdoc = generateNodeTypeJSDoc(nodeType);
|
|
490
|
+
finalCode = jsdoc + '\n' + renamedCode;
|
|
491
|
+
}
|
|
492
|
+
// Insert the function with proper spacing
|
|
493
|
+
const before = source.slice(0, insertPosition);
|
|
494
|
+
const after = source.slice(insertPosition);
|
|
495
|
+
// Ensure proper newlines
|
|
496
|
+
const needsLeadingNewline = before.length > 0 && !before.endsWith('\n\n');
|
|
497
|
+
const needsTrailingNewline = after.length > 0 && !after.startsWith('\n');
|
|
498
|
+
const newCode = before +
|
|
499
|
+
(needsLeadingNewline ? '\n\n' : '') +
|
|
500
|
+
finalCode +
|
|
501
|
+
(needsTrailingNewline ? '\n\n' : '') +
|
|
502
|
+
after;
|
|
503
|
+
return { code: newCode, changed: true };
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Remove nodeType functions that don't match any AST nodeType.
|
|
507
|
+
* This cleans up orphaned functions left behind after renames.
|
|
508
|
+
*/
|
|
509
|
+
function removeOrphanedNodeTypeFunctions(source, ast, allWorkflows) {
|
|
510
|
+
const sourceFile = ts.createSourceFile('temp.ts', source, ts.ScriptTarget.Latest, true);
|
|
511
|
+
// Get all valid functionNames from AST — include ALL workflows' node types when available
|
|
512
|
+
const validFunctionNames = new Set(ast.nodeTypes.map((nt) => nt.functionName));
|
|
513
|
+
if (allWorkflows) {
|
|
514
|
+
for (const workflow of allWorkflows) {
|
|
515
|
+
for (const nt of workflow.nodeTypes) {
|
|
516
|
+
validFunctionNames.add(nt.functionName);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// Find all nodeType functions to potentially remove
|
|
521
|
+
const functionsToRemove = [];
|
|
522
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
523
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
524
|
+
const functionName = node.name.text;
|
|
525
|
+
// Check if this function has @flowWeaver nodeType JSDoc
|
|
526
|
+
const functionStart = node.getFullStart();
|
|
527
|
+
const leadingComments = ts.getLeadingCommentRanges(source, functionStart);
|
|
528
|
+
if (!leadingComments)
|
|
529
|
+
return;
|
|
530
|
+
let isNodeTypeFunction = false;
|
|
531
|
+
let jsdocStart = functionStart;
|
|
532
|
+
for (const comment of leadingComments) {
|
|
533
|
+
if (comment.kind === ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
534
|
+
const commentText = source.slice(comment.pos, comment.end);
|
|
535
|
+
if (commentText.includes('@flowWeaver nodeType')) {
|
|
536
|
+
isNodeTypeFunction = true;
|
|
537
|
+
jsdocStart = comment.pos;
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (!isNodeTypeFunction)
|
|
543
|
+
return;
|
|
544
|
+
// If this nodeType function's name doesn't match any valid functionName, mark for removal
|
|
545
|
+
if (!validFunctionNames.has(functionName)) {
|
|
546
|
+
// Find the start (including JSDoc) and end of the function
|
|
547
|
+
const fullStart = jsdocStart;
|
|
548
|
+
const fullEnd = node.end;
|
|
549
|
+
// Include any trailing newlines
|
|
550
|
+
let endPos = fullEnd;
|
|
551
|
+
while (endPos < source.length && (source[endPos] === '\n' || source[endPos] === '\r')) {
|
|
552
|
+
endPos++;
|
|
553
|
+
}
|
|
554
|
+
functionsToRemove.push({ start: fullStart, end: endPos });
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
if (functionsToRemove.length === 0) {
|
|
559
|
+
return { code: source, changed: false };
|
|
560
|
+
}
|
|
561
|
+
// Remove functions from end to start to preserve positions
|
|
562
|
+
let result = source;
|
|
563
|
+
for (const { start, end } of functionsToRemove.sort((a, b) => b.start - a.start)) {
|
|
564
|
+
result = result.slice(0, start) + result.slice(end);
|
|
565
|
+
}
|
|
566
|
+
return { code: result, changed: true };
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Find a function by @name tag in its JSDoc comment.
|
|
570
|
+
* Returns the function node and the @name value if found.
|
|
571
|
+
*/
|
|
572
|
+
function findFunctionByNameTag(source, sourceFile, targetName) {
|
|
573
|
+
let result;
|
|
574
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
575
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
576
|
+
// Check if this function has a @name tag matching targetName
|
|
577
|
+
const functionStart = node.getFullStart();
|
|
578
|
+
const leadingComments = ts.getLeadingCommentRanges(source, functionStart);
|
|
579
|
+
if (leadingComments) {
|
|
580
|
+
for (const comment of leadingComments) {
|
|
581
|
+
if (comment.kind === ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
582
|
+
const commentText = source.slice(comment.pos, comment.end);
|
|
583
|
+
// Look for @name tag
|
|
584
|
+
const nameMatch = commentText.match(/@name\s+(\S+)/);
|
|
585
|
+
if (nameMatch && nameMatch[1] === targetName) {
|
|
586
|
+
result = node;
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Replace a node type function's JSDoc comment with updated annotations.
|
|
598
|
+
* If the function doesn't exist but nodeType has code/functionText, INSERT the function.
|
|
599
|
+
* Handles function renames by looking up @name tag when functionName doesn't match.
|
|
600
|
+
*/
|
|
601
|
+
function replaceNodeTypeJSDoc(source, nodeType) {
|
|
602
|
+
const sourceFile = ts.createSourceFile('temp.ts', source, ts.ScriptTarget.Latest, true);
|
|
603
|
+
let functionNode;
|
|
604
|
+
let needsRename = false;
|
|
605
|
+
// First, try to find the function by its functionName
|
|
606
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
607
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === nodeType.functionName) {
|
|
608
|
+
functionNode = node;
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
// If not found by functionName, try to find by @name tag (handles renames)
|
|
612
|
+
if (!functionNode && nodeType.name) {
|
|
613
|
+
functionNode = findFunctionByNameTag(source, sourceFile, nodeType.name);
|
|
614
|
+
if (functionNode && functionNode.name?.text !== nodeType.functionName) {
|
|
615
|
+
needsRename = true;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
// If still not found and name !== functionName, try finding by name as function name
|
|
619
|
+
// This handles the case where no @name tag exists yet (first rename after creation)
|
|
620
|
+
if (!functionNode && nodeType.name && nodeType.name !== nodeType.functionName) {
|
|
621
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
622
|
+
if (ts.isFunctionDeclaration(node) &&
|
|
623
|
+
node.name?.text === nodeType.name // Find by stable identifier as function name
|
|
624
|
+
) {
|
|
625
|
+
functionNode = node;
|
|
626
|
+
needsRename = true;
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
if (!functionNode) {
|
|
631
|
+
// Function doesn't exist - try to INSERT it if we have the code
|
|
632
|
+
// Check for code property (dynamically added in some contexts) or functionText
|
|
633
|
+
const nodeTypeWithCode = nodeType;
|
|
634
|
+
const functionCode = nodeTypeWithCode.code || nodeType.functionText;
|
|
635
|
+
if (functionCode) {
|
|
636
|
+
return insertNodeTypeFunction(source, nodeType, functionCode);
|
|
637
|
+
}
|
|
638
|
+
return { code: source, changed: false };
|
|
639
|
+
}
|
|
640
|
+
let result = source;
|
|
641
|
+
let hasChanges = false;
|
|
642
|
+
// If function needs to be renamed (found by @name but has old functionName)
|
|
643
|
+
if (needsRename && functionNode.name) {
|
|
644
|
+
const nameStart = functionNode.name.getStart();
|
|
645
|
+
const nameEnd = functionNode.name.getEnd();
|
|
646
|
+
result = result.slice(0, nameStart) + nodeType.functionName + result.slice(nameEnd);
|
|
647
|
+
hasChanges = true;
|
|
648
|
+
// Re-parse to get updated positions after rename
|
|
649
|
+
const updatedSourceFile = ts.createSourceFile('temp.ts', result, ts.ScriptTarget.Latest, true);
|
|
650
|
+
// Find the renamed function
|
|
651
|
+
ts.forEachChild(updatedSourceFile, (node) => {
|
|
652
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === nodeType.functionName) {
|
|
653
|
+
functionNode = node;
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
if (!functionNode) {
|
|
657
|
+
return { code: result, changed: hasChanges };
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
// Find the JSDoc comment before the function
|
|
661
|
+
const functionStart = functionNode.getFullStart();
|
|
662
|
+
const leadingComments = ts.getLeadingCommentRanges(result, functionStart);
|
|
663
|
+
if (!leadingComments || leadingComments.length === 0) {
|
|
664
|
+
return { code: result, changed: hasChanges };
|
|
665
|
+
}
|
|
666
|
+
// Find ALL /** JSDoc comments in the leading trivia and separate them:
|
|
667
|
+
// - flowWeaverJSDocs: contain @flowWeaver (these are node type annotations)
|
|
668
|
+
// - The LAST one is the primary JSDoc to replace
|
|
669
|
+
// - Any earlier @flowWeaver JSDoc blocks are stale duplicates to remove
|
|
670
|
+
const jsdocComments = [];
|
|
671
|
+
for (const c of leadingComments) {
|
|
672
|
+
if (c.kind === ts.SyntaxKind.MultiLineCommentTrivia &&
|
|
673
|
+
result.slice(c.pos, c.pos + 3) === '/**') {
|
|
674
|
+
jsdocComments.push(c);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (jsdocComments.length === 0) {
|
|
678
|
+
return { code: result, changed: hasChanges };
|
|
679
|
+
}
|
|
680
|
+
// Use the LAST /** comment as the primary JSDoc (closest to the function).
|
|
681
|
+
// This avoids picking up file headers that also start with /**.
|
|
682
|
+
const jsdocComment = jsdocComments[jsdocComments.length - 1];
|
|
683
|
+
// Detect stale duplicate @flowWeaver JSDoc blocks from previous buggy compilations.
|
|
684
|
+
// Any earlier /** that contains @flowWeaver is a stale duplicate to remove.
|
|
685
|
+
const staleDuplicates = [];
|
|
686
|
+
for (let i = 0; i < jsdocComments.length - 1; i++) {
|
|
687
|
+
const text = result.slice(jsdocComments[i].pos, jsdocComments[i].end);
|
|
688
|
+
if (text.includes('@flowWeaver')) {
|
|
689
|
+
staleDuplicates.push(jsdocComments[i]);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
// Generate new JSDoc using AnnotationGenerator
|
|
693
|
+
const newJSDoc = generateNodeTypeJSDoc(nodeType);
|
|
694
|
+
// Remove stale duplicates first (process from end to start to preserve positions)
|
|
695
|
+
if (staleDuplicates.length > 0) {
|
|
696
|
+
for (let i = staleDuplicates.length - 1; i >= 0; i--) {
|
|
697
|
+
const dupe = staleDuplicates[i];
|
|
698
|
+
// Remove the duplicate and any trailing whitespace/newline
|
|
699
|
+
let removeEnd = dupe.end;
|
|
700
|
+
while (removeEnd < result.length &&
|
|
701
|
+
(result[removeEnd] === '\n' || result[removeEnd] === '\r')) {
|
|
702
|
+
removeEnd++;
|
|
703
|
+
}
|
|
704
|
+
result = result.slice(0, dupe.pos) + result.slice(removeEnd);
|
|
705
|
+
hasChanges = true;
|
|
706
|
+
}
|
|
707
|
+
// Re-parse to get updated positions after removal
|
|
708
|
+
const updatedSourceFile = ts.createSourceFile('temp.ts', result, ts.ScriptTarget.Latest, true);
|
|
709
|
+
let updatedFunctionNode;
|
|
710
|
+
ts.forEachChild(updatedSourceFile, (node) => {
|
|
711
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === nodeType.functionName) {
|
|
712
|
+
updatedFunctionNode = node;
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
if (!updatedFunctionNode) {
|
|
716
|
+
return { code: result, changed: hasChanges };
|
|
717
|
+
}
|
|
718
|
+
// Re-find the JSDoc comment with updated positions
|
|
719
|
+
const updatedStart = updatedFunctionNode.getFullStart();
|
|
720
|
+
const updatedComments = ts.getLeadingCommentRanges(result, updatedStart);
|
|
721
|
+
if (!updatedComments) {
|
|
722
|
+
return { code: result, changed: hasChanges };
|
|
723
|
+
}
|
|
724
|
+
let updatedJsdoc;
|
|
725
|
+
for (const c of updatedComments) {
|
|
726
|
+
if (c.kind === ts.SyntaxKind.MultiLineCommentTrivia &&
|
|
727
|
+
result.slice(c.pos, c.pos + 3) === '/**') {
|
|
728
|
+
updatedJsdoc = c;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (!updatedJsdoc) {
|
|
732
|
+
return { code: result, changed: hasChanges };
|
|
733
|
+
}
|
|
734
|
+
// Continue with the updated positions
|
|
735
|
+
return replaceJSDocContent(result, updatedJsdoc, updatedFunctionNode, nodeType, newJSDoc, hasChanges);
|
|
736
|
+
}
|
|
737
|
+
return replaceJSDocContent(result, jsdocComment, functionNode, nodeType, newJSDoc, hasChanges);
|
|
738
|
+
}
|
|
739
|
+
function replaceJSDocContent(source, jsdocComment, functionNode, nodeType, newJSDoc, hasChanges) {
|
|
740
|
+
const originalJSDoc = source.slice(jsdocComment.pos, jsdocComment.end);
|
|
741
|
+
// Check if JSDoc changed
|
|
742
|
+
const jsdocChanged = originalJSDoc.trim() !== newJSDoc.trim();
|
|
743
|
+
// Check if function body needs updating (when functionText is provided)
|
|
744
|
+
const nodeTypeWithCode = nodeType;
|
|
745
|
+
const newFunctionText = nodeTypeWithCode.code || nodeType.functionText;
|
|
746
|
+
let functionBodyChanged = false;
|
|
747
|
+
let newFunctionDeclaration = '';
|
|
748
|
+
if (newFunctionText) {
|
|
749
|
+
// Extract function declaration from functionText (strip ALL leading JSDoc blocks).
|
|
750
|
+
// The parser's functionText may include multiple JSDoc blocks (e.g. file header + nodeType JSDoc).
|
|
751
|
+
// We must strip ALL of them, not just the first one.
|
|
752
|
+
let strippedFunctionText = newFunctionText.trim();
|
|
753
|
+
while (strippedFunctionText.startsWith('/**')) {
|
|
754
|
+
strippedFunctionText = strippedFunctionText.replace(/^\/\*\*[\s\S]*?\*\/\s*/, '').trim();
|
|
755
|
+
}
|
|
756
|
+
newFunctionDeclaration = strippedFunctionText;
|
|
757
|
+
// Get current function declaration (without JSDoc)
|
|
758
|
+
const currentFunctionDeclaration = source
|
|
759
|
+
.slice(functionNode.getStart(), functionNode.getEnd())
|
|
760
|
+
.trim();
|
|
761
|
+
// Normalize for comparison (strip whitespace differences)
|
|
762
|
+
const normalizedNew = newFunctionDeclaration.replace(/\s+/g, ' ');
|
|
763
|
+
const normalizedCurrent = currentFunctionDeclaration.replace(/\s+/g, ' ');
|
|
764
|
+
functionBodyChanged = normalizedNew !== normalizedCurrent;
|
|
765
|
+
}
|
|
766
|
+
// If nothing changed, return early
|
|
767
|
+
if (!jsdocChanged && !functionBodyChanged) {
|
|
768
|
+
return { code: source, changed: hasChanges };
|
|
769
|
+
}
|
|
770
|
+
// Replace the JSDoc and optionally the function body
|
|
771
|
+
if (functionBodyChanged && newFunctionDeclaration) {
|
|
772
|
+
// Replace entire function (JSDoc + body)
|
|
773
|
+
const before = source.slice(0, jsdocComment.pos);
|
|
774
|
+
const after = source.slice(functionNode.getEnd());
|
|
775
|
+
return {
|
|
776
|
+
code: before + newJSDoc + '\n' + newFunctionDeclaration + after,
|
|
777
|
+
changed: true,
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
else if (jsdocChanged) {
|
|
781
|
+
// Only replace JSDoc
|
|
782
|
+
const before = source.slice(0, jsdocComment.pos);
|
|
783
|
+
const after = source.slice(jsdocComment.end);
|
|
784
|
+
return {
|
|
785
|
+
code: before + newJSDoc + after,
|
|
786
|
+
changed: true,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
return { code: source, changed: hasChanges };
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Generate JSDoc comment for node type function
|
|
793
|
+
*/
|
|
794
|
+
function generateNodeTypeJSDoc(nodeType) {
|
|
795
|
+
const lines = [];
|
|
796
|
+
lines.push('/**');
|
|
797
|
+
// Add description
|
|
798
|
+
if (nodeType.description) {
|
|
799
|
+
lines.push(` * ${nodeType.description}`);
|
|
800
|
+
lines.push(` *`);
|
|
801
|
+
}
|
|
802
|
+
// @flowWeaver marker
|
|
803
|
+
lines.push(' * @flowWeaver nodeType');
|
|
804
|
+
// Add @expression tag for expression nodes
|
|
805
|
+
if (nodeType.expression) {
|
|
806
|
+
lines.push(' * @expression');
|
|
807
|
+
}
|
|
808
|
+
// Add @name tag when name differs from functionName (preserves stable identity across renames)
|
|
809
|
+
if (nodeType.name && nodeType.name !== nodeType.functionName) {
|
|
810
|
+
lines.push(` * @name ${nodeType.name}`);
|
|
811
|
+
}
|
|
812
|
+
// Add label if present
|
|
813
|
+
if (nodeType.label) {
|
|
814
|
+
lines.push(` * @label ${nodeType.label}`);
|
|
815
|
+
}
|
|
816
|
+
// Add scope if present
|
|
817
|
+
if (nodeType.scope) {
|
|
818
|
+
lines.push(` * @scope ${nodeType.scope}`);
|
|
819
|
+
}
|
|
820
|
+
// Add pullExecution if present
|
|
821
|
+
if (nodeType.defaultConfig?.pullExecution) {
|
|
822
|
+
lines.push(` * @pullExecution ${nodeType.defaultConfig.pullExecution.triggerPort}`);
|
|
823
|
+
}
|
|
824
|
+
// Add visual annotations
|
|
825
|
+
if (nodeType.visuals) {
|
|
826
|
+
if (nodeType.visuals.color) {
|
|
827
|
+
lines.push(` * @color ${nodeType.visuals.color}`);
|
|
828
|
+
}
|
|
829
|
+
if (nodeType.visuals.icon) {
|
|
830
|
+
lines.push(` * @icon ${nodeType.visuals.icon}`);
|
|
831
|
+
}
|
|
832
|
+
if (nodeType.visuals.tags && nodeType.visuals.tags.length > 0) {
|
|
833
|
+
for (const tag of nodeType.visuals.tags) {
|
|
834
|
+
if (tag.tooltip) {
|
|
835
|
+
lines.push(` * @tag ${tag.label} "${tag.tooltip}"`);
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
lines.push(` * @tag ${tag.label}`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
// Handle both formats: inputs/outputs dictionaries OR ports array
|
|
844
|
+
let inputEntries = [];
|
|
845
|
+
let outputEntries = [];
|
|
846
|
+
if (nodeType.inputs && Object.keys(nodeType.inputs).length > 0) {
|
|
847
|
+
// Use native inputs/outputs format
|
|
848
|
+
// For expression nodes, skip auto-generated control flow ports (execute, onSuccess, onFailure)
|
|
849
|
+
const filterControlFlow = nodeType.expression
|
|
850
|
+
? ([name]) => !isExecutePort(name) && !isSuccessPort(name) && !isFailurePort(name)
|
|
851
|
+
: () => true;
|
|
852
|
+
inputEntries = assignPortOrders(Object.entries(nodeType.inputs).filter(filterControlFlow), 'input');
|
|
853
|
+
outputEntries = assignPortOrders(Object.entries(nodeType.outputs || {}).filter(filterControlFlow), 'output');
|
|
854
|
+
}
|
|
855
|
+
else if (nodeType.ports && nodeType.ports.length > 0) {
|
|
856
|
+
// Convert from ports array format (UI format)
|
|
857
|
+
const inputs = [];
|
|
858
|
+
const outputs = [];
|
|
859
|
+
for (const port of nodeType.ports) {
|
|
860
|
+
// Skip mandatory control flow ports - they're implicit
|
|
861
|
+
if (isExecutePort(port.name) || isSuccessPort(port.name) || isFailurePort(port.name)) {
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
const portDef = {
|
|
865
|
+
dataType: (port.type || port.dataType || 'ANY'),
|
|
866
|
+
label: port.defaultLabel || port.name,
|
|
867
|
+
scope: port.scope,
|
|
868
|
+
metadata: { order: port.defaultOrder },
|
|
869
|
+
};
|
|
870
|
+
if (port.direction === 'INPUT') {
|
|
871
|
+
inputs.push([port.name, portDef]);
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
outputs.push([port.name, portDef]);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
inputEntries = assignPortOrders(inputs, 'input');
|
|
878
|
+
outputEntries = assignPortOrders(outputs, 'output');
|
|
879
|
+
}
|
|
880
|
+
// Add input ports
|
|
881
|
+
for (const [name, port] of inputEntries) {
|
|
882
|
+
const portTag = generateJSDocPortTag(name, port, 'input');
|
|
883
|
+
lines.push(` * ${portTag}`);
|
|
884
|
+
}
|
|
885
|
+
// Add output ports
|
|
886
|
+
for (const [name, port] of outputEntries) {
|
|
887
|
+
const portTag = generateJSDocPortTag(name, port, 'output');
|
|
888
|
+
lines.push(` * ${portTag}`);
|
|
889
|
+
}
|
|
890
|
+
lines.push(' */');
|
|
891
|
+
return lines.join('\n');
|
|
892
|
+
}
|
|
893
|
+
// Note: generateJSDocPortTag and assignPortOrders are now imported from annotation-generator
|
|
894
|
+
// to maintain DRY principle and ensure consistent behavior across all code generation
|
|
895
|
+
/**
|
|
896
|
+
* Replace the workflow function's JSDoc comment with updated annotations
|
|
897
|
+
*/
|
|
898
|
+
function replaceWorkflowJSDoc(source, ast, options = {}) {
|
|
899
|
+
const sourceFile = ts.createSourceFile('temp.ts', source, ts.ScriptTarget.Latest, true);
|
|
900
|
+
let functionNode;
|
|
901
|
+
// Find the workflow function (exported or not)
|
|
902
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
903
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === ast.functionName) {
|
|
904
|
+
functionNode = node;
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
if (!functionNode) {
|
|
908
|
+
return { code: source, changed: false };
|
|
909
|
+
}
|
|
910
|
+
// Find the JSDoc comment before the function
|
|
911
|
+
const functionStart = functionNode.getFullStart();
|
|
912
|
+
const leadingComments = ts.getLeadingCommentRanges(source, functionStart);
|
|
913
|
+
if (!leadingComments || leadingComments.length === 0) {
|
|
914
|
+
return { code: source, changed: false };
|
|
915
|
+
}
|
|
916
|
+
// Find the JSDoc comment (starts with /**)
|
|
917
|
+
const jsdocComment = leadingComments.find((c) => c.kind === ts.SyntaxKind.MultiLineCommentTrivia && source.slice(c.pos, c.pos + 3) === '/**');
|
|
918
|
+
if (!jsdocComment) {
|
|
919
|
+
return { code: source, changed: false };
|
|
920
|
+
}
|
|
921
|
+
// Generate new JSDoc
|
|
922
|
+
const newJSDoc = generateWorkflowJSDoc(ast, { skipParamReturns: options.skipParamReturns });
|
|
923
|
+
// Get the original JSDoc
|
|
924
|
+
const originalJSDoc = source.slice(jsdocComment.pos, jsdocComment.end);
|
|
925
|
+
// Check if changed
|
|
926
|
+
if (originalJSDoc.trim() === newJSDoc.trim()) {
|
|
927
|
+
return { code: source, changed: false };
|
|
928
|
+
}
|
|
929
|
+
// Replace the JSDoc
|
|
930
|
+
const before = source.slice(0, jsdocComment.pos);
|
|
931
|
+
const after = source.slice(jsdocComment.end);
|
|
932
|
+
return {
|
|
933
|
+
code: before + newJSDoc + after,
|
|
934
|
+
changed: true,
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Check if a connection is covered by a macro (and should not be written as @connect).
|
|
939
|
+
* Handles @map and @path macros.
|
|
940
|
+
*/
|
|
941
|
+
function isConnectionCoveredByMacro(conn, macros) {
|
|
942
|
+
for (const macro of macros) {
|
|
943
|
+
if (macro.type === 'map') {
|
|
944
|
+
const [sourceNode, sourcePort] = macro.sourcePort.split('.');
|
|
945
|
+
// Scoped connections between the map instance and its child
|
|
946
|
+
if ((conn.from.node === macro.instanceId || conn.from.node === macro.childId) &&
|
|
947
|
+
(conn.to.node === macro.instanceId || conn.to.node === macro.childId) &&
|
|
948
|
+
(conn.from.scope === 'iterate' || conn.to.scope === 'iterate')) {
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
951
|
+
// Upstream connection: source.port -> mapInstance.items
|
|
952
|
+
if (conn.from.node === sourceNode &&
|
|
953
|
+
conn.from.port === sourcePort &&
|
|
954
|
+
!conn.from.scope &&
|
|
955
|
+
conn.to.node === macro.instanceId &&
|
|
956
|
+
conn.to.port === 'items' &&
|
|
957
|
+
!conn.to.scope) {
|
|
958
|
+
return true;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
else if (macro.type === 'path') {
|
|
962
|
+
if (conn.from.scope || conn.to.scope)
|
|
963
|
+
continue;
|
|
964
|
+
const steps = macro.steps;
|
|
965
|
+
const fromIdx = steps.findIndex(s => s.node === conn.from.node);
|
|
966
|
+
const toIdx = steps.findIndex(s => s.node === conn.to.node);
|
|
967
|
+
if (fromIdx === -1 || toIdx === -1 || fromIdx >= toIdx)
|
|
968
|
+
continue;
|
|
969
|
+
// Control flow: check consecutive pairs
|
|
970
|
+
if (toIdx === fromIdx + 1) {
|
|
971
|
+
const route = steps[fromIdx].route || 'ok';
|
|
972
|
+
if (conn.from.node === 'Start' && conn.from.port === 'execute' && conn.to.port === 'execute')
|
|
973
|
+
return true;
|
|
974
|
+
if (conn.to.node === 'Exit') {
|
|
975
|
+
if (route === 'fail' && conn.from.port === 'onFailure' && conn.to.port === 'onFailure')
|
|
976
|
+
return true;
|
|
977
|
+
if (route === 'ok' && conn.from.port === 'onSuccess' && conn.to.port === 'onSuccess')
|
|
978
|
+
return true;
|
|
979
|
+
}
|
|
980
|
+
if (route === 'fail' && conn.from.port === 'onFailure' && conn.to.port === 'execute')
|
|
981
|
+
return true;
|
|
982
|
+
if (route === 'ok' && conn.from.port === 'onSuccess' && conn.to.port === 'execute')
|
|
983
|
+
return true;
|
|
984
|
+
}
|
|
985
|
+
// Data: same-name non-control-flow, from before to, to is not Exit
|
|
986
|
+
if (conn.to.node !== 'Exit' &&
|
|
987
|
+
!isControlFlowPort(conn.from.port) &&
|
|
988
|
+
!isControlFlowPort(conn.to.port) &&
|
|
989
|
+
conn.from.port === conn.to.port) {
|
|
990
|
+
return true;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Generate JSDoc comment for workflow function
|
|
998
|
+
*/
|
|
999
|
+
function generateWorkflowJSDoc(ast, options = {}) {
|
|
1000
|
+
const lines = [];
|
|
1001
|
+
// Build macro coverage sets for filtering (@map-specific)
|
|
1002
|
+
const macroInstanceIds = new Set();
|
|
1003
|
+
const macroChildIds = new Set();
|
|
1004
|
+
const macroScopeNames = new Set();
|
|
1005
|
+
if (ast.macros && ast.macros.length > 0) {
|
|
1006
|
+
for (const macro of ast.macros) {
|
|
1007
|
+
if (macro.type === 'map') {
|
|
1008
|
+
macroInstanceIds.add(macro.instanceId);
|
|
1009
|
+
macroChildIds.add(macro.childId);
|
|
1010
|
+
macroScopeNames.add(`${macro.instanceId}.iterate`);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
lines.push('/**');
|
|
1015
|
+
// Add description
|
|
1016
|
+
if (ast.description) {
|
|
1017
|
+
lines.push(` * ${ast.description}`);
|
|
1018
|
+
lines.push(` *`);
|
|
1019
|
+
}
|
|
1020
|
+
// @flowWeaver marker
|
|
1021
|
+
lines.push(' * @flowWeaver workflow');
|
|
1022
|
+
// Add workflow options
|
|
1023
|
+
if (ast.options?.strictTypes) {
|
|
1024
|
+
lines.push(' * @strictTypes');
|
|
1025
|
+
}
|
|
1026
|
+
if (ast.options?.autoConnect) {
|
|
1027
|
+
lines.push(' * @autoConnect');
|
|
1028
|
+
}
|
|
1029
|
+
// @trigger round-trip
|
|
1030
|
+
if (ast.options?.trigger) {
|
|
1031
|
+
const t = ast.options.trigger;
|
|
1032
|
+
const parts = [];
|
|
1033
|
+
if (t.event)
|
|
1034
|
+
parts.push(`event="${t.event}"`);
|
|
1035
|
+
if (t.cron)
|
|
1036
|
+
parts.push(`cron="${t.cron}"`);
|
|
1037
|
+
if (parts.length > 0)
|
|
1038
|
+
lines.push(` * @trigger ${parts.join(' ')}`);
|
|
1039
|
+
}
|
|
1040
|
+
// @cancelOn round-trip
|
|
1041
|
+
if (ast.options?.cancelOn) {
|
|
1042
|
+
const c = ast.options.cancelOn;
|
|
1043
|
+
let line = ` * @cancelOn event="${c.event}"`;
|
|
1044
|
+
if (c.match)
|
|
1045
|
+
line += ` match="${c.match}"`;
|
|
1046
|
+
if (c.timeout)
|
|
1047
|
+
line += ` timeout="${c.timeout}"`;
|
|
1048
|
+
lines.push(line);
|
|
1049
|
+
}
|
|
1050
|
+
// @retries round-trip
|
|
1051
|
+
if (ast.options?.retries !== undefined) {
|
|
1052
|
+
lines.push(` * @retries ${ast.options.retries}`);
|
|
1053
|
+
}
|
|
1054
|
+
// @timeout round-trip
|
|
1055
|
+
if (ast.options?.timeout) {
|
|
1056
|
+
lines.push(` * @timeout "${ast.options.timeout}"`);
|
|
1057
|
+
}
|
|
1058
|
+
// @throttle round-trip
|
|
1059
|
+
if (ast.options?.throttle) {
|
|
1060
|
+
const t = ast.options.throttle;
|
|
1061
|
+
let line = ` * @throttle limit=${t.limit}`;
|
|
1062
|
+
if (t.period)
|
|
1063
|
+
line += ` period="${t.period}"`;
|
|
1064
|
+
lines.push(line);
|
|
1065
|
+
}
|
|
1066
|
+
// Add name if different from function name
|
|
1067
|
+
if (ast.name && ast.name !== ast.functionName) {
|
|
1068
|
+
lines.push(` * @name ${ast.name}`);
|
|
1069
|
+
}
|
|
1070
|
+
// Add npm package imports (external node types with importSource)
|
|
1071
|
+
// Format: @fwImport nodeName functionName from "packageName"
|
|
1072
|
+
// This persists npm node types so they survive file re-parsing
|
|
1073
|
+
const npmNodeTypes = ast.nodeTypes.filter((nt) => nt.importSource);
|
|
1074
|
+
const seenImportNames = new Set();
|
|
1075
|
+
for (const npmType of npmNodeTypes) {
|
|
1076
|
+
if (seenImportNames.has(npmType.name))
|
|
1077
|
+
continue;
|
|
1078
|
+
seenImportNames.add(npmType.name);
|
|
1079
|
+
// Derive the actual function name:
|
|
1080
|
+
// - If functionName differs from name, use functionName (explicitly set)
|
|
1081
|
+
// - Otherwise, extract from npm path: "npm/package/funcName" -> "funcName"
|
|
1082
|
+
let actualFunctionName = npmType.functionName;
|
|
1083
|
+
if (npmType.functionName === npmType.name && npmType.name.startsWith('npm/')) {
|
|
1084
|
+
// Extract function name from npm path: npm/package/funcName -> funcName
|
|
1085
|
+
const parts = npmType.name.split('/');
|
|
1086
|
+
actualFunctionName = parts[parts.length - 1];
|
|
1087
|
+
}
|
|
1088
|
+
lines.push(` * @fwImport ${npmType.name} ${actualFunctionName} from "${npmType.importSource}"`);
|
|
1089
|
+
}
|
|
1090
|
+
// Add node instances — skip synthetic MAP_ITERATOR instances, strip parent from macro children
|
|
1091
|
+
for (const instance of ast.instances) {
|
|
1092
|
+
if (macroInstanceIds.has(instance.id))
|
|
1093
|
+
continue;
|
|
1094
|
+
if (macroChildIds.has(instance.id) && instance.parent) {
|
|
1095
|
+
// Write child @node without parent scope — @map handles it
|
|
1096
|
+
const stripped = { ...instance, parent: undefined };
|
|
1097
|
+
lines.push(generateNodeInstanceTag(stripped));
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
lines.push(generateNodeInstanceTag(instance));
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
// Filter stale macros (e.g. paths whose connections were deleted)
|
|
1104
|
+
const existingMacros = filterStaleMacros(ast.macros || [], ast.connections, ast.instances);
|
|
1105
|
+
// Auto-detect @path sugar patterns from connections
|
|
1106
|
+
const detected = detectSugarPatterns(ast.connections, ast.instances, existingMacros, ast.nodeTypes, ast.startPorts, ast.exitPorts);
|
|
1107
|
+
// Merge detected macros with existing ones
|
|
1108
|
+
const allMacros = [
|
|
1109
|
+
...existingMacros,
|
|
1110
|
+
...detected.paths,
|
|
1111
|
+
];
|
|
1112
|
+
// Add @map and @path macros
|
|
1113
|
+
if (allMacros.length > 0) {
|
|
1114
|
+
for (const macro of allMacros) {
|
|
1115
|
+
if (macro.type === 'map') {
|
|
1116
|
+
let mapLine = ` * @map ${macro.instanceId} ${macro.childId}`;
|
|
1117
|
+
if (macro.inputPort || macro.outputPort) {
|
|
1118
|
+
mapLine += `(${macro.inputPort} -> ${macro.outputPort})`;
|
|
1119
|
+
}
|
|
1120
|
+
mapLine += ` over ${macro.sourcePort}`;
|
|
1121
|
+
lines.push(mapLine);
|
|
1122
|
+
}
|
|
1123
|
+
else if (macro.type === 'path') {
|
|
1124
|
+
const stepsStr = macro.steps.map(s => s.route ? `${s.node}:${s.route}` : s.node).join(' -> ');
|
|
1125
|
+
lines.push(` * @path ${stepsStr}`);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
// Auto-position: compute default positions for nodes without explicit positions.
|
|
1130
|
+
// Uses a left-to-right layout with topological ordering when connections are available.
|
|
1131
|
+
const autoPositions = computeAutoPositions(ast);
|
|
1132
|
+
// Add positions - Start node
|
|
1133
|
+
const startX = ast.ui?.startNode?.x ?? autoPositions.get('Start')?.x;
|
|
1134
|
+
const startY = ast.ui?.startNode?.y ?? autoPositions.get('Start')?.y;
|
|
1135
|
+
if (startX !== undefined && startY !== undefined) {
|
|
1136
|
+
lines.push(` * @position Start ${Math.round(startX)} ${Math.round(startY)}`);
|
|
1137
|
+
}
|
|
1138
|
+
// Add positions - instances (use explicit position if available, otherwise auto-computed)
|
|
1139
|
+
for (const instance of ast.instances) {
|
|
1140
|
+
const explicitX = instance.config?.x;
|
|
1141
|
+
const explicitY = instance.config?.y;
|
|
1142
|
+
const autoPos = autoPositions.get(instance.id);
|
|
1143
|
+
const x = explicitX ?? autoPos?.x;
|
|
1144
|
+
const y = explicitY ?? autoPos?.y;
|
|
1145
|
+
if (x !== undefined && y !== undefined) {
|
|
1146
|
+
lines.push(` * @position ${instance.id} ${Math.round(x)} ${Math.round(y)}`);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
// Add positions - Exit node
|
|
1150
|
+
const exitX = ast.ui?.exitNode?.x ?? autoPositions.get('Exit')?.x;
|
|
1151
|
+
const exitY = ast.ui?.exitNode?.y ?? autoPositions.get('Exit')?.y;
|
|
1152
|
+
if (exitX !== undefined && exitY !== undefined) {
|
|
1153
|
+
lines.push(` * @position Exit ${Math.round(exitX)} ${Math.round(exitY)}`);
|
|
1154
|
+
}
|
|
1155
|
+
// Add connections (with scope suffix when present)
|
|
1156
|
+
// Skip connections covered by @map macros and autoConnect-generated connections
|
|
1157
|
+
if (!ast.options?.autoConnect) {
|
|
1158
|
+
for (const conn of ast.connections) {
|
|
1159
|
+
if (allMacros.length > 0 && isConnectionCoveredByMacro(conn, allMacros))
|
|
1160
|
+
continue;
|
|
1161
|
+
const fromScope = conn.from.scope ? `:${conn.from.scope}` : '';
|
|
1162
|
+
const toScope = conn.to.scope ? `:${conn.to.scope}` : '';
|
|
1163
|
+
lines.push(` * @connect ${conn.from.node}.${conn.from.port}${fromScope} -> ${conn.to.node}.${conn.to.port}${toScope}`);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
// Add @param annotations for start ports (workflow inputs)
|
|
1167
|
+
if (!options.skipParamReturns && ast.startPorts && Object.keys(ast.startPorts).length > 0) {
|
|
1168
|
+
const startPortEntries = assignPortOrders(Object.entries(ast.startPorts), 'input');
|
|
1169
|
+
startPortEntries.forEach(([name, port], index) => {
|
|
1170
|
+
const paramTag = generateJSDocPortTag(name, port, 'input', index);
|
|
1171
|
+
// Replace @input with @param for workflow-level JSDoc
|
|
1172
|
+
lines.push(` * ${paramTag.replace('@input', '@param')}`);
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
// Add @returns annotations for exit ports (workflow outputs)
|
|
1176
|
+
if (!options.skipParamReturns && ast.exitPorts && Object.keys(ast.exitPorts).length > 0) {
|
|
1177
|
+
const exitPortEntries = assignPortOrders(Object.entries(ast.exitPorts), 'output');
|
|
1178
|
+
exitPortEntries.forEach(([name, port], index) => {
|
|
1179
|
+
const returnTag = generateJSDocPortTag(name, port, 'output', index);
|
|
1180
|
+
// Replace @output with @returns for workflow-level JSDoc
|
|
1181
|
+
lines.push(` * ${returnTag.replace('@output', '@returns')}`);
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
// Add scopes — skip scopes covered by @map macros
|
|
1185
|
+
if (ast.scopes) {
|
|
1186
|
+
for (const [scopeName, children] of Object.entries(ast.scopes)) {
|
|
1187
|
+
if (macroScopeNames.has(scopeName))
|
|
1188
|
+
continue;
|
|
1189
|
+
lines.push(` * @scope ${scopeName} [${children.join(', ')}]`);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
lines.push(' */');
|
|
1193
|
+
return lines.join('\n');
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Compute auto-layout positions for nodes that don't have explicit positions.
|
|
1197
|
+
* Uses topological order (from connections) for left-to-right layout.
|
|
1198
|
+
* Only computes positions for nodes that are missing them.
|
|
1199
|
+
*
|
|
1200
|
+
* Layout strategy:
|
|
1201
|
+
* - Start node at x=0, y=0
|
|
1202
|
+
* - Each subsequent node gets x += 270 (standard node width + gap)
|
|
1203
|
+
* - Exit node placed after the last instance
|
|
1204
|
+
* - Uses topological order when connections are available
|
|
1205
|
+
* - Falls back to declaration order otherwise
|
|
1206
|
+
*
|
|
1207
|
+
* @returns Map of nodeId -> {x, y} for nodes that need auto-positioned
|
|
1208
|
+
*/
|
|
1209
|
+
function computeAutoPositions(ast) {
|
|
1210
|
+
const positions = new Map();
|
|
1211
|
+
const SPACING_X = 270; // Standard horizontal spacing between nodes
|
|
1212
|
+
// Determine which nodes need positions
|
|
1213
|
+
const needsPosition = (nodeId) => {
|
|
1214
|
+
if (nodeId === 'Start') {
|
|
1215
|
+
return ast.ui?.startNode?.x === undefined || ast.ui?.startNode?.y === undefined;
|
|
1216
|
+
}
|
|
1217
|
+
if (nodeId === 'Exit') {
|
|
1218
|
+
return ast.ui?.exitNode?.x === undefined || ast.ui?.exitNode?.y === undefined;
|
|
1219
|
+
}
|
|
1220
|
+
const instance = ast.instances.find((inst) => inst.id === nodeId);
|
|
1221
|
+
return !instance || instance.config?.x === undefined || instance.config?.y === undefined;
|
|
1222
|
+
};
|
|
1223
|
+
// Check if any nodes need auto-positioning
|
|
1224
|
+
const startNeedsPosition = needsPosition('Start');
|
|
1225
|
+
const exitNeedsPosition = needsPosition('Exit');
|
|
1226
|
+
const instancesNeedingPosition = ast.instances.filter((inst) => needsPosition(inst.id));
|
|
1227
|
+
// If nothing needs positioning, return empty map
|
|
1228
|
+
if (!startNeedsPosition && !exitNeedsPosition && instancesNeedingPosition.length === 0) {
|
|
1229
|
+
return positions;
|
|
1230
|
+
}
|
|
1231
|
+
// Compute topological order from connections, or fall back to declaration order
|
|
1232
|
+
const orderedIds = computeTopologicalOrder(ast);
|
|
1233
|
+
// Assign positions left-to-right
|
|
1234
|
+
let currentX = 0;
|
|
1235
|
+
const y = 0;
|
|
1236
|
+
// Start node
|
|
1237
|
+
if (startNeedsPosition) {
|
|
1238
|
+
positions.set('Start', { x: currentX, y });
|
|
1239
|
+
}
|
|
1240
|
+
currentX += SPACING_X;
|
|
1241
|
+
// Instance nodes in topological/declaration order
|
|
1242
|
+
for (const instanceId of orderedIds) {
|
|
1243
|
+
if (needsPosition(instanceId)) {
|
|
1244
|
+
positions.set(instanceId, { x: currentX, y });
|
|
1245
|
+
}
|
|
1246
|
+
currentX += SPACING_X;
|
|
1247
|
+
}
|
|
1248
|
+
// Exit node
|
|
1249
|
+
if (exitNeedsPosition) {
|
|
1250
|
+
positions.set('Exit', { x: currentX, y });
|
|
1251
|
+
}
|
|
1252
|
+
return positions;
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Compute topological order for workflow instances using connections.
|
|
1256
|
+
* Falls back to declaration order when connections don't provide a clear ordering.
|
|
1257
|
+
*/
|
|
1258
|
+
function computeTopologicalOrder(ast) {
|
|
1259
|
+
const instanceIds = ast.instances.map((inst) => inst.id);
|
|
1260
|
+
// If no connections, use declaration order
|
|
1261
|
+
if (!ast.connections || ast.connections.length === 0) {
|
|
1262
|
+
return instanceIds;
|
|
1263
|
+
}
|
|
1264
|
+
// Build adjacency list from execution flow connections only
|
|
1265
|
+
// (control flow connections determine order, data connections don't)
|
|
1266
|
+
const graph = new Map();
|
|
1267
|
+
const inDegree = new Map();
|
|
1268
|
+
for (const id of instanceIds) {
|
|
1269
|
+
graph.set(id, new Set());
|
|
1270
|
+
inDegree.set(id, 0);
|
|
1271
|
+
}
|
|
1272
|
+
for (const conn of ast.connections) {
|
|
1273
|
+
const fromNode = conn.from.node;
|
|
1274
|
+
const toNode = conn.to.node;
|
|
1275
|
+
// Only consider connections between instances (skip Start/Exit)
|
|
1276
|
+
if (!graph.has(fromNode) || !graph.has(toNode))
|
|
1277
|
+
continue;
|
|
1278
|
+
// Only use execution flow (onSuccess/onFailure -> execute) for ordering
|
|
1279
|
+
const isExecutionFlow = (conn.from.port === 'onSuccess' || conn.from.port === 'onFailure') &&
|
|
1280
|
+
conn.to.port === 'execute';
|
|
1281
|
+
if (isExecutionFlow && !graph.get(fromNode).has(toNode)) {
|
|
1282
|
+
graph.get(fromNode).add(toNode);
|
|
1283
|
+
inDegree.set(toNode, (inDegree.get(toNode) || 0) + 1);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
// Kahn's algorithm for topological sort
|
|
1287
|
+
const queue = [];
|
|
1288
|
+
for (const id of instanceIds) {
|
|
1289
|
+
if ((inDegree.get(id) || 0) === 0) {
|
|
1290
|
+
queue.push(id);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
const sorted = [];
|
|
1294
|
+
while (queue.length > 0) {
|
|
1295
|
+
const node = queue.shift();
|
|
1296
|
+
sorted.push(node);
|
|
1297
|
+
for (const neighbor of graph.get(node) || []) {
|
|
1298
|
+
const newDegree = (inDegree.get(neighbor) || 1) - 1;
|
|
1299
|
+
inDegree.set(neighbor, newDegree);
|
|
1300
|
+
if (newDegree === 0) {
|
|
1301
|
+
queue.push(neighbor);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
// If topological sort didn't include all nodes (cycles or disconnected),
|
|
1306
|
+
// append remaining nodes in declaration order
|
|
1307
|
+
if (sorted.length < instanceIds.length) {
|
|
1308
|
+
const sortedSet = new Set(sorted);
|
|
1309
|
+
for (const id of instanceIds) {
|
|
1310
|
+
if (!sortedSet.has(id)) {
|
|
1311
|
+
sorted.push(id);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return sorted;
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Check if source code has in-place generation markers
|
|
1319
|
+
*/
|
|
1320
|
+
export function hasInPlaceMarkers(source) {
|
|
1321
|
+
return (source.includes(MARKERS.RUNTIME_START) &&
|
|
1322
|
+
source.includes(MARKERS.RUNTIME_END) &&
|
|
1323
|
+
source.includes(MARKERS.BODY_START) &&
|
|
1324
|
+
source.includes(MARKERS.BODY_END));
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Remove all generated sections from source code
|
|
1328
|
+
*/
|
|
1329
|
+
export function stripGeneratedSections(source) {
|
|
1330
|
+
let result = source;
|
|
1331
|
+
// Remove runtime section
|
|
1332
|
+
const runtimeStartIdx = result.indexOf(MARKERS.RUNTIME_START);
|
|
1333
|
+
const runtimeEndIdx = result.indexOf(MARKERS.RUNTIME_END);
|
|
1334
|
+
if (runtimeStartIdx !== -1 && runtimeEndIdx !== -1) {
|
|
1335
|
+
const lineStart = result.lastIndexOf('\n', runtimeStartIdx);
|
|
1336
|
+
const lineEnd = result.indexOf('\n', runtimeEndIdx + MARKERS.RUNTIME_END.length);
|
|
1337
|
+
result =
|
|
1338
|
+
result.slice(0, lineStart === -1 ? 0 : lineStart) +
|
|
1339
|
+
result.slice(lineEnd === -1 ? result.length : lineEnd);
|
|
1340
|
+
}
|
|
1341
|
+
// Remove ALL body sections (multi-workflow files have multiple)
|
|
1342
|
+
while (true) {
|
|
1343
|
+
const bodyStartIdx = result.indexOf(MARKERS.BODY_START);
|
|
1344
|
+
const bodyEndIdx = result.indexOf(MARKERS.BODY_END);
|
|
1345
|
+
if (bodyStartIdx === -1 || bodyEndIdx === -1 || bodyEndIdx < bodyStartIdx)
|
|
1346
|
+
break;
|
|
1347
|
+
const before = result.slice(0, bodyStartIdx);
|
|
1348
|
+
const after = result.slice(bodyEndIdx + MARKERS.BODY_END.length);
|
|
1349
|
+
result = before + `throw new Error('Not implemented');` + after;
|
|
1350
|
+
}
|
|
1351
|
+
return result;
|
|
1352
|
+
}
|
|
1353
|
+
//# sourceMappingURL=generate-in-place.js.map
|