@xano/xanoscript-language-server 11.0.6 → 11.1.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/CLAUDE.md +140 -5
- package/lexer/run.js +37 -0
- package/lexer/tokens.js +3 -0
- package/onHover/functions.md +56 -0
- package/package.json +1 -1
- package/parser/attributes/docsFieldAttribute.spec.js +5 -0
- package/parser/functions/api/apiCallFn.spec.js +2 -2
- package/parser/functions/controls/tryCatchFn.spec.js +0 -1
- package/parser/generic/objectWithAttributes.js +1 -0
- package/parser/parser.js +3 -0
- package/parser/register.js +8 -0
- package/parser/run_parser.js +70 -0
- package/parser/run_parser.spec.js +100 -0
- package/utils.js +1 -0
- package/.claude/settings.local.json +0 -36
package/CLAUDE.md
CHANGED
|
@@ -30,12 +30,14 @@ This is a Language Server Protocol (LSP) implementation for XanoScript, built on
|
|
|
30
30
|
### Core Components
|
|
31
31
|
|
|
32
32
|
**Lexer (`lexer/`)**: Token definitions and lexical analysis
|
|
33
|
+
|
|
33
34
|
- `tokens.js` - Main token registry
|
|
34
35
|
- `lexer.js` - Chevrotain lexer implementation
|
|
35
36
|
- Domain tokens: `api.js`, `db.js`, `cloud.js`, `function.js`, etc.
|
|
36
37
|
- `utils.js` - Token creation utilities (`createToken`, `createTokenByName`)
|
|
37
38
|
|
|
38
39
|
**Parser (`parser/`)**: Grammar rules and parsing logic
|
|
40
|
+
|
|
39
41
|
- `base_parser.js` - Core XanoBaseParser extending Chevrotain
|
|
40
42
|
- Main parsers: `query_parser.js`, `function_parser.js`, `task_parser.js`, `api_group_parser.js`, `table_parser.js`, `workflow_test_parser.js`, `table_trigger_parser.js`
|
|
41
43
|
- `attributes/` - Field attributes (description, disabled, sensitive)
|
|
@@ -45,6 +47,7 @@ This is a Language Server Protocol (LSP) implementation for XanoScript, built on
|
|
|
45
47
|
- `generic/` - Reusable parsing components
|
|
46
48
|
|
|
47
49
|
**Language Server (`server.js` + feature directories)**:
|
|
50
|
+
|
|
48
51
|
- `onCompletion/` - Auto-completion logic
|
|
49
52
|
- `onDidChangeContent/` - Live diagnostics and error reporting
|
|
50
53
|
- `onHover/` - Documentation and hover information
|
|
@@ -53,6 +56,7 @@ This is a Language Server Protocol (LSP) implementation for XanoScript, built on
|
|
|
53
56
|
### XanoScript Object Types
|
|
54
57
|
|
|
55
58
|
Primary constructs parsed by dedicated parsers:
|
|
59
|
+
|
|
56
60
|
- **query** - API endpoints with HTTP verbs, input validation, processing logic, responses
|
|
57
61
|
- **function** - Reusable logic blocks with testing capabilities
|
|
58
62
|
- **task** - Scheduled operations with cron-like triggers
|
|
@@ -63,18 +67,147 @@ Primary constructs parsed by dedicated parsers:
|
|
|
63
67
|
|
|
64
68
|
## Adding New Features
|
|
65
69
|
|
|
66
|
-
### New
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
### New Top-Level Parser (e.g., `run.job`, `run.service`)
|
|
71
|
+
|
|
72
|
+
Follow this step-by-step process:
|
|
73
|
+
|
|
74
|
+
**1. Create lexer tokens (`lexer/my_feature.js`)**
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
import { Identifier } from "./identifier.js";
|
|
78
|
+
import { createTokenByName } from "./utils.js";
|
|
79
|
+
|
|
80
|
+
export const MyToken = createTokenByName("my_keyword", {
|
|
81
|
+
longer_alt: Identifier,
|
|
82
|
+
categories: [Identifier],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export const MyFeatureTokens = [MyToken];
|
|
86
|
+
|
|
87
|
+
export function mapTokenToType(token) {
|
|
88
|
+
switch (token) {
|
|
89
|
+
case MyToken.name:
|
|
90
|
+
return "keyword";
|
|
91
|
+
default:
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**2. Register tokens in `lexer/tokens.js`**
|
|
98
|
+
|
|
99
|
+
- Import tokens and mapper at the top
|
|
100
|
+
- Add `...MyFeatureTokens` to `allTokens` array
|
|
101
|
+
- Add `mapMyFeatureTokenToType` to `tokenMappers` array
|
|
102
|
+
|
|
103
|
+
**3. Create parser (`parser/my_feature_parser.js`)**
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
import { StringLiteral } from "../lexer/literal.js";
|
|
107
|
+
import { MyToken } from "../lexer/my_feature.js";
|
|
108
|
+
import { Identifier, NewlineToken } from "../lexer/tokens.js";
|
|
109
|
+
|
|
110
|
+
export function myFeatureDeclaration($) {
|
|
111
|
+
return () => {
|
|
112
|
+
$.sectionStack.push("myFeatureDeclaration");
|
|
113
|
+
$.SUBRULE($.optionalCommentBlockFn);
|
|
114
|
+
|
|
115
|
+
const parent = $.CONSUME(MyToken);
|
|
116
|
+
$.OR([
|
|
117
|
+
{ ALT: () => $.CONSUME(StringLiteral) },
|
|
118
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
// Use schemaParseAttributeFn for body with declarative schema
|
|
122
|
+
$.SUBRULE($.schemaParseAttributeFn, {
|
|
123
|
+
ARGS: [
|
|
124
|
+
parent,
|
|
125
|
+
{
|
|
126
|
+
required_attr: "[string]",
|
|
127
|
+
"optional_attr?": "[boolean]",
|
|
128
|
+
"nested?": {
|
|
129
|
+
name: "[string]",
|
|
130
|
+
"value?": { "[string]": "[constant]" },
|
|
131
|
+
},
|
|
132
|
+
"array_of_strings?": ["[string]"],
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
$.MANY2(() => $.CONSUME2(NewlineToken));
|
|
138
|
+
$.sectionStack.pop();
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**4. Register parser in `parser/register.js`**
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
import { myFeatureDeclaration } from "./my_feature_parser.js";
|
|
147
|
+
// In register function:
|
|
148
|
+
$.myFeatureDeclaration = $.RULE(
|
|
149
|
+
"myFeatureDeclaration",
|
|
150
|
+
myFeatureDeclaration($)
|
|
151
|
+
);
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**5. Add scheme detection in `utils.js`**
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
const schemeByFirstWord = {
|
|
158
|
+
// ... existing entries
|
|
159
|
+
my_feature: "my_feature",
|
|
160
|
+
};
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**6. Route scheme in `parser/parser.js`**
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
case "my_feature":
|
|
167
|
+
parser.myFeatureDeclaration();
|
|
168
|
+
return parser;
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**7. Write tests (`parser/my_feature_parser.spec.js`)**
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
import { expect } from "chai";
|
|
175
|
+
import { describe, it } from "mocha";
|
|
176
|
+
import { xanoscriptParser } from "./parser.js";
|
|
177
|
+
|
|
178
|
+
describe("my_feature", () => {
|
|
179
|
+
it("should parse a basic my_feature", () => {
|
|
180
|
+
const parser = xanoscriptParser(`my_feature "name" {
|
|
181
|
+
required_attr = "value"
|
|
182
|
+
}`);
|
|
183
|
+
expect(parser.errors).to.be.empty;
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Schema Definition Types (for `schemaParseAttributeFn`)
|
|
189
|
+
|
|
190
|
+
| Schema | Description | Example |
|
|
191
|
+
| ------------------------------ | ---------------------------------- | ------------------------- |
|
|
192
|
+
| `"[string]"` | String literal | `"hello"` |
|
|
193
|
+
| `"[number]"` | Number literal | `123` |
|
|
194
|
+
| `"[boolean]"` | Boolean value | `true` / `false` |
|
|
195
|
+
| `"[constant]"` | Value expression (no variables) | `"text"`, `123`, `{a: 1}` |
|
|
196
|
+
| `"[expression]"` | Any expression including variables | `$var`, `$input.name` |
|
|
197
|
+
| `["[string]"]` | Array of strings | `["a", "b"]` |
|
|
198
|
+
| `{ "[string]": "[constant]" }` | Object with string keys | `{key: "value"}` |
|
|
199
|
+
| `"attr?"` | Optional attribute | May be omitted |
|
|
200
|
+
| `"!attr"` | Can be disabled | `!attr = value` |
|
|
70
201
|
|
|
71
202
|
### New Keyword/Function
|
|
203
|
+
|
|
72
204
|
1. Add token definition in appropriate `lexer/` file
|
|
73
205
|
2. Create function implementation in `parser/functions/[domain]/`
|
|
74
206
|
3. Register in parent clause or parser
|
|
75
207
|
4. Add comprehensive tests in corresponding `.spec.js` file
|
|
76
208
|
|
|
77
209
|
### Token Creation Pattern
|
|
210
|
+
|
|
78
211
|
```javascript
|
|
79
212
|
export const MyToken = createTokenByName("keyword", {
|
|
80
213
|
longer_alt: Identifier,
|
|
@@ -83,6 +216,7 @@ export const MyToken = createTokenByName("keyword", {
|
|
|
83
216
|
```
|
|
84
217
|
|
|
85
218
|
### Parser Rule Pattern
|
|
219
|
+
|
|
86
220
|
```javascript
|
|
87
221
|
myRule = this.RULE("myRule", () => {
|
|
88
222
|
this.CONSUME(MyToken);
|
|
@@ -100,6 +234,7 @@ myRule = this.RULE("myRule", () => {
|
|
|
100
234
|
- CI requires all tests to pass
|
|
101
235
|
|
|
102
236
|
### Test Pattern
|
|
237
|
+
|
|
103
238
|
```javascript
|
|
104
239
|
function parse(inputText) {
|
|
105
240
|
const lexResult = lexDocument(inputText);
|
|
@@ -118,4 +253,4 @@ function parse(inputText) {
|
|
|
118
253
|
- Follow existing code conventions when adding features
|
|
119
254
|
- Check neighboring files for patterns and conventions
|
|
120
255
|
- Never assume a library is available - check `package.json` first
|
|
121
|
-
- 3 low-severity npm audit warnings are known and acceptable
|
|
256
|
+
- 3 low-severity npm audit warnings are known and acceptable
|
package/lexer/run.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Identifier } from "./identifier.js";
|
|
2
|
+
import { createTokenByName } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
// "run"
|
|
5
|
+
export const RunToken = createTokenByName("run", {
|
|
6
|
+
longer_alt: Identifier,
|
|
7
|
+
categories: [Identifier],
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// "job"
|
|
11
|
+
export const JobToken = createTokenByName("job", {
|
|
12
|
+
longer_alt: Identifier,
|
|
13
|
+
categories: [Identifier],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// "service"
|
|
17
|
+
export const ServiceToken = createTokenByName("service", {
|
|
18
|
+
longer_alt: Identifier,
|
|
19
|
+
categories: [Identifier],
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const RunTokens = [RunToken, JobToken, ServiceToken];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Maps a token name to a type
|
|
26
|
+
* @param {string} token the token name
|
|
27
|
+
*/
|
|
28
|
+
export function mapTokenToType(token) {
|
|
29
|
+
switch (token) {
|
|
30
|
+
case RunToken.name:
|
|
31
|
+
case JobToken.name:
|
|
32
|
+
case ServiceToken.name:
|
|
33
|
+
return "keyword";
|
|
34
|
+
default:
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
package/lexer/tokens.js
CHANGED
|
@@ -93,6 +93,7 @@ import {
|
|
|
93
93
|
RealtimeTriggerTokens,
|
|
94
94
|
} from "./realtime_trigger.js";
|
|
95
95
|
import { mapTokenToType as mapRedisTokenToType, RedisTokens } from "./redis.js";
|
|
96
|
+
import { mapTokenToType as mapRunTokenToType, RunTokens } from "./run.js";
|
|
96
97
|
import {
|
|
97
98
|
mapTokenToType as mapSecurityTokenToType,
|
|
98
99
|
SecurityTokens,
|
|
@@ -382,6 +383,7 @@ export const allTokens = uniq([
|
|
|
382
383
|
...DbTokens,
|
|
383
384
|
...RedisTokens,
|
|
384
385
|
...TextTokens,
|
|
386
|
+
...RunTokens,
|
|
385
387
|
...ObjectTokens,
|
|
386
388
|
...StreamTokens,
|
|
387
389
|
...DebugTokens,
|
|
@@ -430,6 +432,7 @@ const tokenMappers = [
|
|
|
430
432
|
mapQueryTokenToType,
|
|
431
433
|
mapRealtimeTriggerTokenToType,
|
|
432
434
|
mapRedisTokenToType,
|
|
435
|
+
mapRunTokenToType,
|
|
433
436
|
mapSecurityTokenToType,
|
|
434
437
|
mapStorageTokenToType,
|
|
435
438
|
mapStreamTokenToType,
|
package/onHover/functions.md
CHANGED
|
@@ -287,6 +287,62 @@ A `task` file defines a scheduled job that runs automatically at specified times
|
|
|
287
287
|
|
|
288
288
|
Tasks are ideal for automating recurring operations like generating reports or syncing data.
|
|
289
289
|
|
|
290
|
+
# run.job
|
|
291
|
+
|
|
292
|
+
```xs
|
|
293
|
+
run.job "Gemini -> Image Understanding" {
|
|
294
|
+
main = {
|
|
295
|
+
name: "Gemini -> Image Understanding"
|
|
296
|
+
input: {
|
|
297
|
+
model: "gemini-1.5-flash"
|
|
298
|
+
prompt: "Describe what is happening in this image."
|
|
299
|
+
image: "(attach image file)"
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
env = ["gemini_api_key"]
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
A `run.job` file defines a job configuration for execution in the Xano Job Runner. It includes:
|
|
307
|
+
|
|
308
|
+
- A name (e.g., `"Gemini -> Image Understanding"`) to identify the job,
|
|
309
|
+
- A required `main` attribute specifying the function to execute:
|
|
310
|
+
- `name`: The name of the function to call,
|
|
311
|
+
- `input`: Optional input parameters to pass to the function,
|
|
312
|
+
- An optional `env` array listing environment variable names required by the job.
|
|
313
|
+
|
|
314
|
+
Jobs are used to run functions as standalone processes, typically for long-running or resource-intensive operations.
|
|
315
|
+
|
|
316
|
+
# run.service
|
|
317
|
+
|
|
318
|
+
```xs
|
|
319
|
+
run.service "email proxy" {
|
|
320
|
+
pre = {
|
|
321
|
+
name: "email_proxy_init"
|
|
322
|
+
input: {
|
|
323
|
+
config: "default"
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
env = ["email_proxy_api_key"]
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
A `run.service` file defines a service configuration for the Xano Job Runner. It includes:
|
|
331
|
+
|
|
332
|
+
- A name (e.g., `"email proxy"`) to identify the service,
|
|
333
|
+
- An optional `pre` attribute specifying an initialization function to run before the service starts:
|
|
334
|
+
- `name`: The name of the initialization function,
|
|
335
|
+
- `input`: Optional input parameters for the initialization,
|
|
336
|
+
- An optional `env` array listing environment variable names required by the service.
|
|
337
|
+
|
|
338
|
+
Services can also be defined in minimal form without a body:
|
|
339
|
+
|
|
340
|
+
```xs
|
|
341
|
+
run.service "email proxy"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Services are ideal for long-running background processes like proxies, webhooks listeners, or daemon-style operations.
|
|
345
|
+
|
|
290
346
|
# action.call
|
|
291
347
|
|
|
292
348
|
```xs
|
package/package.json
CHANGED
|
@@ -32,6 +32,11 @@ describe("docsFieldAttribute", () => {
|
|
|
32
32
|
expect(parser.errors).to.be.empty;
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
it("docsFieldAttribute can be multiline", () => {
|
|
36
|
+
const parser = parse('docs="""\nanother docs\n"""');
|
|
37
|
+
expect(parser.errors).to.be.empty;
|
|
38
|
+
});
|
|
39
|
+
|
|
35
40
|
it("docsFieldAttribute does not require a new line", () => {
|
|
36
41
|
const parser = parse('docs="some docs"');
|
|
37
42
|
expect(parser.errors).to.be.empty;
|
|
@@ -47,12 +47,12 @@ describe("apiCallFn", () => {
|
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
it("apiCallFn requires a url and method field", () => {
|
|
50
|
-
let parser = parse(`
|
|
50
|
+
let parser = parse(`call foo {
|
|
51
51
|
url = "https://www.example.com"
|
|
52
52
|
} as $user`);
|
|
53
53
|
expect(parser.errors).to.not.be.empty;
|
|
54
54
|
|
|
55
|
-
parser = parse(`
|
|
55
|
+
parser = parse(`call foo {
|
|
56
56
|
method = "GET"
|
|
57
57
|
} as $user`);
|
|
58
58
|
expect(parser.errors).to.not.be.empty;
|
|
@@ -5,6 +5,7 @@ import { Identifier, NewlineToken } from "../../lexer/tokens.js";
|
|
|
5
5
|
import { getVarName } from "./utils.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
+
* @deprecated use parser/functions/schema/schemaParseAttributeFn.js instead
|
|
8
9
|
* @param {import('../base_parser.js').XanoBaseParser} $
|
|
9
10
|
*/
|
|
10
11
|
export function objectWithAttributes($) {
|
package/parser/parser.js
CHANGED
|
@@ -53,6 +53,9 @@ export function xanoscriptParser(text, scheme, preTokenized = null) {
|
|
|
53
53
|
case "realtime_channel":
|
|
54
54
|
parser.realtimeChannelDeclaration();
|
|
55
55
|
return parser;
|
|
56
|
+
case "run":
|
|
57
|
+
parser.runDeclaration();
|
|
58
|
+
return parser;
|
|
56
59
|
case "table_trigger":
|
|
57
60
|
parser.tableTriggerDeclaration();
|
|
58
61
|
return parser;
|
package/parser/register.js
CHANGED
|
@@ -16,6 +16,11 @@ import { middlewareDeclaration } from "./middleware_parser.js";
|
|
|
16
16
|
import { queryDeclaration } from "./query_parser.js";
|
|
17
17
|
import { realtimeChannelDeclaration } from "./realtime_channel_parser.js";
|
|
18
18
|
import { realtimeTriggerDeclaration } from "./realtime_trigger_parser.js";
|
|
19
|
+
import {
|
|
20
|
+
runDeclaration,
|
|
21
|
+
runJobClause,
|
|
22
|
+
runServiceClause,
|
|
23
|
+
} from "./run_parser.js";
|
|
19
24
|
import { tableDeclaration } from "./table_parser.js";
|
|
20
25
|
import { tableTriggerDeclaration } from "./table_trigger_parser.js";
|
|
21
26
|
import { taskDeclaration } from "./task_parser.js";
|
|
@@ -66,6 +71,9 @@ export const register = ($) => {
|
|
|
66
71
|
"realtimeChannelDeclaration",
|
|
67
72
|
realtimeChannelDeclaration($)
|
|
68
73
|
);
|
|
74
|
+
$.runDeclaration = $.RULE("runDeclaration", runDeclaration($));
|
|
75
|
+
$.runJobClause = $.RULE("runJobClause", runJobClause($));
|
|
76
|
+
$.runServiceClause = $.RULE("runServiceClause", runServiceClause($));
|
|
69
77
|
$.tableTriggerDeclaration = $.RULE(
|
|
70
78
|
"tableTriggerDeclaration",
|
|
71
79
|
tableTriggerDeclaration($)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { StringLiteral } from "../lexer/literal.js";
|
|
2
|
+
import { JobToken, RunToken, ServiceToken } from "../lexer/run.js";
|
|
3
|
+
import { DotToken, Identifier, NewlineToken } from "../lexer/tokens.js";
|
|
4
|
+
|
|
5
|
+
export function runDeclaration($) {
|
|
6
|
+
return () => {
|
|
7
|
+
$.sectionStack.push("runDeclaration");
|
|
8
|
+
// Allow leading comments and newlines before the run declaration
|
|
9
|
+
$.SUBRULE($.optionalCommentBlockFn);
|
|
10
|
+
|
|
11
|
+
$.CONSUME(RunToken); // "run"
|
|
12
|
+
$.CONSUME(DotToken); // "."
|
|
13
|
+
|
|
14
|
+
$.OR([
|
|
15
|
+
{ ALT: () => $.SUBRULE($.runJobClause) },
|
|
16
|
+
{ ALT: () => $.SUBRULE($.runServiceClause) },
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
$.MANY2(() => $.CONSUME2(NewlineToken)); // optional trailing newlines
|
|
20
|
+
$.sectionStack.pop();
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function runJobClause($) {
|
|
25
|
+
return () => {
|
|
26
|
+
const parent = $.CONSUME(JobToken); // "job"
|
|
27
|
+
|
|
28
|
+
$.OR([
|
|
29
|
+
{ ALT: () => $.CONSUME(StringLiteral) },
|
|
30
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
$.SUBRULE($.schemaParseAttributeFn, {
|
|
34
|
+
ARGS: [
|
|
35
|
+
parent,
|
|
36
|
+
{
|
|
37
|
+
main: {
|
|
38
|
+
name: "[string]",
|
|
39
|
+
input: { "[string]?": "[constant]" },
|
|
40
|
+
},
|
|
41
|
+
"env?": ["[string]"],
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function runServiceClause($) {
|
|
49
|
+
return () => {
|
|
50
|
+
const parent = $.CONSUME(ServiceToken); // "service"
|
|
51
|
+
|
|
52
|
+
$.OR([
|
|
53
|
+
{ ALT: () => $.CONSUME(StringLiteral) },
|
|
54
|
+
{ ALT: () => $.CONSUME(Identifier) },
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
$.SUBRULE($.schemaParseAttributeFn, {
|
|
58
|
+
ARGS: [
|
|
59
|
+
parent,
|
|
60
|
+
{
|
|
61
|
+
"pre?": {
|
|
62
|
+
name: "[string]",
|
|
63
|
+
input: { "[string]?": "[constant]" },
|
|
64
|
+
},
|
|
65
|
+
"env?": ["[string]"],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { expect } from "chai";
|
|
2
|
+
import { describe, it } from "mocha";
|
|
3
|
+
import { xanoscriptParser } from "./parser.js";
|
|
4
|
+
|
|
5
|
+
describe("run", () => {
|
|
6
|
+
it("should parse a basic run.job", () => {
|
|
7
|
+
const parser = xanoscriptParser(`run.job "Average of values" {
|
|
8
|
+
main = {name: "avg_value", input: {}}
|
|
9
|
+
}`);
|
|
10
|
+
expect(parser.errors).to.be.empty;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should parse a basic run.job with inputs", () => {
|
|
14
|
+
const parser = xanoscriptParser(`run.job "Average of values" {
|
|
15
|
+
main = {name: "avg_value", input: {left: 1, right: 2}}
|
|
16
|
+
}`);
|
|
17
|
+
expect(parser.errors).to.be.empty;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("run.job requires a main attribute", () => {
|
|
21
|
+
let parser = xanoscriptParser(`run.job "Average of values" {
|
|
22
|
+
pre = {name: "avg_value", input: {left: 1, right: 2}}
|
|
23
|
+
}`);
|
|
24
|
+
expect(parser.errors).to.not.be.empty;
|
|
25
|
+
|
|
26
|
+
parser = xanoscriptParser(`run.job "Average of values" {}`);
|
|
27
|
+
expect(parser.errors).to.not.be.empty;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should accept env variable", () => {
|
|
31
|
+
const parser = xanoscriptParser(`run.job "Gemini -> Image Understanding" {
|
|
32
|
+
main = {
|
|
33
|
+
name : "Gemini -> Image Understanding"
|
|
34
|
+
input: {
|
|
35
|
+
model : "gemini-1.5-flash"
|
|
36
|
+
prompt: "Describe what is happening in this image."
|
|
37
|
+
image : "(attach image file)"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
env = ["gemini_api_key"]
|
|
42
|
+
}`);
|
|
43
|
+
expect(parser.errors).to.be.empty;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should parse a basic run.service", () => {
|
|
47
|
+
const parser = xanoscriptParser(`run.service "email proxy" {
|
|
48
|
+
pre = {name: "email_proxy_fn", input: {}}
|
|
49
|
+
}`);
|
|
50
|
+
expect(parser.errors).to.be.empty;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("run.service has a minimal form", () => {
|
|
54
|
+
const parser = xanoscriptParser(`run.service "email proxy"`);
|
|
55
|
+
expect(parser.errors).to.be.empty;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("environment variables can only be an array of strings", () => {
|
|
59
|
+
let parser = xanoscriptParser(`run.service "email proxy" {
|
|
60
|
+
env = ["email_proxy_api_key", 123, true]
|
|
61
|
+
}`);
|
|
62
|
+
expect(parser.errors).to.not.be.empty;
|
|
63
|
+
|
|
64
|
+
parser = xanoscriptParser(`run.job "Average of values" {
|
|
65
|
+
main = {name: "avg_value", input: {}}
|
|
66
|
+
env = ["email_proxy_api_key", 123, true]
|
|
67
|
+
}`);
|
|
68
|
+
expect(parser.errors).to.not.be.empty;
|
|
69
|
+
|
|
70
|
+
parser = xanoscriptParser(`run.service "email proxy" {
|
|
71
|
+
env = []
|
|
72
|
+
}`);
|
|
73
|
+
expect(parser.errors).to.be.empty;
|
|
74
|
+
|
|
75
|
+
parser = xanoscriptParser(`run.service "email proxy" {
|
|
76
|
+
env = ["a_value", "another value"]
|
|
77
|
+
}`);
|
|
78
|
+
expect(parser.errors).to.be.empty;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("run.service does not require a pre", () => {
|
|
82
|
+
const parser = xanoscriptParser(`run.service "email proxy"`);
|
|
83
|
+
expect(parser.errors).to.be.empty;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("run.service does not accept a main", () => {
|
|
87
|
+
const parser = xanoscriptParser(`run.service "email proxy" {
|
|
88
|
+
main = { name : "some_fn", input: {} }
|
|
89
|
+
}`);
|
|
90
|
+
expect(parser.errors).to.not.be.empty;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("run.service accepts env variables", () => {
|
|
94
|
+
const parser = xanoscriptParser(`run.service "email proxy" {
|
|
95
|
+
pre = {name: "email_proxy_init", input: {}}
|
|
96
|
+
env = ["email_proxy_api_key"]
|
|
97
|
+
}`);
|
|
98
|
+
expect(parser.errors).to.be.empty;
|
|
99
|
+
});
|
|
100
|
+
});
|
package/utils.js
CHANGED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(npm test:*)",
|
|
5
|
-
"Bash(npm run lint)",
|
|
6
|
-
"Bash(npm run test:*)",
|
|
7
|
-
"Bash(npx eslint:*)",
|
|
8
|
-
"Bash(node:*)",
|
|
9
|
-
"Bash(npm install)",
|
|
10
|
-
"Bash(grep:*)",
|
|
11
|
-
"Bash(git -C /Users/justinalbrecht/git/xs-language-server log --all --pretty=format:\"%h %s\")",
|
|
12
|
-
"Bash(git -C /Users/justinalbrecht/git/xs-language-server log --all -S \"password\\\\|secret\\\\|api_key\\\\|token\" --oneline)",
|
|
13
|
-
"Bash(xargs file:*)",
|
|
14
|
-
"Bash(find:*)",
|
|
15
|
-
"Bash(git -C /Users/justinalbrecht/git/xs-language-server log -p --all -S \"webhook\\\\|Bearer\\\\|discord\\\\|slack\\\\|xano_insiders\" -- \"*.xs\" \"*.js\")",
|
|
16
|
-
"Bash(git -C /Users/justinalbrecht/git/xs-language-server ls-files:*)",
|
|
17
|
-
"Bash(git -C /Users/justinalbrecht/git/xs-language-server status)",
|
|
18
|
-
"Bash(git -C /Users/justinalbrecht/git/xs-language-server diff --stat)",
|
|
19
|
-
"Bash(git -C /Users/justinalbrecht/git/xs-language-server log --oneline -5)",
|
|
20
|
-
"Bash(git -C /Users/justinalbrecht/git/xs-language-server add README.md LICENSE package.json lexer/tests/query/valid_sources/basic_query.xs parser/functions/stream/streamFromRequestFn.js parser/functions/stream/streamFromRequestFn.spec.js parser/tests/function/valid_sources/discord_poll_send_to_slack.xs parser/tests/query/valid_sources/all_basics.xs)",
|
|
21
|
-
"Bash(git -C /Users/justinalbrecht/git/xs-language-server commit -m \"$\\(cat <<''EOF''\nPrepare package for public npm release as @xano/xanoscript-language-server\n\n- Rename package to @xano/xanoscript-language-server\n- Add MIT LICENSE file\n- Add npm metadata \\(description, repository, author, license, bugs, homepage\\)\n- Remove private flag to allow publishing\n- Sanitize test files: replace internal URLs, IDs, and env var names with placeholders\n- Simplify README maintainer section\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
22
|
-
"Bash(git -C /Users/justinalbrecht/git/xs-language-server log -1 --oneline)",
|
|
23
|
-
"Bash(git add:*)",
|
|
24
|
-
"Bash(git commit:*)",
|
|
25
|
-
"Bash(git push)",
|
|
26
|
-
"Bash(npm whoami:*)",
|
|
27
|
-
"Bash(npm view:*)",
|
|
28
|
-
"Bash(npm version:*)",
|
|
29
|
-
"Bash(npm publish:*)",
|
|
30
|
-
"Bash(wc:*)",
|
|
31
|
-
"Bash(git push:*)"
|
|
32
|
-
],
|
|
33
|
-
"deny": [],
|
|
34
|
-
"ask": []
|
|
35
|
-
}
|
|
36
|
-
}
|