blokctl 0.2.11 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/create/project.js +63 -3
- package/dist/commands/create/utils/Examples.d.ts +3 -3
- package/dist/commands/create/utils/Examples.js +109 -13
- package/dist/commands/dev/index.js +50 -13
- package/dist/commands/generate/validators/WorkflowValidator.js +1 -1
- package/dist/commands/migrate/index.js +22 -0
- package/dist/commands/migrate/paths.d.ts +2 -0
- package/dist/commands/migrate/paths.js +267 -0
- package/dist/commands/migrate/workflows.d.ts +3 -0
- package/dist/commands/migrate/workflows.js +333 -0
- package/dist/commands/trace/index.js +6 -2
- package/dist/commands/trace/startStudio.d.ts +2 -0
- package/dist/commands/trace/startStudio.js +76 -11
- package/dist/index.js +1 -0
- package/dist/services/health-probe.d.ts +5 -0
- package/dist/services/health-probe.js +35 -0
- package/dist/services/runtime-detector.d.ts +3 -0
- package/dist/services/runtime-detector.js +21 -2
- package/dist/services/runtime-setup.d.ts +3 -0
- package/dist/services/runtime-setup.js +11 -2
- package/dist/studio-dist/assets/charts-Dh48HebV.js +68 -0
- package/dist/studio-dist/assets/graph-DWteCadQ.js +7 -0
- package/dist/studio-dist/assets/{icons-zP8LLgPh.js → icons-N5J4OhGx.js} +66 -51
- package/dist/studio-dist/assets/index-D6hOoOID.css +1 -0
- package/dist/studio-dist/assets/index-D_CdNmTc.js +42 -0
- package/dist/studio-dist/assets/react-vendor-l0sNRNKZ.js +1 -0
- package/dist/studio-dist/assets/tanstack-query-Day3Mt-4.js +17 -0
- package/dist/studio-dist/assets/tanstack-router-BB95iErN.js +25 -0
- package/dist/studio-dist/assets/{tanstack-table-DhwRvuH2.js → tanstack-table-Biem1hxK.js} +1 -1
- package/dist/studio-dist/favicon.svg +7 -4
- package/dist/studio-dist/index.html +19 -10
- package/package.json +15 -12
- package/dist/studio-dist/assets/charts-Dso0hPUR.js +0 -68
- package/dist/studio-dist/assets/graph-CsV2nWGn.js +0 -23
- package/dist/studio-dist/assets/index-CLyEkXMx.css +0 -1
- package/dist/studio-dist/assets/index-CNXFX_ar.js +0 -27
- package/dist/studio-dist/assets/react-vendor--Eh9ivFN.js +0 -17
- package/dist/studio-dist/assets/tanstack-query-CiM1U6F5.js +0 -1
- package/dist/studio-dist/assets/tanstack-router-Btjy0MKq.js +0 -25
|
@@ -16,7 +16,7 @@ const exec = util.promisify(child_process.exec);
|
|
|
16
16
|
const HOME_DIR = `${os.homedir()}/.blok`;
|
|
17
17
|
const GITHUB_REPO_LOCAL = `${HOME_DIR}/blok`;
|
|
18
18
|
const GITHUB_REPO_REMOTE = "https://github.com/well-prado/blok.git";
|
|
19
|
-
const GITHUB_REPO_RELEASE_TAG = "v0.
|
|
19
|
+
const GITHUB_REPO_RELEASE_TAG = "v0.4.0";
|
|
20
20
|
fsExtra.ensureDirSync(HOME_DIR);
|
|
21
21
|
const options = {
|
|
22
22
|
baseDir: HOME_DIR,
|
|
@@ -135,6 +135,7 @@ export async function createProject(opts, version, currentPath = false, localRep
|
|
|
135
135
|
{ label: "RabbitMQ", value: "rabbitmq" },
|
|
136
136
|
{ label: "AWS SQS", value: "sqs" },
|
|
137
137
|
{ label: "Redis/BullMQ", value: "redis" },
|
|
138
|
+
{ label: "NATS JetStream", value: "nats" },
|
|
138
139
|
],
|
|
139
140
|
})
|
|
140
141
|
: Promise.resolve(null),
|
|
@@ -262,6 +263,34 @@ export async function createProject(opts, version, currentPath = false, localRep
|
|
|
262
263
|
fsExtra.copySync(src, `${dirPath}/${file}`);
|
|
263
264
|
}
|
|
264
265
|
}
|
|
266
|
+
const gitignorePath = `${dirPath}/.gitignore`;
|
|
267
|
+
const gitignoreLine = "\n# Blok Studio trace data (managed by blokctl)\n.blok/\n";
|
|
268
|
+
if (fsExtra.existsSync(gitignorePath)) {
|
|
269
|
+
const existing = fsExtra.readFileSync(gitignorePath, "utf8");
|
|
270
|
+
if (!existing.includes(".blok/")) {
|
|
271
|
+
fsExtra.appendFileSync(gitignorePath, gitignoreLine);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
fsExtra.writeFileSync(gitignorePath, gitignoreLine.trimStart());
|
|
276
|
+
}
|
|
277
|
+
fsExtra.ensureDirSync(`${dirPath}/.blok`);
|
|
278
|
+
fsExtra.writeFileSync(`${dirPath}/.blok/README.md`, [
|
|
279
|
+
"# .blok/",
|
|
280
|
+
"",
|
|
281
|
+
"Auto-generated by `blokctl dev`. This directory holds the SQLite trace",
|
|
282
|
+
"database that powers Blok Studio (run history, logs, events, dashboards).",
|
|
283
|
+
"",
|
|
284
|
+
"- `trace.db` — SQLite file, persists across restarts",
|
|
285
|
+
"- 7-day retention by default; tune with `BLOK_TRACE_RETENTION_DAYS`",
|
|
286
|
+
"",
|
|
287
|
+
"Open it visually with `blokctl studio` (no trigger required — works",
|
|
288
|
+
"like `prisma studio`). Or wipe everything via the **Clear all data**",
|
|
289
|
+
"button in Settings.",
|
|
290
|
+
"",
|
|
291
|
+
"This directory is gitignored.",
|
|
292
|
+
"",
|
|
293
|
+
].join("\n"));
|
|
265
294
|
if (fsExtra.existsSync(`${primaryTriggerDir}/Dockerfile`)) {
|
|
266
295
|
fsExtra.copySync(`${primaryTriggerDir}/Dockerfile`, `${dirPath}/Dockerfile`);
|
|
267
296
|
}
|
|
@@ -383,6 +412,7 @@ export async function createProject(opts, version, currentPath = false, localRep
|
|
|
383
412
|
"@blokjs/trigger-pubsub": "triggers/pubsub",
|
|
384
413
|
"@blokjs/trigger-queue": "triggers/queue",
|
|
385
414
|
};
|
|
415
|
+
const BLOKJS_DEP_RANGE = "^0.4.0";
|
|
386
416
|
for (const depGroup of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
387
417
|
const deps = packageJsonContent[depGroup];
|
|
388
418
|
if (!deps)
|
|
@@ -390,7 +420,7 @@ export async function createProject(opts, version, currentPath = false, localRep
|
|
|
390
420
|
for (const pkg of Object.keys(deps)) {
|
|
391
421
|
if (pkg.startsWith("@blok/")) {
|
|
392
422
|
const newPkg = pkg.replace("@blok/", "@blokjs/");
|
|
393
|
-
deps[newPkg] =
|
|
423
|
+
deps[newPkg] = BLOKJS_DEP_RANGE;
|
|
394
424
|
delete deps[pkg];
|
|
395
425
|
}
|
|
396
426
|
}
|
|
@@ -400,9 +430,14 @@ export async function createProject(opts, version, currentPath = false, localRep
|
|
|
400
430
|
deps[pkg] = `file:${path.resolve(repoSource, workspacePackageMap[pkg])}`;
|
|
401
431
|
}
|
|
402
432
|
else {
|
|
403
|
-
deps[pkg] =
|
|
433
|
+
deps[pkg] = BLOKJS_DEP_RANGE;
|
|
404
434
|
}
|
|
405
435
|
}
|
|
436
|
+
else if (typeof ver === "string" &&
|
|
437
|
+
(ver === "^0.2.0" || ver === "^0.2" || ver === "0.2.0" || ver.startsWith("^0.2.") || ver.startsWith("0.2.")) &&
|
|
438
|
+
(pkg.startsWith("@blokjs/") || pkg === "blokctl")) {
|
|
439
|
+
deps[pkg] = BLOKJS_DEP_RANGE;
|
|
440
|
+
}
|
|
406
441
|
}
|
|
407
442
|
}
|
|
408
443
|
if (localRepoPath) {
|
|
@@ -539,6 +574,14 @@ export async function createProject(opts, version, currentPath = false, localRep
|
|
|
539
574
|
console.log(color.dim(" docker compose up -d redis redis-commander"));
|
|
540
575
|
console.log(" Redis Commander UI: http://localhost:8081");
|
|
541
576
|
}
|
|
577
|
+
if (selectedTriggers.includes("queue") && queueProvider === "nats") {
|
|
578
|
+
console.log(color.cyan("\n📦 NATS JetStream Setup (for Queue trigger):"));
|
|
579
|
+
console.log(" Start NATS with Docker:");
|
|
580
|
+
console.log(color.dim(" cd infra/development"));
|
|
581
|
+
console.log(color.dim(" docker network create shared-network"));
|
|
582
|
+
console.log(color.dim(" docker compose up -d nats"));
|
|
583
|
+
console.log(" NATS Monitoring: http://localhost:8222");
|
|
584
|
+
}
|
|
542
585
|
console.log("\nFor more documentation, visit https://blok.build/");
|
|
543
586
|
if (examples) {
|
|
544
587
|
console.log(examples_url);
|
|
@@ -907,6 +950,12 @@ function updateQueueProvider(triggerDestDir, provider) {
|
|
|
907
950
|
port: Number(process.env.REDIS_PORT) || 6379,
|
|
908
951
|
})`,
|
|
909
952
|
},
|
|
953
|
+
nats: {
|
|
954
|
+
importName: "NATSAdapter",
|
|
955
|
+
init: `new NATSAdapter({
|
|
956
|
+
servers: (process.env.NATS_SERVERS || "localhost:4222").split(","),
|
|
957
|
+
})`,
|
|
958
|
+
},
|
|
910
959
|
};
|
|
911
960
|
const config = adapterConfigs[provider];
|
|
912
961
|
if (!config)
|
|
@@ -914,6 +963,12 @@ function updateQueueProvider(triggerDestDir, provider) {
|
|
|
914
963
|
content = content.replace(/import \{ (\w+), (\w+) \} from ["']@blokjs\/trigger-queue["'];/, `import { ${config.importName}, QueueTrigger } from "@blokjs/trigger-queue";`);
|
|
915
964
|
content = content.replace(/(export default class \w+ extends QueueTrigger \{[\s\S]*?)\n\tprotected adapter = new \w+\(\{[\s\S]*?\}\);/, `$1\n\tprotected adapter = ${config.init};`);
|
|
916
965
|
fsExtra.writeFileSync(serverPath, content);
|
|
966
|
+
const workflowPath = `${triggerDestDir}/workflows/messages/on-message.ts`;
|
|
967
|
+
if (fsExtra.existsSync(workflowPath)) {
|
|
968
|
+
let workflowContent = fsExtra.readFileSync(workflowPath, "utf8");
|
|
969
|
+
workflowContent = workflowContent.replace(/provider: "kafka"/, `provider: "${provider}"`);
|
|
970
|
+
fsExtra.writeFileSync(workflowPath, workflowContent);
|
|
971
|
+
}
|
|
917
972
|
}
|
|
918
973
|
function getProviderDependencies(triggers, pubsubProvider, queueProvider) {
|
|
919
974
|
const deps = {};
|
|
@@ -927,6 +982,7 @@ function getProviderDependencies(triggers, pubsubProvider, queueProvider) {
|
|
|
927
982
|
rabbitmq: { amqplib: "^0.10.9" },
|
|
928
983
|
sqs: { "@aws-sdk/client-sqs": "^3.980.0" },
|
|
929
984
|
redis: { ioredis: "^5.9.2", bullmq: "^5.67.2" },
|
|
985
|
+
nats: { nats: "^2.28.0" },
|
|
930
986
|
};
|
|
931
987
|
if (triggers.includes("pubsub") && pubsubProviderDeps[pubsubProvider]) {
|
|
932
988
|
Object.assign(deps, pubsubProviderDeps[pubsubProvider]);
|
|
@@ -970,6 +1026,10 @@ SQS_QUEUE_URL=`,
|
|
|
970
1026
|
REDIS_HOST=localhost
|
|
971
1027
|
REDIS_PORT=6379
|
|
972
1028
|
REDIS_PASSWORD=`,
|
|
1029
|
+
nats: `
|
|
1030
|
+
# NATS JetStream
|
|
1031
|
+
NATS_SERVERS=localhost:4222
|
|
1032
|
+
NATS_STREAM_NAME=blok-queue`,
|
|
973
1033
|
};
|
|
974
1034
|
if (triggers.includes("pubsub") && pubsubEnvVars[pubsubProvider]) {
|
|
975
1035
|
lines.push(pubsubEnvVars[pubsubProvider]);
|
|
@@ -12,7 +12,7 @@ declare const package_dev_dependencies: {
|
|
|
12
12
|
};
|
|
13
13
|
declare const python3_file = "\nfrom core.blok import BlokService\nfrom core.types.context import Context\nfrom core.types.blok_response import BlokResponse\nfrom core.types.global_error import GlobalError\nfrom typing import Any, Dict\nimport traceback\n\nclass Node(BlokService):\n def __init__(self):\n BlokService.__init__(self)\n self.input_schema = {}\n self.output_schema = {}\n\n async def handle(self, ctx: Context, inputs: Dict[str, Any]) -> BlokResponse:\n response = BlokResponse()\n\n try:\n response.setSuccess({ \"message\": \"Hello World from Python3!\" })\n except Exception as error:\n err = GlobalError(error)\n err.setCode(500)\n err.setName(self.name)\n\n stack_trace = traceback.format_exc()\n err.setStack(stack_trace)\n response.success = False\n response.setError(err)\n\n return response\n";
|
|
14
14
|
declare const examples_url = "\nExamples:\n1- Open \"workflow-docs.json\" in your browser at http://localhost:4000/workflow-docs\n2- Open \"db-manager.json\" in your browser at http://localhost:4000/db-manager\n3- Open \"dashboard-gen.json\" in your browser at http://localhost:4000/dashboard-gen\n4- Open \"countries.json\" in your browser at http://localhost:4000/countries\n\nFor more documentation, visit src/nodes/examples/README.md. The first three examples require a PostgreSQL database to function.\n";
|
|
15
|
-
declare const workflow_template = "\n{\n\t\"name\": \"\",\n\t\"description\": \"\",\n\t\"version\": \"1.0.0\",\n\t\"trigger\": {\n\t\t\"http\": {\n\t\t\t\"method\": \"GET\",\n\t\t\t\"
|
|
15
|
+
declare const workflow_template = "\n{\n\t\"name\": \"My Workflow\",\n\t\"description\": \"What this workflow does\",\n\t\"version\": \"1.0.0\",\n\t\"trigger\": {\n\t\t\"http\": {\n\t\t\t\"method\": \"GET\",\n\t\t\t\"accept\": \"application/json\"\n\t\t}\n\t},\n\t\"steps\": [\n\t\t{\n\t\t\t\"id\": \"echo\",\n\t\t\t\"use\": \"@blokjs/respond\",\n\t\t\t\"inputs\": {\n\t\t\t\t\"body\": \"$.req.body\"\n\t\t\t}\n\t\t}\n\t]\n}\n";
|
|
16
16
|
declare const supervisord_nodejs = "\n[supervisord]\nnodaemon=true\n\n[program:nodejs_app]\ncommand=npm start\ndirectory=/app\nautostart=true\nautorestart=true\nstderr_logfile=/var/log/nodejs.err.log\nstdout_logfile=/var/log/nodejs.out.log\n";
|
|
17
17
|
declare const supervisord_python = "\n[program:python_app]\ncommand=python3 /app/.blok/runtimes/python3/server.py\ndirectory=/app\nautostart=true\nautorestart=true\nstderr_logfile=/var/log/python.err.log\nstdout_logfile=/var/log/python.out.log\n";
|
|
18
18
|
declare const go_node_file = "package main\n\nimport (\n\t\"github.com/blok/sdk\"\n)\n\ntype HelloWorldNode struct{}\n\nfunc (n *HelloWorldNode) Execute(ctx *sdk.Context, config map[string]interface{}) (*sdk.ExecutionResult, error) {\n\t// Access request body\n\tname := \"World\"\n\tif body, ok := ctx.Request.Body.(map[string]interface{}); ok {\n\t\tif nameVal, ok := body[\"name\"].(string); ok {\n\t\t\tname = nameVal\n\t\t}\n\t}\n\n\t// Access configuration\n\tprefix := \"Hello\"\n\tif prefixVal, ok := config[\"prefix\"].(string); ok {\n\t\tprefix = prefixVal\n\t}\n\n\t// Store result in context for downstream nodes\n\tctx.Vars[\"greeting\"] = prefix + \", \" + name + \"!\"\n\n\t// Return successful result\n\treturn &sdk.ExecutionResult{\n\t\tSuccess: true,\n\t\tData: map[string]interface{}{\n\t\t\t\"message\": prefix + \", \" + name + \"!\",\n\t\t\t\"timestamp\": sdk.GetCurrentTimestamp(),\n\t\t\t\"language\": \"Go\",\n\t\t},\n\t\tErrors: nil,\n\t}, nil\n}\n\nfunc main() {\n\t// Register node\n\tregistry := sdk.NewNodeRegistry()\n\tregistry.Register(\"{{NODE_NAME}}\", &HelloWorldNode{})\n\n\t// Start HTTP server\n\tserver := sdk.NewHTTPServer(registry, \":8080\")\n\tif err := server.Start(); err != nil {\n\t\tpanic(err)\n\t}\n}\n";
|
|
@@ -33,7 +33,7 @@ declare const php_dockerfile = "FROM php:8.2-cli-alpine AS builder\nWORKDIR /app
|
|
|
33
33
|
declare const ruby_node_file = "require_relative '../../lib/blok'\n\nmodule Blok\n module Nodes\n class {{NODE_NAME_PASCAL}}Node < Blok::NodeHandler\n def execute(ctx, config)\n # Access request body\n name = ctx.request.body.is_a?(Hash) ? ctx.request.body['name'] : nil\n name ||= 'World'\n\n # Access configuration\n prefix = config['prefix'] || 'Hello'\n\n message = \"#{prefix}, #{name}!\"\n\n # Store in context for downstream nodes\n ctx.vars['greeting'] = message\n\n # Return response\n {\n 'message' => message,\n 'timestamp' => Time.now.utc.iso8601,\n 'language' => 'Ruby'\n }\n end\n end\n end\nend\n";
|
|
34
34
|
declare const ruby_gemfile = "source 'https://rubygems.org'\n\nruby '>= 3.1'\n\ngem 'sinatra', '~> 4.0'\ngem 'puma', '~> 6.4'\ngem 'rackup', '~> 2.1'\n";
|
|
35
35
|
declare const ruby_dockerfile = "FROM ruby:3.2-alpine AS builder\nRUN apk add --no-cache build-base\nWORKDIR /app\nCOPY Gemfile Gemfile.lock ./\nRUN bundle install --without development test\n\nFROM ruby:3.2-alpine\nRUN apk --no-cache add ca-certificates wget\nWORKDIR /app\nCOPY --from=builder /usr/local/bundle /usr/local/bundle\nCOPY . .\n\nEXPOSE 8080\nENV PORT=8080\nENV RACK_ENV=production\nHEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\\n CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1\n\nCMD [\"bundle\", \"exec\", \"puma\", \"-b\", \"tcp://0.0.0.0:8080\"]\n";
|
|
36
|
-
declare const agents_md = "# Blok Project\n\nBlok is a TypeScript-first workflow orchestration framework. It executes declarative workflows (JSON or TypeScript DSL) composed of steps (nodes) that run across 8 language runtimes: NodeJS, Python3, Go, Rust, Java, C#, PHP, and Ruby.\n\n## Project Structure\n\n```\n\u251C\u2500\u2500 src/\n\u2502 \u2514\u2500\u2500 nodes/ # TypeScript node implementations\n\u251C\u2500\u2500 runtimes/ # Non-NodeJS runtime nodes (Go, Python3, etc.)\n\u2502 \u2514\u2500\u2500 {lang}/nodes/ # Language-specific node implementations\n\u251C\u2500\u2500 workflows/\n\u2502 \u251C\u2500\u2500 json/ # Workflow definitions (JSON)\n\u2502 \u251C\u2500\u2500 yaml/ # Workflow definitions (YAML)\n\u2502 \u2514\u2500\u2500 toml/ # Workflow definitions (TOML)\n\u251C\u2500\u2500 .blok/\n\u2502 \u251C\u2500\u2500 config.json # Runtime configuration (ports, start commands)\n\u2502 \u2514\u2500\u2500 runtimes/ # Auto-generated runtime scaffolds\n\u251C\u2500\u2500 .env.local # Environment variables (ports, paths)\n\u2514\u2500\u2500 supervisord.conf # Process management config\n```\n\n## Commands\n\n```bash\nnpm run dev # Start dev server (or blokctl dev for multi-runtime)\nnpm run build # Build project\nnpm test # Run tests\nblokctl create node <name> # Scaffold a new node\nblokctl create workflow <n># Scaffold a new workflow\nblokctl trace # Open Blok Studio (trace visualization)\nblokctl studio # Alias for blokctl trace\n```\n\n## Context \u2014 Critical Data Flow\n\nThe Context type is the central execution state passed through every step.\n\n```typescript\ntype Context = {\n id: string; // Unique request ID\n request: RequestContext; // Incoming request (body, headers, params, query)\n response: ResponseContext; // Current step output \u2014 OVERWRITTEN every step\n vars?: VarsContext; // Persistent variables \u2014 PERSISTS across workflow\n config: ConfigContext; // Node config (inputs resolved by Mapper)\n env?: EnvContext; // process.env access\n logger: LoggerContext;\n error: ErrorContext;\n};\n```\n\n### The Two Critical Rules\n\n**Rule 1: \\`ctx.response.data\\` is OVERWRITTEN after every step.**\nEach step's output replaces the previous \\`ctx.response.data\\`. If you need a step's output later, store it in \\`ctx.vars\\`.\n\n**Rule 2: \\`ctx.vars\\` PERSISTS across the entire workflow.**\nUse \\`set_var: true\\` on a step to auto-store its output in \\`ctx.vars[stepName]\\`. Downstream steps access it via \\`ctx.vars['step-name']\\`.\n\n### Data Flow Example\n\n```\nStep 1: \"fetch-user\" (set_var: true)\n \u2192 ctx.response.data = { id: \"123\", name: \"Alice\" }\n \u2192 ctx.vars[\"fetch-user\"] = { id: \"123\", name: \"Alice\" }\n\nStep 2: \"transform\"\n \u2192 ctx.response.data = { result: \"done\" } \u2190 Step 1 output GONE from response\n \u2192 ctx.vars[\"fetch-user\"] still available\n\nStep 3: \"output\"\n \u2192 Can read ctx.vars[\"fetch-user\"].name \u2190 still \"Alice\"\n```\n\n### Blueprint Mapper \u2014 Expression Resolution\n\nNode inputs support dynamic expressions resolved BEFORE node execution:\n\n```json\n{\n \"inputs\": {\n \"userId\": \"js/ctx.request.body.userId\",\n \"chain\": \"js/ctx.vars['previous-step'].chain\",\n \"previous\": \"js/ctx.response.data.result\"\n }\n}\n```\n\nAvailable in js/ expressions: \\`ctx\\`, \\`data\\` (ctx.response.data), \\`func\\` (ctx.func), \\`vars\\` (ctx.vars)\n\n---\n\n## Creating Nodes with defineNode\n\nUse \\`defineNode()\\` for all new nodes. Never use the legacy class-based pattern.\n\n```typescript\nimport { defineNode } from \"@blokjs/runner\";\nimport { z } from \"zod\";\n\nexport default defineNode({\n name: \"fetch-user\",\n description: \"Fetches user by ID\",\n\n input: z.object({\n userId: z.string().uuid(),\n }),\n\n output: z.object({\n user: z.object({\n id: z.string(),\n name: z.string(),\n email: z.string().email(),\n }),\n }),\n\n async execute(ctx, input) {\n const user = await fetchUser(input.userId);\n return { user };\n },\n});\n```\n\n### Key Behaviors\n\n- Zod input/output validation runs automatically\n- ZodError is mapped to GlobalError with HTTP 400\n- \\`flow: true\\` nodes return NodeBase[] for conditional execution\n- \\`contentType\\` sets response Content-Type (e.g., \"text/html\")\n- Always \\`export default defineNode(...)\\`\n\n---\n\n## Workflow Structure (JSON)\n\n```json\n{\n \"name\": \"My Workflow\",\n \"version\": \"1.0.0\",\n \"trigger\": {\n \"http\": { \"method\": \"POST\", \"path\": \"/api/process\", \"accept\": \"application/json\" }\n },\n \"steps\": [\n { \"name\": \"fetch\", \"node\": \"@blokjs/api-call\", \"type\": \"module\" },\n { \"name\": \"process\", \"node\": \"my-node\", \"type\": \"module\", \"set_var\": true },\n { \"name\": \"go-step\", \"node\": \"chain-test\", \"type\": \"runtime.go\" }\n ],\n \"nodes\": {\n \"fetch\": { \"inputs\": { \"url\": \"https://api.example.com\", \"method\": \"GET\" } },\n \"process\": { \"inputs\": { \"data\": \"js/ctx.response.data\" } },\n \"go-step\": { \"inputs\": { \"processed\": \"js/ctx.vars['process']\" } }\n }\n}\n```\n\n### Step Types\n\n| Type | Description |\n|------|-------------|\n| \\`module\\` | TypeScript node from registered modules |\n| \\`local\\` | TypeScript node from filesystem (NODES_PATH) |\n| \\`runtime.python3\\` | Python3 SDK container (port 9007) |\n| \\`runtime.go\\` | Go SDK container (port 9001) |\n| \\`runtime.rust\\` | Rust SDK container (port 9002) |\n| \\`runtime.java\\` | Java SDK container (port 9003) |\n| \\`runtime.csharp\\` | C# SDK container (port 9004) |\n| \\`runtime.php\\` | PHP SDK container (port 9005) |\n| \\`runtime.ruby\\` | Ruby SDK container (port 9006) |\n\n### Conditional Workflow (if-else)\n\n```json\n{\n \"nodes\": {\n \"filter\": {\n \"conditions\": [\n {\n \"type\": \"if\",\n \"condition\": \"ctx.request.query.active === \\\\\"true\\\\\"\",\n \"steps\": [{ \"name\": \"active-path\", \"node\": \"handle-active\", \"type\": \"module\" }]\n },\n {\n \"type\": \"else\",\n \"steps\": [{ \"name\": \"default-path\", \"node\": \"handle-default\", \"type\": \"module\" }]\n }\n ]\n }\n }\n}\n```\n\n---\n\n## Trigger Types\n\n| Trigger | Example Config |\n|---------|---------------|\n| \\`http\\` | \\`{ \"method\": \"GET\", \"path\": \"/\", \"accept\": \"application/json\" }\\` |\n| \\`grpc\\` | \\`{ \"service\": \"UserService\", \"method\": \"GetUser\" }\\` |\n| \\`cron\\` | \\`{ \"schedule\": \"0 * * * *\", \"timezone\": \"UTC\" }\\` |\n| \\`queue\\` | \\`{ \"provider\": \"kafka\", \"topic\": \"events\" }\\` |\n| \\`pubsub\\` | \\`{ \"provider\": \"gcp\", \"topic\": \"updates\" }\\` |\n| \\`webhook\\` | \\`{ \"source\": \"github\", \"events\": [\"push\"] }\\` |\n| \\`websocket\\` | \\`{ \"events\": [\"message\"], \"path\": \"/ws\" }\\` |\n| \\`sse\\` | \\`{ \"events\": [\"update\"], \"path\": \"/stream\" }\\` |\n| \\`worker\\` | \\`{ \"queue\": \"jobs\", \"concurrency\": 5 }\\` |\n\n---\n\n## Runtime Adapter System\n\nAll non-NodeJS SDKs communicate via HTTP:\n- **POST /execute** \u2014 Execute node with context\n- **GET /health** \u2014 Health check\n\nEnvironment variables: \\`RUNTIME_{LANG}_HOST\\` / \\`RUNTIME_{LANG}_PORT\\`\n\nRuntime nodes auto-save \\`result.data\\` to \\`ctx.vars[stepName]\\`.\n\n---\n\n## Blok Studio\n\nReal-time workflow trace visualization UI.\n\n- Launch: \\`blokctl trace\\` or \\`blokctl studio\\`\n- API: \\`/__blok/runs\\`, \\`/__blok/runs/:id\\`, \\`/__blok/runs/:id/stream\\` (SSE)\n- Disable: \\`BLOK_TRACE_ENABLED=false\\`\n\n---\n\n## Do NOT\n\n- Do NOT rely on \\`ctx.response.data\\` for data from non-previous steps \u2014 it gets overwritten\n- Do NOT create class-based nodes \u2014 use \\`defineNode()\\` instead\n- Do NOT use \\`any\\` type \u2014 use \\`unknown\\` and narrow with Zod\n- Do NOT hardcode runtime ports \u2014 use environment variables\n- Do NOT skip Zod input/output schemas\n- Do NOT edit files in \\`.blok/runtimes/\\` \u2014 they are auto-generated\n\n## Do\n\n- Use \\`ctx.vars\\` with \\`set_var: true\\` to pass data between non-adjacent steps\n- Use \\`js/ctx.vars['step-name'].field\\` in workflow inputs for data flow\n- Use Zod schemas for all input/output validation\n- Use \\`defineNode()\\` for all new nodes\n- Handle errors via GlobalError with appropriate HTTP status codes\n- Keep nodes focused \u2014 one responsibility per node\n";
|
|
37
|
-
declare const claude_md = "# Blok Project \u2014 Claude Code Guide\n\nRead \\`AGENTS.md\\` for full architecture and API details. This file contains Claude-specific guidance.\n\n## Quick Commands\n\n\\`\\`\\`bash\nnpm run dev # Start dev server\nblokctl dev # Multi-runtime dev server\nblokctl create node <name> # Scaffold new node\nblokctl create workflow <name> # Scaffold new workflow\nblokctl trace # Open Blok Studio\nnpm test # Run tests\n\\`\\`\\`\n\n## Context Rules (Memorize These)\n\n1. **\\`ctx.response.data\\` is OVERWRITTEN every step.** Previous output GONE unless stored in vars.\n2. **\\`ctx.vars\\` PERSISTS across the workflow.** Use \\`set_var: true\\` or \\`js/ctx.vars['step']\\`.\n3. **Blueprint Mapper resolves \\`js/\\` expressions BEFORE node execution.**\n\nWhen users have data flow issues, check these three things first.\n\n## Debugging Workflows\n\n1. **Verify structure**: Every \\`steps[].name\\` must match a key in \\`nodes\\`\n2. **Trace data flow**: Which steps have \\`set_var: true\\`? Do \\`js/\\` expressions reference correct step names?\n3. **Check runtimes**: SDK containers running? \\`GET http://localhost:{port}/health\\`\n4. **Check Studio traces**: \\`/__blok/runs/:id\\` shows step-by-step inputs/outputs/errors\n\n### Common Errors\n\n| Error | Fix |\n|-------|-----|\n| \\`Node type X not found\\` | Wrong \\`type\\` in step \u2014 use module, local, or runtime.* |\n| \\`Validation failed\\` | Zod schema mismatch \u2014 check input schema vs actual data |\n| \\`Runtime execution error\\` | SDK container not running \u2014 check health endpoint |\n| \\`ctx.vars['X'] undefined\\` | Source step missing \\`set_var: true\\` or name mismatch |\n\n## Generating Code\n\nAlways use \\`defineNode()\\`. Never class-based BlokService.\n\n\\`\\`\\`typescript\nimport { defineNode } from \"@blokjs/runner\";\nimport { z } from \"zod\";\n\nexport default defineNode({\n name: \"node-name\",\n description: \"What this node does\",\n input: z.object({ /* Zod schema */ }),\n output: z.object({ /* Zod schema */ }),\n async execute(ctx, input) {\n return { /* must match output schema */ };\n },\n});\n\\`\\`\\`\n\n### Checklist:\n- Zod input schema covers all inputs\n- Zod output schema matches execute() return\n- Node name matches workflow references\n- No \\`any\\` types \u2014 use \\`z.unknown()\\` if dynamic\n- \\`export default defineNode(...)\\`\n\n## Blok Studio Help\n\n- Launch: \\`blokctl trace\\` or navigate to \\`/__blok\\`\n- \"No output\" \u2192 Node not returning data or Zod output validation failed\n- \"Step error\" \u2192 Expand error \u2014 check if 400 (validation) or 500 (runtime)\n- \"Vars not passing\" \u2192 Source step needs \\`set_var: true\\`, target needs \\`js/ctx.vars['name']\\`\n\n## Do NOT\n\n- Do NOT suggest class-based BlokService for new nodes\n- Do NOT generate code with \\`any\\` types\n- Do NOT assume \\`ctx.response.data\\` persists across steps\n- Do NOT skip Zod schemas when creating nodes\n- Do NOT edit files in \\`.blok/runtimes/\\`\n";
|
|
36
|
+
declare const agents_md = "# Blok Project\n\nBlok is a TypeScript-first workflow orchestration framework. It executes declarative workflows (JSON or TypeScript DSL) composed of steps (nodes) that run across 8 language runtimes: NodeJS, Python3, Go, Rust, Java, C#, PHP, and Ruby.\n\n## Project Structure\n\n```\n\u251C\u2500\u2500 src/\n\u2502 \u2514\u2500\u2500 nodes/ # TypeScript node implementations\n\u251C\u2500\u2500 runtimes/ # Non-NodeJS runtime nodes (Go, Python3, etc.)\n\u2502 \u2514\u2500\u2500 {lang}/nodes/ # Language-specific node implementations\n\u251C\u2500\u2500 workflows/\n\u2502 \u251C\u2500\u2500 json/ # Workflow definitions (JSON)\n\u2502 \u251C\u2500\u2500 yaml/ # Workflow definitions (YAML)\n\u2502 \u2514\u2500\u2500 toml/ # Workflow definitions (TOML)\n\u251C\u2500\u2500 .blok/\n\u2502 \u251C\u2500\u2500 config.json # Runtime configuration (ports, start commands)\n\u2502 \u2514\u2500\u2500 runtimes/ # Auto-generated runtime scaffolds\n\u251C\u2500\u2500 .env.local # Environment variables (ports, paths)\n\u2514\u2500\u2500 supervisord.conf # Process management config\n```\n\n## Commands\n\n```bash\nnpm run dev # Start dev server (or blokctl dev for multi-runtime)\nnpm run build # Build project\nnpm test # Run tests\nblokctl create node <name> # Scaffold a new node\nblokctl create workflow <n># Scaffold a new workflow\nblokctl trace # Open Blok Studio (trace visualization)\nblokctl studio # Alias for blokctl trace\n```\n\n## Context \u2014 Critical Data Flow\n\nThe Context type is the central execution state passed through every step.\n\n```typescript\ntype Context = {\n id: string; // Unique request ID\n request: RequestContext; // Incoming request (body, headers, params, query)\n response: ResponseContext; // Current step output \u2014 OVERWRITTEN every step\n vars?: VarsContext; // Persistent variables \u2014 PERSISTS across workflow\n config: ConfigContext; // Node config (inputs resolved by Mapper)\n env?: EnvContext; // process.env access\n logger: LoggerContext;\n error: ErrorContext;\n};\n```\n\n### The Two Critical Rules\n\n**Rule 1: \\`ctx.response.data\\` is OVERWRITTEN after every step.**\nEach step's output replaces the previous \\`ctx.response.data\\`. If you need a step's output later, store it in \\`ctx.vars\\`.\n\n**Rule 2: \\`ctx.vars\\` PERSISTS across the entire workflow.**\nUse \\`set_var: true\\` on a step to auto-store its output in \\`ctx.vars[stepName]\\`. Downstream steps access it via \\`ctx.vars['step-name']\\`.\n\n### Data Flow Example\n\n```\nStep 1: \"fetch-user\" (set_var: true)\n \u2192 ctx.response.data = { id: \"123\", name: \"Alice\" }\n \u2192 ctx.vars[\"fetch-user\"] = { id: \"123\", name: \"Alice\" }\n\nStep 2: \"transform\"\n \u2192 ctx.response.data = { result: \"done\" } \u2190 Step 1 output GONE from response\n \u2192 ctx.vars[\"fetch-user\"] still available\n\nStep 3: \"output\"\n \u2192 Can read ctx.vars[\"fetch-user\"].name \u2190 still \"Alice\"\n```\n\n### Blueprint Mapper \u2014 Expression Resolution\n\nNode inputs support dynamic expressions resolved BEFORE node execution:\n\n```json\n{\n \"inputs\": {\n \"userId\": \"js/ctx.request.body.userId\",\n \"chain\": \"js/ctx.vars['previous-step'].chain\",\n \"previous\": \"js/ctx.response.data.result\"\n }\n}\n```\n\nAvailable in js/ expressions: \\`ctx\\`, \\`data\\` (ctx.response.data), \\`func\\` (ctx.func), \\`vars\\` (ctx.vars)\n\n---\n\n## Creating Nodes with defineNode\n\nUse \\`defineNode()\\` for all new nodes. Never use the legacy class-based pattern.\n\n```typescript\nimport { defineNode } from \"@blokjs/runner\";\nimport { z } from \"zod\";\n\nexport default defineNode({\n name: \"fetch-user\",\n description: \"Fetches user by ID\",\n\n input: z.object({\n userId: z.string().uuid(),\n }),\n\n output: z.object({\n user: z.object({\n id: z.string(),\n name: z.string(),\n email: z.string().email(),\n }),\n }),\n\n async execute(ctx, input) {\n const user = await fetchUser(input.userId);\n return { user };\n },\n});\n```\n\n### Key Behaviors\n\n- Zod input/output validation runs automatically\n- ZodError is mapped to GlobalError with HTTP 400\n- \\`flow: true\\` nodes return NodeBase[] for conditional execution\n- \\`contentType\\` sets response Content-Type (e.g., \"text/html\")\n- Always \\`export default defineNode(...)\\`\n\n---\n\n## Workflow Structure (JSON)\n\n```json\n{\n \"name\": \"My Workflow\",\n \"version\": \"1.0.0\",\n \"trigger\": {\n \"http\": { \"method\": \"POST\", \"path\": \"/api/process\", \"accept\": \"application/json\" }\n },\n \"steps\": [\n { \"name\": \"fetch\", \"node\": \"@blokjs/api-call\", \"type\": \"module\" },\n { \"name\": \"process\", \"node\": \"my-node\", \"type\": \"module\", \"set_var\": true },\n { \"name\": \"go-step\", \"node\": \"chain-test\", \"type\": \"runtime.go\" }\n ],\n \"nodes\": {\n \"fetch\": { \"inputs\": { \"url\": \"https://api.example.com\", \"method\": \"GET\" } },\n \"process\": { \"inputs\": { \"data\": \"js/ctx.response.data\" } },\n \"go-step\": { \"inputs\": { \"processed\": \"js/ctx.vars['process']\" } }\n }\n}\n```\n\n### Step Types\n\n| Type | Description |\n|------|-------------|\n| \\`module\\` | TypeScript node from registered modules |\n| \\`local\\` | TypeScript node from filesystem (NODES_PATH) |\n| \\`runtime.python3\\` | Python3 SDK container (port 9007) |\n| \\`runtime.go\\` | Go SDK container (port 9001) |\n| \\`runtime.rust\\` | Rust SDK container (port 9002) |\n| \\`runtime.java\\` | Java SDK container (port 9003) |\n| \\`runtime.csharp\\` | C# SDK container (port 9004) |\n| \\`runtime.php\\` | PHP SDK container (port 9005) |\n| \\`runtime.ruby\\` | Ruby SDK container (port 9006) |\n\n### Conditional Workflow (if-else)\n\n```json\n{\n \"nodes\": {\n \"filter\": {\n \"conditions\": [\n {\n \"type\": \"if\",\n \"condition\": \"ctx.request.query.active === \\\\\"true\\\\\"\",\n \"steps\": [{ \"name\": \"active-path\", \"node\": \"handle-active\", \"type\": \"module\" }]\n },\n {\n \"type\": \"else\",\n \"steps\": [{ \"name\": \"default-path\", \"node\": \"handle-default\", \"type\": \"module\" }]\n }\n ]\n }\n }\n}\n```\n\n---\n\n## Trigger Types\n\n| Trigger | Example Config |\n|---------|---------------|\n| \\`http\\` | \\`{ \"method\": \"GET\", \"path\": \"/\", \"accept\": \"application/json\" }\\` |\n| \\`grpc\\` | \\`{ \"service\": \"UserService\", \"method\": \"GetUser\" }\\` |\n| \\`cron\\` | \\`{ \"schedule\": \"0 * * * *\", \"timezone\": \"UTC\" }\\` |\n| \\`queue\\` | \\`{ \"provider\": \"kafka\", \"topic\": \"events\" }\\` |\n| \\`pubsub\\` | \\`{ \"provider\": \"gcp\", \"topic\": \"updates\" }\\` |\n| \\`webhook\\` | \\`{ \"source\": \"github\", \"events\": [\"push\"] }\\` |\n| \\`websocket\\` | \\`{ \"events\": [\"message\"], \"path\": \"/ws\" }\\` |\n| \\`sse\\` | \\`{ \"events\": [\"update\"], \"path\": \"/stream\" }\\` |\n| \\`worker\\` | \\`{ \"queue\": \"jobs\", \"concurrency\": 5, \"retries\": 3 }\\` |\n\n### Worker Trigger\n\nThe worker trigger processes background jobs from a queue with retry logic and concurrency control.\n\n\\`\\`\\`typescript\nWorkflow({ name: \"Process Job\", version: \"1.0.0\" })\n .addTrigger(\"worker\", { queue: \"background-jobs\" })\n .addStep({\n name: \"process\",\n node: \"my-processor\",\n type: \"module\",\n inputs: { payload: \"js/ctx.request.body\", jobId: \"js/ctx.request.params.jobId\" },\n });\n\\`\\`\\`\n\nJob context: \\`ctx.request.body\\` = payload, \\`ctx.request.params.queue\\` = queue name, \\`ctx.request.params.jobId\\` = job ID, \\`ctx.request.params.attempt\\` = attempt count, \\`ctx.vars._worker_job\\` = full metadata.\n\nAdapters: NATS JetStream (recommended), BullMQ (Redis), InMemory (dev only).\n\n### NATS JetStream\n\nRecommended queue/worker backend. Environment variables:\n\\`\\`\\`\nNATS_SERVERS=localhost:4222\nNATS_STREAM_NAME=blok-queue # or blok-worker for worker trigger\nNATS_TOKEN= # optional auth\n\\`\\`\\`\n\nQueue providers: \\`kafka\\`, \\`rabbitmq\\`, \\`sqs\\`, \\`redis\\`, \\`beanstalk\\`, \\`nats\\`\n\n### Standalone Workers (Go, Rust, Python)\n\nGo, Rust, and Python SDKs include standalone NATS workers that connect directly to NATS without the TypeScript runner:\n\n\\`\\`\\`\nWORKER_CONCURRENCY=1 # Max concurrent jobs\nWORKER_MAX_RETRIES=3 # Max delivery attempts\nWORKER_QUEUES=queue1,queue2 # Queues to consume\n\\`\\`\\`\n\n---\n\n## Testing Utilities\n\n\\`@blokjs/runner\\` provides testing utilities for nodes and workflows.\n\n### NodeTestHarness \u2014 Unit test a single node:\n\\`\\`\\`typescript\nimport { NodeTestHarness } from \"@blokjs/runner\";\nconst harness = new NodeTestHarness(myNode);\nconst result = await harness.execute({ input: \"data\" });\nharness.assertSuccess(result);\nharness.assertOutput(result, { expected: \"output\" });\n\\`\\`\\`\n\n### WorkflowTestRunner \u2014 Integration test a workflow:\n\\`\\`\\`typescript\nimport { WorkflowTestRunner } from \"@blokjs/runner\";\nconst runner = new WorkflowTestRunner({ verbose: true });\nrunner.registerNode(\"validate\", ValidateNode);\nrunner.mockNode(\"external-api\", async (input) => ({ result: \"mocked\" }));\nrunner.loadWorkflow(workflowDefinition);\nconst result = await runner.execute({ input: \"data\" });\n// result.success, result.output, result.trace, result.nodeResults\n\\`\\`\\`\n\n---\n\n## Runtime Adapter System\n\nAll non-NodeJS SDKs communicate via HTTP:\n- **POST /execute** \u2014 Execute node with context\n- **GET /health** \u2014 Health check\n\nEnvironment variables: \\`RUNTIME_{LANG}_HOST\\` / \\`RUNTIME_{LANG}_PORT\\`\n\nRuntime nodes auto-save \\`result.data\\` to \\`ctx.vars[stepName]\\`.\n\n---\n\n## Blok Studio\n\nReal-time workflow trace visualization UI.\n\n- Launch: \\`blokctl trace\\` or \\`blokctl studio\\`\n- API: \\`/__blok/runs\\`, \\`/__blok/runs/:id\\`, \\`/__blok/runs/:id/stream\\` (SSE)\n- Disable: \\`BLOK_TRACE_ENABLED=false\\`\n\n---\n\n## Do NOT\n\n- Do NOT rely on \\`ctx.response.data\\` for data from non-previous steps \u2014 it gets overwritten\n- Do NOT create class-based nodes \u2014 use \\`defineNode()\\` instead\n- Do NOT use \\`any\\` type \u2014 use \\`unknown\\` and narrow with Zod\n- Do NOT hardcode runtime ports \u2014 use environment variables\n- Do NOT skip Zod input/output schemas\n- Do NOT edit files in \\`.blok/runtimes/\\` \u2014 they are auto-generated\n\n## Do\n\n- Use \\`ctx.vars\\` with \\`set_var: true\\` to pass data between non-adjacent steps\n- Use \\`js/ctx.vars['step-name'].field\\` in workflow inputs for data flow\n- Use Zod schemas for all input/output validation\n- Use \\`defineNode()\\` for all new nodes\n- Handle errors via GlobalError with appropriate HTTP status codes\n- Keep nodes focused \u2014 one responsibility per node\n";
|
|
37
|
+
declare const claude_md = "# Blok Project \u2014 Claude Code Guide\n\nRead \\`AGENTS.md\\` for full architecture and API details. This file contains Claude-specific guidance.\n\n## Quick Commands\n\n\\`\\`\\`bash\nnpm run dev # Start dev server\nblokctl dev # Multi-runtime dev server\nblokctl create node <name> # Scaffold new node\nblokctl create workflow <name> # Scaffold new workflow\nblokctl trace # Open Blok Studio\nnpm test # Run tests\n\\`\\`\\`\n\n## Context Rules (Memorize These)\n\n1. **\\`ctx.response.data\\` is OVERWRITTEN every step.** Previous output GONE unless stored in vars.\n2. **\\`ctx.vars\\` PERSISTS across the workflow.** Use \\`set_var: true\\` or \\`js/ctx.vars['step']\\`.\n3. **Blueprint Mapper resolves \\`js/\\` expressions BEFORE node execution.**\n\nWhen users have data flow issues, check these three things first.\n\n## Debugging Workflows\n\n1. **Verify structure**: Every \\`steps[].name\\` must match a key in \\`nodes\\`\n2. **Trace data flow**: Which steps have \\`set_var: true\\`? Do \\`js/\\` expressions reference correct step names?\n3. **Check runtimes**: SDK containers running? \\`GET http://localhost:{port}/health\\`\n4. **Check Studio traces**: \\`/__blok/runs/:id\\` shows step-by-step inputs/outputs/errors\n\n### Common Errors\n\n| Error | Fix |\n|-------|-----|\n| \\`Node type X not found\\` | Wrong \\`type\\` in step \u2014 use module, local, or runtime.* |\n| \\`Validation failed\\` | Zod schema mismatch \u2014 check input schema vs actual data |\n| \\`Runtime execution error\\` | SDK container not running \u2014 check health endpoint |\n| \\`ctx.vars['X'] undefined\\` | Source step missing \\`set_var: true\\` or name mismatch |\n\n## Generating Code\n\nAlways use \\`defineNode()\\`. Never class-based BlokService.\n\n\\`\\`\\`typescript\nimport { defineNode } from \"@blokjs/runner\";\nimport { z } from \"zod\";\n\nexport default defineNode({\n name: \"node-name\",\n description: \"What this node does\",\n input: z.object({ /* Zod schema */ }),\n output: z.object({ /* Zod schema */ }),\n async execute(ctx, input) {\n return { /* must match output schema */ };\n },\n});\n\\`\\`\\`\n\n### Checklist:\n- Zod input schema covers all inputs\n- Zod output schema matches execute() return\n- Node name matches workflow references\n- No \\`any\\` types \u2014 use \\`z.unknown()\\` if dynamic\n- \\`export default defineNode(...)\\`\n\n## Worker Workflows\n\nWorker trigger processes background jobs from a queue:\n\n\\`\\`\\`typescript\nWorkflow({ name: \"Process Job\", version: \"1.0.0\" })\n .addTrigger(\"worker\", { queue: \"background-jobs\" })\n .addStep({ name: \"process\", node: \"my-processor\", type: \"module\",\n inputs: { payload: \"js/ctx.request.body\", jobId: \"js/ctx.request.params.jobId\" } });\n\\`\\`\\`\n\nJob data: \\`ctx.request.body\\` = payload, \\`ctx.request.params.queue/jobId/attempt\\` = metadata.\nAdapters: NATS JetStream (recommended), BullMQ (Redis), InMemory (dev).\n\n## Testing\n\n\\`\\`\\`typescript\nimport { NodeTestHarness, WorkflowTestRunner } from \"@blokjs/runner\";\n\n// Unit test a node\nconst harness = new NodeTestHarness(myNode);\nconst result = await harness.execute({ input: \"data\" });\nharness.assertSuccess(result);\n\n// Integration test a workflow\nconst runner = new WorkflowTestRunner({ mockAllNodes: true });\nrunner.loadWorkflow(definition);\nconst wfResult = await runner.execute({ input: \"data\" });\n\\`\\`\\`\n\n## Blok Studio Help\n\n- Launch: \\`blokctl trace\\` or navigate to \\`/__blok\\`\n- \"No output\" \u2192 Node not returning data or Zod output validation failed\n- \"Step error\" \u2192 Expand error \u2014 check if 400 (validation) or 500 (runtime)\n- \"Vars not passing\" \u2192 Source step needs \\`set_var: true\\`, target needs \\`js/ctx.vars['name']\\`\n\n## Debugging Workers\n\n- NATS not reachable \u2192 Check \\`NATS_SERVERS\\` env var, ensure NATS is running\n- Job timeout \u2192 Increase \\`timeout\\` in trigger config or optimize node\n- Max retries exceeded \u2192 Check node errors, job moves to DLQ\n\n## Do NOT\n\n- Do NOT suggest class-based BlokService for new nodes\n- Do NOT generate code with \\`any\\` types\n- Do NOT assume \\`ctx.response.data\\` persists across steps\n- Do NOT skip Zod schemas when creating nodes\n- Do NOT edit files in \\`.blok/runtimes/\\`\n";
|
|
38
38
|
declare const function_first_node_file = "import { defineNode } from \"@blokjs/runner\";\nimport { z } from \"zod\";\n\n/**\n * A function-first node that demonstrates the modern defineNode pattern.\n * This node is type-safe, validated, and requires 60% less boilerplate.\n */\nexport default defineNode({\n\tname: \"{{NODE_NAME}}\",\n\tdescription: \"A function-first node with Zod validation\",\n\n\t// Input schema using Zod - automatically validated\n\tinput: z.object({\n\t\tmessage: z.string().optional().default(\"Hello World\"),\n\t}),\n\n\t// Output schema using Zod - automatically validated\n\toutput: z.object({\n\t\tmessage: z.string(),\n\t\ttimestamp: z.string(),\n\t}),\n\n\t// Execute function - type-safe with inferred types from Zod schemas\n\tasync execute(ctx, input) {\n\t\t// Your business logic here\n\t\t// - ctx.vars: Access workflow variables\n\t\t// - ctx.request: Access HTTP request data\n\t\t// - ctx.logger: Log messages\n\t\t// - ctx.env: Access environment variables\n\n\t\t// Example: Store data for downstream nodes\n\t\tctx.vars[\"processed-message\"] = input.message;\n\n\t\t// Return type-safe output (validated automatically)\n\t\treturn {\n\t\t\tmessage: `Processed: ${input.message}`,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t};\n\t},\n});\n";
|
|
39
39
|
export { node_file, package_dependencies, package_dev_dependencies, python3_file, examples_url, workflow_template, supervisord_nodejs, supervisord_python, go_node_file, go_mod_file, go_dockerfile, java_node_file, java_pom_file, java_dockerfile, rust_node_file, rust_cargo_file, rust_dockerfile, csharp_node_file, csharp_csproj_file, csharp_dockerfile, php_node_file, php_composer_file, php_dockerfile, ruby_node_file, ruby_gemfile, ruby_dockerfile, function_first_node_file, agents_md, claude_md, };
|
|
@@ -72,30 +72,24 @@ For more documentation, visit src/nodes/examples/README.md. The first three exam
|
|
|
72
72
|
`;
|
|
73
73
|
const workflow_template = `
|
|
74
74
|
{
|
|
75
|
-
"name": "",
|
|
76
|
-
"description": "",
|
|
75
|
+
"name": "My Workflow",
|
|
76
|
+
"description": "What this workflow does",
|
|
77
77
|
"version": "1.0.0",
|
|
78
78
|
"trigger": {
|
|
79
79
|
"http": {
|
|
80
80
|
"method": "GET",
|
|
81
|
-
"path": "/",
|
|
82
81
|
"accept": "application/json"
|
|
83
82
|
}
|
|
84
83
|
},
|
|
85
84
|
"steps": [
|
|
86
85
|
{
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"type": "module"
|
|
90
|
-
}
|
|
91
|
-
],
|
|
92
|
-
"nodes": {
|
|
93
|
-
"name": {
|
|
86
|
+
"id": "echo",
|
|
87
|
+
"use": "@blokjs/respond",
|
|
94
88
|
"inputs": {
|
|
95
|
-
|
|
89
|
+
"body": "$.req.body"
|
|
96
90
|
}
|
|
97
91
|
}
|
|
98
|
-
|
|
92
|
+
]
|
|
99
93
|
}
|
|
100
94
|
`;
|
|
101
95
|
const supervisord_nodejs = `
|
|
@@ -815,7 +809,73 @@ export default defineNode({
|
|
|
815
809
|
| \\\`webhook\\\` | \\\`{ "source": "github", "events": ["push"] }\\\` |
|
|
816
810
|
| \\\`websocket\\\` | \\\`{ "events": ["message"], "path": "/ws" }\\\` |
|
|
817
811
|
| \\\`sse\\\` | \\\`{ "events": ["update"], "path": "/stream" }\\\` |
|
|
818
|
-
| \\\`worker\\\` | \\\`{ "queue": "jobs", "concurrency": 5 }\\\` |
|
|
812
|
+
| \\\`worker\\\` | \\\`{ "queue": "jobs", "concurrency": 5, "retries": 3 }\\\` |
|
|
813
|
+
|
|
814
|
+
### Worker Trigger
|
|
815
|
+
|
|
816
|
+
The worker trigger processes background jobs from a queue with retry logic and concurrency control.
|
|
817
|
+
|
|
818
|
+
\\\`\\\`\\\`typescript
|
|
819
|
+
Workflow({ name: "Process Job", version: "1.0.0" })
|
|
820
|
+
.addTrigger("worker", { queue: "background-jobs" })
|
|
821
|
+
.addStep({
|
|
822
|
+
name: "process",
|
|
823
|
+
node: "my-processor",
|
|
824
|
+
type: "module",
|
|
825
|
+
inputs: { payload: "js/ctx.request.body", jobId: "js/ctx.request.params.jobId" },
|
|
826
|
+
});
|
|
827
|
+
\\\`\\\`\\\`
|
|
828
|
+
|
|
829
|
+
Job context: \\\`ctx.request.body\\\` = payload, \\\`ctx.request.params.queue\\\` = queue name, \\\`ctx.request.params.jobId\\\` = job ID, \\\`ctx.request.params.attempt\\\` = attempt count, \\\`ctx.vars._worker_job\\\` = full metadata.
|
|
830
|
+
|
|
831
|
+
Adapters: NATS JetStream (recommended), BullMQ (Redis), InMemory (dev only).
|
|
832
|
+
|
|
833
|
+
### NATS JetStream
|
|
834
|
+
|
|
835
|
+
Recommended queue/worker backend. Environment variables:
|
|
836
|
+
\\\`\\\`\\\`
|
|
837
|
+
NATS_SERVERS=localhost:4222
|
|
838
|
+
NATS_STREAM_NAME=blok-queue # or blok-worker for worker trigger
|
|
839
|
+
NATS_TOKEN= # optional auth
|
|
840
|
+
\\\`\\\`\\\`
|
|
841
|
+
|
|
842
|
+
Queue providers: \\\`kafka\\\`, \\\`rabbitmq\\\`, \\\`sqs\\\`, \\\`redis\\\`, \\\`beanstalk\\\`, \\\`nats\\\`
|
|
843
|
+
|
|
844
|
+
### Standalone Workers (Go, Rust, Python)
|
|
845
|
+
|
|
846
|
+
Go, Rust, and Python SDKs include standalone NATS workers that connect directly to NATS without the TypeScript runner:
|
|
847
|
+
|
|
848
|
+
\\\`\\\`\\\`
|
|
849
|
+
WORKER_CONCURRENCY=1 # Max concurrent jobs
|
|
850
|
+
WORKER_MAX_RETRIES=3 # Max delivery attempts
|
|
851
|
+
WORKER_QUEUES=queue1,queue2 # Queues to consume
|
|
852
|
+
\\\`\\\`\\\`
|
|
853
|
+
|
|
854
|
+
---
|
|
855
|
+
|
|
856
|
+
## Testing Utilities
|
|
857
|
+
|
|
858
|
+
\\\`@blokjs/runner\\\` provides testing utilities for nodes and workflows.
|
|
859
|
+
|
|
860
|
+
### NodeTestHarness — Unit test a single node:
|
|
861
|
+
\\\`\\\`\\\`typescript
|
|
862
|
+
import { NodeTestHarness } from "@blokjs/runner";
|
|
863
|
+
const harness = new NodeTestHarness(myNode);
|
|
864
|
+
const result = await harness.execute({ input: "data" });
|
|
865
|
+
harness.assertSuccess(result);
|
|
866
|
+
harness.assertOutput(result, { expected: "output" });
|
|
867
|
+
\\\`\\\`\\\`
|
|
868
|
+
|
|
869
|
+
### WorkflowTestRunner — Integration test a workflow:
|
|
870
|
+
\\\`\\\`\\\`typescript
|
|
871
|
+
import { WorkflowTestRunner } from "@blokjs/runner";
|
|
872
|
+
const runner = new WorkflowTestRunner({ verbose: true });
|
|
873
|
+
runner.registerNode("validate", ValidateNode);
|
|
874
|
+
runner.mockNode("external-api", async (input) => ({ result: "mocked" }));
|
|
875
|
+
runner.loadWorkflow(workflowDefinition);
|
|
876
|
+
const result = await runner.execute({ input: "data" });
|
|
877
|
+
// result.success, result.output, result.trace, result.nodeResults
|
|
878
|
+
\\\`\\\`\\\`
|
|
819
879
|
|
|
820
880
|
---
|
|
821
881
|
|
|
@@ -924,6 +984,36 @@ export default defineNode({
|
|
|
924
984
|
- No \\\`any\\\` types — use \\\`z.unknown()\\\` if dynamic
|
|
925
985
|
- \\\`export default defineNode(...)\\\`
|
|
926
986
|
|
|
987
|
+
## Worker Workflows
|
|
988
|
+
|
|
989
|
+
Worker trigger processes background jobs from a queue:
|
|
990
|
+
|
|
991
|
+
\\\`\\\`\\\`typescript
|
|
992
|
+
Workflow({ name: "Process Job", version: "1.0.0" })
|
|
993
|
+
.addTrigger("worker", { queue: "background-jobs" })
|
|
994
|
+
.addStep({ name: "process", node: "my-processor", type: "module",
|
|
995
|
+
inputs: { payload: "js/ctx.request.body", jobId: "js/ctx.request.params.jobId" } });
|
|
996
|
+
\\\`\\\`\\\`
|
|
997
|
+
|
|
998
|
+
Job data: \\\`ctx.request.body\\\` = payload, \\\`ctx.request.params.queue/jobId/attempt\\\` = metadata.
|
|
999
|
+
Adapters: NATS JetStream (recommended), BullMQ (Redis), InMemory (dev).
|
|
1000
|
+
|
|
1001
|
+
## Testing
|
|
1002
|
+
|
|
1003
|
+
\\\`\\\`\\\`typescript
|
|
1004
|
+
import { NodeTestHarness, WorkflowTestRunner } from "@blokjs/runner";
|
|
1005
|
+
|
|
1006
|
+
// Unit test a node
|
|
1007
|
+
const harness = new NodeTestHarness(myNode);
|
|
1008
|
+
const result = await harness.execute({ input: "data" });
|
|
1009
|
+
harness.assertSuccess(result);
|
|
1010
|
+
|
|
1011
|
+
// Integration test a workflow
|
|
1012
|
+
const runner = new WorkflowTestRunner({ mockAllNodes: true });
|
|
1013
|
+
runner.loadWorkflow(definition);
|
|
1014
|
+
const wfResult = await runner.execute({ input: "data" });
|
|
1015
|
+
\\\`\\\`\\\`
|
|
1016
|
+
|
|
927
1017
|
## Blok Studio Help
|
|
928
1018
|
|
|
929
1019
|
- Launch: \\\`blokctl trace\\\` or navigate to \\\`/__blok\\\`
|
|
@@ -931,6 +1021,12 @@ export default defineNode({
|
|
|
931
1021
|
- "Step error" → Expand error — check if 400 (validation) or 500 (runtime)
|
|
932
1022
|
- "Vars not passing" → Source step needs \\\`set_var: true\\\`, target needs \\\`js/ctx.vars['name']\\\`
|
|
933
1023
|
|
|
1024
|
+
## Debugging Workers
|
|
1025
|
+
|
|
1026
|
+
- NATS not reachable → Check \\\`NATS_SERVERS\\\` env var, ensure NATS is running
|
|
1027
|
+
- Job timeout → Increase \\\`timeout\\\` in trigger config or optimize node
|
|
1028
|
+
- Max retries exceeded → Check node errors, job moves to DLQ
|
|
1029
|
+
|
|
934
1030
|
## Do NOT
|
|
935
1031
|
|
|
936
1032
|
- Do NOT suggest class-based BlokService for new nodes
|
|
@@ -2,6 +2,8 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import http from "node:http";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fsExtra from "fs-extra";
|
|
5
|
+
import { waitForGrpcPort } from "../../services/health-probe.js";
|
|
6
|
+
import { detectRr } from "../../services/runtime-detector.js";
|
|
5
7
|
import { readProjectConfig } from "../../services/runtime-setup.js";
|
|
6
8
|
const runningProcesses = [];
|
|
7
9
|
function spawnProcess(cmd, args, name, currentPath, cwd, env) {
|
|
@@ -38,7 +40,7 @@ function killAllGroups(signal) {
|
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
|
-
function
|
|
43
|
+
function waitForHttpHealth(port, timeoutMs, proc) {
|
|
42
44
|
return new Promise((resolve) => {
|
|
43
45
|
if (proc && proc.exitCode !== null) {
|
|
44
46
|
resolve(false);
|
|
@@ -74,13 +76,25 @@ function waitForHealth(port, timeoutMs, proc) {
|
|
|
74
76
|
}
|
|
75
77
|
export async function devProject(opts) {
|
|
76
78
|
const currentPath = process.cwd();
|
|
77
|
-
|
|
79
|
+
const useHttpFallback = opts.withHttpFallback === true;
|
|
80
|
+
const transport = useHttpFallback ? "http" : "grpc";
|
|
81
|
+
console.log(`Starting the development server (transport=${transport})...`);
|
|
78
82
|
console.log("Current path: ", currentPath);
|
|
83
|
+
if (useHttpFallback) {
|
|
84
|
+
console.log(" ⚠ --with-http-fallback is deprecated and will be removed in v0.4.0.");
|
|
85
|
+
}
|
|
79
86
|
const config = readProjectConfig(currentPath);
|
|
80
87
|
const runtimeDefs = [];
|
|
81
88
|
if (config?.runtimes) {
|
|
82
89
|
for (const [, rt] of Object.entries(config.runtimes)) {
|
|
83
|
-
|
|
90
|
+
let bootCmd = useHttpFallback ? rt.startCmd : (rt.grpcStartCmd ?? rt.startCmd);
|
|
91
|
+
if (rt.kind === "php" && !useHttpFallback && bootCmd.startsWith("rr ")) {
|
|
92
|
+
const rrBin = detectRr();
|
|
93
|
+
if (rrBin && rrBin !== "rr") {
|
|
94
|
+
bootCmd = `${rrBin}${bootCmd.slice(2)}`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const cmdParts = bootCmd.split(" ");
|
|
84
98
|
const cmd = cmdParts[0];
|
|
85
99
|
const args = cmdParts.slice(1);
|
|
86
100
|
const runtimeCwd = path.resolve(currentPath, rt.cwd);
|
|
@@ -88,16 +102,21 @@ export async function devProject(opts) {
|
|
|
88
102
|
console.log(` Warning: ${rt.label} runtime directory not found at ${rt.cwd}. Skipping.`);
|
|
89
103
|
continue;
|
|
90
104
|
}
|
|
105
|
+
const grpcPort = rt.grpcPort ?? rt.port + 1000;
|
|
106
|
+
const probePort = useHttpFallback ? rt.port : grpcPort;
|
|
107
|
+
const env = {
|
|
108
|
+
PORT: String(rt.port),
|
|
109
|
+
GRPC_PORT: String(grpcPort),
|
|
110
|
+
HOST: "0.0.0.0",
|
|
111
|
+
BLOK_TRANSPORT: transport,
|
|
112
|
+
};
|
|
91
113
|
runtimeDefs.push({
|
|
92
114
|
cmd,
|
|
93
115
|
args,
|
|
94
|
-
name: `${rt.label} Runtime (port ${
|
|
116
|
+
name: `${rt.label} Runtime (${transport} port ${probePort})`,
|
|
95
117
|
cwd: runtimeCwd,
|
|
96
|
-
env
|
|
97
|
-
|
|
98
|
-
HOST: "0.0.0.0",
|
|
99
|
-
},
|
|
100
|
-
port: rt.port,
|
|
118
|
+
env,
|
|
119
|
+
port: probePort,
|
|
101
120
|
});
|
|
102
121
|
}
|
|
103
122
|
}
|
|
@@ -132,15 +151,21 @@ export async function devProject(opts) {
|
|
|
132
151
|
}
|
|
133
152
|
}
|
|
134
153
|
if (config?.runtimes && Object.keys(config.runtimes).length > 0) {
|
|
135
|
-
console.log("\nRuntime
|
|
154
|
+
console.log("\nRuntime listeners:");
|
|
136
155
|
for (const [, rt] of Object.entries(config.runtimes)) {
|
|
137
|
-
|
|
156
|
+
if (useHttpFallback) {
|
|
157
|
+
console.log(` ${rt.label}: http://localhost:${rt.port}/health`);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
const grpcPort = rt.grpcPort ?? rt.port + 1000;
|
|
161
|
+
console.log(` ${rt.label}: gRPC 127.0.0.1:${grpcPort}`);
|
|
162
|
+
}
|
|
138
163
|
}
|
|
139
164
|
}
|
|
140
165
|
if (healthChecks.length > 0) {
|
|
141
166
|
console.log("\nWaiting for runtimes to be ready...");
|
|
142
167
|
const maxWait = 120_000;
|
|
143
|
-
const results = await Promise.all(healthChecks.map((hc) =>
|
|
168
|
+
const results = await Promise.all(healthChecks.map((hc) => useHttpFallback ? waitForHttpHealth(hc.port, maxWait, hc.proc) : waitForGrpcPort(hc.port, maxWait, hc.proc)));
|
|
144
169
|
const allReady = results.every(Boolean);
|
|
145
170
|
if (allReady) {
|
|
146
171
|
console.log("All runtimes ready.\n");
|
|
@@ -151,6 +176,17 @@ export async function devProject(opts) {
|
|
|
151
176
|
console.log("Starting NodeJS runner anyway.\n");
|
|
152
177
|
}
|
|
153
178
|
}
|
|
179
|
+
const traceEnv = {};
|
|
180
|
+
if (!process.env.BLOK_TRACE_STORE) {
|
|
181
|
+
traceEnv.BLOK_TRACE_STORE = "sqlite";
|
|
182
|
+
}
|
|
183
|
+
if (!process.env.BLOK_TRACE_SQLITE_PATH) {
|
|
184
|
+
traceEnv.BLOK_TRACE_SQLITE_PATH = path.join(".blok", "trace.db");
|
|
185
|
+
}
|
|
186
|
+
const triggerEnv = {
|
|
187
|
+
...traceEnv,
|
|
188
|
+
BLOK_TRANSPORT: transport,
|
|
189
|
+
};
|
|
154
190
|
if (config?.triggers && Object.keys(config.triggers).length > 0) {
|
|
155
191
|
console.log("Starting triggers...");
|
|
156
192
|
for (const [, trigger] of Object.entries(config.triggers)) {
|
|
@@ -162,11 +198,12 @@ export async function devProject(opts) {
|
|
|
162
198
|
}
|
|
163
199
|
spawnProcess(cmd, args, `${trigger.label} (port ${trigger.port})`, currentPath, undefined, {
|
|
164
200
|
PORT: String(trigger.port),
|
|
201
|
+
...triggerEnv,
|
|
165
202
|
});
|
|
166
203
|
}
|
|
167
204
|
}
|
|
168
205
|
else {
|
|
169
|
-
spawnProcess("bun", ["--watch", "run", "src/index.ts"], "Blok Runner", currentPath);
|
|
206
|
+
spawnProcess("bun", ["--watch", "run", "src/index.ts"], "Blok Runner", currentPath, undefined, triggerEnv);
|
|
170
207
|
}
|
|
171
208
|
const keepAlive = setInterval(() => { }, 60_000);
|
|
172
209
|
let stopping = false;
|
|
@@ -12,7 +12,7 @@ const VALID_TRIGGER_TYPES = [
|
|
|
12
12
|
];
|
|
13
13
|
const VALID_STEP_TYPES = ["module", "local", "runtime.python3", "runtime.go", "runtime.java"];
|
|
14
14
|
const VALID_HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "ANY", "*"];
|
|
15
|
-
const VALID_QUEUE_PROVIDERS = ["kafka", "rabbitmq", "sqs", "redis", "beanstalk"];
|
|
15
|
+
const VALID_QUEUE_PROVIDERS = ["kafka", "rabbitmq", "sqs", "redis", "beanstalk", "nats"];
|
|
16
16
|
const VALID_PUBSUB_PROVIDERS = ["gcp", "aws", "azure"];
|
|
17
17
|
const VALID_WEBHOOK_SOURCES = ["github", "stripe", "shopify", "custom"];
|
|
18
18
|
export function validateWorkflow(jsonString) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { program } from "../../services/commander.js";
|
|
3
3
|
import { migrateNode } from "./node.js";
|
|
4
|
+
import { migratePaths } from "./paths.js";
|
|
5
|
+
import { migrateWorkflows } from "./workflows.js";
|
|
4
6
|
const migrate = new Command("migrate").description("Migrate nodes and workflows to newer patterns");
|
|
5
7
|
const node = new Command("node")
|
|
6
8
|
.description("Migrate a class-based node to function-first pattern")
|
|
@@ -10,5 +12,25 @@ const node = new Command("node")
|
|
|
10
12
|
.action(async (options) => {
|
|
11
13
|
await migrateNode(options);
|
|
12
14
|
});
|
|
15
|
+
const workflows = new Command("workflows")
|
|
16
|
+
.description("Migrate v1 JSON workflows to the v2 shape (id+use+inputs, branch primitive, ANY method)")
|
|
17
|
+
.option("-d, --dir <value>", "Path to the JSON workflows directory (defaults to ./workflows/json or ./triggers/http/workflows/json)")
|
|
18
|
+
.option("--dry-run", "Print what would change without writing files")
|
|
19
|
+
.option("--backup", "Create .bak files next to each migrated workflow (default true)")
|
|
20
|
+
.option("--no-backup", "Skip backup creation")
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
await migrateWorkflows(options);
|
|
23
|
+
});
|
|
24
|
+
const paths = new Command("paths")
|
|
25
|
+
.description("Add explicit `trigger.http.path` to every JSON HTTP workflow (prep for v0.4 explicit-path-only routing)")
|
|
26
|
+
.option("-d, --dir <value>", "Path to the JSON workflows directory (defaults to ./workflows/json or ./triggers/http/workflows/json)")
|
|
27
|
+
.option("--dry-run", "Print what would change without writing files")
|
|
28
|
+
.option("--backup", "Create .bak files next to each migrated workflow (default true)")
|
|
29
|
+
.option("--no-backup", "Skip backup creation")
|
|
30
|
+
.action(async (options) => {
|
|
31
|
+
await migratePaths(options);
|
|
32
|
+
});
|
|
13
33
|
migrate.addCommand(node);
|
|
34
|
+
migrate.addCommand(workflows);
|
|
35
|
+
migrate.addCommand(paths);
|
|
14
36
|
program.addCommand(migrate);
|