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,2 @@
1
+ import type { OptionValues } from "commander";
2
+ export declare function createWorkflow(opts: OptionValues, currentPath?: boolean): Promise<void>;
@@ -0,0 +1,109 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import * as p from "@clack/prompts";
4
+ import figlet from "figlet";
5
+ import fsExtra from "fs-extra";
6
+ import color from "picocolors";
7
+ import { isNonInteractive, resolveOrThrow } from "../../services/non-interactive.js";
8
+ import { workflow_template } from "./utils/Examples.js";
9
+ const HOME_DIR = `${os.homedir()}/.blok`;
10
+ const GITHUB_REPO_LOCAL = `${HOME_DIR}/blok`;
11
+ export async function createWorkflow(opts, currentPath = false) {
12
+ const nonInteractive = isNonInteractive();
13
+ const isDefault = opts.name !== undefined;
14
+ const skipPrompts = isDefault || nonInteractive;
15
+ let workflowName = opts.name ? opts.name : "";
16
+ if (!skipPrompts) {
17
+ console.log(figlet.textSync("blok CLI".toUpperCase(), {
18
+ font: "Digital",
19
+ horizontalLayout: "default",
20
+ verticalLayout: "default",
21
+ width: 100,
22
+ whitespaceBreak: true,
23
+ }));
24
+ console.log("");
25
+ const resolveWorkflowName = async () => {
26
+ if (workflowName !== "") {
27
+ return workflowName;
28
+ }
29
+ return (await p.text({
30
+ message: "Please provide a name for the workflow",
31
+ placeholder: "workflow-name",
32
+ defaultValue: "",
33
+ }));
34
+ };
35
+ p.intro(color.inverse(" Creating a new Workflow "));
36
+ const blokctlNode = await p.group({
37
+ workflowName: () => resolveWorkflowName(),
38
+ }, {
39
+ onCancel: () => {
40
+ p.cancel("Operation canceled.");
41
+ process.exit(0);
42
+ },
43
+ });
44
+ workflowName = blokctlNode.workflowName;
45
+ }
46
+ else if (nonInteractive) {
47
+ workflowName = resolveOrThrow("name", opts.name);
48
+ }
49
+ const s = p.spinner();
50
+ if (!skipPrompts)
51
+ s.start("Creating the workflow...");
52
+ try {
53
+ const mainDirExists = fsExtra.existsSync(GITHUB_REPO_LOCAL);
54
+ if (!mainDirExists)
55
+ throw new Error("The blok repository was not found. Please run 'npx blokctl@latest create project' to clone the repository.");
56
+ let dirPath = process.cwd();
57
+ if (!currentPath) {
58
+ const currentDir = `${process.cwd()}/src`;
59
+ const nodeProjectDirExists = fsExtra.existsSync(currentDir);
60
+ if (!nodeProjectDirExists)
61
+ throw new Error("ops1");
62
+ const currentWorkflowsDir = `${dirPath}/workflows/json`;
63
+ if (!skipPrompts) {
64
+ fsExtra.ensureDirSync(currentWorkflowsDir);
65
+ }
66
+ else {
67
+ const workflowDirExists = fsExtra.existsSync(currentWorkflowsDir);
68
+ if (!workflowDirExists)
69
+ throw new Error("ops1");
70
+ }
71
+ dirPath = path.join(currentWorkflowsDir, `${workflowName.replaceAll(" ", "-").toLowerCase()}.json`);
72
+ }
73
+ else {
74
+ dirPath = path.join(dirPath, `${workflowName.replaceAll(" ", "-").toLowerCase()}.json`);
75
+ }
76
+ if (!skipPrompts)
77
+ s.message("Creating workflow...");
78
+ if (!currentPath) {
79
+ const workflowDirExists = fsExtra.existsSync(dirPath);
80
+ if (workflowDirExists)
81
+ throw new Error("ops2");
82
+ }
83
+ const workflow_json = JSON.parse(workflow_template);
84
+ workflow_json.name = workflowName;
85
+ fsExtra.writeFileSync(dirPath, JSON.stringify(workflow_json, null, 2));
86
+ if (!skipPrompts)
87
+ s.stop(`Node "${workflowName}" created successfully.`);
88
+ if (!currentPath)
89
+ console.log("\nNavigate to the workflow directory by running: cd workflows/json");
90
+ console.log("For more documentation, visit https://blok.build/docs/d/core-concepts/workflows");
91
+ }
92
+ catch (error) {
93
+ if (!skipPrompts)
94
+ s.stop("An error occurred");
95
+ const message = error.message;
96
+ if (message === "ops1") {
97
+ console.log("Oops! It seems like you haven't created a project yet... or have you? 🤔\n" +
98
+ "If you already did, you can navigate to it using: cd project-name\n" +
99
+ "Otherwise, you can create a new project with: npx blokctl@latest create project");
100
+ }
101
+ if (message === "ops2") {
102
+ console.log("The workflow you are trying to create already exists in the project.\n" +
103
+ "Please use a different name, or delete the existing workflow to create a new one.");
104
+ }
105
+ if (message !== "ops1" && message !== "ops2") {
106
+ console.log(error.message);
107
+ }
108
+ }
109
+ }
@@ -0,0 +1,2 @@
1
+ import { type OptionValues } from "../../services/commander.js";
2
+ export declare function deploy(opts: OptionValues): Promise<boolean>;
@@ -0,0 +1,176 @@
1
+ import * as p from "@clack/prompts";
2
+ import fs from "fs-extra";
3
+ import color from "picocolors";
4
+ import { program, trackCommandExecution } from "../../services/commander.js";
5
+ import { isNonInteractive } from "../../services/non-interactive.js";
6
+ import { BLOK_URL } from "../../services/constants.js";
7
+ import { tokenManager } from "../../services/local-token-manager.js";
8
+ import { build as buildCommand } from "../build/index.js";
9
+ async function getDeploymentStatus(opts) {
10
+ const deploymentStatus = await fetch(`${BLOK_URL}/deploy-status/${opts.name}`, {
11
+ method: "GET",
12
+ headers: {
13
+ Authorization: `Bearer ${opts.token}`,
14
+ },
15
+ });
16
+ if (!deploymentStatus.ok)
17
+ throw new Error(deploymentStatus.statusText);
18
+ const deploymentStatusData = await deploymentStatus.json();
19
+ return deploymentStatusData.data;
20
+ }
21
+ export async function deploy(opts) {
22
+ const logger = p.spinner();
23
+ try {
24
+ logger.start("Deploying blok...");
25
+ const blokFile = `${opts.directory}/.blok.json`;
26
+ logger.message("Checking files...");
27
+ if (!fs.existsSync(opts.directory))
28
+ throw new Error(`Directory ${opts.directory} does not exist`);
29
+ if (!fs.existsSync(blokFile))
30
+ throw new Error(`.blok.json file not found in ${opts.directory}`);
31
+ logger.message("Loading .blok.json file...");
32
+ const json = fs.readJSONSync(blokFile);
33
+ const nameRegex = /^[a-z](?:[a-z-]*[a-z])?$/;
34
+ if (!nameRegex.test(opts.name))
35
+ throw new Error(`Invalid name ${opts.name}. Name should only contain letters and dashes.`);
36
+ if (!json.name || json.name === "") {
37
+ json.name = opts.name;
38
+ fs.writeJSONSync(blokFile, json, { spaces: 2 });
39
+ logger.message(`Updated .blok.json name to ${opts.name}`);
40
+ }
41
+ else {
42
+ if (json.name !== opts.name) {
43
+ logger.message(`.blok.json name is ${json.name}, but you provided ${opts.name}`);
44
+ if (opts.yes || isNonInteractive()) {
45
+ json.name = opts.name;
46
+ fs.writeJSONSync(blokFile, json, { spaces: 2 });
47
+ logger.message(`Updated .blok.json name to ${opts.name}`);
48
+ }
49
+ else {
50
+ const confirm = await p.confirm({
51
+ message: `Do you want to update the name in .blok.json to ${opts.name}?`,
52
+ initialValue: false,
53
+ });
54
+ if (!confirm)
55
+ throw new Error("Aborting deployment");
56
+ json.name = opts.name;
57
+ fs.writeJSONSync(blokFile, json, { spaces: 2 });
58
+ logger.message(`Updated .blok.json name to ${opts.name}`);
59
+ }
60
+ }
61
+ }
62
+ if (!json.lastBuild)
63
+ throw new Error("No last build found. Please build first.");
64
+ if (!json.lastBuild.id)
65
+ throw new Error("No last build id found. Please build first.");
66
+ if (!json.lastBuild.reason)
67
+ throw new Error("No last build status found. Please build first.");
68
+ if (json.lastBuild.reason !== "Succeeded")
69
+ throw new Error("Last build was not successful. Please build again.");
70
+ opts.id = json.lastBuild.id;
71
+ logger.message("Validating authentication...");
72
+ opts.token = tokenManager.getToken();
73
+ if (!opts.token)
74
+ throw new Error("No token found. Please login first.");
75
+ logger.message("Deployment started...");
76
+ const deployment = await fetch(`${BLOK_URL}/deploy/${opts.id}`, {
77
+ method: "POST",
78
+ headers: {
79
+ "Content-Type": "application/json",
80
+ Authorization: `Bearer ${opts.token}`,
81
+ },
82
+ body: JSON.stringify({
83
+ serviceName: opts.name,
84
+ access: opts.public ? "public" : "private",
85
+ }),
86
+ });
87
+ if (!deployment.ok)
88
+ throw new Error(deployment.statusText);
89
+ const deploymentData = await deployment.json();
90
+ if (deploymentData.error)
91
+ throw new Error(deploymentData.error);
92
+ let isReady = false;
93
+ let deploymentStatus = {};
94
+ let errorCount = 0;
95
+ do {
96
+ logger.message("Deploying...");
97
+ await new Promise((resolve) => setTimeout(resolve, 2000));
98
+ deploymentStatus = await getDeploymentStatus(opts);
99
+ isReady = true;
100
+ for (const condition of deploymentStatus?.conditions || []) {
101
+ if (condition.reason?.includes("Failed")) {
102
+ errorCount++;
103
+ if (errorCount > 3)
104
+ throw new Error(condition.message);
105
+ }
106
+ if (condition.status !== "True") {
107
+ isReady = false;
108
+ break;
109
+ }
110
+ }
111
+ } while (!isReady);
112
+ logger.message("Updating .blok.json with deployment results...");
113
+ deploymentData.data.status = deploymentStatus;
114
+ json.deployments = json.deployments || [];
115
+ json.deployments.push(deploymentData);
116
+ json.lastDeployment = deploymentData;
117
+ fs.writeJSONSync(blokFile, json, { spaces: 2 });
118
+ logger.message("Deployment completed");
119
+ logger.stop(`Blok deployed successfully! ${color.gray(`Version: ${deploymentStatus?.latestReadyRevisionName}`)}`);
120
+ p.log.success(`Service live at: ${color.greenBright(deploymentData?.data?.url)}`);
121
+ p.log.success(`Monitoring live at: ${color.greenBright(deploymentData?.data?.prometheusUrl)}`);
122
+ return true;
123
+ }
124
+ catch (error) {
125
+ logger.error(`Error: ${error}`);
126
+ return false;
127
+ }
128
+ }
129
+ const deployCmd = program
130
+ .command("deploy")
131
+ .description("Deploy blok")
132
+ .requiredOption("-n, --name <name>", "Name of the blok")
133
+ .option("--build", "Build before deploying", () => true)
134
+ .option("--public", "Make the blok public (default: false)", false)
135
+ .option("-d, --directory [value]", "Directory of the blok (defaults to current directory)", process.cwd())
136
+ .option("-y, --yes", "Auto-confirm name mismatch update")
137
+ .action(async (options) => {
138
+ if (options.build) {
139
+ await trackCommandExecution({
140
+ command: "deploy --build",
141
+ args: options,
142
+ execution: async () => {
143
+ await buildCommand(options);
144
+ },
145
+ });
146
+ }
147
+ await trackCommandExecution({
148
+ command: "deploy",
149
+ args: options,
150
+ execution: async () => {
151
+ await deploy(options);
152
+ },
153
+ });
154
+ });
155
+ deployCmd
156
+ .command(".")
157
+ .description("Deploy blok in the current directory")
158
+ .action(async (options) => {
159
+ if (options.build) {
160
+ await trackCommandExecution({
161
+ command: "deploy . --build",
162
+ args: options,
163
+ execution: async () => {
164
+ await buildCommand(options);
165
+ },
166
+ });
167
+ }
168
+ await trackCommandExecution({
169
+ command: "deploy .",
170
+ args: options,
171
+ execution: async () => {
172
+ options.directory = process.cwd();
173
+ await deploy(options);
174
+ },
175
+ });
176
+ });
@@ -0,0 +1,2 @@
1
+ import type { OptionValues } from "commander";
2
+ export declare function devProject(opts: OptionValues): Promise<void>;
@@ -0,0 +1,190 @@
1
+ import { spawn } from "node:child_process";
2
+ import http from "node:http";
3
+ import path from "node:path";
4
+ import fsExtra from "fs-extra";
5
+ import { readProjectConfig } from "../../services/runtime-setup.js";
6
+ const runningProcesses = [];
7
+ function spawnProcess(cmd, args, name, currentPath, cwd, env) {
8
+ const child = spawn(cmd, args, {
9
+ stdio: "inherit",
10
+ cwd: cwd || currentPath,
11
+ env: { ...process.env, BLOK_HMR: "true", NODE_ENV: "development", ...env },
12
+ detached: true,
13
+ });
14
+ console.log(` ${name} started (PID: ${child.pid})`);
15
+ runningProcesses.push(child);
16
+ child.on("exit", (code) => {
17
+ console.log(` ${name} exited with code ${code}`);
18
+ });
19
+ child.on("error", (err) => {
20
+ console.error(` ${name} error: ${err}`);
21
+ });
22
+ return child;
23
+ }
24
+ function killAllGroups(signal) {
25
+ const sig = signal === "SIGKILL" ? "9" : "15";
26
+ for (const child of runningProcesses) {
27
+ if (child.pid && child.exitCode === null) {
28
+ try {
29
+ spawn("kill", [`-${sig}`, "--", `-${child.pid}`], { stdio: "ignore" });
30
+ }
31
+ catch {
32
+ try {
33
+ child.kill(signal);
34
+ }
35
+ catch {
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ function waitForHealth(port, timeoutMs, proc) {
42
+ return new Promise((resolve) => {
43
+ if (proc && proc.exitCode !== null) {
44
+ resolve(false);
45
+ return;
46
+ }
47
+ const start = Date.now();
48
+ let done = false;
49
+ function finish(result) {
50
+ if (done)
51
+ return;
52
+ done = true;
53
+ clearInterval(interval);
54
+ resolve(result);
55
+ }
56
+ proc?.on("exit", () => finish(false));
57
+ const interval = setInterval(() => {
58
+ if (done)
59
+ return;
60
+ if (Date.now() - start > timeoutMs) {
61
+ finish(false);
62
+ return;
63
+ }
64
+ const req = http.get(`http://localhost:${port}/health`, (res) => {
65
+ res.resume();
66
+ if (res.statusCode === 200)
67
+ finish(true);
68
+ });
69
+ req.on("error", () => {
70
+ });
71
+ req.setTimeout(1000, () => req.destroy());
72
+ }, 500);
73
+ });
74
+ }
75
+ export async function devProject(opts) {
76
+ const currentPath = process.cwd();
77
+ console.log("Starting the development server...");
78
+ console.log("Current path: ", currentPath);
79
+ const config = readProjectConfig(currentPath);
80
+ const runtimeDefs = [];
81
+ if (config?.runtimes) {
82
+ for (const [, rt] of Object.entries(config.runtimes)) {
83
+ const cmdParts = rt.startCmd.split(" ");
84
+ const cmd = cmdParts[0];
85
+ const args = cmdParts.slice(1);
86
+ const runtimeCwd = path.resolve(currentPath, rt.cwd);
87
+ if (!fsExtra.existsSync(runtimeCwd)) {
88
+ console.log(` Warning: ${rt.label} runtime directory not found at ${rt.cwd}. Skipping.`);
89
+ continue;
90
+ }
91
+ runtimeDefs.push({
92
+ cmd,
93
+ args,
94
+ name: `${rt.label} Runtime (port ${rt.port})`,
95
+ cwd: runtimeCwd,
96
+ env: {
97
+ PORT: String(rt.port),
98
+ HOST: "0.0.0.0",
99
+ },
100
+ port: rt.port,
101
+ });
102
+ }
103
+ }
104
+ else {
105
+ const legacyPythonConfig = path.join(currentPath, ".blok", "runtimes", "python3", "nodemon.json");
106
+ if (fsExtra.existsSync(legacyPythonConfig)) {
107
+ runtimeDefs.push({
108
+ cmd: "npx",
109
+ args: [
110
+ "nodemon@3.1.9",
111
+ "--config",
112
+ "./.blok/runtimes/python3/nodemon.json",
113
+ "--exec",
114
+ "./.blok/runtimes/python3/python3_runtime/bin/python3",
115
+ "./.blok/runtimes/python3/server.py",
116
+ ],
117
+ name: "Python3 Runner (legacy)",
118
+ });
119
+ }
120
+ }
121
+ const healthChecks = [];
122
+ for (const def of runtimeDefs) {
123
+ const child = spawnProcess(def.cmd, def.args, def.name, currentPath, def.cwd, def.env);
124
+ if (def.port) {
125
+ healthChecks.push({ port: def.port, proc: child });
126
+ }
127
+ }
128
+ if (config?.triggers && Object.keys(config.triggers).length > 0) {
129
+ console.log("\nTrigger endpoints:");
130
+ for (const [, trigger] of Object.entries(config.triggers)) {
131
+ console.log(` ${trigger.label}: http://localhost:${trigger.port}/health-check`);
132
+ }
133
+ }
134
+ if (config?.runtimes && Object.keys(config.runtimes).length > 0) {
135
+ console.log("\nRuntime health endpoints:");
136
+ for (const [, rt] of Object.entries(config.runtimes)) {
137
+ console.log(` ${rt.label}: http://localhost:${rt.port}/health`);
138
+ }
139
+ }
140
+ if (healthChecks.length > 0) {
141
+ console.log("\nWaiting for runtimes to be ready...");
142
+ const maxWait = 120_000;
143
+ const results = await Promise.all(healthChecks.map((hc) => waitForHealth(hc.port, maxWait, hc.proc)));
144
+ const allReady = results.every(Boolean);
145
+ if (allReady) {
146
+ console.log("All runtimes ready.\n");
147
+ }
148
+ else {
149
+ const failedPorts = healthChecks.filter((_, i) => !results[i]).map((hc) => hc.port);
150
+ console.log(`Warning: Some runtimes did not become healthy: ports ${failedPorts.join(", ")}`);
151
+ console.log("Starting NodeJS runner anyway.\n");
152
+ }
153
+ }
154
+ if (config?.triggers && Object.keys(config.triggers).length > 0) {
155
+ console.log("Starting triggers...");
156
+ for (const [, trigger] of Object.entries(config.triggers)) {
157
+ const cmdParts = trigger.startCmd.split(" ");
158
+ const cmd = cmdParts[0];
159
+ const args = cmdParts.slice(1);
160
+ if (cmd === "bun" && !args.includes("--watch")) {
161
+ args.unshift("--watch");
162
+ }
163
+ spawnProcess(cmd, args, `${trigger.label} (port ${trigger.port})`, currentPath, undefined, {
164
+ PORT: String(trigger.port),
165
+ });
166
+ }
167
+ }
168
+ else {
169
+ spawnProcess("bun", ["--watch", "run", "src/index.ts"], "Blok Runner", currentPath);
170
+ }
171
+ const keepAlive = setInterval(() => { }, 60_000);
172
+ let stopping = false;
173
+ function shutdown() {
174
+ if (stopping)
175
+ return;
176
+ stopping = true;
177
+ console.log("\nStopping processes...");
178
+ clearInterval(keepAlive);
179
+ killAllGroups("SIGTERM");
180
+ setTimeout(() => {
181
+ killAllGroups("SIGKILL");
182
+ process.exit(0);
183
+ }, 3000);
184
+ }
185
+ process.on("SIGINT", shutdown);
186
+ process.on("SIGTERM", shutdown);
187
+ process.on("exit", () => {
188
+ killAllGroups("SIGKILL");
189
+ });
190
+ }
@@ -0,0 +1,61 @@
1
+ export type GenerationType = "node" | "workflow" | "trigger";
2
+ export interface GenerationEvent {
3
+ id: string;
4
+ timestamp: string;
5
+ type: GenerationType;
6
+ subtype: string;
7
+ name: string;
8
+ success: boolean;
9
+ attempts: number;
10
+ durationMs: number;
11
+ errors: string[];
12
+ promptVersion: string;
13
+ }
14
+ export interface GenerationStats {
15
+ totalGenerations: number;
16
+ successCount: number;
17
+ failureCount: number;
18
+ successRate: number;
19
+ averageAttempts: number;
20
+ averageDurationMs: number;
21
+ topErrors: Array<{
22
+ pattern: string;
23
+ count: number;
24
+ }>;
25
+ byType: Record<GenerationType, TypeStats>;
26
+ }
27
+ export interface TypeStats {
28
+ total: number;
29
+ success: number;
30
+ failure: number;
31
+ successRate: number;
32
+ averageAttempts: number;
33
+ }
34
+ export declare class GenerationAnalytics {
35
+ private events;
36
+ private static instance;
37
+ static getInstance(): GenerationAnalytics;
38
+ recordEvent(event: Omit<GenerationEvent, "id" | "timestamp">): GenerationEvent;
39
+ startTimer(): () => number;
40
+ getStats(): GenerationStats;
41
+ private getTypeStats;
42
+ private getTopErrors;
43
+ private normalizeErrorPattern;
44
+ getEvents(filter?: {
45
+ type?: GenerationType;
46
+ success?: boolean;
47
+ since?: string;
48
+ }): GenerationEvent[];
49
+ getFirstAttemptSuccessRate(): number;
50
+ getSuccessRateByPromptVersion(): Record<string, {
51
+ total: number;
52
+ success: number;
53
+ rate: number;
54
+ }>;
55
+ toJSON(): string;
56
+ fromJSON(json: string): void;
57
+ clear(): void;
58
+ static resetInstance(): void;
59
+ private generateId;
60
+ }
61
+ export default GenerationAnalytics;