blokctl 0.6.20 → 0.7.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.
Files changed (77) hide show
  1. package/dist/__tests__/modular-observability.capstone.e2e.test.js +72 -0
  2. package/dist/commands/create/node.js +46 -66
  3. package/dist/commands/create/project.js +55 -9
  4. package/dist/commands/create/utils/Examples.d.ts +8 -20
  5. package/dist/commands/create/utils/Examples.js +138 -412
  6. package/dist/commands/dev/index.js +40 -1
  7. package/dist/commands/gen/appTypes.js +40 -1
  8. package/dist/commands/generate/NodeGenerator.d.ts +0 -2
  9. package/dist/commands/generate/NodeGenerator.js +0 -20
  10. package/dist/commands/generate/RuntimeGenerator.d.ts +0 -2
  11. package/dist/commands/generate/RuntimeGenerator.js +0 -19
  12. package/dist/commands/generate/RuntimeGenerator.test.js +0 -29
  13. package/dist/commands/generate/TriggerGenerator.d.ts +0 -2
  14. package/dist/commands/generate/TriggerGenerator.js +0 -19
  15. package/dist/commands/generate/WorkflowGenerator.d.ts +0 -2
  16. package/dist/commands/generate/WorkflowGenerator.js +0 -19
  17. package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +0 -12
  18. package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.js +0 -12
  19. package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +0 -14
  20. package/dist/commands/monitor/monitor-component.js +5 -5
  21. package/dist/commands/observability/add.d.ts +2 -0
  22. package/dist/commands/observability/add.js +113 -0
  23. package/dist/commands/observability/alerting-module.test.js +43 -0
  24. package/dist/commands/observability/apply.d.ts +10 -0
  25. package/dist/commands/observability/apply.js +11 -0
  26. package/dist/commands/observability/descriptor.d.ts +37 -0
  27. package/dist/commands/observability/descriptor.js +203 -0
  28. package/dist/commands/observability/descriptor.test.d.ts +1 -0
  29. package/dist/commands/observability/descriptor.test.js +40 -0
  30. package/dist/commands/observability/index.d.ts +1 -0
  31. package/dist/commands/observability/index.js +53 -0
  32. package/dist/commands/observability/list.d.ts +2 -0
  33. package/dist/commands/observability/list.js +45 -0
  34. package/dist/commands/observability/logging-module.test.d.ts +1 -0
  35. package/dist/commands/observability/logging-module.test.js +43 -0
  36. package/dist/commands/observability/obs-stack-module.test.d.ts +1 -0
  37. package/dist/commands/observability/obs-stack-module.test.js +33 -0
  38. package/dist/commands/observability/remove.d.ts +2 -0
  39. package/dist/commands/observability/remove.js +62 -0
  40. package/dist/commands/observability/shared.d.ts +6 -0
  41. package/dist/commands/observability/shared.js +23 -0
  42. package/dist/commands/observability/status.d.ts +2 -0
  43. package/dist/commands/observability/status.js +36 -0
  44. package/dist/commands/observability/tracing-module.test.d.ts +1 -0
  45. package/dist/commands/observability/tracing-module.test.js +42 -0
  46. package/dist/commands/profile/index.js +7 -10
  47. package/dist/commands/watch/format.d.ts +23 -0
  48. package/dist/commands/watch/format.js +60 -0
  49. package/dist/commands/watch/index.d.ts +1 -0
  50. package/dist/commands/watch/index.js +53 -0
  51. package/dist/commands/watch/sse.d.ts +16 -0
  52. package/dist/commands/watch/sse.js +82 -0
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.js +4 -0
  55. package/dist/services/obs-setup.d.ts +5 -0
  56. package/dist/services/obs-setup.js +68 -0
  57. package/dist/services/obs-setup.test.d.ts +1 -0
  58. package/dist/services/obs-setup.test.js +71 -0
  59. package/dist/services/obs-tiers.d.ts +9 -0
  60. package/dist/services/obs-tiers.js +16 -0
  61. package/dist/services/observability-mutations.d.ts +4 -0
  62. package/dist/services/observability-mutations.js +46 -0
  63. package/dist/services/observability-mutations.test.d.ts +1 -0
  64. package/dist/services/observability-mutations.test.js +57 -0
  65. package/dist/services/runtime-setup.d.ts +12 -1
  66. package/dist/services/runtime-setup.js +274 -14
  67. package/dist/studio-dist/assets/{index-BD8_9YPN.js → index-CnFqCRQe.js} +17 -17
  68. package/dist/studio-dist/index.html +1 -1
  69. package/package.json +3 -3
  70. package/dist/commands/generate/GenerationAnalytics.d.ts +0 -61
  71. package/dist/commands/generate/GenerationAnalytics.js +0 -163
  72. package/dist/commands/generate/GenerationAnalytics.test.js +0 -407
  73. package/dist/commands/generate/PromptVersioning.d.ts +0 -25
  74. package/dist/commands/generate/PromptVersioning.js +0 -71
  75. package/dist/commands/generate/PromptVersioning.test.js +0 -120
  76. /package/dist/{commands/generate/GenerationAnalytics.test.d.ts → __tests__/modular-observability.capstone.e2e.test.d.ts} +0 -0
  77. /package/dist/commands/{generate/PromptVersioning.test.d.ts → observability/alerting-module.test.d.ts} +0 -0
