blokctl 0.6.21 → 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 (76) 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/generate/NodeGenerator.d.ts +0 -2
  8. package/dist/commands/generate/NodeGenerator.js +0 -20
  9. package/dist/commands/generate/RuntimeGenerator.d.ts +0 -2
  10. package/dist/commands/generate/RuntimeGenerator.js +0 -19
  11. package/dist/commands/generate/RuntimeGenerator.test.js +0 -29
  12. package/dist/commands/generate/TriggerGenerator.d.ts +0 -2
  13. package/dist/commands/generate/TriggerGenerator.js +0 -19
  14. package/dist/commands/generate/WorkflowGenerator.d.ts +0 -2
  15. package/dist/commands/generate/WorkflowGenerator.js +0 -19
  16. package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +0 -12
  17. package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.js +0 -12
  18. package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +0 -14
  19. package/dist/commands/monitor/monitor-component.js +5 -5
  20. package/dist/commands/observability/add.d.ts +2 -0
  21. package/dist/commands/observability/add.js +113 -0
  22. package/dist/commands/observability/alerting-module.test.js +43 -0
  23. package/dist/commands/observability/apply.d.ts +10 -0
  24. package/dist/commands/observability/apply.js +11 -0
  25. package/dist/commands/observability/descriptor.d.ts +37 -0
  26. package/dist/commands/observability/descriptor.js +203 -0
  27. package/dist/commands/observability/descriptor.test.d.ts +1 -0
  28. package/dist/commands/observability/descriptor.test.js +40 -0
  29. package/dist/commands/observability/index.d.ts +1 -0
  30. package/dist/commands/observability/index.js +53 -0
  31. package/dist/commands/observability/list.d.ts +2 -0
  32. package/dist/commands/observability/list.js +45 -0
  33. package/dist/commands/observability/logging-module.test.d.ts +1 -0
  34. package/dist/commands/observability/logging-module.test.js +43 -0
  35. package/dist/commands/observability/obs-stack-module.test.d.ts +1 -0
  36. package/dist/commands/observability/obs-stack-module.test.js +33 -0
  37. package/dist/commands/observability/remove.d.ts +2 -0
  38. package/dist/commands/observability/remove.js +62 -0
  39. package/dist/commands/observability/shared.d.ts +6 -0
  40. package/dist/commands/observability/shared.js +23 -0
  41. package/dist/commands/observability/status.d.ts +2 -0
  42. package/dist/commands/observability/status.js +36 -0
  43. package/dist/commands/observability/tracing-module.test.d.ts +1 -0
  44. package/dist/commands/observability/tracing-module.test.js +42 -0
  45. package/dist/commands/profile/index.js +7 -10
  46. package/dist/commands/watch/format.d.ts +23 -0
  47. package/dist/commands/watch/format.js +60 -0
  48. package/dist/commands/watch/index.d.ts +1 -0
  49. package/dist/commands/watch/index.js +53 -0
  50. package/dist/commands/watch/sse.d.ts +16 -0
  51. package/dist/commands/watch/sse.js +82 -0
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.js +4 -0
  54. package/dist/services/obs-setup.d.ts +5 -0
  55. package/dist/services/obs-setup.js +68 -0
  56. package/dist/services/obs-setup.test.d.ts +1 -0
  57. package/dist/services/obs-setup.test.js +71 -0
  58. package/dist/services/obs-tiers.d.ts +9 -0
  59. package/dist/services/obs-tiers.js +16 -0
  60. package/dist/services/observability-mutations.d.ts +4 -0
  61. package/dist/services/observability-mutations.js +46 -0
  62. package/dist/services/observability-mutations.test.d.ts +1 -0
  63. package/dist/services/observability-mutations.test.js +57 -0
  64. package/dist/services/runtime-setup.d.ts +12 -1
  65. package/dist/services/runtime-setup.js +274 -14
  66. package/dist/studio-dist/assets/{index-BD8_9YPN.js → index-CnFqCRQe.js} +17 -17
  67. package/dist/studio-dist/index.html +1 -1
  68. package/package.json +3 -3
  69. package/dist/commands/generate/GenerationAnalytics.d.ts +0 -61
  70. package/dist/commands/generate/GenerationAnalytics.js +0 -163
  71. package/dist/commands/generate/GenerationAnalytics.test.js +0 -407
  72. package/dist/commands/generate/PromptVersioning.d.ts +0 -25
  73. package/dist/commands/generate/PromptVersioning.js +0 -71
  74. package/dist/commands/generate/PromptVersioning.test.js +0 -120
  75. /package/dist/{commands/generate/GenerationAnalytics.test.d.ts → __tests__/modular-observability.capstone.e2e.test.d.ts} +0 -0
  76. /package/dist/commands/{generate/PromptVersioning.test.d.ts → observability/alerting-module.test.d.ts} +0 -0
