@xano/xanoscript-language-server 11.0.6 → 11.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/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 Object Type
67
- 1. Create a new parser in `parser/` (e.g., `my_object_parser.js`)
68
- 2. Follow existing parser patterns (see `query_parser.js`, `function_parser.js`)
69
- 3. Register it in `server.js`
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,
@@ -1,5 +1,4 @@
1
1
  import { documentCache } from "../cache/documentCache.js";
2
- import { debugError, debugLog } from "../debug.js";
3
2
 
4
3
  // Diagnostic severity constants
5
4
  const SEVERITY = {
@@ -61,7 +60,7 @@ export function onDidChangeContent(params, connection) {
61
60
  const document = params.document;
62
61
 
63
62
  if (!document) {
64
- debugError(
63
+ console.error(
65
64
  "onDidChangeContent(): Document not found for URI:",
66
65
  params.textDocument.uri
67
66
  );
@@ -84,7 +83,7 @@ export function onDidChangeContent(params, connection) {
84
83
  }
85
84
 
86
85
  for (const error of parser.errors) {
87
- debugError(
86
+ console.error(
88
87
  `onDidChangeContent(): Error parsing document: ${error.name}`
89
88
  );
90
89
  }
@@ -92,7 +91,7 @@ export function onDidChangeContent(params, connection) {
92
91
  // Create diagnostics in a single pass
93
92
  const diagnostics = createDiagnostics(parser, document);
94
93
 
95
- debugLog(
94
+ console.log(
96
95
  `onDidChangeContent(): sending diagnostic (${parser.errors.length} errors) for scheme:`,
97
96
  scheme
98
97
  );
@@ -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
@@ -1,5 +1,4 @@
1
1
  import { documentCache } from "../cache/documentCache.js";
2
- import { debugError } from "../debug.js";
3
2
 
4
3
  /**
5
4
  * Binary search to find token index at the given offset.
@@ -38,7 +37,7 @@ export function onHoverDocument(params, documents, hoverProviders = []) {
38
37
  const document = documents.get(params.textDocument.uri);
39
38
 
40
39
  if (!document) {
41
- debugError(
40
+ console.error(
42
41
  "onHover(): Document not found for URI:",
43
42
  params.textDocument.uri
44
43
  );
@@ -1,4 +1,3 @@
1
- import { debugLog } from "../debug.js";
2
1
  import { lexDocument } from "../lexer/lexer.js";
3
2
  import { mapTokenToType } from "../lexer/tokens.js";
4
3
  import { encodeTokenType } from "./tokens.js";
@@ -34,7 +33,7 @@ function higlightDefault(text, SemanticTokensBuilder) {
34
33
  0 // No modifiers for now
35
34
  );
36
35
  } else if (tokenType === undefined) {
37
- debugLog(
36
+ console.log(
38
37
  `token type not mapped to a type: ${JSON.stringify(
39
38
  token.tokenType.name
40
39
  )}`
@@ -1,4 +1,3 @@
1
- import { debugError } from "../debug.js";
2
1
  import { getSchemeFromContent } from "../utils";
3
2
  import { higlightText } from "./highlight";
4
3
 
@@ -12,7 +11,7 @@ export function onSemanticCheck(params, documents, SemanticTokensBuilder) {
12
11
  const document = documents.get(params.textDocument.uri);
13
12
 
14
13
  if (!document) {
15
- debugError(
14
+ console.error(
16
15
  "onSemanticCheck(): Document not found for URI:",
17
16
  params.textDocument.uri
18
17
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xano/xanoscript-language-server",
3
- "version": "11.0.6",
3
+ "version": "11.2.0",
4
4
  "description": "Language Server Protocol implementation for XanoScript",
5
5
  "type": "module",
6
6
  "main": "server.js",
@@ -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(`request {
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(`request {
55
+ parser = parse(`call foo {
56
56
  method = "GET"
57
57
  } as $user`);
58
58
  expect(parser.errors).to.not.be.empty;
@@ -59,7 +59,6 @@ describe("tryCatchFn", () => {
59
59
  }
60
60
  }
61
61
  }`);
62
- console.log(parser.errors);
63
62
  expect(parser.errors).to.be.empty;
64
63
  });
65
64
 
@@ -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;
@@ -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/server.js CHANGED
@@ -5,7 +5,6 @@ import {
5
5
  TextDocuments,
6
6
  } from "vscode-languageserver/node.js";
7
7
  import { TextDocument } from "vscode-languageserver-textdocument";
8
- import { debugLog } from "./debug.js";
9
8
  import { onCompletion } from "./onCompletion/onCompletion.js";
10
9
  import { onDidChangeContent } from "./onDidChangeContent/onDidChangeContent.js";
11
10
  import { onHover } from "./onHover/onHover.js";
@@ -51,7 +50,7 @@ documents.onDidChangeContent((params) =>
51
50
  onDidChangeContent(params, connection)
52
51
  );
53
52
  connection.onDidOpenTextDocument((params) => {
54
- debugLog("Document opened:", params.textDocument.uri);
53
+ console.log("Document opened:", params.textDocument.uri);
55
54
  // Existing handler logic
56
55
  });
57
56
 
package/utils.js CHANGED
@@ -30,6 +30,7 @@ const schemeByFirstWord = {
30
30
  query: "api",
31
31
  realtime_trigger: "realtime_trigger",
32
32
  realtime_channel: "realtime_channel",
33
+ run: "run",
33
34
  table: "db",
34
35
  table_trigger: "table_trigger",
35
36
  task: "task",
@@ -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
- }
package/debug.js DELETED
@@ -1,18 +0,0 @@
1
- /* global process */
2
- /**
3
- * Debug logging utility.
4
- * Set XS_DEBUG=1 environment variable to enable debug logging.
5
- */
6
- const isDebug = process.env.XS_DEBUG === "1";
7
-
8
- export function debugLog(...args) {
9
- if (isDebug) {
10
- console.log(...args);
11
- }
12
- }
13
-
14
- export function debugError(...args) {
15
- if (isDebug) {
16
- console.error(...args);
17
- }
18
- }