blokctl 0.2.11 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/create/project.js +63 -3
- package/dist/commands/create/utils/Examples.d.ts +3 -3
- package/dist/commands/create/utils/Examples.js +109 -13
- package/dist/commands/dev/index.js +50 -13
- package/dist/commands/generate/validators/WorkflowValidator.js +1 -1
- package/dist/commands/migrate/index.js +22 -0
- package/dist/commands/migrate/paths.d.ts +2 -0
- package/dist/commands/migrate/paths.js +267 -0
- package/dist/commands/migrate/workflows.d.ts +3 -0
- package/dist/commands/migrate/workflows.js +333 -0
- package/dist/commands/trace/index.js +6 -2
- package/dist/commands/trace/startStudio.d.ts +2 -0
- package/dist/commands/trace/startStudio.js +76 -11
- package/dist/index.js +1 -0
- package/dist/services/health-probe.d.ts +5 -0
- package/dist/services/health-probe.js +35 -0
- package/dist/services/runtime-detector.d.ts +3 -0
- package/dist/services/runtime-detector.js +21 -2
- package/dist/services/runtime-setup.d.ts +3 -0
- package/dist/services/runtime-setup.js +11 -2
- package/dist/studio-dist/assets/charts-Dh48HebV.js +68 -0
- package/dist/studio-dist/assets/graph-DWteCadQ.js +7 -0
- package/dist/studio-dist/assets/{icons-zP8LLgPh.js → icons-N5J4OhGx.js} +66 -51
- package/dist/studio-dist/assets/index-D6hOoOID.css +1 -0
- package/dist/studio-dist/assets/index-D_CdNmTc.js +42 -0
- package/dist/studio-dist/assets/react-vendor-l0sNRNKZ.js +1 -0
- package/dist/studio-dist/assets/tanstack-query-Day3Mt-4.js +17 -0
- package/dist/studio-dist/assets/tanstack-router-BB95iErN.js +25 -0
- package/dist/studio-dist/assets/{tanstack-table-DhwRvuH2.js → tanstack-table-Biem1hxK.js} +1 -1
- package/dist/studio-dist/favicon.svg +7 -4
- package/dist/studio-dist/index.html +19 -10
- package/package.json +15 -12
- package/dist/studio-dist/assets/charts-Dso0hPUR.js +0 -68
- package/dist/studio-dist/assets/graph-CsV2nWGn.js +0 -23
- package/dist/studio-dist/assets/index-CLyEkXMx.css +0 -1
- package/dist/studio-dist/assets/index-CNXFX_ar.js +0 -27
- package/dist/studio-dist/assets/react-vendor--Eh9ivFN.js +0 -17
- package/dist/studio-dist/assets/tanstack-query-CiM1U6F5.js +0 -1
- package/dist/studio-dist/assets/tanstack-router-Btjy0MKq.js +0 -25
|
@@ -4,6 +4,7 @@ import https from "node:https";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import * as p from "@clack/prompts";
|
|
7
|
+
import express from "express";
|
|
7
8
|
import open from "open";
|
|
8
9
|
import color from "picocolors";
|
|
9
10
|
import serveHandler from "serve-handler";
|
|
@@ -18,6 +19,10 @@ function resolveStaticPath() {
|
|
|
18
19
|
return workspace;
|
|
19
20
|
return null;
|
|
20
21
|
}
|
|
22
|
+
function autoDetectDbPath() {
|
|
23
|
+
const candidate = path.resolve(process.cwd(), ".blok", "trace.db");
|
|
24
|
+
return fs.existsSync(candidate) ? candidate : null;
|
|
25
|
+
}
|
|
21
26
|
async function checkBackendHealth(backendUrl) {
|
|
22
27
|
return new Promise((resolve) => {
|
|
23
28
|
const url = new URL("/__blok/health", backendUrl);
|
|
@@ -33,7 +38,7 @@ async function checkBackendHealth(backendUrl) {
|
|
|
33
38
|
});
|
|
34
39
|
}
|
|
35
40
|
function proxyRequest(req, res, backendUrl) {
|
|
36
|
-
const targetUrl = new URL(req.url, backendUrl);
|
|
41
|
+
const targetUrl = new URL(req.url ?? "/", backendUrl);
|
|
37
42
|
const client = targetUrl.protocol === "https:" ? https : http;
|
|
38
43
|
const proxyReq = client.request(targetUrl, {
|
|
39
44
|
method: req.method,
|
|
@@ -56,28 +61,83 @@ function proxyRequest(req, res, backendUrl) {
|
|
|
56
61
|
});
|
|
57
62
|
req.pipe(proxyReq, { end: true });
|
|
58
63
|
}
|
|
64
|
+
async function buildStandaloneApp(dbPath) {
|
|
65
|
+
const runner = (await import("@blokjs/runner"));
|
|
66
|
+
const { RunTracker, createStore, registerTraceRoutes } = runner;
|
|
67
|
+
const absoluteDb = path.resolve(dbPath);
|
|
68
|
+
const dir = path.dirname(absoluteDb);
|
|
69
|
+
if (dir && !fs.existsSync(dir)) {
|
|
70
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
const store = createStore({ type: "sqlite", sqlitePath: absoluteDb });
|
|
73
|
+
const tracker = new RunTracker(undefined, store);
|
|
74
|
+
RunTracker.instance = tracker;
|
|
75
|
+
const app = express();
|
|
76
|
+
app.use(express.json({ limit: "10mb" }));
|
|
77
|
+
const traceRouter = express.Router();
|
|
78
|
+
registerTraceRoutes(traceRouter, tracker);
|
|
79
|
+
app.use("/__blok", traceRouter);
|
|
80
|
+
return app;
|
|
81
|
+
}
|
|
59
82
|
export async function startStudio(options) {
|
|
60
83
|
const { port, url: backendUrl, open: shouldOpen } = options;
|
|
61
84
|
p.intro(color.bgCyan(color.black(" Blok Studio ")));
|
|
62
85
|
const staticPath = resolveStaticPath();
|
|
63
86
|
if (!staticPath) {
|
|
64
|
-
p.log.error(`Studio assets not found.\n
|
|
87
|
+
p.log.error(`Studio assets not found.\n Build them first: ${color.cyan("bun run --filter @blokjs/studio build")}`);
|
|
65
88
|
process.exit(1);
|
|
66
89
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
let mode = "proxy";
|
|
91
|
+
let dbPath = null;
|
|
92
|
+
if (options.db) {
|
|
93
|
+
mode = "standalone";
|
|
94
|
+
dbPath = path.resolve(options.db);
|
|
95
|
+
}
|
|
96
|
+
else if (options.standalone) {
|
|
97
|
+
mode = "standalone";
|
|
98
|
+
dbPath = autoDetectDbPath() ?? path.resolve(process.cwd(), ".blok", "trace.db");
|
|
74
99
|
}
|
|
75
100
|
else {
|
|
76
|
-
s.
|
|
101
|
+
const s = p.spinner();
|
|
102
|
+
s.start("Checking backend health...");
|
|
103
|
+
const healthy = await checkBackendHealth(backendUrl);
|
|
104
|
+
if (healthy) {
|
|
105
|
+
s.stop(color.green("Backend healthy"));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const auto = autoDetectDbPath();
|
|
109
|
+
if (auto) {
|
|
110
|
+
s.stop(color.green(`Backend not reachable; serving from ${color.cyan(path.relative(process.cwd(), auto))}`));
|
|
111
|
+
mode = "standalone";
|
|
112
|
+
dbPath = auto;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
s.stop(color.yellow("Backend not reachable"));
|
|
116
|
+
p.log.warn(`Blok backend not found at ${color.cyan(backendUrl)}\n` +
|
|
117
|
+
` Start it first: ${color.cyan("blokctl dev")}\n` +
|
|
118
|
+
` Or open a trace file directly: ${color.cyan("blokctl studio --db <path>")}`);
|
|
119
|
+
p.log.info("Starting Studio anyway — it will connect when the backend is up.");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
let standaloneApp = null;
|
|
124
|
+
if (mode === "standalone" && dbPath) {
|
|
125
|
+
try {
|
|
126
|
+
standaloneApp = await buildStandaloneApp(dbPath);
|
|
127
|
+
p.log.success(`Standalone mode · reading ${color.cyan(path.relative(process.cwd(), dbPath))}`);
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
p.log.error(`Failed to open trace file ${color.cyan(dbPath)}\n ${e.message}\n` +
|
|
131
|
+
` Make sure better-sqlite3 is available: ${color.cyan("npm install better-sqlite3")}`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
77
134
|
}
|
|
78
135
|
const server = http.createServer((req, res) => {
|
|
79
136
|
const url = req.url || "/";
|
|
80
137
|
if (url.startsWith("/__blok")) {
|
|
138
|
+
if (standaloneApp) {
|
|
139
|
+
return standaloneApp(req, res);
|
|
140
|
+
}
|
|
81
141
|
return proxyRequest(req, res, backendUrl);
|
|
82
142
|
}
|
|
83
143
|
serveHandler(req, res, {
|
|
@@ -95,7 +155,12 @@ export async function startStudio(options) {
|
|
|
95
155
|
studioUrl += `/runs/${encodeURIComponent(options.run)}`;
|
|
96
156
|
}
|
|
97
157
|
p.log.success(`Studio running at ${color.cyan(studioUrl)}`);
|
|
98
|
-
|
|
158
|
+
if (mode === "standalone" && dbPath) {
|
|
159
|
+
p.log.info(`Standalone · ${color.dim(`reading ${path.relative(process.cwd(), dbPath)}`)}`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
p.log.info(`Proxying to backend at ${color.dim(backendUrl)}`);
|
|
163
|
+
}
|
|
99
164
|
console.log(color.dim(" Press Ctrl+C to stop\n"));
|
|
100
165
|
if (shouldOpen) {
|
|
101
166
|
await open(studioUrl);
|
package/dist/index.js
CHANGED
|
@@ -168,6 +168,7 @@ async function main() {
|
|
|
168
168
|
program
|
|
169
169
|
.command("dev")
|
|
170
170
|
.description("Start the development server")
|
|
171
|
+
.option("--with-http-fallback", "Spawn SDKs on HTTP transport instead of gRPC. Use only when one of your SDKs lacks a working gRPC listener (e.g. PHP without RoadRunner). Removed in v0.4.0.")
|
|
171
172
|
.action(async (options) => {
|
|
172
173
|
await analytics.trackCommandExecution({
|
|
173
174
|
command: "dev",
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function tryConnect(host: string, port: number, timeoutMs: number): Promise<boolean>;
|
|
2
|
+
export declare function waitForGrpcPort(port: number, timeoutMs: number, proc?: {
|
|
3
|
+
exitCode: number | null;
|
|
4
|
+
on(event: "exit", listener: () => void): void;
|
|
5
|
+
}): Promise<boolean>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Socket } from "node:net";
|
|
2
|
+
export function tryConnect(host, port, timeoutMs) {
|
|
3
|
+
return new Promise((resolve) => {
|
|
4
|
+
const sock = new Socket();
|
|
5
|
+
const done = (ok) => {
|
|
6
|
+
sock.destroy();
|
|
7
|
+
resolve(ok);
|
|
8
|
+
};
|
|
9
|
+
sock.setTimeout(timeoutMs);
|
|
10
|
+
sock
|
|
11
|
+
.once("connect", () => done(true))
|
|
12
|
+
.once("timeout", () => done(false))
|
|
13
|
+
.once("error", () => done(false));
|
|
14
|
+
sock.connect(port, host);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function waitForGrpcPort(port, timeoutMs, proc) {
|
|
18
|
+
if (proc && proc.exitCode !== null)
|
|
19
|
+
return false;
|
|
20
|
+
const start = Date.now();
|
|
21
|
+
let exited = false;
|
|
22
|
+
proc?.on("exit", () => {
|
|
23
|
+
exited = true;
|
|
24
|
+
});
|
|
25
|
+
while (Date.now() - start < timeoutMs) {
|
|
26
|
+
if (exited)
|
|
27
|
+
return false;
|
|
28
|
+
if (await tryConnect("127.0.0.1", port, 500))
|
|
29
|
+
return true;
|
|
30
|
+
if (await tryConnect("::1", port, 500))
|
|
31
|
+
return true;
|
|
32
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
@@ -5,10 +5,12 @@ export interface RuntimeInfo {
|
|
|
5
5
|
version?: string;
|
|
6
6
|
installHint: string;
|
|
7
7
|
defaultPort: number;
|
|
8
|
+
defaultGrpcPort: number;
|
|
8
9
|
commands: string[];
|
|
9
10
|
toolchain: string;
|
|
10
11
|
installDeps: string;
|
|
11
12
|
startCmd: string;
|
|
13
|
+
grpcStartCmd?: string;
|
|
12
14
|
sdkDir: string;
|
|
13
15
|
secondaryTool?: {
|
|
14
16
|
name: string;
|
|
@@ -18,6 +20,7 @@ export interface RuntimeInfo {
|
|
|
18
20
|
installHint: string;
|
|
19
21
|
};
|
|
20
22
|
}
|
|
23
|
+
export declare function detectRr(): string | null;
|
|
21
24
|
export declare function detectRuntimes(): Promise<RuntimeInfo[]>;
|
|
22
25
|
export declare function getRuntimeDefinition(kind: string): Omit<RuntimeInfo, "available" | "version"> | undefined;
|
|
23
26
|
export declare function getAllRuntimeDefinitions(): Omit<RuntimeInfo, "available" | "version">[];
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import child_process from "node:child_process";
|
|
2
2
|
import util from "node:util";
|
|
3
3
|
const exec = util.promisify(child_process.exec);
|
|
4
|
+
export function detectRr() {
|
|
5
|
+
for (const bin of ["/opt/homebrew/bin/rr", "rr"]) {
|
|
6
|
+
try {
|
|
7
|
+
child_process.execSync(`${bin} --version`, { stdio: "ignore" });
|
|
8
|
+
return bin;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
4
15
|
const RUNTIME_DEFINITIONS = [
|
|
5
16
|
{
|
|
6
17
|
kind: "python3",
|
|
7
18
|
label: "Python 3",
|
|
8
19
|
installHint: "Install Python: https://python.org/downloads/",
|
|
9
20
|
defaultPort: 9007,
|
|
21
|
+
defaultGrpcPort: 10007,
|
|
10
22
|
commands: ["python3 --version"],
|
|
11
23
|
toolchain: "python3",
|
|
12
24
|
installDeps: "pip3 install -r requirements.txt",
|
|
@@ -18,6 +30,7 @@ const RUNTIME_DEFINITIONS = [
|
|
|
18
30
|
label: "Go",
|
|
19
31
|
installHint: "Install Go: https://go.dev/dl/",
|
|
20
32
|
defaultPort: 9001,
|
|
33
|
+
defaultGrpcPort: 10001,
|
|
21
34
|
commands: ["go version"],
|
|
22
35
|
toolchain: "go",
|
|
23
36
|
installDeps: "go mod download",
|
|
@@ -29,6 +42,7 @@ const RUNTIME_DEFINITIONS = [
|
|
|
29
42
|
label: "Rust",
|
|
30
43
|
installHint: "Install Rust: https://rustup.rs/",
|
|
31
44
|
defaultPort: 9002,
|
|
45
|
+
defaultGrpcPort: 10002,
|
|
32
46
|
commands: ["rustc --version"],
|
|
33
47
|
toolchain: "rustc + cargo",
|
|
34
48
|
installDeps: "cargo build --release",
|
|
@@ -40,6 +54,7 @@ const RUNTIME_DEFINITIONS = [
|
|
|
40
54
|
label: "Java",
|
|
41
55
|
installHint: "Install JDK 17+: https://adoptium.net/",
|
|
42
56
|
defaultPort: 9003,
|
|
57
|
+
defaultGrpcPort: 10003,
|
|
43
58
|
commands: [
|
|
44
59
|
"java --version",
|
|
45
60
|
"/opt/homebrew/opt/openjdk/bin/java --version",
|
|
@@ -60,6 +75,7 @@ const RUNTIME_DEFINITIONS = [
|
|
|
60
75
|
label: "C# / .NET",
|
|
61
76
|
installHint: "Install .NET SDK: https://dotnet.microsoft.com/download",
|
|
62
77
|
defaultPort: 9004,
|
|
78
|
+
defaultGrpcPort: 10004,
|
|
63
79
|
commands: ["dotnet --version"],
|
|
64
80
|
toolchain: "dotnet",
|
|
65
81
|
installDeps: "dotnet restore",
|
|
@@ -69,12 +85,14 @@ const RUNTIME_DEFINITIONS = [
|
|
|
69
85
|
{
|
|
70
86
|
kind: "php",
|
|
71
87
|
label: "PHP",
|
|
72
|
-
installHint: "Install PHP 8.2
|
|
88
|
+
installHint: "Install PHP 8.2+ + RoadRunner: https://php.net/downloads (rr: brew install roadrunner)",
|
|
73
89
|
defaultPort: 9005,
|
|
90
|
+
defaultGrpcPort: 10005,
|
|
74
91
|
commands: ["php --version"],
|
|
75
|
-
toolchain: "php + composer",
|
|
92
|
+
toolchain: "php + composer + rr",
|
|
76
93
|
installDeps: "composer install",
|
|
77
94
|
startCmd: "php bin/serve.php",
|
|
95
|
+
grpcStartCmd: "rr serve -c .rr.yaml --override grpc.listen=tcp://127.0.0.1:10005",
|
|
78
96
|
sdkDir: "php",
|
|
79
97
|
secondaryTool: {
|
|
80
98
|
name: "Composer",
|
|
@@ -87,6 +105,7 @@ const RUNTIME_DEFINITIONS = [
|
|
|
87
105
|
label: "Ruby",
|
|
88
106
|
installHint: "Install Ruby 3.2+: https://ruby-lang.org/en/downloads/",
|
|
89
107
|
defaultPort: 9006,
|
|
108
|
+
defaultGrpcPort: 10006,
|
|
90
109
|
commands: ["ruby --version", "/opt/homebrew/opt/ruby/bin/ruby --version"],
|
|
91
110
|
toolchain: "ruby + bundler",
|
|
92
111
|
installDeps: "bundle install",
|
|
@@ -6,10 +6,13 @@ type SpinnerHandler = {
|
|
|
6
6
|
};
|
|
7
7
|
export interface RuntimeConfig {
|
|
8
8
|
port: number;
|
|
9
|
+
grpcPort?: number;
|
|
9
10
|
startCmd: string;
|
|
11
|
+
grpcStartCmd?: string;
|
|
10
12
|
cwd: string;
|
|
11
13
|
kind: string;
|
|
12
14
|
label: string;
|
|
15
|
+
transport?: "grpc" | "http";
|
|
13
16
|
}
|
|
14
17
|
export interface TriggerConfig {
|
|
15
18
|
kind: string;
|
|
@@ -43,10 +43,13 @@ export async function setupRuntime(runtime, githubRepoLocal, projectDir, spinner
|
|
|
43
43
|
spinner.message(`${runtime.label} runtime setup complete.`);
|
|
44
44
|
return {
|
|
45
45
|
port: runtime.defaultPort,
|
|
46
|
+
grpcPort: runtime.defaultGrpcPort,
|
|
46
47
|
startCmd: startCmdOverride || runtime.startCmd,
|
|
48
|
+
grpcStartCmd: runtime.grpcStartCmd,
|
|
47
49
|
cwd: path.relative(projectDir, blokctlRuntimeDir),
|
|
48
50
|
kind: runtime.kind,
|
|
49
51
|
label: runtime.label,
|
|
52
|
+
transport: "grpc",
|
|
50
53
|
};
|
|
51
54
|
}
|
|
52
55
|
async function setupPython3(sdkDir, projectRuntimeDir, spinner) {
|
|
@@ -166,17 +169,23 @@ export function generateRuntimeEnvVars(runtimeConfigs) {
|
|
|
166
169
|
const envKey = rc.kind === "csharp" ? "CSHARP" : rc.kind.toUpperCase();
|
|
167
170
|
lines.push(`RUNTIME_${envKey}_HOST=localhost`);
|
|
168
171
|
lines.push(`RUNTIME_${envKey}_PORT=${rc.port}`);
|
|
172
|
+
if (rc.grpcPort !== undefined) {
|
|
173
|
+
lines.push(`RUNTIME_${envKey}_GRPC_PORT=${rc.grpcPort}`);
|
|
174
|
+
}
|
|
169
175
|
}
|
|
176
|
+
lines.push("BLOK_TRANSPORT=grpc");
|
|
170
177
|
return lines.join("\n");
|
|
171
178
|
}
|
|
172
179
|
export function generateSupervisordConfig(runtimeConfigs) {
|
|
173
180
|
let config = "";
|
|
174
181
|
for (const rc of runtimeConfigs) {
|
|
182
|
+
const cmd = rc.grpcStartCmd ?? rc.startCmd;
|
|
183
|
+
const grpcPortLine = rc.grpcPort !== undefined ? `,GRPC_PORT="${rc.grpcPort}"` : "";
|
|
175
184
|
config += `
|
|
176
185
|
[program:${rc.kind}_runtime]
|
|
177
|
-
command=${
|
|
186
|
+
command=${cmd}
|
|
178
187
|
directory=/app/${rc.cwd}
|
|
179
|
-
environment=PORT="${rc.port}",HOST="0.0.0.0"
|
|
188
|
+
environment=PORT="${rc.port}"${grpcPortLine},HOST="0.0.0.0",BLOK_TRANSPORT="grpc"
|
|
180
189
|
autostart=true
|
|
181
190
|
autorestart=true
|
|
182
191
|
stderr_logfile=/var/log/${rc.kind}.err.log
|