@@ -32,36 +32,27 @@ const package_dev_dependencies = {
32
32
  "@types/ejs": "^3.1.5",
33
33
  "@types/pg": "^8.11.11",
34
34
  };
35
- const python3_file = `
36
- from core.blok import BlokService
37
- from core.types.context import Context
38
- from core.types.blok_response import BlokResponse
39
- from core.types.global_error import GlobalError
40
- from typing import Any, Dict
41
- import traceback
42
-
43
- class Node(BlokService):
44
- def __init__(self):
45
- BlokService.__init__(self)
46
- self.input_schema = {}
47
- self.output_schema = {}
48
-
49
- async def handle(self, ctx: Context, inputs: Dict[str, Any]) -> BlokResponse:
50
- response = BlokResponse()
51
-
52
- try:
53
- response.setSuccess({ "message": "Hello World from Python3!" })
54
- except Exception as error:
55
- err = GlobalError(error)
56
- err.setCode(500)
57
- err.setName(self.name)
58
-
59
- stack_trace = traceback.format_exc()
60
- err.setStack(stack_trace)
61
- response.success = False
62
- response.setError(err)
63
-
64
- return response
35
+ const python3_file = `from pydantic import BaseModel
36
+
37
+ from blok import node
38
+ from blok.types.context import Context
39
+
40
+
41
+ class Input(BaseModel):
42
+ """Validated inputs for the {{NODE_NAME}} node."""
43
+
44
+ name: str = "world"
45
+
46
+
47
+ class Output(BaseModel):
48
+ message: str
49
+
50
+
51
+ @node("{{NODE_NAME}}", "Describe what {{NODE_NAME}} does")
52
+ def run(ctx: Context, input: Input) -> Output:
53
+ # \`input\` is already validated against Input; the return is validated
54
+ # against Output and serialized for you.
55
+ return Output(message=f"Hello, {input.name}!")
65
56
  `;
66
57
  const examples_url = `
67
58
  Examples:
@@ -121,316 +112,151 @@ autorestart=true
121
112
  stderr_logfile=/var/log/python.err.log
122
113
  stdout_logfile=/var/log/python.out.log
