blokctl 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/commands/build/index.d.ts +2 -0
  2. package/dist/commands/build/index.js +210 -0
  3. package/dist/commands/config/index.d.ts +1 -0
  4. package/dist/commands/config/index.js +46 -0
  5. package/dist/commands/cost/index.d.ts +1 -0
  6. package/dist/commands/cost/index.js +74 -0
  7. package/dist/commands/create/node.d.ts +2 -0
  8. package/dist/commands/create/node.js +541 -0
  9. package/dist/commands/create/project.d.ts +2 -0
  10. package/dist/commands/create/project.js +941 -0
  11. package/dist/commands/create/utils/Examples.d.ts +39 -0
  12. package/dist/commands/create/utils/Examples.js +983 -0
  13. package/dist/commands/create/workflow.d.ts +2 -0
  14. package/dist/commands/create/workflow.js +109 -0
  15. package/dist/commands/deploy/index.d.ts +2 -0
  16. package/dist/commands/deploy/index.js +176 -0
  17. package/dist/commands/dev/index.d.ts +2 -0
  18. package/dist/commands/dev/index.js +190 -0
  19. package/dist/commands/generate/GenerationAnalytics.d.ts +61 -0
  20. package/dist/commands/generate/GenerationAnalytics.js +162 -0
  21. package/dist/commands/generate/GenerationAnalytics.test.d.ts +1 -0
  22. package/dist/commands/generate/GenerationAnalytics.test.js +407 -0
  23. package/dist/commands/generate/NodeFileWriter.d.ts +5 -0
  24. package/dist/commands/generate/NodeFileWriter.js +240 -0
  25. package/dist/commands/generate/NodeGenerator.d.ts +20 -0
  26. package/dist/commands/generate/NodeGenerator.js +181 -0
  27. package/dist/commands/generate/NodeGenerator.test.d.ts +1 -0
  28. package/dist/commands/generate/NodeGenerator.test.js +101 -0
  29. package/dist/commands/generate/PromptVersioning.d.ts +25 -0
  30. package/dist/commands/generate/PromptVersioning.js +71 -0
  31. package/dist/commands/generate/PromptVersioning.test.d.ts +1 -0
  32. package/dist/commands/generate/PromptVersioning.test.js +120 -0
  33. package/dist/commands/generate/RegisterNode.d.ts +3 -0
  34. package/dist/commands/generate/RegisterNode.js +37 -0
  35. package/dist/commands/generate/RuntimeGenerator.d.ts +40 -0
  36. package/dist/commands/generate/RuntimeGenerator.js +369 -0
  37. package/dist/commands/generate/RuntimeGenerator.test.d.ts +1 -0
  38. package/dist/commands/generate/RuntimeGenerator.test.js +553 -0
  39. package/dist/commands/generate/TriggerGenerator.d.ts +22 -0
  40. package/dist/commands/generate/TriggerGenerator.js +220 -0
  41. package/dist/commands/generate/TriggerGenerator.test.d.ts +1 -0
  42. package/dist/commands/generate/TriggerGenerator.test.js +209 -0
  43. package/dist/commands/generate/WorkflowGenerator.d.ts +20 -0
  44. package/dist/commands/generate/WorkflowGenerator.js +131 -0
  45. package/dist/commands/generate/WorkflowGenerator.test.d.ts +1 -0
  46. package/dist/commands/generate/WorkflowGenerator.test.js +77 -0
  47. package/dist/commands/generate/e2e/NodeGenerator.e2e.test.d.ts +1 -0
  48. package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +216 -0
  49. package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.d.ts +1 -0
  50. package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.js +759 -0
  51. package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.d.ts +1 -0
  52. package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +295 -0
  53. package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.d.ts +1 -0
  54. package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.js +353 -0
  55. package/dist/commands/generate/index.d.ts +1 -0
  56. package/dist/commands/generate/index.js +418 -0
  57. package/dist/commands/generate/prompts/create-fn-node.system.d.ts +5 -0
  58. package/dist/commands/generate/prompts/create-fn-node.system.js +256 -0
  59. package/dist/commands/generate/prompts/create-node-manifest.system.d.ts +4 -0
  60. package/dist/commands/generate/prompts/create-node-manifest.system.js +41 -0
  61. package/dist/commands/generate/prompts/create-node.system.d.ts +5 -0
  62. package/dist/commands/generate/prompts/create-node.system.js +114 -0
  63. package/dist/commands/generate/prompts/create-readme.system.d.ts +4 -0
  64. package/dist/commands/generate/prompts/create-readme.system.js +83 -0
  65. package/dist/commands/generate/prompts/create-runtime.system.d.ts +5 -0
  66. package/dist/commands/generate/prompts/create-runtime.system.js +284 -0
  67. package/dist/commands/generate/prompts/create-trigger.system.d.ts +5 -0
  68. package/dist/commands/generate/prompts/create-trigger.system.js +293 -0
  69. package/dist/commands/generate/prompts/create-workflow.system.d.ts +5 -0
  70. package/dist/commands/generate/prompts/create-workflow.system.js +476 -0
  71. package/dist/commands/generate/prompts/register-node.system.d.ts +4 -0
  72. package/dist/commands/generate/prompts/register-node.system.js +26 -0
  73. package/dist/commands/generate/validators/CompilationValidator.d.ts +9 -0
  74. package/dist/commands/generate/validators/CompilationValidator.js +86 -0
  75. package/dist/commands/generate/validators/CompilationValidator.test.d.ts +1 -0
  76. package/dist/commands/generate/validators/CompilationValidator.test.js +161 -0
  77. package/dist/commands/generate/validators/NodeValidator.d.ts +18 -0
  78. package/dist/commands/generate/validators/NodeValidator.js +217 -0
  79. package/dist/commands/generate/validators/NodeValidator.test.d.ts +1 -0
  80. package/dist/commands/generate/validators/NodeValidator.test.js +281 -0
  81. package/dist/commands/generate/validators/WorkflowValidator.d.ts +6 -0
  82. package/dist/commands/generate/validators/WorkflowValidator.js +301 -0
  83. package/dist/commands/generate/validators/WorkflowValidator.test.d.ts +1 -0
  84. package/dist/commands/generate/validators/WorkflowValidator.test.js +647 -0
  85. package/dist/commands/generate/validators/index.d.ts +4 -0
  86. package/dist/commands/generate/validators/index.js +2 -0
  87. package/dist/commands/graph/index.d.ts +1 -0
  88. package/dist/commands/graph/index.js +69 -0
  89. package/dist/commands/install/index.d.ts +1 -0
  90. package/dist/commands/install/index.js +4 -0
  91. package/dist/commands/install/node.d.ts +4 -0
  92. package/dist/commands/install/node.js +136 -0
  93. package/dist/commands/install/workflow.d.ts +4 -0
  94. package/dist/commands/install/workflow.js +62 -0
  95. package/dist/commands/login/index.d.ts +2 -0
  96. package/dist/commands/login/index.js +77 -0
  97. package/dist/commands/logout/index.d.ts +2 -0
  98. package/dist/commands/logout/index.js +20 -0
  99. package/dist/commands/marketplace/runtime.d.ts +54 -0
  100. package/dist/commands/marketplace/runtime.js +350 -0
  101. package/dist/commands/migrate/index.d.ts +1 -0
  102. package/dist/commands/migrate/index.js +14 -0
  103. package/dist/commands/migrate/node.d.ts +2 -0
  104. package/dist/commands/migrate/node.js +110 -0
  105. package/dist/commands/monitor/index.d.ts +1 -0
  106. package/dist/commands/monitor/index.js +28 -0
  107. package/dist/commands/monitor/monitor-component.d.ts +1 -0
  108. package/dist/commands/monitor/monitor-component.js +271 -0
  109. package/dist/commands/monitor/static/index.html +2124 -0
  110. package/dist/commands/monitor/static-web-server.d.ts +1 -0
  111. package/dist/commands/monitor/static-web-server.js +89 -0
  112. package/dist/commands/profile/index.d.ts +1 -0
  113. package/dist/commands/profile/index.js +112 -0
  114. package/dist/commands/publish/index.d.ts +1 -0
  115. package/dist/commands/publish/index.js +4 -0
  116. package/dist/commands/publish/node.d.ts +4 -0
  117. package/dist/commands/publish/node.js +231 -0
  118. package/dist/commands/publish/workflow.d.ts +4 -0
  119. package/dist/commands/publish/workflow.js +165 -0
  120. package/dist/commands/search/docs.d.ts +17 -0
  121. package/dist/commands/search/docs.js +179 -0
  122. package/dist/commands/search/index.d.ts +1 -0
  123. package/dist/commands/search/index.js +5 -0
  124. package/dist/commands/search/indexer.d.ts +10 -0
  125. package/dist/commands/search/indexer.js +265 -0
  126. package/dist/commands/search/nodes.d.ts +4 -0
  127. package/dist/commands/search/nodes.js +101 -0
  128. package/dist/commands/search/workflow.d.ts +4 -0
  129. package/dist/commands/search/workflow.js +100 -0
  130. package/dist/commands/trace/index.d.ts +1 -0
  131. package/dist/commands/trace/index.js +26 -0
  132. package/dist/commands/trace/startStudio.d.ts +8 -0
  133. package/dist/commands/trace/startStudio.js +116 -0
  134. package/dist/index.d.ts +17 -0
  135. package/dist/index.js +186 -0
  136. package/dist/services/commander.d.ts +9 -0
  137. package/dist/services/commander.js +20 -0
  138. package/dist/services/constants.d.ts +1 -0
  139. package/dist/services/constants.js +3 -0
  140. package/dist/services/local-token-manager.d.ts +14 -0
  141. package/dist/services/local-token-manager.js +99 -0
  142. package/dist/services/non-interactive.d.ts +5 -0
  143. package/dist/services/non-interactive.js +30 -0
  144. package/dist/services/package-manager.d.ts +35 -0
  145. package/dist/services/package-manager.js +111 -0
  146. package/dist/services/posthog.d.ts +31 -0
  147. package/dist/services/posthog.js +159 -0
  148. package/dist/services/registry-manager.d.ts +9 -0
  149. package/dist/services/registry-manager.js +26 -0
  150. package/dist/services/runtime-detector.d.ts +23 -0
  151. package/dist/services/runtime-detector.js +181 -0
  152. package/dist/services/runtime-setup.d.ts +36 -0
  153. package/dist/services/runtime-setup.js +250 -0
  154. package/dist/services/utils.d.ts +2 -0
  155. package/dist/services/utils.js +29 -0
  156. package/dist/services/workflow-loader.d.ts +30 -0
  157. package/dist/services/workflow-loader.js +46 -0
  158. package/dist/studio-dist/assets/charts-Dso0hPUR.js +68 -0
  159. package/dist/studio-dist/assets/graph-CsV2nWGn.js +23 -0
  160. package/dist/studio-dist/assets/icons-zP8LLgPh.js +311 -0
  161. package/dist/studio-dist/assets/index-CLyEkXMx.css +1 -0
  162. package/dist/studio-dist/assets/index-CNXFX_ar.js +27 -0
  163. package/dist/studio-dist/assets/react-vendor--Eh9ivFN.js +17 -0
  164. package/dist/studio-dist/assets/tanstack-query-CiM1U6F5.js +1 -0
  165. package/dist/studio-dist/assets/tanstack-router-Btjy0MKq.js +25 -0
  166. package/dist/studio-dist/assets/tanstack-table-DhwRvuH2.js +22 -0
  167. package/dist/studio-dist/favicon.svg +5 -0
  168. package/dist/studio-dist/index.html +21 -0
  169. package/package.json +75 -0