@@ -0,0 +1,72 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { parse as parseYaml } from "yaml";
6
+ import { resolveObservabilitySelection } from "../commands/observability/apply.js";
7
+ import { setupObservabilityStack } from "../services/obs-setup.js";
8
+ import { rewriteObservabilityEnvBlock } from "../services/observability-mutations.js";
9
+ import { readProjectConfig, writeProjectConfig } from "../services/runtime-setup.js";
10
+ function findRepoRoot() {
11
+ let dir = import.meta.dirname;
12
+ for (let i = 0; i < 8; i++) {
13
+ if (fs.existsSync(path.join(dir, "infra", "metrics", "docker-compose.yml")))
14
+ return dir;
15
+ const up = path.dirname(dir);
16
+ if (up === dir)
17
+ break;
18
+ dir = up;
19
+ }
20
+ return null;
21
+ }
22
+ const REPO = findRepoRoot();
23
+ describe("capstone — modular observability footprint", () => {
24
+ let tmp;
25
+ let envLocal;
26
+ beforeEach(() => {
27
+ tmp = fs.mkdtempSync(path.join(os.tmpdir(), "blok-capstone-"));
28
+ envLocal = path.join(tmp, ".env.local");
29
+ });
30
+ afterEach(() => fs.rmSync(tmp, { recursive: true, force: true }));
31
+ function scaffold(modules, tier) {
32
+ const sel = resolveObservabilitySelection(modules.filter((m) => m !== "obs-stack"), { addedAt: "2026-01-01T00:00:00.000Z", version: "0.6.0", projectDir: tmp });
33
+ const obsConfig = Object.keys(sel.configMap).length > 0 ? sel.configMap : undefined;
34
+ writeProjectConfig(tmp, [], [], obsConfig);
35
+ fs.writeFileSync(envLocal, rewriteObservabilityEnvBlock("", sel.envBlocks));
36
+ if (REPO)
37
+ setupObservabilityStack(REPO, tmp, tier);
38
+ return { added: sel.added };
39
+ }
40
+ it("a metrics+tracing subset enables ONLY those — unselected modules leave no footprint", () => {
41
+ scaffold(["metrics", "tracing"], "none");
42
+ expect(Object.keys(readProjectConfig(tmp)?.observability ?? {}).sort()).toEqual(["metrics", "tracing"]);
43
+ const env = fs.readFileSync(envLocal, "utf8");
44
+ expect(env).toContain("BLOK_METRICS_DISABLED");
45
+ expect(env).toContain("OTEL_EXPORTER_OTLP_ENDPOINT");
46
+ expect(env).not.toContain("CONSOLE_LOG_ACTIVE");
47
+ expect(env).not.toContain("SENTRY_DSN");
48
+ expect(env).not.toContain("BLOK_TRACE_STORE");
49
+ expect(env).not.toContain("BLOK_ALERTING_ENABLED");
50
+ });
51
+ it("selecting alerting auto-pulls its metrics dependency", () => {
52
+ const { added } = scaffold(["alerting"], "none");
53
+ expect(added).toEqual(["metrics"]);
54
+ expect(Object.keys(readProjectConfig(tmp)?.observability ?? {}).sort()).toEqual(["alerting", "metrics"]);
55
+ });
56
+ it("BLOK_METRICS_DISABLED round-trips inert (commented) — metrics stay ON by default", () => {
57
+ scaffold(["metrics"], "none");
58
+ for (const line of fs.readFileSync(envLocal, "utf8").split("\n")) {
59
+ if (/BLOK_METRICS_DISABLED=/.test(line))
60
+ expect(line.trim().startsWith("#")).toBe(true);
61
+ }
62
+ });
63
+ it("obs-stack=none copies no infra/metrics", () => {
64
+ scaffold(["metrics"], "none");
65
+ expect(fs.existsSync(path.join(tmp, "infra", "metrics"))).toBe(false);
66
+ });
67
+ it.skipIf(!REPO)("obs-stack=lite copies exactly prometheus + grafana", () => {
68
+ scaffold(["metrics"], "lite");
69
+ const compose = parseYaml(fs.readFileSync(path.join(tmp, "infra", "metrics", "docker-compose.yml"), "utf8"));
70
+ expect(Object.keys(compose.services ?? {}).sort()).toEqual(["grafana", "prometheus"]);
71
+ });
72
+ });
@@ -8,7 +8,7 @@ import fsExtra from "fs-extra";
8
8
  import color from "picocolors";
9
9
  import { isNonInteractive, resolveOrThrow } from "../../services/non-interactive.js";
10
10
  import { manager as pm } from "../../services/package-manager.js";