123
114
  `;
124
- const go_node_file = `package main
115
+ const go_node_file = `// {{NODE_NAME}} — a Blok node for the Go runtime.
116
+ package {{NODE_PKG}}
125
117
 
126
118
  import (
127
- "github.com/blok/sdk"
119
+ blok "github.com/nickincloud/blok-go"
128
120
  )
129
121
 
130
- type HelloWorldNode struct{}
122
+ // {{NODE_NAME_PASCAL}}Node implements blok.NodeHandler.
123
+ type {{NODE_NAME_PASCAL}}Node struct{}
131
124
 
132
- func (n *HelloWorldNode) Execute(ctx *sdk.Context, config map[string]interface{}) (*sdk.ExecutionResult, error) {
133
- // Access request body
125
+ // Execute runs the node. Input arrives on ctx.Request; config holds the step's
126
+ // inputs from the workflow. Return any JSON-serialisable value (or an error).
127
+ func (n *{{NODE_NAME_PASCAL}}Node) Execute(ctx *blok.Context, config map[string]interface{}) (interface{}, error) {
134
128
  name := "World"
135
- if body, ok := ctx.Request.Body.(map[string]interface{}); ok {
136
- if nameVal, ok := body["name"].(string); ok {
137
- name = nameVal
129
+ if body := ctx.Request.BodyMap(); body != nil {
130
+ if v, ok := body["name"].(string); ok {
131
+ name = v
138
132
  }
139
133
  }
140
134
 
141
- // Access configuration
142
135
  prefix := "Hello"
143
- if prefixVal, ok := config["prefix"].(string); ok {
144
- prefix = prefixVal
136
+ if v, ok := config["prefix"].(string); ok {
137
+ prefix = v
145
138
  }
146
139
 
147
- // Store result in context for downstream nodes
148
- ctx.Vars["greeting"] = prefix + ", " + name + "!"
149
-
150
- // Return successful result
151
- return &sdk.ExecutionResult{
152
- Success: true,
153
- Data: map[string]interface{}{
154
- "message": prefix + ", " + name + "!",
155
- "timestamp": sdk.GetCurrentTimestamp(),
156
- "language": "Go",
157
- },
158
- Errors: nil,
140
+ return map[string]interface{}{
141
+ "message": prefix + ", " + name + "!",
142
+ "language": "Go",
159
143
  }, nil
160
144
  }
161
145
 
162
- func main() {
163
- // Register node
164
- registry := sdk.NewNodeRegistry()
165
- registry.Register("{{NODE_NAME}}", &HelloWorldNode{})
166
-
167
- // Start HTTP server
168
- server := sdk.NewHTTPServer(registry, ":8080")
169
- if err := server.Start(); err != nil {
170
- panic(err)
171
- }
146
+ // Register wires this node into the runtime registry. The generated
147
+ // register_user_nodes.go calls it for every node under runtimes/go/nodes.
148
+ func Register(registry *blok.NodeRegistry) {
149
+ registry.Register("{{NODE_NAME}}", &{{NODE_NAME_PASCAL}}Node{})
172
150
  }
173
151
  `;
174
- const go_mod_file = `module github.com/blok/nodes/{{NODE_NAME}}
152
+ const java_node_file = `package com.blok.blok.nodes;
175
153
 
176
- go 1.21
177
-
178
- require github.com/blok/sdk v1.0.0
179
- `;
180
- const go_dockerfile = `FROM golang:1.21-alpine AS builder
181
-
182
- WORKDIR /app
183
- COPY go.mod go.sum ./
184
- RUN go mod download
185
-
186
- COPY . .
187
- RUN CGO_ENABLED=0 GOOS=linux go build -o /node main.go
188
-
189
- FROM alpine:latest
190
- RUN apk --no-cache add ca-certificates
191
- WORKDIR /root/
192
- COPY --from=builder /node .
193
-
194
- EXPOSE 8080
195
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
196
- CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
197
-
198
- CMD ["./node"]
199
- `;
200
- const java_node_file = `package com.blok.nodes;
154
+ import com.blok.blok.node.NodeHandler;
155
+ import com.blok.blok.types.Context;
201
156
 
202
- import com.blok.runtime.Blok;
203
- import com.blok.runtime.NodeRegistry;
204
- import com.blok.server.RuntimeServer;
157
+ import java.time.Instant;
205
158
  import java.util.HashMap;
206
159
  import java.util.Map;