@@ -0,0 +1 @@
1
+ export default function startWebMonitorUI(host?: string, token?: string): Promise<void>;
@@ -0,0 +1,89 @@
1
+ import fs from "node:fs";
2
+ import http from "node:http";
3
+ import https from "node:https";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import open from "open";
7
+ import serveHandler from "serve-handler";
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ let signalHandlerRegistered = false;
11
+ export default async function startWebMonitorUI(host, token) {
12
+ const staticPath = path.join(__dirname, "./static");
13
+ const port = 4040;
14
+ if (!fs.existsSync(staticPath)) {
15
+ console.error(`❌ Static path not found: ${staticPath}`);
16
+ process.exit(1);
17
+ }
18
+ const server = http.createServer((req, res) => {
19
+ handleRequest(req, res, host, token, staticPath);
20
+ });
21
+ server.listen(port, async () => {
22
+ const url = `http://localhost:${port}`;
23
+ console.log(`📂 Serving UI from: ${staticPath}`);
24
+ console.log(`🧪 Monitor UI available at: ${url}`);
25
+ await open(url);
26
+ });
27
+ const stop = () => {
28
+ server.close(() => {
29
+ console.log("🛑 Monitor UI server stopped.");
30
+ process.exit(0);
31
+ });
32
+ };
33
+ if (!signalHandlerRegistered) {
34
+ const signals = ["SIGQUIT", "SIGHUP"];
35
+ for (const signal of signals) {
36
+ process.once(signal, stop);
37
+ }
38
+ signalHandlerRegistered = true;
39
+ }
40
+ }
41
+ function handleRequest(req, res, host, token, staticPath) {
42
+ const url = req.url || "/";
43
+ const parsedUrl = new URL(url, `http://${req.headers.host}`);
44
+ const isRootOrIndex = parsedUrl.pathname === "/" || parsedUrl.pathname.startsWith("/index.html");
45
+ if (parsedUrl.pathname.startsWith("/api/metrics")) {
46
+ if (!host) {
47
+ res.writeHead(400, { "Content-Type": "application/json" });
48
+ res.end(JSON.stringify({ error: "Missing Prometheus host." }));
49
+ return;
50
+ }
51
+ let query_path = "query_range";
52
+ if (req.headers["x-table"]) {
53
+ query_path = "query";
54
+ }
55
+ let proxiedUrl = `${host}/api/v1/${query_path}${parsedUrl.search}`;
56
+ if (req.headers["x-label"]) {
57
+ const label = req.headers["x-label"];
58
+ proxiedUrl = `${host}/api/v1/label/${label}/values`;
59
+ }
60
+ const isSecure = host.startsWith("https://");
61
+ const httpModule = isSecure ? https : http;
62
+ const proxyReq = httpModule.request(proxiedUrl, {
63
+ method: "GET",
64
+ headers: {
65
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
66
+ Accept: "application/json",
67
+ "Accept-Encoding": "identity",
68
+ },
69
+ }, (proxyRes) => {
70
+ res.writeHead(proxyRes.statusCode || 500, proxyRes.headers);
71
+ proxyRes.pipe(res, { end: true });
72
+ });
73
+ proxyReq.on("error", (err) => {
74
+ console.error("Proxy error:", err);
75
+ res.writeHead(500, { "Content-Type": "application/json" });
76
+ res.end(JSON.stringify({ error: "Failed to proxy Prometheus request." }));
77
+ });
78
+ proxyReq.end();
79
+ return;
80
+ }
81
+ if (isRootOrIndex || parsedUrl.pathname.endsWith(".js") || parsedUrl.pathname.endsWith(".css")) {
82
+ return serveHandler(req, res, {
83
+ public: staticPath,
84
+ cleanUrls: true,
85
+ });
86
+ }
87
+ res.writeHead(404, { "Content-Type": "text/plain" });
88
+ res.end("404 Not Found");
89
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,112 @@
1
+ import fs from "node:fs";
2
+ import * as p from "@clack/prompts";
3
+ import { program, trackCommandExecution } from "../../services/commander.js";
4
+ async function getPerformanceProfiler() {
5
+ const { PerformanceProfiler } = await import("@blok/runner");
6
+ return PerformanceProfiler;
7
+ }
8
+ async function queryPrometheus(query, host, token) {
9
+ try {
10
+ const headers = { "Content-Type": "application/x-www-form-urlencoded" };
11
+ if (token)
12
+ headers.Authorization = `Bearer ${token}`;
13
+ const response = await fetch(`${host}/api/v1/query`, {
14
+ method: "POST",
15
+ headers,
16
+ body: `query=${encodeURIComponent(query)}`,
17
+ });
18
+ if (!response.ok)
19
+ return [];
20
+ const data = (await response.json());
21
+ return data?.data?.result ?? [];
22
+ }
23
+ catch {
24
+ return [];
25
+ }
26
+ }
27
+ program
28
+ .command("profile [workflow-name]")
29
+ .description("Profile workflow execution performance and identify bottlenecks")
30
+ .option("--duration <seconds>", "Duration to collect metrics", "30")
31
+ .option("--format <format>", "Output format: table, flamechart, json", "table")
32
+ .option("--output <file>", "Write output to file")
33
+ .option("--host <host>", "Prometheus host", "http://localhost:9090")
34
+ .option("--token <token>", "Prometheus auth token")
35
+ .option("--top <count>", "Show top N bottlenecks", "10")
36
+ .action(async (workflowName, options) => {
37
+ await trackCommandExecution({
38
+ command: "profile",
39
+ args: options,
40
+ execution: async () => {
41
+ const logger = p.spinner();
42
+ logger.start("Collecting performance metrics from Prometheus...");
43
+ const host = options.host || "http://localhost:9090";
44
+ const token = options.token;
45
+ const topN = Number.parseInt(options.top, 10) || 10;
46
+ const [nodeTimeResults, _nodeCountResults, nodeMemResults, _nodeCpuResults, _nodeErrResults] = await Promise.all([
47
+ queryPrometheus("node_time", host, token),
48
+ queryPrometheus("node_total", host, token),
49
+ queryPrometheus("node_memory", host, token),
50
+ queryPrometheus("node_cpu", host, token),
51
+ queryPrometheus("node_errors_total", host, token),
52
+ ]);
53
+ const PerformanceProfiler = await getPerformanceProfiler();
54
+ const profiler = new PerformanceProfiler({ topN });
55
+ let hasData = false;
56
+ for (const result of nodeTimeResults) {
57
+ const wf = result.metric.workflow || "unknown";
58
+ const node = result.metric.node || result.metric.name || "unknown";
59
+ if (workflowName && wf !== workflowName)
60
+ continue;
61
+ const timeMs = Number.parseFloat(result.value[1]) || 0;
62
+ if (timeMs > 0) {
63
+ profiler.addSample(wf, node, timeMs);
64
+ hasData = true;
65
+ }
66
+ }
67
+ for (const result of nodeMemResults) {
68
+ const wf = result.metric.workflow || "unknown";
69
+ const node = result.metric.node || result.metric.name || "unknown";
70
+ if (workflowName && wf !== workflowName)
71
+ continue;
72
+ const memMb = Number.parseFloat(result.value[1]) || 0;
73
+ if (memMb > 0) {
74
+ profiler.addSample(wf, node, 0, memMb);
75
+ }
76
+ }
77
+ if (!hasData) {
78
+ logger.error("No profiling data available.");
79
+ p.log.warn("Make sure Prometheus is running and workflows have been executed.");
80
+ p.log.info(`Tried connecting to: ${host}`);
81
+ return;
82
+ }
83
+ logger.stop("Metrics collected.");
84
+ let output;
85
+ const format = options.format;
86
+ switch (format) {
87
+ case "flamechart":
88
+ output = profiler.toFlameChart();
89
+ break;
90
+ case "json":
91
+ output = profiler.toJson();
92
+ break;
93
+ default:
94
+ output = profiler.toTable();
95
+ }
96
+ if (options.output) {
97
+ fs.writeFileSync(options.output, output, "utf-8");
98
+ p.log.success(`Profile written to ${options.output}`);
99
+ }
100
+ else {
101
+ console.log(output);
102
+ }
103
+ const bottlenecks = profiler.getBottlenecks(3);
104
+ if (bottlenecks.length > 0) {
105
+ p.log.info("Top bottlenecks:");
106
+ for (const b of bottlenecks) {
107
+ p.log.message(` ${b.nodeName}: avg ${b.avgTimeMs.toFixed(1)}ms (${b.percentOfTotal.toFixed(0)}% of total)`);
108
+ }
109
+ }
110
+ },
111
+ });
112
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import { program } from "../../services/commander.js";
2
+ import node from "./node.js";
3
+ import workflow from "./workflow.js";
4
+ program.command("publish").description("Publish commands").addCommand(node).addCommand(workflow);
@@ -0,0 +1,4 @@
1
+ import { Command, type OptionValues } from "../../services/commander.js";
2
+ export declare function publish(opts: OptionValues): Promise<void>;
3
+ declare const _default: Command;
4
+ export default _default;
@@ -0,0 +1,231 @@
1
+ import child_process from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import path from "node:path";
4
+ import util from "node:util";
5
+ import * as p from "@clack/prompts";
6
+ import { Command, trackCommandExecution } from "../../services/commander.js";
7
+ import { tokenManager } from "../../services/local-token-manager.js";
8
+ import { isNonInteractive } from "../../services/non-interactive.js";
9
+ import { VersionUpdateType, manager as pm } from "../../services/package-manager.js";
10
+ import { registryManager } from "../../services/registry-manager.js";
11
+ const exec = util.promisify(child_process.exec);
12
+ const packagePublisherRuntimes = [
13
+ {
14
+ label: "node",
15
+ value: "npm",
16
+ },
17
+ ];
18
+ const packagePublishVerion = [VersionUpdateType.PATCH, VersionUpdateType.MINOR, VersionUpdateType.MAJOR];
19
+ function findSimilarDirectories(directory, searchTerm) {
20
+ if (!fs.existsSync(directory))
21
+ return [];
22
+ const dirs = fs.readdirSync(directory).filter((file) => fs.statSync(path.join(directory, file)).isDirectory());
23
+ return dirs.filter((dir) => {
24
+ const dirName = dir.toLowerCase();
25
+ const search = searchTerm.toLowerCase();
26
+ if (dirName === search)
27
+ return true;
28
+ if (dirName.startsWith(search))
29
+ return true;
30
+ const words = dirName.split(/[-_]/);
31
+ if (words.some((word) => word.startsWith(search)))
32
+ return true;
33
+ if (search.length >= 3) {
34
+ let matches = 0;
35
+ let searchIndex = 0;
36
+ for (let i = 0; i < dirName.length && searchIndex < search.length; i++) {
37
+ if (dirName[i] === search[searchIndex]) {
38
+ matches++;
39
+ searchIndex++;
40
+ }
41
+ }
42
+ return matches >= search.length * 0.5;
43
+ }
44
+ return false;
45
+ });
46
+ }
47
+ async function loadNodeDirectories(baseDir) {
48
+ const nodesDir = path.join(baseDir, "src/nodes");
49
+ if (!fs.existsSync(nodesDir)) {
50
+ throw new Error("src/nodes directory not found");
51
+ }
52
+ const dirs = fs.readdirSync(nodesDir).filter((file) => fs.statSync(path.join(nodesDir, file)).isDirectory());
53
+ return dirs.map((dir) => ({
54
+ label: dir,
55
+ value: dir,
56
+ }));
57
+ }
58
+ export async function publish(opts) {
59
+ const token = tokenManager.getToken();
60
+ const npmrcFile = `${opts.directory}/.npmrc`;
61
+ const logger = p.spinner();
62
+ let packageJsonOriginal = null;
63
+ try {
64
+ if (!token)
65
+ throw new Error("Authentication token not found. Please run 'blokctl login' before publishing.");
66
+ if (!opts.directory)
67
+ throw new Error("Directory is required.");
68
+ logger.start("Publishing node to the registry...");
69
+ if (opts.node === ".") {
70
+ logger.message("Using current directory for publishing");
71
+ }
72
+ else {
73
+ const nodesDir = path.join(opts.directory, "src/nodes");
74
+ let targetNodeDir;
75
+ if (opts.node) {
76
+ const nodePath = path.join(nodesDir, opts.node);
77
+ if (!fs.existsSync(nodePath)) {
78
+ const similarDirs = findSimilarDirectories(nodesDir, opts.node);
79
+ if (similarDirs.length > 0) {
80
+ if (isNonInteractive()) {
81
+ throw new Error(`Node "${opts.node}" not found. Similar nodes: ${similarDirs.join(", ")}. Provide an exact node name in non-interactive mode.`);
82
+ }
83
+ logger.message("Similar nodes found");
84
+ const selection = await p.select({
85
+ message: "Select a node to publish",
86
+ options: similarDirs.map((dir) => ({
87
+ label: dir,
88
+ value: dir,
89
+ })),
90
+ });
91
+ if (p.isCancel(selection)) {
92
+ throw new Error("Operation cancelled");
93
+ }
94
+ targetNodeDir = selection;
95
+ opts.directory = path.join(nodesDir, targetNodeDir);
96
+ }
97
+ else {
98
+ throw new Error(`Node "${opts.node}" not found and no similar nodes found in src/nodes directory`);
99
+ }
100
+ }
101
+ else {
102
+ targetNodeDir = opts.node;
103
+ opts.directory = nodePath;
104
+ }
105
+ }
106
+ else {
107
+ if (isNonInteractive()) {
108
+ throw new Error("Missing required argument <node> (non-interactive mode). Provide the node name as an argument.");
109
+ }
110
+ logger.message("Select a node to publish");
111
+ const nodeOptions = await loadNodeDirectories(opts.directory);
112
+ if (nodeOptions.length === 0) {
113
+ throw new Error("No nodes found in src/nodes directory");
114
+ }
115
+ const selection = await p.select({
116
+ message: "Select a node to publish",
117
+ options: nodeOptions,
118
+ });
119
+ if (p.isCancel(selection)) {
120
+ throw new Error("Operation cancelled");
121
+ }
122
+ targetNodeDir = selection;
123
+ opts.directory = path.join(nodesDir, targetNodeDir);
124
+ }
125
+ }
126
+ const runtimesToPublish = opts.runtime
127
+ ? opts.runtime
128
+ : isNonInteractive()
129
+ ? "npm"
130
+ : await p.select({
131
+ message: "Select node runtime",
132
+ options: packagePublisherRuntimes,
133
+ initialValue: "npm",
134
+ });
135
+ const manager = await pm.getManager(runtimesToPublish);
136
+ const versionType = opts.bump
137
+ ? opts.bump
138
+ : isNonInteractive()
139
+ ? "patch"
140
+ : await p.select({
141
+ message: "Select the version bump type",
142
+ options: packagePublishVerion.map((v) => ({ label: v, value: v })),
143
+ initialValue: "patch",
144
+ });
145
+ if (opts.build) {
146
+ logger.start("Running build before publishing...");
147
+ const { stderr: buildError } = await exec(manager.BUILD, { cwd: opts.directory });
148
+ if (buildError) {
149
+ logger.stop(buildError);
150
+ throw new Error(`Error running build: ${buildError}`);
151
+ }
152
+ logger.stop("Build completed successfully.");
153
+ }
154
+ logger.message(`Updating package version to ${String(versionType)}...`);
155
+ const { stderr: versionError } = await exec(manager.UPDATE_VERSION({ type: versionType }), {
156
+ cwd: opts.directory,
157
+ });
158
+ if (versionError) {
159
+ throw new Error(`Error updating package version: ${versionError}`);
160
+ }
161
+ const registry = await registryManager.getRegistryToken(token);
162
+ if (registry.error)
163
+ throw new Error("Failed to get registry token.");
164
+ const REGISTRY_URL = `https://${registry.url}`;
165
+ const npmrcContent = `registry=${REGISTRY_URL}\n//${registry.url}:_authToken=${registry.token}`;
166
+ fs.writeFileSync(npmrcFile, npmrcContent);
167
+ logger.message("Created .npmrc file for authentication.");
168
+ const packageJsonPath = `${opts.directory}/package.json`;
169
+ if (!fs.existsSync(packageJsonPath)) {
170
+ logger.stop("package.json not found in the specified directory.");
171
+ throw new Error("package.json not found in the specified directory.");
172
+ }
173
+ packageJsonOriginal = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
174
+ const packageJson = { ...packageJsonOriginal };
175
+ if (!packageJson.name) {
176
+ logger.stop("package.json does not have a name field.");
177
+ throw new Error("package.json does not have a name field.");
178
+ }
179
+ if (packageJson.name.startsWith("@") && !packageJson.name.startsWith(`@${registry.namespace}`)) {
180
+ throw new Error("If you are publishing to the bloks registry, the package.json shouldn't be scoped.");
181
+ }
182
+ if (!packageJson.name.startsWith(`@${registry.namespace}`)) {
183
+ packageJson.name = `@${registry.namespace}/${packageJson.name}`;
184
+ packageJson.private = false;
185
+ packageJson.files = ["dist"];
186
+ }
187
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
188
+ const { stdout } = await exec(manager.PUBLISH({ registry: REGISTRY_URL, npmrcDir: npmrcFile }), {
189
+ cwd: opts.directory,
190
+ });
191
+ const publishResult = JSON.parse(stdout);
192
+ if (publishResult.error) {
193
+ logger.stop(publishResult.error);
194
+ throw new Error(`Error publishing node: ${publishResult.error}`);
195
+ }
196
+ logger.stop(`Node published successfully \n Node: ${publishResult.id} \n Version: ${publishResult.version} \n Packed Size: ${publishResult.size} bytes / Unpacked Size: ${publishResult.unpackedSize} bytes \n Amount of files: ${publishResult.entryCount}`);
197
+ }
198
+ catch (error) {
199
+ if (fs.existsSync(npmrcFile))
200
+ fs.unlinkSync(npmrcFile);
201
+ logger.error(error.message);
202
+ }
203
+ finally {
204
+ if (fs.existsSync(npmrcFile))
205
+ fs.unlinkSync(npmrcFile);
206
+ if (packageJsonOriginal) {
207
+ const packageJsonPath = `${opts.directory}/package.json`;
208
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonOriginal, null, 2));
209
+ }
210
+ }
211
+ }
212
+ export default new Command()
213
+ .command("node")
214
+ .description("Publish a node to the bloks registry")
215
+ .option("-d, --directory <value>", "Directory to publish")
216
+ .option("-b, --build", "Run build before publishing")
217
+ .option("-r, --runtime <value>", "Publishing runtime (default: npm)")
218
+ .option("--bump <value>", "Version bump: patch, minor, major (default: patch)")
219
+ .argument("<node>", "Node name")
220
+ .action(async (node, options) => {
221
+ await trackCommandExecution({
222
+ command: "publish",
223
+ args: options,
224
+ execution: async () => {
225
+ if (!options.directory)
226
+ options.directory = process.cwd();
227
+ options.node = node;
228
+ await publish(options);
229
+ },
230
+ });
231
+ });
@@ -0,0 +1,4 @@
1
+ import { Command, type OptionValues } from "../../services/commander.js";
2
+ export declare function publish(opts: OptionValues): Promise<void>;
3
+ declare const _default: Command;
4
+ export default _default;
@@ -0,0 +1,165 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as p from "@clack/prompts";
4
+ import { Command, trackCommandExecution } from "../../services/commander.js";
5
+ import { BLOK_URL } from "../../services/constants.js";
6
+ import { tokenManager } from "../../services/local-token-manager.js";
7
+ import { isNonInteractive } from "../../services/non-interactive.js";
8
+ function findSimilarFiles(directory, searchTerm) {
9
+ const files = fs.readdirSync(directory);
10
+ return files
11
+ .filter((file) => file.endsWith(".json"))
12
+ .filter((file) => {
13
+ const fileName = file.toLowerCase().replace(".json", "");
14
+ const search = searchTerm.toLowerCase();
15
+ if (fileName === search)
16
+ return true;
17
+ if (fileName.startsWith(search))
18
+ return true;
19
+ const words = fileName.split(/[-_]/);
20
+ if (words.some((word) => word.startsWith(search)))
21
+ return true;
22
+ if (search.length >= 3) {
23
+ let matches = 0;
24
+ let searchIndex = 0;
25
+ for (let i = 0; i < fileName.length && searchIndex < search.length; i++) {
26
+ if (fileName[i] === search[searchIndex]) {
27
+ matches++;
28
+ searchIndex++;
29
+ }
30
+ }
31
+ return matches >= search.length * 0.5;
32
+ }
33
+ return false;
34
+ });
35
+ }
36
+ async function loadWorkflowFiles(directory) {
37
+ const workflowsDir = path.join(directory, "workflows/json");
38
+ if (!fs.existsSync(workflowsDir)) {
39
+ throw new Error("workflows/json directory not found");
40
+ }
41
+ const files = fs.readdirSync(workflowsDir);
42
+ return files
43
+ .filter((file) => file.endsWith(".json"))
44
+ .map((file) => {
45
+ const content = JSON.parse(fs.readFileSync(path.join(workflowsDir, file), "utf-8"));
46
+ return {
47
+ label: file.replace(".json", ""),
48
+ value: {
49
+ id: file.replace(".json", ""),
50
+ content: content,
51
+ },
52
+ };
53
+ });
54
+ }
55
+ async function publishWorkflow(token, workflow, name) {
56
+ const response = await fetch(`${BLOK_URL}/publish-workflow`, {
57
+ method: "POST",
58
+ headers: {
59
+ "Content-Type": "application/json",
60
+ Authorization: `Bearer ${token}`,
61
+ },
62
+ body: JSON.stringify({
63
+ workflow: workflow,
64
+ id: name,
65
+ }),
66
+ });
67
+ if (!response.ok)
68
+ throw new Error(response.statusText);
69
+ const responseJson = await response.json();
70
+ return responseJson;
71
+ }
72
+ export async function publish(opts) {
73
+ const token = tokenManager.getToken();
74
+ const logger = p.spinner();
75
+ try {
76
+ if (!token)
77
+ throw new Error("Authentication token not found. Please run 'blokctl login' before publishing.");
78
+ if (!opts.directory)
79
+ throw new Error("Directory is required.");
80
+ logger.start("Loading workflows...");
81
+ const workflowsDir = path.join(opts.directory, "workflows/json");
82
+ if (!fs.existsSync(workflowsDir)) {
83
+ throw new Error("workflows/json directory not found");
84
+ }
85
+ let workflow;
86
+ let workflowId = "";
87
+ if (opts.workflow) {
88
+ const workflowPath = path.join(workflowsDir, `${opts.workflow}.json`);
89
+ if (!fs.existsSync(workflowPath)) {
90
+ const similarFiles = findSimilarFiles(workflowsDir, opts.workflow);
91
+ if (similarFiles.length > 0) {
92
+ if (isNonInteractive()) {
93
+ throw new Error(`Workflow "${opts.workflow}" not found. Similar workflows: ${similarFiles.map((f) => f.replace(".json", "")).join(", ")}. Provide an exact workflow name in non-interactive mode.`);
94
+ }
95
+ logger.stop("Similar workflows found");
96
+ const selection = await p.select({
97
+ message: "Select a workflow to publish",
98
+ options: similarFiles.map((file) => ({
99
+ label: file.replace(".json", ""),
100
+ value: {
101
+ id: file.replace(".json", ""),
102
+ content: JSON.parse(fs.readFileSync(path.join(workflowsDir, file), "utf-8")),
103
+ },
104
+ })),
105
+ });
106
+ if (p.isCancel(selection)) {
107
+ throw new Error("Operation cancelled");
108
+ }
109
+ workflow = selection.content;
110
+ workflowId = selection.id;
111
+ }
112
+ else {
113
+ throw new Error(`Workflow with ID "${opts.workflow}" not found and no similar workflows found`);
114
+ }
115
+ }
116
+ else {
117
+ workflow = JSON.parse(fs.readFileSync(workflowPath, "utf-8"));
118
+ workflowId = opts.workflow;
119
+ }
120
+ }
121
+ else {
122
+ if (isNonInteractive()) {
123
+ throw new Error("Missing required argument <workflow> (non-interactive mode). Provide the workflow name as an argument.");
124
+ }
125
+ logger.stop("Select a workflow to publish");
126
+ const workflowOptions = await loadWorkflowFiles(opts.directory);
127
+ if (workflowOptions.length === 0) {
128
+ throw new Error("No workflows found in workflows/json directory");
129
+ }
130
+ const selection = await p.select({
131
+ message: "Select a workflow to publish",
132
+ options: workflowOptions,
133
+ });
134
+ if (p.isCancel(selection)) {
135
+ throw new Error("Operation cancelled");
136
+ }
137
+ workflow = selection.content;
138
+ workflowId = selection.id;
139
+ }
140
+ logger.start("Publishing workflow...");
141
+ const name = opts.name || workflowId;
142
+ await publishWorkflow(token, workflow, name);
143
+ logger.stop("Workflow published successfully");
144
+ }
145
+ catch (error) {
146
+ logger.error(error.message);
147
+ }
148
+ }
149
+ export default new Command()
150
+ .command("workflow")
151
+ .description("Publish a workflow")
152
+ .option("-d, --directory <value>", "Directory to publish")
153
+ .argument("<workflow>", "Workflow name")
154
+ .action(async (workflow, options) => {
155
+ await trackCommandExecution({
156
+ command: "publish workflow",
157
+ args: options,
158
+ execution: async () => {
159
+ options.workflow = workflow;
160
+ if (!options.directory)
161
+ options.directory = process.cwd();
162
+ await publish(options);
163
+ },
164
+ });
165
+ });
@@ -0,0 +1,17 @@
1
+ import { Command } from "../../services/commander.js";
2
+ interface SearchOptions {
3
+ noCache?: boolean;
4
+ useAI?: boolean;
5
+ }
6
+ export declare class SearchService {
7
+ private readonly MAX_TOKENS;
8
+ private readonly indexer;
9
+ private extractKeywords;
10
+ private estimateTokens;
11
+ private getRankedFilesFromIndex;
12
+ private getLimitedContext;
13
+ ask(question: string, options?: SearchOptions): Promise<void>;
14
+ private formatResponseText;
15
+ }
16
+ declare const _default: Command;
17
+ export default _default;