11
- import { csharp_csproj_file, csharp_dockerfile, csharp_node_file, function_first_node_file, go_dockerfile, go_mod_file, go_node_file, java_dockerfile, java_node_file, java_pom_file, php_composer_file, php_dockerfile, php_node_file, python3_file, ruby_dockerfile, ruby_gemfile, ruby_node_file, rust_cargo_file, rust_dockerfile, rust_node_file, } from "./utils/Examples.js";
11
+ import { csharp_node_file, function_first_node_file, go_node_file, java_node_file, php_node_file, python3_file, ruby_node_file, rust_node_file, } from "./utils/Examples.js";
12
12
  const exec = util.promisify(child_process.exec);
13
13
  const HOME_DIR = `${os.homedir()}/.blok`;
14
14
  function toPascalCase(name) {
@@ -252,7 +252,8 @@ export async function createNode(opts, currentPath = false) {
252
252
  throw new Error("ops2");
253
253
  }
254
254
  fsExtra.ensureDirSync(dirPath);
255
- fsExtra.writeFileSync(`${dirPath}/node.py`, python3_file);
255
+ const pythonNodeContent = python3_file.replace(/\{\{NODE_NAME\}\}/g, nodeName);
256
+ fsExtra.writeFileSync(`${dirPath}/node.py`, pythonNodeContent);
256
257
  fsExtra.writeFileSync(`${dirPath}/__init__.py`, "");
257
258
  }
258
259
  if (node_runtime === "go") {
@@ -283,14 +284,13 @@ export async function createNode(opts, currentPath = false) {
283
284
  throw new Error("ops2");
284
285
  }
285
286
  fsExtra.ensureDirSync(dirPath);
286
- const goNodeContent = go_node_file.replace(/\{\{NODE_NAME\}\}/g, nodeName);
287
- const goModContent = go_mod_file.replace(/\{\{NODE_NAME\}\}/g, nodeName);
288
- const goDockerContent = go_dockerfile.replace(/\{\{NODE_NAME\}\}/g, nodeName);
289
- fsExtra.writeFileSync(`${dirPath}/main.go`, goNodeContent);
290
- fsExtra.writeFileSync(`${dirPath}/go.mod`, goModContent);
291
- fsExtra.writeFileSync(`${dirPath}/go.sum`, "");
292
- fsExtra.writeFileSync(`${dirPath}/Dockerfile`, goDockerContent);
293
- const readmeContent = `# ${nodeName}\n\nGo-based Blok node.\n\n## Build\n\n\`\`\`bash\ndocker build -t blok-${nodeName}:latest .\n\`\`\`\n\n## Run\n\n\`\`\`bash\ndocker run -p 8080:8080 blok-${nodeName}:latest\n\`\`\`\n`;
287
+ const goPkg = nodeName.replace(/[^a-z0-9]/gi, "").toLowerCase();
288
+ const goNodeContent = go_node_file
289
+ .replace(/\{\{NODE_NAME_PASCAL\}\}/g, toPascalCase(nodeName))
290
+ .replace(/\{\{NODE_PKG\}\}/g, goPkg)
291
+ .replace(/\{\{NODE_NAME\}\}/g, nodeName);
292
+ fsExtra.writeFileSync(`${dirPath}/node.go`, goNodeContent);
293
+ const readmeContent = `# ${nodeName}\n\nGo-based Blok node, served by the Go runtime over gRPC.\n\nRun \`blokctl dev\` — the node is discovered under \`runtimes/go/nodes/\` and registered automatically.\n`;
294
294
  fsExtra.writeFileSync(`${dirPath}/README.md`, readmeContent);
295
295
  }
296
296
  if (node_runtime === "java") {
@@ -321,15 +321,14 @@ export async function createNode(opts, currentPath = false) {
321
321
  throw new Error("ops2");
322
322
  }
323
323
  fsExtra.ensureDirSync(dirPath);
324
- const srcDir = `${dirPath}/src/main/java/com/blok/nodes`;
324
+ const srcDir = `${dirPath}/src/main/java/com/blok/blok/nodes`;
325
325
  fsExtra.ensureDirSync(srcDir);
326
- const javaNodeContent = java_node_file.replace(/\{\{NODE_NAME\}\}/g, nodeName);
327
- const javaPomContent = java_pom_file.replace(/\{\{NODE_NAME\}\}/g, nodeName);
328
- const javaDockerContent = java_dockerfile.replace(/\{\{NODE_NAME\}\}/g, nodeName);
329
- fsExtra.writeFileSync(`${srcDir}/HelloWorldNode.java`, javaNodeContent);
330
- fsExtra.writeFileSync(`${dirPath}/pom.xml`, javaPomContent);
331
- fsExtra.writeFileSync(`${dirPath}/Dockerfile`, javaDockerContent);
332
- const readmeContent = `# ${nodeName}\n\nJava-based Blok node.\n\n## Build\n\n\`\`\`bash\ndocker build -t blok-${nodeName}:latest .\n\`\`\`\n\n## Run\n\n\`\`\`bash\ndocker run -p 8080:8080 blok-${nodeName}:latest\n\`\`\`\n`;
326
+ const pascalName = toPascalCase(nodeName);
327
+ const javaNodeContent = java_node_file
328
+ .replace(/\{\{NODE_NAME_PASCAL\}\}/g, pascalName)
329
+ .replace(/\{\{NODE_NAME\}\}/g, nodeName);
330
+ fsExtra.writeFileSync(`${srcDir}/${pascalName}Node.java`, javaNodeContent);
331
+ const readmeContent = `# ${nodeName}\n\nJava-based Blok node, compiled into the Java runtime jar.\n\nRun \`blokctl dev\` — the node is discovered under \`runtimes/java/nodes/\` and registered automatically.\n`;
333
332
  fsExtra.writeFileSync(`${dirPath}/README.md`, readmeContent);
334
333
  }