207
160
 
208
- public class HelloWorldNode implements Blok.NodeHandler {
161
+ /**
162
+ * {{NODE_NAME}} — a Blok node for the Java runtime.
163
+ *
164
+ * <p>Input arrives on {@code ctx.getRequest().bodyMap()} (a Map, or null when
165
+ * the body isn't an object); {@code config} holds the step's inputs from the
166
+ * workflow. Return any JSON-serialisable value (a Map here), or throw to fail
167
+ * the step. {@code blokctl dev} discovers this class under
168
+ * {@code runtimes/java/nodes/} and registers it into the Java runtime.
169
+ */
170
+ public class {{NODE_NAME_PASCAL}}Node implements NodeHandler {
171
+
209
172
  @Override
210
- public Blok.ExecutionResult execute(Blok.Context ctx, Map<String, Object> config) {
211
- try {
212
- // Access request body
213
- String name = "World";
214
- if (ctx.request.body != null && ctx.request.body.containsKey("name")) {
215
- name = (String) ctx.request.body.get("name");
216
- }
217
-
218
- // Access configuration
219
- String prefix = "Hello";
220
- if (config != null && config.containsKey("prefix")) {
221
- prefix = (String) config.get("prefix");
222
- }
223
-
224
- // Store result in context for downstream nodes
225
- String greeting = prefix + ", " + name + "!";
226
- ctx.vars.put("greeting", greeting);
227
-
228
- // Build response data
229
- Map<String, Object> data = new HashMap<>();
230
- data.put("message", greeting);
231
- data.put("timestamp", System.currentTimeMillis());
232
- data.put("language", "Java");
233
-
234
- // Return successful result
235
- return new Blok.ExecutionResult(true, data, null, null, null);
236
- } catch (Exception e) {
237
- return new Blok.ExecutionResult(false, null, e.getMessage(), null, null);
173
+ public Object execute(Context ctx, Map<String, Object> config) throws Exception {
174
+ String name = "World";
175
+ Map<String, Object> body = ctx.getRequest().bodyMap();
176
+ if (body != null && body.get("name") instanceof String s && !s.isEmpty()) {
177
+ name = s;
238
178
  }
239
- }
240
179
 
241
- public static void main(String[] args) {
242
- try {
243
- // Register node
244
- NodeRegistry registry = new NodeRegistry();
245
- registry.register("{{NODE_NAME}}", new HelloWorldNode());
246
-
247
- // Start HTTP server
248
- RuntimeServer server = new RuntimeServer(registry, 8080);
249
- server.start();
250
- } catch (Exception e) {
251
- e.printStackTrace();
252
- System.exit(1);
180
+ String prefix = "Hello";
181
+ if (config != null && config.get("prefix") instanceof String s && !s.isEmpty()) {
182
+ prefix = s;
253
183
  }
254
- }
255
- }
256
- `;
257
- const java_pom_file = `<?xml version="1.0" encoding="UTF-8"?>
258
- <project xmlns="http://maven.apache.org/POM/4.0.0"
259
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
260
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
261
- http://maven.apache.org/xsd/maven-4.0.0.xsd">
262
- <modelVersion>4.0.0</modelVersion>
263
-
264
- <groupId>com.blok</groupId>
265
- <artifactId>{{NODE_NAME}}</artifactId>
266
- <version>1.0.0</version>
267
-
268
- <properties>
269
- <maven.compiler.source>17</maven.compiler.source>
270
- <maven.compiler.target>17</maven.compiler.target>
271
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
272
- </properties>
273
-
274
- <dependencies>
275
- <dependency>
276
- <groupId>com.google.code.gson</groupId>
277
- <artifactId>gson</artifactId>
278
- <version>2.10.1</version>
279
- </dependency>
280
- </dependencies>
281
-
282
- <build>
283
- <plugins>
284
- <plugin>
285
- <groupId>org.apache.maven.plugins</groupId>
286
- <artifactId>maven-shade-plugin</artifactId>
287
- <version>3.5.0</version>
288
- <executions>
289
- <execution>
290
- <phase>package</phase>
291
- <goals>
292
- <goal>shade</goal>
293
- </goals>
294
- <configuration>
295
- <transformers>
296
- <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
297
- <mainClass>com.blok.nodes.HelloWorldNode</mainClass>
298
- </transformer>
299
- </transformers>
300
- </configuration>
301
- </execution>
302
- </executions>
303
- </plugin>
304
- </plugins>
305
- </build>
306
- </project>
307
- `;
308
- const java_dockerfile = `FROM maven:3.9-eclipse-temurin-17 AS builder
309
-
310
- WORKDIR /app
311
- COPY pom.xml .
312
- RUN mvn dependency:go-offline
313
-
314
- COPY src ./src
315
- RUN mvn clean package -DskipTests
316
-
317
- FROM eclipse-temurin:17-jre-alpine
318
184
 
319
- WORKDIR /root/
320
- COPY --from=builder /app/target/{{NODE_NAME}}-1.0.0.jar ./app.jar
185
+ String message = prefix + ", " + name + "!";
321
186
 
322
- EXPOSE 8080
323
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
324
- CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
187
+ // Publish a value for downstream nodes via context vars.
188
+ ctx.setVar("greeting", message);
325
189
 
326
- CMD ["java", "-jar", "app.jar"]
190
+ Map<String, Object> result = new HashMap<>();
191
+ result.put("message", message);
192
+ result.put("timestamp", Instant.now().toString());
193
+ result.put("language", "Java");
194
+ return result;
195
+ }
196
+ }
327
197
  `;
328
198
  const rust_node_file = `use async_trait::async_trait;
329
- use blok::{NodeHandler, NodeRegistry, Context};
199
+ use blok::registry::NodeRegistry;
200
+ use blok::{Context, NodeHandler};
330
201
  use std::collections::HashMap;
331
202
 
332
- /// {{NODE_NAME}} - A Blok node implemented in Rust
333
- struct {{NODE_NAME_PASCAL}};
203
+ /// {{NODE_NAME_PASCAL}}Node implements blok::NodeHandler.
204
+ struct {{NODE_NAME_PASCAL}}Node;
334
205
 
335
206
  #[async_trait]
336
- impl NodeHandler for {{NODE_NAME_PASCAL}} {
207
+ impl NodeHandler for {{NODE_NAME_PASCAL}}Node {
208
+ /// Execute runs the node. Input arrives on ctx.request; config holds the
209
+ /// step's inputs from the workflow. Return any JSON value (or an error).
337
210
  async fn execute(
338
211
  &self,
339
212
  ctx: &mut Context,
340
213
  config: &HashMap<String, serde_json::Value>,
341
214
  ) -> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> {
342
- // Access request body
343
- let name = ctx.request.body
215
+ let name = ctx
216
+ .request
217
+ .body
344
218
  .get("name")
345
219
  .and_then(|v| v.as_str())
346
220
  .unwrap_or("World");
347
221
 
348
- // Access configuration
349
222
  let prefix = config
350
223
  .get("prefix")
351
224
  .and_then(|v| v.as_str())
352
225
  .unwrap_or("Hello");
353
226
 
354
- let message = format!("{}, {}!", prefix, name);
355
-
356
- // Store in context vars for downstream nodes
357
- ctx.vars.insert(
358
- "greeting".to_string(),
359
- serde_json::Value::String(message.clone()),
360
- );
361
-
362
- // Return response
363
227
  Ok(serde_json::json!({
364
- "message": message,
228
+ "message": format!("{}, {}!", prefix, name),
365
229
  "language": "Rust"
366
230
  }))
367
231
  }
368
232
  }
369
233
 
370
- #[tokio::main]
371
- async fn main() {
372
- // Initialize tracing
373
- tracing_subscriber::fmt::init();
374
-
375
- // Register nodes
376
- let mut registry = NodeRegistry::new("1.0.0");
377
- registry.register("{{NODE_NAME}}", {{NODE_NAME_PASCAL}});
378
-
379
- // Start HTTP server
380
- blok::server::serve(registry, 8080).await.unwrap();
234
+ /// register wires this node into the runtime registry. The generated
235
+ /// user_nodes/mod.rs calls it for every node under runtimes/rust/nodes.
236
+ pub fn register(registry: &mut NodeRegistry) {
237
+ registry.register("{{NODE_NAME}}", {{NODE_NAME_PASCAL}}Node);
381
238
  }
382
239
  `;
383
- const rust_cargo_file = `[package]
384
- name = "{{NODE_NAME}}"
385
- version = "1.0.0"
386
- edition = "2021"
387
-
388
- [[bin]]
389
- name = "{{NODE_NAME}}"
390
- path = "src/main.rs"
391
-
392
- [dependencies]
393
- blok = { path = "../../sdk" }
394
- tokio = { version = "1", features = ["full"] }
395
- serde = { version = "1", features = ["derive"] }
396
- serde_json = "1"
397
- async-trait = "0.1"
398
- tracing = "0.1"
399
- tracing-subscriber = "0.3"
400
- `;
401
- const rust_dockerfile = `FROM rust:1.77-alpine AS builder
402
-
403
- RUN apk add --no-cache musl-dev
404
-
405
- WORKDIR /app
406
- COPY Cargo.toml Cargo.lock ./
407
- RUN mkdir -p src && echo 'fn main() {}' > src/main.rs && \\
408
- cargo build --release 2>/dev/null || true && rm -rf src
409
-
410
- COPY . .
411
- RUN cargo build --release
412
-
413
- FROM alpine:latest
414
- RUN apk --no-cache add ca-certificates
415
- WORKDIR /root/
416
- COPY --from=builder /app/target/release/{{NODE_NAME}} .
417
-
418
- EXPOSE 8080
419
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
420
- CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
421
-
422
- CMD ["./{{NODE_NAME}}"]
423
- `;
424
240
  const csharp_node_file = `using System.Text.Json;
425
- using Blok.Runtime;
241
+ using Blok.Core.Node;
242
+ using Blok.Core.Types;
426
243
 
427
- namespace Blok.Runtime.Nodes;
244
+ namespace Blok.Core.Nodes;
428
245
 
246
+ /// <summary>
247
+ /// {{NODE_NAME_PASCAL}}Node — a Blok node for the C# runtime. Discovered under
248
+ /// runtimes/csharp/nodes/ and registered into the shared gRPC server alongside
249
+ /// the built-in nodes (same model as the other SDKs). Run \`blokctl dev\`; no
250
+ /// per-node csproj/Dockerfile — it isn't a standalone service.
251
+ /// </summary>
429
252
  public class {{NODE_NAME_PASCAL}}Node : INodeHandler
430
253
  {
254
+ /// <summary>
255
+ /// Execute the node. Input arrives on ctx.Request; config holds the step's
256
+ /// inputs from the workflow. Return any JSON-serialisable value.
257
+ /// </summary>
431
258
  public Task<JsonElement> ExecuteAsync(Context ctx, Dictionary<string, JsonElement> config)
432
259
  {
433
- // Access request body
434
260
  var name = "World";
435
261
  if (ctx.Request.Body.ValueKind == JsonValueKind.Object &&
436
262
  ctx.Request.Body.TryGetProperty("name", out var nameEl) &&
@@ -439,7 +265,6 @@ public class {{NODE_NAME_PASCAL}}Node : INodeHandler
439
265
  name = nameEl.GetString() ?? "World";
440
266
  }
441
267
 
442
- // Access configuration
443
268
  var prefix = "Hello";
444
269
  if (config.TryGetValue("prefix", out var prefixEl) &&
445
270
  prefixEl.ValueKind == JsonValueKind.String)
@@ -448,11 +273,8 @@ public class {{NODE_NAME_PASCAL}}Node : INodeHandler
448
273
  }
449
274
 
450
275
  var message = $"{prefix}, {name}!";
276
+ ctx.SetVar("greeting", message);
451
277
 
452
- // Store in context for downstream nodes
453
- ctx.Vars["greeting"] = JsonSerializer.SerializeToElement(message);
454
-
455
- // Return response
456
278
  var result = JsonSerializer.SerializeToElement(new
457
279
  {
458
280
  message,
@@ -464,154 +286,58 @@ public class {{NODE_NAME_PASCAL}}Node : INodeHandler
464
286
  }
465
287
  }
466
288
  `;
467
- const csharp_csproj_file = `<Project Sdk="Microsoft.NET.Sdk.Web">
468
- <PropertyGroup>
469
- <TargetFramework>net8.0</TargetFramework>
470
- <RootNamespace>Blok.Runtime</RootNamespace>
471
- <ImplicitUsings>enable</ImplicitUsings>
472
- <Nullable>enable</Nullable>
473
- </PropertyGroup>
474
- </Project>
475
- `;
476
- const csharp_dockerfile = `FROM mcr.microsoft.com/dotnet/sdk:8.0 AS builder
477
- WORKDIR /app
478
- COPY *.csproj .
479
- RUN dotnet restore
480
- COPY . .
481
- RUN dotnet publish -c Release -o /out
482
-
483
- FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
484
- WORKDIR /app
485
- COPY --from=builder /out .
486
-
487
- EXPOSE 8080
488
- ENV PORT=8080
489
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
490
- CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
491
-
492
- CMD ["dotnet", "BlokRuntime.dll"]
493
- `;
494
289
  const php_node_file = `<?php
495
290
 
496
- namespace Blok\\Nodes;
291
+ declare(strict_types=1);
497
292
 
498
- use Blok\\NodeHandler;
499
- use Blok\\Context;
293
+ namespace Blok\\Blok\\Nodes\\{{NODE_NAME_PASCAL}};
500
294
 
501
- class {{NODE_NAME_PASCAL}}Node implements NodeHandler
295
+ use Blok\\Blok\\Node\\NodeHandler;
296
+ use Blok\\Blok\\Types\\Context;
297
+
298
+ /**
299
+ * {{NODE_NAME}} — a Blok node for the PHP runtime.
300
+ *
301
+ * Input arrives on \$ctx->request; \$config holds the step's inputs from the
302
+ * workflow. Return any JSON-serialisable value (array, scalar, etc.).
303
+ */
304
+ final class {{NODE_NAME_PASCAL}}Node implements NodeHandler
502
305
  {
503
306
  public function execute(Context $ctx, array $config): mixed
504
307
  {
505
- // Access request body
506
- $name = $ctx->request->body['name'] ?? 'World';
507
-
508
- // Access configuration
509
- $prefix = $config['prefix'] ?? 'Hello';
308
+ $name = $ctx->request->bodyStr('name') ?? 'World';
309
+ $prefix = isset($config['prefix']) && is_string($config['prefix'])
310
+ ? $config['prefix']
311
+ : 'Hello';
510
312
 
511
- $message = "$prefix, $name!";
512
-
513
- // Store in context for downstream nodes
514
- $ctx->vars['greeting'] = $message;
515
-
516
- // Return response
517
313
  return [
518
- 'message' => $message,
519
- 'timestamp' => date('c'),
314
+ 'message' => sprintf('%s, %s!', $prefix, $name),
520
315
  'language' => 'PHP',
521
316
  ];
522
317
  }
523
318
  }
524
319
  `;
525
- const php_composer_file = `{
526
- "name": "blok/{{NODE_NAME}}",
527
- "type": "project",
528
- "require": {
529
- "php": ">=8.2",
530
- "react/http": "^1.9",
531
- "react/socket": "^1.15"
532
- },
533
- "autoload": {
534
- "psr-4": {
535
- "Blok\\\\": "src/"
536
- }
537
- }
538
- }
539
- `;
540
- const php_dockerfile = `FROM php:8.2-cli-alpine AS builder
541
- WORKDIR /app
542
- COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
543
- COPY composer.json .
544
- RUN composer install --no-dev --optimize-autoloader
545
- COPY . .
546
-
547
- FROM php:8.2-cli-alpine
548
- WORKDIR /app
549
- COPY --from=builder /app .
550
-
551
- EXPOSE 8080
552
- ENV PORT=8080
553
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
554
- CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
555
-
556
- CMD ["php", "index.php"]
557
- `;
558
- const ruby_node_file = `require_relative '../../lib/blok'
559
-
560
- module Blok
561
- module Nodes
562
- class {{NODE_NAME_PASCAL}}Node < Blok::NodeHandler
563
- def execute(ctx, config)
564
- # Access request body
565
- name = ctx.request.body.is_a?(Hash) ? ctx.request.body['name'] : nil
566
- name ||= 'World'
320
+ const ruby_node_file = `# frozen_string_literal: true
567
321
 
568
- # Access configuration
569
- prefix = config['prefix'] || 'Hello'
322
+ # {{NODE_NAME_PASCAL}}Node — registered as "{{NODE_NAME}}" by the Ruby runtime.
323
+ #
324
+ # Request body arrives on ctx.request (use body_str(key) for a String|nil);
325
+ # config holds the step's inputs from the workflow. Return any
326
+ # JSON-serialisable Hash. Publish to downstream steps with ctx.set_var(k, v).
327
+ class {{NODE_NAME_PASCAL}}Node < Blok::Node::NodeHandler
328
+ def execute(ctx, config)
329
+ name = ctx.request.body_str("name") || "World"
330
+ prefix = config["prefix"] || "Hello"
570
331
 
571
- message = "#{prefix}, #{name}!"
332
+ message = "#{prefix}, #{name}!"
333
+ ctx.set_var("greeting", message)
572
334
 
573
- # Store in context for downstream nodes
574
- ctx.vars['greeting'] = message
575
-
576
- # Return response
577
- {
578
- 'message' => message,
579
- 'timestamp' => Time.now.utc.iso8601,
580
- 'language' => 'Ruby'
581
- }
582
- end
583
- end
335
+ {
336
+ "message" => message,
337
+ "language" => "Ruby"
338
+ }
584
339
  end
585
- end
586
- `;
587
- const ruby_gemfile = `source 'https://rubygems.org'
588
-
589
- ruby '>= 3.1'
590
-
591
- gem 'sinatra', '~> 4.0'
592
- gem 'puma', '~> 6.4'
593
- gem 'rackup', '~> 2.1'
594
- `;
595
- const ruby_dockerfile = `FROM ruby:3.2-alpine AS builder
596
- RUN apk add --no-cache build-base
597
- WORKDIR /app
598
- COPY Gemfile Gemfile.lock ./
599
- RUN bundle install --without development test
600
-
601
- FROM ruby:3.2-alpine
602
- RUN apk --no-cache add ca-certificates wget
603
- WORKDIR /app
604
- COPY --from=builder /usr/local/bundle /usr/local/bundle
605
- COPY . .
606
-
607
- EXPOSE 8080
608
- ENV PORT=8080
609
- ENV RACK_ENV=production
610
- HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
611
- CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
612
-
613
- CMD ["bundle", "exec", "puma", "-b", "tcp://0.0.0.0:8080"]
614
- `;
340
+ end`;
615
341
  const agents_md = `
616
342
  # AGENTS.md — Blok Framework AI Context
617
343
 
@@ -1776,4 +1502,4 @@ export default defineNode({
1776
1502
  },
1777
1503
  });
1778
1504
  `;
1779
- 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, };
1505
+ export { node_file, package_dependencies, package_dev_dependencies, python3_file, examples_url, workflow_template, supervisord_nodejs, supervisord_python, go_node_file, java_node_file, rust_node_file, csharp_node_file, php_node_file, ruby_node_file, function_first_node_file, agents_md, claude_md, };