@blokjs/lsp-server 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.
- package/CHANGELOG.md +15 -0
- package/dist/completion.d.ts +11 -0
- package/dist/completion.js +269 -0
- package/dist/completion.js.map +1 -0
- package/dist/constants.d.ts +37 -0
- package/dist/constants.js +161 -0
- package/dist/constants.js.map +1 -0
- package/dist/diagnostics.d.ts +17 -0
- package/dist/diagnostics.js +466 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/hover.d.ts +12 -0
- package/dist/hover.js +118 -0
- package/dist/hover.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +130 -0
- package/dist/server.js.map +1 -0
- package/editors/emacs-lsp.el +21 -0
- package/editors/helix-languages.toml +12 -0
- package/editors/neovim.lua +59 -0
- package/editors/sublime-lsp.json +16 -0
- package/package.json +40 -0
- package/src/__tests__/completion.test.ts +184 -0
- package/src/__tests__/constants.test.ts +142 -0
- package/src/__tests__/diagnostics.test.ts +513 -0
- package/src/__tests__/hover.test.ts +227 -0
- package/src/completion.ts +308 -0
- package/src/constants.ts +194 -0
- package/src/diagnostics.ts +493 -0
- package/src/hover.ts +135 -0
- package/src/server.ts +162 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { getHover } from "../hover";
|
|
3
|
+
|
|
4
|
+
describe("WorkflowHoverProvider (LSP)", () => {
|
|
5
|
+
describe("trigger type hover", () => {
|
|
6
|
+
it("should show hover for http trigger key", () => {
|
|
7
|
+
const text = '{\n "trigger": {\n "http": {\n "method": "GET"\n }\n }\n}';
|
|
8
|
+
// Line 2: ' "http": {' - cursor on "http"
|
|
9
|
+
const hover = getHover(text, 2, 6);
|
|
10
|
+
expect(hover).not.toBeNull();
|
|
11
|
+
expect(hover!.contents).toHaveProperty("value");
|
|
12
|
+
const value = (hover!.contents as { value: string }).value;
|
|
13
|
+
expect(value).toContain("HTTP Trigger");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should show hover for cron trigger key", () => {
|
|
17
|
+
const text = '{\n "trigger": {\n "cron": {\n "schedule": "* * * * *"\n }\n }\n}';
|
|
18
|
+
const hover = getHover(text, 2, 6);
|
|
19
|
+
expect(hover).not.toBeNull();
|
|
20
|
+
const value = (hover!.contents as { value: string }).value;
|
|
21
|
+
expect(value).toContain("Cron Trigger");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should show hover for queue trigger key", () => {
|
|
25
|
+
const text = '{\n "trigger": {\n "queue": {\n "provider": "kafka"\n }\n }\n}';
|
|
26
|
+
const hover = getHover(text, 2, 6);
|
|
27
|
+
expect(hover).not.toBeNull();
|
|
28
|
+
const value = (hover!.contents as { value: string }).value;
|
|
29
|
+
expect(value).toContain("Queue Trigger");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should show hover for webhook trigger key", () => {
|
|
33
|
+
const text = '{\n "trigger": {\n "webhook": {\n "source": "github"\n }\n }\n}';
|
|
34
|
+
const hover = getHover(text, 2, 6);
|
|
35
|
+
expect(hover).not.toBeNull();
|
|
36
|
+
const value = (hover!.contents as { value: string }).value;
|
|
37
|
+
expect(value).toContain("Webhook Trigger");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should show hover for websocket trigger key", () => {
|
|
41
|
+
const text = '{\n "trigger": {\n "websocket": {\n "path": "/ws"\n }\n }\n}';
|
|
42
|
+
const hover = getHover(text, 2, 6);
|
|
43
|
+
expect(hover).not.toBeNull();
|
|
44
|
+
const value = (hover!.contents as { value: string }).value;
|
|
45
|
+
expect(value).toContain("WebSocket Trigger");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should show hover for sse trigger key", () => {
|
|
49
|
+
const text = '{\n "trigger": {\n "sse": {\n "path": "/events"\n }\n }\n}';
|
|
50
|
+
const hover = getHover(text, 2, 6);
|
|
51
|
+
expect(hover).not.toBeNull();
|
|
52
|
+
const value = (hover!.contents as { value: string }).value;
|
|
53
|
+
expect(value).toContain("SSE Trigger");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("workflow field hover", () => {
|
|
58
|
+
it("should show hover for name field", () => {
|
|
59
|
+
const text = '{\n "name": "my-workflow"\n}';
|
|
60
|
+
const hover = getHover(text, 1, 4);
|
|
61
|
+
expect(hover).not.toBeNull();
|
|
62
|
+
const value = (hover!.contents as { value: string }).value;
|
|
63
|
+
expect(value).toContain("Workflow Name");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should show hover for version field", () => {
|
|
67
|
+
const text = '{\n "version": "1.0.0"\n}';
|
|
68
|
+
const hover = getHover(text, 1, 5);
|
|
69
|
+
expect(hover).not.toBeNull();
|
|
70
|
+
const value = (hover!.contents as { value: string }).value;
|
|
71
|
+
expect(value).toContain("Workflow Version");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should show hover for steps field", () => {
|
|
75
|
+
const text = '{\n "steps": []\n}';
|
|
76
|
+
const hover = getHover(text, 1, 5);
|
|
77
|
+
expect(hover).not.toBeNull();
|
|
78
|
+
const value = (hover!.contents as { value: string }).value;
|
|
79
|
+
expect(value).toContain("Workflow Steps");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should show hover for nodes field", () => {
|
|
83
|
+
const text = '{\n "nodes": {}\n}';
|
|
84
|
+
const hover = getHover(text, 1, 5);
|
|
85
|
+
expect(hover).not.toBeNull();
|
|
86
|
+
const value = (hover!.contents as { value: string }).value;
|
|
87
|
+
expect(value).toContain("Node Configurations");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should show hover for inputs field", () => {
|
|
91
|
+
const text = '{\n "nodes": {\n "step1": {\n "inputs": {}\n }\n }\n}';
|
|
92
|
+
const hover = getHover(text, 3, 8);
|
|
93
|
+
expect(hover).not.toBeNull();
|
|
94
|
+
const value = (hover!.contents as { value: string }).value;
|
|
95
|
+
expect(value).toContain("Node Inputs");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should show hover for conditions field", () => {
|
|
99
|
+
const text = '{\n "nodes": {\n "router": {\n "conditions": []\n }\n }\n}';
|
|
100
|
+
const hover = getHover(text, 3, 10);
|
|
101
|
+
expect(hover).not.toBeNull();
|
|
102
|
+
const value = (hover!.contents as { value: string }).value;
|
|
103
|
+
expect(value).toContain("Conditional Branches");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("step field hover", () => {
|
|
108
|
+
it("should show hover for node field in step", () => {
|
|
109
|
+
const text = '{\n "steps": [\n {\n "node": "@blokjs/api-call"\n }\n ]\n}';
|
|
110
|
+
const hover = getHover(text, 3, 8);
|
|
111
|
+
expect(hover).not.toBeNull();
|
|
112
|
+
const value = (hover!.contents as { value: string }).value;
|
|
113
|
+
expect(value).toContain("Step Node Reference");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should show hover for type field in step", () => {
|
|
117
|
+
const text = '{\n "steps": [\n {\n "type": "module"\n }\n ]\n}';
|
|
118
|
+
const hover = getHover(text, 3, 8);
|
|
119
|
+
expect(hover).not.toBeNull();
|
|
120
|
+
const value = (hover!.contents as { value: string }).value;
|
|
121
|
+
expect(value).toContain("Step Type");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should show hover for runtime field in step", () => {
|
|
125
|
+
const text = '{\n "steps": [\n {\n "runtime": "python3"\n }\n ]\n}';
|
|
126
|
+
const hover = getHover(text, 3, 9);
|
|
127
|
+
expect(hover).not.toBeNull();
|
|
128
|
+
const value = (hover!.contents as { value: string }).value;
|
|
129
|
+
expect(value).toContain("Step Runtime");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("value hover", () => {
|
|
134
|
+
it("should show hover for HTTP method value", () => {
|
|
135
|
+
const text = '{\n "trigger": {\n "http": {\n "method": "POST"\n }\n }\n}';
|
|
136
|
+
// "POST" is at position after "method": "
|
|
137
|
+
const hover = getHover(text, 3, 20);
|
|
138
|
+
expect(hover).not.toBeNull();
|
|
139
|
+
const value = (hover!.contents as { value: string }).value;
|
|
140
|
+
expect(value).toContain("HTTP Method: POST");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should show hover for runtime type value", () => {
|
|
144
|
+
const text = '{\n "steps": [\n {\n "type": "runtime.go"\n }\n ]\n}';
|
|
145
|
+
const hover = getHover(text, 3, 18);
|
|
146
|
+
expect(hover).not.toBeNull();
|
|
147
|
+
const value = (hover!.contents as { value: string }).value;
|
|
148
|
+
expect(value).toContain("Runtime Type: go");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should show hover for @blokjs/api-call value", () => {
|
|
152
|
+
const text = '{\n "steps": [\n {\n "node": "@blokjs/api-call"\n }\n ]\n}';
|
|
153
|
+
const hover = getHover(text, 3, 18);
|
|
154
|
+
expect(hover).not.toBeNull();
|
|
155
|
+
const value = (hover!.contents as { value: string }).value;
|
|
156
|
+
expect(value).toContain("api-call");
|
|
157
|
+
expect(value).toContain("HTTP API calls");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should show hover for @blokjs/if-else value", () => {
|
|
161
|
+
const text = '{\n "steps": [\n {\n "node": "@blokjs/if-else"\n }\n ]\n}';
|
|
162
|
+
const hover = getHover(text, 3, 18);
|
|
163
|
+
expect(hover).not.toBeNull();
|
|
164
|
+
const value = (hover!.contents as { value: string }).value;
|
|
165
|
+
expect(value).toContain("if-else");
|
|
166
|
+
expect(value).toContain("Conditional");
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("no hover", () => {
|
|
171
|
+
it("should return null for unknown keys", () => {
|
|
172
|
+
const text = '{\n "unknown_key": "value"\n}';
|
|
173
|
+
const hover = getHover(text, 1, 5);
|
|
174
|
+
expect(hover).toBeNull();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should return null for plain string values", () => {
|
|
178
|
+
const text = '{\n "name": "my-workflow"\n}';
|
|
179
|
+
// Hover on the value "my-workflow"
|
|
180
|
+
const hover = getHover(text, 1, 14);
|
|
181
|
+
expect(hover).toBeNull();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should return null when cursor is not on a quoted string", () => {
|
|
185
|
+
const text = '{\n "steps": []\n}';
|
|
186
|
+
// Hover on []
|
|
187
|
+
const hover = getHover(text, 1, 12);
|
|
188
|
+
expect(hover).toBeNull();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should return null for empty line", () => {
|
|
192
|
+
const text = "{\n\n}";
|
|
193
|
+
const hover = getHover(text, 1, 0);
|
|
194
|
+
expect(hover).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe("hover includes examples", () => {
|
|
199
|
+
it("should include code example for trigger types with examples", () => {
|
|
200
|
+
const text = '{\n "trigger": {\n "http": {}\n }\n}';
|
|
201
|
+
const hover = getHover(text, 2, 6);
|
|
202
|
+
expect(hover).not.toBeNull();
|
|
203
|
+
const value = (hover!.contents as { value: string }).value;
|
|
204
|
+
expect(value).toContain("```json");
|
|
205
|
+
expect(value).toContain("method");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should include code example for inputs field", () => {
|
|
209
|
+
const text = '{\n "inputs": {}\n}';
|
|
210
|
+
const hover = getHover(text, 1, 5);
|
|
211
|
+
expect(hover).not.toBeNull();
|
|
212
|
+
const value = (hover!.contents as { value: string }).value;
|
|
213
|
+
expect(value).toContain("```json");
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe("hover range", () => {
|
|
218
|
+
it("should return correct range for hovered word", () => {
|
|
219
|
+
const text = '{\n "name": "my-workflow"\n}';
|
|
220
|
+
const hover = getHover(text, 1, 4);
|
|
221
|
+
expect(hover).not.toBeNull();
|
|
222
|
+
expect(hover!.range).toBeDefined();
|
|
223
|
+
expect(hover!.range!.start.line).toBe(1);
|
|
224
|
+
expect(hover!.range!.end.line).toBe(1);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
});
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { type CompletionItem, CompletionItemKind, InsertTextFormat, MarkupKind } from "vscode-languageserver";
|
|
2
|
+
import {
|
|
3
|
+
NODE_PACKAGES,
|
|
4
|
+
PUBSUB_PROVIDERS,
|
|
5
|
+
QUEUE_PROVIDERS,
|
|
6
|
+
VALID_HTTP_METHODS,
|
|
7
|
+
VALID_RUNTIMES,
|
|
8
|
+
VALID_STEP_TYPES,
|
|
9
|
+
VALID_TRIGGERS,
|
|
10
|
+
WEBHOOK_SOURCES,
|
|
11
|
+
} from "./constants";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Provides contextual auto-completion for Blok workflow JSON files via LSP.
|
|
15
|
+
*
|
|
16
|
+
* Offers completions for:
|
|
17
|
+
* - Trigger types, HTTP methods, step types, runtime kinds
|
|
18
|
+
* - Queue/pubsub providers, webhook sources
|
|
19
|
+
* - Node packages, condition types
|
|
20
|
+
* - Top-level and context-specific keys
|
|
21
|
+
*/
|
|
22
|
+
export function getCompletions(text: string, offset: number): CompletionItem[] {
|
|
23
|
+
const lines = text.split("\n");
|
|
24
|
+
const pos = offsetToLineChar(text, offset);
|
|
25
|
+
const lineText = lines[pos.line] || "";
|
|
26
|
+
const textBefore = lineText.substring(0, pos.character);
|
|
27
|
+
|
|
28
|
+
// Detect which key we're providing a value for
|
|
29
|
+
const keyMatch = textBefore.match(/"(\w+)"\s*:\s*"?$/);
|
|
30
|
+
const parentKey = keyMatch?.[1];
|
|
31
|
+
|
|
32
|
+
// Detect enclosing context
|
|
33
|
+
const context = detectContext(text, offset);
|
|
34
|
+
|
|
35
|
+
const items: CompletionItem[] = [];
|
|
36
|
+
|
|
37
|
+
// Trigger type completions
|
|
38
|
+
if (context === "trigger" || parentKey === "trigger") {
|
|
39
|
+
items.push(...createTriggerTypeCompletions());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// HTTP method completions
|
|
43
|
+
if (parentKey === "method" && (context === "trigger" || context === "http")) {
|
|
44
|
+
items.push(...createHttpMethodCompletions());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Step type completions
|
|
48
|
+
if (parentKey === "type" && context === "steps") {
|
|
49
|
+
items.push(...createStepTypeCompletions());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Runtime completions
|
|
53
|
+
if (parentKey === "runtime") {
|
|
54
|
+
items.push(...createRuntimeCompletions());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Node package completions
|
|
58
|
+
if (parentKey === "node") {
|
|
59
|
+
items.push(...createNodeCompletions());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Queue provider completions
|
|
63
|
+
if (parentKey === "provider" && context === "queue") {
|
|
64
|
+
items.push(...createQueueProviderCompletions());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Pubsub provider completions
|
|
68
|
+
if (parentKey === "provider" && context === "pubsub") {
|
|
69
|
+
items.push(...createPubsubProviderCompletions());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Webhook source completions
|
|
73
|
+
if (parentKey === "source" && context === "webhook") {
|
|
74
|
+
items.push(...createWebhookSourceCompletions());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Condition type completions
|
|
78
|
+
if (parentKey === "type" && context === "conditions") {
|
|
79
|
+
items.push(...createConditionTypeCompletions());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Top-level key completions
|
|
83
|
+
if (textBefore.match(/^\s*"$/)) {
|
|
84
|
+
items.push(...createTopLevelKeyCompletions(context));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return items;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function detectContext(text: string, offset: number): string {
|
|
91
|
+
let depth = 0;
|
|
92
|
+
for (let i = offset - 1; i >= 0; i--) {
|
|
93
|
+
if (text[i] === "}" || text[i] === "]") depth++;
|
|
94
|
+
if (text[i] === "{" || text[i] === "[") {
|
|
95
|
+
if (depth === 0) {
|
|
96
|
+
const before = text.substring(Math.max(0, i - 100), i);
|
|
97
|
+
const keyMatch = before.match(/"(\w+)"\s*:\s*$/);
|
|
98
|
+
if (keyMatch) return keyMatch[1];
|
|
99
|
+
const arrayMatch = before.match(/"(\w+)"\s*:\s*\[[\s\S]*$/);
|
|
100
|
+
if (arrayMatch) return arrayMatch[1];
|
|
101
|
+
}
|
|
102
|
+
depth--;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return "root";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function offsetToLineChar(text: string, offset: number): { line: number; character: number } {
|
|
109
|
+
let line = 0;
|
|
110
|
+
let character = 0;
|
|
111
|
+
for (let i = 0; i < offset && i < text.length; i++) {
|
|
112
|
+
if (text[i] === "\n") {
|
|
113
|
+
line++;
|
|
114
|
+
character = 0;
|
|
115
|
+
} else {
|
|
116
|
+
character++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { line, character };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function createTriggerTypeCompletions(): CompletionItem[] {
|
|
123
|
+
const triggers: Array<{ label: string; detail: string; docs: string }> = [
|
|
124
|
+
{ label: "http", detail: "HTTP trigger", docs: "Trigger on HTTP requests (GET, POST, PUT, DELETE)" },
|
|
125
|
+
{ label: "grpc", detail: "gRPC trigger", docs: "Trigger on gRPC method calls" },
|
|
126
|
+
{ label: "manual", detail: "Manual trigger", docs: "No auto-trigger, invoke programmatically" },
|
|
127
|
+
{ label: "cron", detail: "Cron trigger", docs: "Scheduled execution with cron expressions" },
|
|
128
|
+
{ label: "queue", detail: "Queue trigger", docs: "Message queue consumer (Kafka, RabbitMQ, SQS)" },
|
|
129
|
+
{ label: "pubsub", detail: "Pub/Sub trigger", docs: "Pub/Sub subscriber (GCP, AWS, Azure, Redis, NATS)" },
|
|
130
|
+
{ label: "worker", detail: "Worker trigger", docs: "Background job processing with retries" },
|
|
131
|
+
{ label: "webhook", detail: "Webhook trigger", docs: "External webhook events (GitHub, Stripe, Shopify)" },
|
|
132
|
+
{ label: "websocket", detail: "WebSocket trigger", docs: "Real-time bidirectional communication" },
|
|
133
|
+
{ label: "sse", detail: "SSE trigger", docs: "Server-Sent Events streaming" },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
return triggers.map((t) => ({
|
|
137
|
+
label: t.label,
|
|
138
|
+
kind: CompletionItemKind.Enum,
|
|
139
|
+
detail: t.detail,
|
|
140
|
+
documentation: { kind: MarkupKind.Markdown, value: t.docs },
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function createHttpMethodCompletions(): CompletionItem[] {
|
|
145
|
+
const methods = [
|
|
146
|
+
{ label: "GET", docs: "Retrieve resources" },
|
|
147
|
+
{ label: "POST", docs: "Create resources" },
|
|
148
|
+
{ label: "PUT", docs: "Replace resources" },
|
|
149
|
+
{ label: "DELETE", docs: "Delete resources" },
|
|
150
|
+
{ label: "PATCH", docs: "Partially update resources" },
|
|
151
|
+
{ label: "ANY", docs: "Match any HTTP method" },
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
return methods.map((m) => ({
|
|
155
|
+
label: m.label,
|
|
156
|
+
kind: CompletionItemKind.EnumMember,
|
|
157
|
+
documentation: m.docs,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function createStepTypeCompletions(): CompletionItem[] {
|
|
162
|
+
const types = [
|
|
163
|
+
{ label: "module", docs: "Node from npm package (e.g., @blokjs/api-call)", priority: "1" },
|
|
164
|
+
{ label: "local", docs: "Node defined locally in the project (e.g., ./nodes/my-node)", priority: "2" },
|
|
165
|
+
{ label: "runtime.nodejs", docs: "Execute using Node.js runtime adapter", priority: "3" },
|
|
166
|
+
{ label: "runtime.python3", docs: "Execute using Python 3 runtime adapter (via gRPC)", priority: "4" },
|
|
167
|
+
{ label: "runtime.go", docs: "Execute using Go runtime adapter (Docker container)", priority: "5" },
|
|
168
|
+
{ label: "runtime.java", docs: "Execute using Java runtime adapter (Docker container)", priority: "6" },
|
|
169
|
+
{ label: "runtime.rust", docs: "Execute using Rust runtime adapter (Docker/WASM)", priority: "7" },
|
|
170
|
+
{ label: "runtime.php", docs: "Execute using PHP runtime adapter (Docker container)", priority: "8" },
|
|
171
|
+
{ label: "runtime.csharp", docs: "Execute using C#/.NET runtime adapter", priority: "9" },
|
|
172
|
+
{ label: "runtime.ruby", docs: "Execute using Ruby runtime adapter", priority: "a" },
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
return types.map((t) => ({
|
|
176
|
+
label: t.label,
|
|
177
|
+
kind: CompletionItemKind.EnumMember,
|
|
178
|
+
documentation: t.docs,
|
|
179
|
+
sortText: t.priority,
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function createRuntimeCompletions(): CompletionItem[] {
|
|
184
|
+
const runtimes = [
|
|
185
|
+
{ label: "nodejs", docs: "Node.js in-process execution (fastest)" },
|
|
186
|
+
{ label: "bun", docs: "Bun runtime execution" },
|
|
187
|
+
{ label: "python3", docs: "Python 3 via gRPC protocol" },
|
|
188
|
+
{ label: "go", docs: "Go via Docker container" },
|
|
189
|
+
{ label: "java", docs: "Java via Docker container" },
|
|
190
|
+
{ label: "rust", docs: "Rust via Docker container or WASM" },
|
|
191
|
+
{ label: "php", docs: "PHP via Docker container" },
|
|
192
|
+
{ label: "csharp", docs: "C#/.NET via Docker container" },
|
|
193
|
+
{ label: "ruby", docs: "Ruby via Docker container" },
|
|
194
|
+
{ label: "docker", docs: "Generic Docker container runtime" },
|
|
195
|
+
{ label: "wasm", docs: "WebAssembly runtime" },
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
return runtimes.map((r) => ({
|
|
199
|
+
label: r.label,
|
|
200
|
+
kind: CompletionItemKind.EnumMember,
|
|
201
|
+
documentation: r.docs,
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function createNodeCompletions(): CompletionItem[] {
|
|
206
|
+
return NODE_PACKAGES.map((n) => ({
|
|
207
|
+
label: n.name,
|
|
208
|
+
kind: CompletionItemKind.Module,
|
|
209
|
+
documentation: n.description,
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function createQueueProviderCompletions(): CompletionItem[] {
|
|
214
|
+
const providers = [
|
|
215
|
+
{ label: "kafka", docs: "Apache Kafka distributed event streaming" },
|
|
216
|
+
{ label: "rabbitmq", docs: "RabbitMQ message broker (AMQP)" },
|
|
217
|
+
{ label: "sqs", docs: "AWS Simple Queue Service" },
|
|
218
|
+
{ label: "redis", docs: "Redis-based queue (BullMQ)" },
|
|
219
|
+
{ label: "beanstalk", docs: "Beanstalk work queue" },
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
return providers.map((p) => ({
|
|
223
|
+
label: p.label,
|
|
224
|
+
kind: CompletionItemKind.EnumMember,
|
|
225
|
+
documentation: p.docs,
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function createPubsubProviderCompletions(): CompletionItem[] {
|
|
230
|
+
const providers = [
|
|
231
|
+
{ label: "gcp", docs: "Google Cloud Pub/Sub" },
|
|
232
|
+
{ label: "aws", docs: "AWS SNS (Simple Notification Service)" },
|
|
233
|
+
{ label: "azure", docs: "Azure Service Bus" },
|
|
234
|
+
{ label: "redis", docs: "Redis Pub/Sub" },
|
|
235
|
+
{ label: "nats", docs: "NATS messaging system" },
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
return providers.map((p) => ({
|
|
239
|
+
label: p.label,
|
|
240
|
+
kind: CompletionItemKind.EnumMember,
|
|
241
|
+
documentation: p.docs,
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function createWebhookSourceCompletions(): CompletionItem[] {
|
|
246
|
+
const sources = [
|
|
247
|
+
{ label: "github", docs: "GitHub webhook events (push, PR, issues, etc.)" },
|
|
248
|
+
{ label: "stripe", docs: "Stripe payment events (checkout, invoice, etc.)" },
|
|
249
|
+
{ label: "shopify", docs: "Shopify e-commerce events (order, product, etc.)" },
|
|
250
|
+
{ label: "custom", docs: "Custom webhook source with HMAC verification" },
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
return sources.map((s) => ({
|
|
254
|
+
label: s.label,
|
|
255
|
+
kind: CompletionItemKind.EnumMember,
|
|
256
|
+
documentation: s.docs,
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function createConditionTypeCompletions(): CompletionItem[] {
|
|
261
|
+
return [
|
|
262
|
+
{
|
|
263
|
+
label: "if",
|
|
264
|
+
kind: CompletionItemKind.Keyword,
|
|
265
|
+
documentation: "Conditional branch - executes steps when condition is true",
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
label: "else",
|
|
269
|
+
kind: CompletionItemKind.Keyword,
|
|
270
|
+
documentation: "Default branch - executes when no 'if' condition matches",
|
|
271
|
+
},
|
|
272
|
+
];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function createTopLevelKeyCompletions(context: string): CompletionItem[] {
|
|
276
|
+
if (context === "root") {
|
|
277
|
+
const keys = [
|
|
278
|
+
{ label: "name", docs: "Workflow name" },
|
|
279
|
+
{ label: "description", docs: "Workflow description" },
|
|
280
|
+
{ label: "version", docs: "Semantic version (e.g., 1.0.0)" },
|
|
281
|
+
{ label: "trigger", docs: "Workflow trigger configuration" },
|
|
282
|
+
{ label: "steps", docs: "Ordered list of execution steps" },
|
|
283
|
+
{ label: "nodes", docs: "Node configuration map" },
|
|
284
|
+
];
|
|
285
|
+
|
|
286
|
+
return keys.map((k) => ({
|
|
287
|
+
label: k.label,
|
|
288
|
+
kind: CompletionItemKind.Property,
|
|
289
|
+
documentation: k.docs,
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (context === "http") {
|
|
294
|
+
return ["method", "path", "accept", "jwt_secret"].map((k) => ({
|
|
295
|
+
label: k,
|
|
296
|
+
kind: CompletionItemKind.Property,
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (context === "steps") {
|
|
301
|
+
return ["name", "node", "type", "runtime"].map((k) => ({
|
|
302
|
+
label: k,
|
|
303
|
+
kind: CompletionItemKind.Property,
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return [];
|
|
308
|
+
}
|