335
334
  if (node_runtime === "rust") {
@@ -360,18 +359,12 @@ export async function createNode(opts, currentPath = false) {
360
359
  throw new Error("ops2");
361
360
  }
362
361
  fsExtra.ensureDirSync(dirPath);
363
- const srcDir = `${dirPath}/src`;
364
- fsExtra.ensureDirSync(srcDir);
365
362
  const pascalName = toPascalCase(nodeName);
366
363
  const rustNodeContent = rust_node_file
367
- .replace(/\{\{NODE_NAME\}\}/g, nodeName)
368
- .replace(/\{\{NODE_NAME_PASCAL\}\}/g, pascalName);
369
- const rustCargoContent = rust_cargo_file.replace(/\{\{NODE_NAME\}\}/g, nodeName);
370
- const rustDockerContent = rust_dockerfile.replace(/\{\{NODE_NAME\}\}/g, nodeName);
371
- fsExtra.writeFileSync(`${srcDir}/main.rs`, rustNodeContent);
372
- fsExtra.writeFileSync(`${dirPath}/Cargo.toml`, rustCargoContent);
373
- fsExtra.writeFileSync(`${dirPath}/Dockerfile`, rustDockerContent);
374
- const readmeContent = `# ${nodeName}\n\nRust-based Blok node.\n\n## Build\n\n\`\`\`bash\ndocker build -t blok-${nodeName}:latest .\n\`\`\`\n\n## Run\n\n\`\`\`bash\ndocker run -p 8080:8080 blok-${nodeName}:latest\n\`\`\`\n`;
364
+ .replace(/\{\{NODE_NAME_PASCAL\}\}/g, pascalName)
365
+ .replace(/\{\{NODE_NAME\}\}/g, nodeName);
366
+ fsExtra.writeFileSync(`${dirPath}/node.rs`, rustNodeContent);
367
+ const readmeContent = `# ${nodeName}\n\nRust-based Blok node, served by the Rust runtime over gRPC.\n\nRun \`blokctl dev\` — the node is discovered under \`runtimes/rust/nodes/\` and registered automatically.\n`;
375
368
  fsExtra.writeFileSync(`${dirPath}/README.md`, readmeContent);
376
369
  }
377
370
  if (node_runtime === "csharp") {
@@ -382,7 +375,15 @@ export async function createNode(opts, currentPath = false) {
382
375
  fsExtra.ensureDirSync(currentDir);
383
376
  }
384
377
  const currentNodesDir = `${currentDir}/nodes`;
385
- fsExtra.ensureDirSync(currentNodesDir);
378
+ if (!skipPrompts) {
379
+ fsExtra.ensureDirSync(currentNodesDir);
380
+ }
381
+ else {
382
+ const nodeDirExists = fsExtra.existsSync(currentNodesDir);
383
+ if (!nodeDirExists) {
384
+ fsExtra.ensureDirSync(currentNodesDir);
385
+ }
386
+ }
386
387
  dirPath = path.join(currentNodesDir, nodeName);
387
388
  }
388
389
  if (!skipPrompts)
@@ -392,16 +393,12 @@ export async function createNode(opts, currentPath = false) {
392
393
  throw new Error("ops2");
393
394
  }
394
395
  fsExtra.ensureDirSync(dirPath);
395
- const srcDir = `${dirPath}/src/Nodes`;
396
- fsExtra.ensureDirSync(srcDir);
397
396
  const pascalName = toPascalCase(nodeName);
398
- const csNodeContent = csharp_node_file.replace(/\{\{NODE_NAME_PASCAL\}\}/g, pascalName);
399
- const csprojContent = csharp_csproj_file.replace(/\{\{NODE_NAME\}\}/g, nodeName);
400
- const csDockerContent = csharp_dockerfile.replace(/\{\{NODE_NAME\}\}/g, nodeName);
401
- fsExtra.writeFileSync(`${srcDir}/${pascalName}Node.cs`, csNodeContent);
402
- fsExtra.writeFileSync(`${dirPath}/BlokRuntime.csproj`, csprojContent);
403
- fsExtra.writeFileSync(`${dirPath}/Dockerfile`, csDockerContent);
404
- const readmeContent = `# ${nodeName}\n\nC#/.NET-based Blok node.\n\n## Build\n\n\`\`\`bash\ndocker build -t blok-${nodeName}:latest .\n\`\`\`\n\n## Run\n\n\`\`\`bash\ndocker run -p 8080:8080 blok-${nodeName}:latest\n\`\`\`\n`;
397
+ const csNodeContent = csharp_node_file
398
+ .replace(/\{\{NODE_NAME_PASCAL\}\}/g, pascalName)
399
+ .replace(/\{\{NODE_NAME\}\}/g, nodeName);
400
+ fsExtra.writeFileSync(`${dirPath}/${pascalName}Node.cs`, csNodeContent);
401
+ const readmeContent = `# ${nodeName}\n\nC#/.NET-based Blok node, served by the C# runtime over gRPC.\n\nRun \`blokctl dev\` — the node is discovered under \`runtimes/csharp/nodes/\` and registered automatically.\n`;
405
402
  fsExtra.writeFileSync(`${dirPath}/README.md`, readmeContent);
406
403
  }
407
404
  if (node_runtime === "php") {
@@ -428,12 +425,8 @@ export async function createNode(opts, currentPath = false) {
428
425
  const phpNodeContent = php_node_file
429
426
  .replace(/\{\{NODE_NAME\}\}/g, nodeName)
430
427
  .replace(/\{\{NODE_NAME_PASCAL\}\}/g, pascalName);
431
- const phpComposerContent = php_composer_file.replace(/\{\{NODE_NAME\}\}/g, nodeName);
432
- const phpDockerContent = php_dockerfile.replace(/\{\{NODE_NAME\}\}/g, nodeName);
433
428
  fsExtra.writeFileSync(`${srcDir}/${pascalName}Node.php`, phpNodeContent);
434
- fsExtra.writeFileSync(`${dirPath}/composer.json`, phpComposerContent);
435
- fsExtra.writeFileSync(`${dirPath}/Dockerfile`, phpDockerContent);
436
- const readmeContent = `# ${nodeName}\n\nPHP-based Blok node.\n\n## Build\n\n\`\`\`bash\ndocker build -t blok-${nodeName}:latest .\n\`\`\`\n\n## Run\n\n\`\`\`bash\ndocker run -p 8080:8080 blok-${nodeName}:latest\n\`\`\`\n`;
429
+ const readmeContent = `# ${nodeName}\n\nPHP-based Blok node, served by the PHP runtime over gRPC.\n\nRun \`blokctl dev\` — the node is discovered under \`runtimes/php/nodes/\` and registered automatically.\n`;
437
430
  fsExtra.writeFileSync(`${dirPath}/README.md`, readmeContent);
438
431
  }
439
432
  if (node_runtime === "ruby") {
@@ -454,18 +447,11 @@ export async function createNode(opts, currentPath = false) {
454
447
  throw new Error("ops2");
455
448
  }
456
449
  fsExtra.ensureDirSync(dirPath);
457
- const libDir = `${dirPath}/lib/nodes`;
458
- fsExtra.ensureDirSync(libDir);
459
- const pascalName = toPascalCase(nodeName);
460
450
  const rubyNodeContent = ruby_node_file
461
- .replace(/\{\{NODE_NAME\}\}/g, nodeName)
462
- .replace(/\{\{NODE_NAME_PASCAL\}\}/g, pascalName);
463
- const rubyGemContent = ruby_gemfile.replace(/\{\{NODE_NAME\}\}/g, nodeName);
464
- const rubyDockerContent = ruby_dockerfile.replace(/\{\{NODE_NAME\}\}/g, nodeName);
465
- fsExtra.writeFileSync(`${libDir}/${nodeName.replace(/-/g, "_")}.rb`, rubyNodeContent);
466
- fsExtra.writeFileSync(`${dirPath}/Gemfile`, rubyGemContent);
467
- fsExtra.writeFileSync(`${dirPath}/Dockerfile`, rubyDockerContent);
468
- const readmeContent = `# ${nodeName}\n\nRuby-based Blok node.\n\n## Build\n\n\`\`\`bash\ndocker build -t blok-${nodeName}:latest .\n\`\`\`\n\n## Run\n\n\`\`\`bash\ndocker run -p 8080:8080 blok-${nodeName}:latest\n\`\`\`\n`;
451
+ .replace(/\{\{NODE_NAME_PASCAL\}\}/g, toPascalCase(nodeName))
452
+ .replace(/\{\{NODE_NAME\}\}/g, nodeName);
453
+ fsExtra.writeFileSync(`${dirPath}/node.rb`, rubyNodeContent);
454
+ const readmeContent = `# ${nodeName}\n\nRuby-based Blok node, served by the Ruby runtime over gRPC.\n\nRun \`blokctl dev\` — the node is discovered under \`runtimes/ruby/nodes/\` and registered automatically.\n`;
469
455
  fsExtra.writeFileSync(`${dirPath}/README.md`, readmeContent);
470
456
  }
471
457
  if (!skipPrompts)
@@ -486,33 +472,27 @@ export async function createNode(opts, currentPath = false) {
486
472
  }
487
473
  if (!currentPath && node_runtime === "go") {
488
474
  console.log(`\nNavigate to the node directory by running: cd runtimes/go/nodes/${nodeName}`);
489
- console.log(`\nBuild the Docker image: docker build -t blok-${nodeName}:latest .`);
490
- console.log(`Run the container: docker run -p 8080:8080 blok-${nodeName}:latest`);
475
+ console.log(`\nRun "blokctl dev" — the Go runtime discovers and registers this node automatically.`);
491
476
  }
492
477
  if (!currentPath && node_runtime === "java") {
493
478
  console.log(`\nNavigate to the node directory by running: cd runtimes/java/nodes/${nodeName}`);
494
- console.log(`\nBuild the Docker image: docker build -t blok-${nodeName}:latest .`);
495
- console.log(`Run the container: docker run -p 8080:8080 blok-${nodeName}:latest`);
479
+ console.log(`\nRun "blokctl dev" — the Java runtime discovers and registers this node automatically.`);
496
480
  }
497
481
  if (!currentPath && node_runtime === "rust") {
498
482
  console.log(`\nNavigate to the node directory by running: cd runtimes/rust/nodes/${nodeName}`);
499
- console.log(`\nBuild the Docker image: docker build -t blok-${nodeName}:latest .`);
500
- console.log(`Run the container: docker run -p 8080:8080 blok-${nodeName}:latest`);
483
+ console.log(`\nRun "blokctl dev" — the Rust runtime discovers and registers this node automatically.`);
501
484
  }
502
485
  if (!currentPath && node_runtime === "csharp") {
503
486
  console.log(`\nNavigate to the node directory by running: cd runtimes/csharp/nodes/${nodeName}`);
504
- console.log(`\nBuild the Docker image: docker build -t blok-${nodeName}:latest .`);
505
- console.log(`Run the container: docker run -p 8080:8080 blok-${nodeName}:latest`);
487
+ console.log(`\nRun "blokctl dev" — the C# runtime discovers and registers this node automatically.`);
506
488
  }
507
489
  if (!currentPath && node_runtime === "php") {
508
490
  console.log(`\nNavigate to the node directory by running: cd runtimes/php/nodes/${nodeName}`);
509
- console.log(`\nBuild the Docker image: docker build -t blok-${nodeName}:latest .`);
510
- console.log(`Run the container: docker run -p 8080:8080 blok-${nodeName}:latest`);
491
+ console.log(`\nRun "blokctl dev" — the PHP runtime discovers and registers this node automatically.`);
511
492
  }
512
493
  if (!currentPath && node_runtime === "ruby") {
513
494
  console.log(`\nNavigate to the node directory by running: cd runtimes/ruby/nodes/${nodeName}`);
514
- console.log(`\nBuild the Docker image: docker build -t blok-${nodeName}:latest .`);
515
- console.log(`Run the container: docker run -p 8080:8080 blok-${nodeName}:latest`);
495
+ console.log(`\nRun "blokctl dev" — the Ruby runtime discovers and registers this node automatically.`);
516
496
  }
517
497
  console.log("\nFor more documentation, visit https://blok.build/docs/d/core-concepts/nodes");
518
498
  }
@@ -8,16 +8,21 @@ import fsExtra from "fs-extra";
8
8
  import color from "picocolors";
9
9
  import simpleGit from "simple-git";
10
10
  import { isNonInteractive, parseCommaSeparated, resolveOrThrow } from "../../services/non-interactive.js";
11
+ import { setupObservabilityStack } from "../../services/obs-setup.js";
12
+ import { parseObsTier } from "../../services/obs-tiers.js";
13
+ import { rewriteObservabilityEnvBlock } from "../../services/observability-mutations.js";
11
14
  import { manager as pm } from "../../services/package-manager.js";
12
15
  import { detectRuntimes } from "../../services/runtime-detector.js";
13
16
  import { createTriggerConfig, generateRuntimeEnvVars, generateSupervisordConfig, generateTriggerEnvVars, generateTriggerSupervisordConfig, setupRuntime, writeProjectConfig, } from "../../services/runtime-setup.js";
14
17
  import { computeDefaultConstraint } from "../../services/semver-utils.js";
18
+ import { resolveObservabilitySelection } from "../observability/apply.js";
19
+ import { OBSERVABILITY_MODULE_IDS, allObservabilityModules, getObservabilityModule, } from "../observability/descriptor.js";
15
20
  import { agents_md, claude_md, examples_url, node_file, package_dependencies, package_dev_dependencies, } from "./utils/Examples.js";
16
21
  const exec = util.promisify(child_process.exec);
17
22
  const HOME_DIR = `${os.homedir()}/.blok`;
18
23
  const GITHUB_REPO_LOCAL = `${HOME_DIR}/blok`;
19
24
  const GITHUB_REPO_REMOTE = "https://github.com/well-prado/blok.git";
20
- const GITHUB_REPO_RELEASE_TAG = "v0.6.21";
25
+ const GITHUB_REPO_RELEASE_TAG = "v0.7.0";
21
26
  const RUNTIME_HELLO_EXAMPLES = {
22
27
  go: "runtime-go-hello.ts",
23
28
  rust: "runtime-rust-hello.ts",
@@ -53,6 +58,15 @@ export async function createProject(opts, version, currentPath = false, localRep
53
58
  let pubsubProvider = opts.pubsubProvider || "gcp";
54
59
  let queueProvider = opts.queueProvider || "kafka";
55
60
  let explicitQueueProvider = Boolean(opts.queueProvider);
61
+ let selectedObsTier = opts.obsStack ? parseObsTier(opts.obsStack) : "none";
62
+ let selectedObsModules = opts.observability
63
+ ? parseCommaSeparated(opts.observability).map((s) => s.trim().toLowerCase())
64
+ : [];
65
+ for (const id of selectedObsModules) {
66
+ if (id !== "obs-stack" && !getObservabilityModule(id)) {
67
+ throw new Error(`Invalid --observability "${id}". Known: ${OBSERVABILITY_MODULE_IDS.join(", ")}.`);
68
+ }
69
+ }
56
70
  let detectedRuntimes = [];
57
71
  if (!skipPrompts) {
58
72
  console.log(figlet.textSync("blok CLI".toUpperCase(), {
@@ -168,6 +182,27 @@ export async function createProject(opts, version, currentPath = false, localRep
168
182
  initialValues: ["node"],
169
183
  required: true,
170
184
  }),
185
+ obsStack: () => opts.obsStack
186
+ ? Promise.resolve(opts.obsStack)
187
+ : p.select({
188
+ message: "Observability dev stack?",
189
+ options: [
190
+ { label: "None", value: "none", hint: "no infra/metrics — boots standalone (default)" },
191
+ { label: "Lite", value: "lite", hint: "Prometheus + Grafana only" },
192
+ { label: "Full", value: "full", hint: "Prometheus, Grafana, Loki, Tempo, Alloy, …" },
193
+ ],
194
+ initialValue: "none",
195
+ }),
196
+ observability: () => opts.observability
197
+ ? Promise.resolve(parseCommaSeparated(opts.observability))
198
+ : p.multiselect({
199
+ message: "Observability modules (optional — none by default)",
200
+ options: allObservabilityModules()
201
+ .filter((m) => m.id !== "obs-stack")
202
+ .map((m) => ({ value: m.id, label: m.label, hint: m.description })),
203
+ initialValues: [],
204
+ required: false,
205
+ }),
171
206
  selectedManager: () => resolveSelectedManager(),
172
207
  }, {
173
208
  onCancel: () => {
@@ -181,6 +216,8 @@ export async function createProject(opts, version, currentPath = false, localRep
181
216
  explicitQueueProvider = blokctlProject.queueProvider != null;
182
217
  queueProvider = blokctlProject.queueProvider || "kafka";
183
218
  selectedRuntimeKinds = blokctlProject.runtimes;
219
+ selectedObsTier = parseObsTier(blokctlProject.obsStack);
220
+ selectedObsModules = blokctlProject.observability ?? [];
184
221
  selectedManager = blokctlProject.selectedManager;
185
222
  const unavailableSelected = selectedRuntimeKinds.filter((kind) => {
186
223
  if (kind === "node")
@@ -286,7 +323,7 @@ export async function createProject(opts, version, currentPath = false, localRep
286
323
  const primaryTriggerDir = primaryTrigger === "pubsub" || primaryTrigger === "queue" || primaryTrigger === "worker"
287
324
  ? `${repoSource}/triggers/${primaryTrigger === "queue" || primaryTrigger === "worker" ? "worker" : primaryTrigger}/template`
288
325
  : `${repoSource}/triggers/${primaryTrigger}`;
289
- const baseFiles = ["package.json", "tsconfig.json", ".env.example", ".gitignore", "vitest.config.ts"];
326
+ const baseFiles = ["package.json", "tsconfig.json", ".env.example", ".gitignore"];
290
327
  for (const file of baseFiles) {
291
328
  const src = `${primaryTriggerDir}/${file}`;
292
329
  if (fsExtra.existsSync(src)) {
@@ -441,9 +478,7 @@ export async function createProject(opts, version, currentPath = false, localRep
441
478
  catch (error) {
442
479
  console.error(`Failed to change ownership of directory ${dirPath}:`, error);
443
480
  }
444
- fsExtra.ensureDirSync(`${dirPath}/infra`);
445
- fsExtra.ensureDirSync(`${dirPath}/infra/metrics`);
446
- fsExtra.copySync(`${repoSource}/infra/metrics`, `${dirPath}/infra/metrics`);
481
+ setupObservabilityStack(repoSource, dirPath, selectedObsTier);
447
482
  fsExtra.removeSync(`${dirPath}/public/metric`);
448
483
  if (selectedTriggers.includes("queue") || selectedTriggers.includes("worker")) {
449
484
  fsExtra.ensureDirSync(`${dirPath}/infra/development`);
@@ -515,7 +550,7 @@ export async function createProject(opts, version, currentPath = false, localRep
515
550
  "@blokjs/trigger-websocket": "triggers/websocket",
516
551
  "@blokjs/trigger-worker": "triggers/worker",
517
552
  };
518
- const BLOKJS_DEP_RANGE = "^0.6.21";
553
+ const BLOKJS_DEP_RANGE = "^0.7.0";
519
554
  for (const depGroup of ["dependencies", "devDependencies", "peerDependencies"]) {
520
555
  const deps = packageJsonContent[depGroup];
521
556
  if (!deps)
@@ -571,6 +606,8 @@ export async function createProject(opts, version, currentPath = false, localRep
571
606
  ...packageJsonContent.devDependencies,
572
607
  blokctl: blokctlRef,
573
608
  };
609
+ packageJsonContent.scripts = Object.fromEntries(Object.entries(packageJsonContent.scripts).filter(([s]) => s !== "test" && s !== "test:dev"));
610
+ packageJsonContent.devDependencies = Object.fromEntries(Object.entries(packageJsonContent.devDependencies).filter(([d]) => d !== "vitest" && !d.startsWith("@vitest/")));
574
611
  const providerDeps = getProviderDependencies(selectedTriggers, pubsubProvider, queueProvider, explicitQueueProvider);
575
612
  if (Object.keys(providerDeps).length > 0) {
576
613
  packageJsonContent.dependencies = {
@@ -673,7 +710,16 @@ export async function createProject(opts, version, currentPath = false, localRep
673
710
  fsExtra.appendFileSync(envLocal, envVars);
674
711
  }
675
712
  }
676
- writeProjectConfig(dirPath, runtimeConfigs, spawnedTriggerConfigs);
713
+ const obsSelection = resolveObservabilitySelection(selectedObsModules.filter((id) => id !== "obs-stack"), { addedAt: new Date().toISOString(), version, projectDir: dirPath });
714
+ if (obsSelection.added.length > 0) {
715
+ p.log.info(`Auto-enabling required observability dependencies: ${obsSelection.added.join(", ")}`);
716
+ }
717
+ const obsConfigMap = Object.keys(obsSelection.configMap).length > 0 ? obsSelection.configMap : undefined;
718
+ writeProjectConfig(dirPath, runtimeConfigs, spawnedTriggerConfigs, obsConfigMap);
719
+ if (obsSelection.envBlocks.some((b) => b.trim())) {
720
+ const current = fsExtra.existsSync(envLocal) ? fsExtra.readFileSync(envLocal, "utf8") : "";
721
+ fsExtra.writeFileSync(envLocal, rewriteObservabilityEnvBlock(current, obsSelection.envBlocks));
722
+ }
677
723
  if (triggerConfigs.length > 0) {
678
724
  const triggerEnvVars = generateTriggerEnvVars(triggerConfigs);
679
725
  fsExtra.appendFileSync(envLocal, triggerEnvVars);
@@ -868,10 +914,10 @@ function generateSharedWorkflowsFile(triggers, runtimeKinds = [], examples = fal
868
914
  }
869
915
  const importSection = imports.length > 0 ? `${imports.join("\n")}\n` : "";
870
916
  const entriesSection = workflowEntries.length > 0 ? workflowEntries.join("\n") : "\t// Add your workflows here";
871
- return `import type { HelperResponse, WorkflowV2Builder } from "@blokjs/helper";
917
+ return `import type { WorkflowV2Builder } from "@blokjs/helper";
872
918
 
873
919
  ${importSection}
874
- const workflows: Record<string, HelperResponse | WorkflowV2Builder> = {
920
+ const workflows: Record<string, WorkflowV2Builder> = {
875
921
  ${entriesSection}
876
922
  };
877
923