@valbuild/language-server 0.98.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/LICENSE.md ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2025 Fredrik Ekholdt and Blank AS
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/bin.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ require(".").main();
@@ -0,0 +1,30 @@
1
+ import type { IValFSHost } from "@valbuild/server";
2
+ /**
3
+ * Access to the editor's in-memory view of a file.
4
+ *
5
+ * An editor holds unsaved ("dirty") buffers that differ from disk. Validation
6
+ * must see what the user is looking at, not what was last saved, so every read
7
+ * goes through this first.
8
+ */
9
+ export type OpenDocuments = {
10
+ /**
11
+ * Latest editor content for an absolute filesystem path, or `undefined` if the
12
+ * file is not open in the editor.
13
+ */
14
+ read(fsPath: string): string | undefined;
15
+ };
16
+ /** An {@link OpenDocuments} backed by a plain map. Useful for tests. */
17
+ export declare function mapOpenDocuments(entries?: Map<string, string>): OpenDocuments & {
18
+ set(fsPath: string, content: string): void;
19
+ };
20
+ /**
21
+ * An `IValFSHost` that overlays the editor's unsaved buffers on top of the real
22
+ * filesystem.
23
+ *
24
+ * `IValFSHost` is the filesystem seam that `createService`, `loadValModules`
25
+ * and `ValSourceFileHandler` all read through, so overriding it here is what
26
+ * makes Val evaluate the user's *current* editor state. Everything else
27
+ * delegates to `ts.sys`, exactly like the default host in
28
+ * `@valbuild/server`'s `createService`.
29
+ */
30
+ export declare function createEditorFsHost(open: OpenDocuments): IValFSHost;
@@ -0,0 +1,76 @@
1
+ import type { ModuleFilePath } from "@valbuild/core";
2
+ import type { SchemaSourceSnapshot } from "@valbuild/shared/internal";
3
+ import { type Service } from "@valbuild/server";
4
+ import { type OpenDocuments } from "./EditorFsHost.js";
5
+ /**
6
+ * A single Val root, and everything needed to evaluate its modules.
7
+ *
8
+ * One of these per Val root — never one shared across roots. Different roots in
9
+ * a monorepo can pin different versions of Val, and each gets its own
10
+ * `Service` (and therefore its own evaluated `val.modules` and module cache).
11
+ */
12
+ /** What `Service.get` returns; re-declared to avoid depending on an internal type. */
13
+ export type ValModuleContent = Awaited<ReturnType<Service["get"]>>;
14
+ export type ValProjectInitError = {
15
+ /**
16
+ * - `no-config` — no tsconfig.json/jsconfig.json at the Val root.
17
+ * - `missing-core` — `@valbuild/core` is not resolvable from the Val root.
18
+ * - `service-failed` — anything else that stopped the service starting.
19
+ */
20
+ code: "no-config" | "missing-core" | "service-failed";
21
+ message: string;
22
+ };
23
+ export type ValProject = {
24
+ readonly valRoot: string;
25
+ /**
26
+ * Evaluate a module and return its schema, source and validation errors.
27
+ * Resolves to an init error instead of throwing if the project could not be
28
+ * set up at all.
29
+ */
30
+ getModule(moduleFilePath: ModuleFilePath, options?: {
31
+ validate: boolean;
32
+ }): Promise<{
33
+ status: "ok";
34
+ content: ValModuleContent;
35
+ cached: boolean;
36
+ } | {
37
+ status: "error";
38
+ error: ValProjectInitError;
39
+ }>;
40
+ /**
41
+ * Schemas and sources for every Val module in the project.
42
+ *
43
+ * Needed for anything that has to look across modules: resolving `keyOf` and
44
+ * `route` validation, and offering route/key completions. Built once and then
45
+ * updated per module, so an edit costs one re-evaluation rather than N.
46
+ */
47
+ getSnapshot(): Promise<{
48
+ status: "ok";
49
+ snapshot: SchemaSourceSnapshot;
50
+ } | {
51
+ status: "error";
52
+ error: ValProjectInitError;
53
+ }>;
54
+ /** Val module file paths found under the Val root. */
55
+ listModuleFilePaths(): ModuleFilePath[];
56
+ /** Drop cached results. Pass a path to invalidate one module. */
57
+ invalidate(moduleFilePath?: ModuleFilePath): void;
58
+ /** Number of cached module results — for tests and diagnostics. */
59
+ cacheSize(): number;
60
+ dispose(): Promise<void>;
61
+ };
62
+ /**
63
+ * Whether `@valbuild/core` is resolvable from a Val root.
64
+ *
65
+ * Injectable because jest's module registry intercepts `createRequire` and
66
+ * resolves against the repo regardless of the base path given, so the real
67
+ * implementation always reports success under test.
68
+ */
69
+ export type CoreResolver = (valRoot: string) => boolean;
70
+ export declare const defaultCoreResolver: CoreResolver;
71
+ export declare function createValProject({ valRoot, open, isCoreResolvable, }: {
72
+ valRoot: string;
73
+ open: OpenDocuments;
74
+ /** Override for tests; see {@link CoreResolver}. */
75
+ isCoreResolvable?: CoreResolver;
76
+ }): ValProject;
@@ -0,0 +1,26 @@
1
+ import { type ValidationFix } from "@valbuild/core";
2
+ import { CodeAction, type Diagnostic, type TextEdit } from "vscode-languageserver";
3
+ import type { TextDocument } from "vscode-languageserver-textdocument";
4
+ import type { ValModuleContent } from "./ValProject.js";
5
+ export declare function isLocalFix(fix: string): fix is ValidationFix;
6
+ /**
7
+ * Build quick fixes for the diagnostics the client sent back.
8
+ *
9
+ * The client returns our `Diagnostic.data` verbatim, which is where the source
10
+ * path and available fixes come from — no re-deriving them from a code string.
11
+ */
12
+ export declare function createValCodeActions({ document, diagnostics, content, valRoot, remoteHost, }: {
13
+ document: TextDocument;
14
+ diagnostics: Diagnostic[];
15
+ content: ValModuleContent;
16
+ valRoot: string;
17
+ remoteHost?: string;
18
+ }): Promise<CodeAction[]>;
19
+ /**
20
+ * Narrow an edit down to the region that actually changed.
21
+ *
22
+ * A whole-document replacement would work, but it moves the cursor and shows up
23
+ * as a full-file change in review. Trimming the common prefix and suffix keeps
24
+ * the edit tight without needing a real diff algorithm.
25
+ */
26
+ export declare function minimalTextEdit(before: string, after: string, document: TextDocument): TextEdit | undefined;
@@ -0,0 +1,77 @@
1
+ import ts from "typescript";
2
+ /**
3
+ * Works out what the cursor is sitting in, so completions can be offered for it.
4
+ *
5
+ * AST-based rather than text/regex-based: `c.image(` can be nested, wrapped or
6
+ * multi-line, and matching on text gets that wrong in exactly the cases where a
7
+ * user most wants help.
8
+ */
9
+ export type ValCompletionContext = ValFileRefContext | ValStringValueContext;
10
+ /** The cursor is inside a plain string in the module's content. */
11
+ export type ValStringValueContext = {
12
+ kind: "string-value";
13
+ currentText: string;
14
+ contentStart: number;
15
+ contentEnd: number;
16
+ /**
17
+ * True when the string is an object key rather than a value.
18
+ *
19
+ * The distinction decides which schema to consult: a value is described by the
20
+ * schema at its own path, whereas a key is described by its *container* — a
21
+ * gallery record, for instance, is keyed by file reference.
22
+ */
23
+ isPropertyName: boolean;
24
+ /**
25
+ * Name of the property this string is the value of, when it is one.
26
+ *
27
+ * Used for structures Val treats as opaque: a richtext link node is a plain
28
+ * object with `href`, so there is no schema at that path to consult.
29
+ */
30
+ valueOfProperty?: string;
31
+ };
32
+ export type ValFileRefContext = {
33
+ kind: "file-ref";
34
+ /** Which constructor: `c.image(...)` or `c.file(...)`. */
35
+ subType: "image" | "file";
36
+ /** The string literal being edited, without quotes. */
37
+ currentText: string;
38
+ /** Offsets of the string literal's contents, excluding the quotes. */
39
+ contentStart: number;
40
+ contentEnd: number;
41
+ /**
42
+ * Start offset of the reference argument, including its opening quote.
43
+ *
44
+ * Stable while the user types to filter the completion list, because every
45
+ * such keystroke lands *inside* the literal. That makes it the anchor
46
+ * {@link findFileRefArgument} re-locates the call by at resolve time.
47
+ */
48
+ refArgStart: number;
49
+ /** End offset of the reference argument, where a metadata argument follows. */
50
+ refArgEnd: number;
51
+ /** Offsets of an existing metadata argument, when there is one. */
52
+ metadataStart?: number;
53
+ metadataEnd?: number;
54
+ };
55
+ /**
56
+ * Find the innermost `c.image(...)` / `c.file(...)` call whose first argument
57
+ * contains `offset`.
58
+ */
59
+ export declare function getValCompletionContext(sourceFile: ts.SourceFile, offset: number): ValCompletionContext | undefined;
60
+ /**
61
+ * Re-find the `c.image(...)` / `c.file(...)` call whose reference argument starts
62
+ * at `refArgStart`, and report where its arguments are *now*.
63
+ *
64
+ * `completionItem/resolve` runs against a document the user may have typed into
65
+ * since the list was computed, so the offsets captured back then have moved.
66
+ * Applying them anyway inserts the metadata object into the middle of the string
67
+ * literal and corrupts the file, so the offsets are re-derived here instead.
68
+ *
69
+ * Returns `undefined` when no such call is found — the document changed in some
70
+ * way this anchor does not survive, and the caller must then offer no edit
71
+ * rather than a wrong one.
72
+ */
73
+ export declare function findFileRefArgument(sourceFile: ts.SourceFile, refArgStart: number): {
74
+ refArgEnd: number;
75
+ metadataStart?: number;
76
+ metadataEnd?: number;
77
+ } | undefined;
@@ -0,0 +1,50 @@
1
+ import { type SchemaSourceSnapshot } from "@valbuild/shared/internal";
2
+ import { CompletionItem } from "vscode-languageserver";
3
+ import type { TextDocument } from "vscode-languageserver-textdocument";
4
+ import type { PublicValFiles } from "./publicValFiles.js";
5
+ /**
6
+ * Completions for file and image references.
7
+ *
8
+ * Offers the files that actually exist under the project's files directory, and
9
+ * — when the item is accepted — fills in the metadata argument by reading the
10
+ * chosen file. Getting width/height/mimeType right by hand is tedious and a
11
+ * frequent source of the very validation errors this server reports.
12
+ */
13
+ /** Stashed on the item so `resolve` can do the expensive work lazily. */
14
+ export type ValCompletionItemData = {
15
+ kind: "file-ref";
16
+ uri: string;
17
+ /** Val-style ref of the chosen file. */
18
+ ref: string;
19
+ /** Absolute path of the chosen file. */
20
+ filePath: string;
21
+ subType: "image" | "file";
22
+ /** Where a metadata argument goes, or what it replaces. */
23
+ /**
24
+ * Start offset of the reference argument, used to re-find the call at resolve
25
+ * time. Offsets captured now cannot be replayed later: see
26
+ * {@link findFileRefArgument}.
27
+ */
28
+ refArgStart: number;
29
+ };
30
+ export declare function createValCompletions({ document, offset, files, moduleFilePath, snapshot, }: {
31
+ document: TextDocument;
32
+ offset: number;
33
+ files: PublicValFiles;
34
+ /** This module's path, needed to look its schema up in the snapshot. */
35
+ moduleFilePath?: string;
36
+ /** Project-wide schemas and sources, for schema-driven completions. */
37
+ snapshot?: SchemaSourceSnapshot;
38
+ }): CompletionItem[];
39
+ /**
40
+ * Fill in the metadata argument for an accepted file reference.
41
+ *
42
+ * Done at resolve time because it reads the file from disk, and an editor
43
+ * requests completions far more often than it accepts one.
44
+ */
45
+ export declare function resolveValCompletion({ item, documents, }: {
46
+ item: CompletionItem;
47
+ documents: {
48
+ get(uri: string): TextDocument | undefined;
49
+ };
50
+ }): Promise<CompletionItem>;
@@ -0,0 +1,105 @@
1
+ import { type ModuleFilePath, type ValidationFix } from "@valbuild/core";
2
+ import { type SchemaSourceSnapshot } from "@valbuild/shared/internal";
3
+ import { Diagnostic, DiagnosticSeverity } from "vscode-languageserver";
4
+ import type { ValModuleContent } from "./ValProject.js";
5
+ /** Marks diagnostics as ours, so a client can filter on it. */
6
+ export declare const VAL_DIAGNOSTIC_SOURCE = "val";
7
+ /**
8
+ * Every diagnostic this server can produce.
9
+ *
10
+ * One naming convention, deliberately: `val/` followed by kebab-case. The
11
+ * VS Code extension this replaces had accumulated three conventions at once
12
+ * (`file-not-found`, `val:missing-module`, `image:add-to-gallery`), which made
13
+ * codes impossible to match on reliably.
14
+ *
15
+ * These are *diagnostic* codes and are distinct from *fix* names, which come
16
+ * from `ValidationFix` in `@valbuild/core` and keep Val's own `image:`/`file:`
17
+ * vocabulary. A diagnostic says what is wrong; a fix says what can be done
18
+ * about it, and travels in {@link ValDiagnosticData.fixes}.
19
+ */
20
+ export declare const VAL_DIAGNOSTIC_CODES: readonly ["val/validation", "val/schema", "val/fatal", "val/file-not-found", "val/missing-module"];
21
+ export type ValDiagnosticCode = (typeof VAL_DIAGNOSTIC_CODES)[number];
22
+ /**
23
+ * Structured payload attached to every Val diagnostic.
24
+ *
25
+ * Carried in `Diagnostic.data` (LSP 3.16), which is round-tripped back to the
26
+ * server on `textDocument/codeAction`. This replaces encoding information into
27
+ * the diagnostic's `code` string and parsing it out again — that approach could
28
+ * not carry anything but strings and broke whenever the format shifted.
29
+ */
30
+ export type ValDiagnosticData = {
31
+ code: ValDiagnosticCode;
32
+ /** Full source path the problem was reported at. */
33
+ sourcePath: string;
34
+ /**
35
+ * Fixes Val says are available, from `ValidationFix` in `@valbuild/core` —
36
+ * so this always reflects the installed Val rather than a list copied into a
37
+ * client, which is how the previous list drifted to 13 of 18 fixes.
38
+ */
39
+ fixes?: ValidationFix[];
40
+ /** Fix-specific payload, for example the offending value or route. */
41
+ value?: unknown;
42
+ /** Absolute path of the missing file, for `val/file-not-found`. */
43
+ filePath?: string;
44
+ };
45
+ /**
46
+ * Severity policy, in one place.
47
+ *
48
+ * - **Warning** — Val can fix it automatically. Mirrors the CLI, which prints
49
+ * these with `⚠` (its `validation-fixable-error` event) rather than `✘`. In an
50
+ * editor a one-click-fixable metadata mismatch should not shout as loudly as a
51
+ * type error.
52
+ * - **Error** — everything else: the content is wrong, the schema is wrong, or
53
+ * the module does not work at all.
54
+ *
55
+ * Note that `val validate` still exits non-zero for fixable errors, so Warning
56
+ * here is about presentation, not about the problem being optional.
57
+ */
58
+ export declare function severityFor({ code, fixes, }: {
59
+ code: ValDiagnosticCode;
60
+ fixes?: ValidationFix[];
61
+ }): DiagnosticSeverity;
62
+ export declare function createValDiagnostics({ moduleFilePath, content, text, valRoot, snapshot, }: {
63
+ moduleFilePath: ModuleFilePath;
64
+ content: ValModuleContent;
65
+ /** Current text of the module, as the editor sees it. */
66
+ text: string;
67
+ /**
68
+ * Val root, used to resolve file references. When omitted, file existence is
69
+ * not checked.
70
+ */
71
+ valRoot?: string;
72
+ /**
73
+ * Project-wide schemas and sources.
74
+ *
75
+ * `keyOf` and `route` validation cannot be completed by core alone — it has to
76
+ * look at other modules — so core emits a placeholder error carrying the fix
77
+ * name and a developer-facing message. Given a snapshot, those are resolved
78
+ * here: valid references drop out, invalid ones become real messages. Without
79
+ * one they are suppressed, since showing the placeholder would put
80
+ * unactionable noise on correct code.
81
+ */
82
+ snapshot?: SchemaSourceSnapshot;
83
+ }): Diagnostic[];
84
+ /**
85
+ * Diagnostic for a project that could not be evaluated at all.
86
+ *
87
+ * `createService` evaluates the whole `val.modules` graph, so one module that
88
+ * throws stops every module from being evaluated — as do a missing tsconfig and
89
+ * an unresolvable `@valbuild/core`. Reporting it on the file the user is looking
90
+ * at is imprecise, but the alternative is that all Val diagnostics silently
91
+ * disappear the moment a project stops evaluating.
92
+ */
93
+ export declare function createProjectErrorDiagnostic({ moduleFilePath, message, }: {
94
+ moduleFilePath: ModuleFilePath;
95
+ message: string;
96
+ }): Diagnostic;
97
+ /**
98
+ * Diagnostic for a Val module that is not registered in `val.modules`.
99
+ *
100
+ * Val only serves modules listed there, so an unregistered module silently does
101
+ * nothing — worth surfacing even though it is not a validation error.
102
+ */
103
+ export declare function createMissingModuleDiagnostic({ moduleFilePath, }: {
104
+ moduleFilePath: ModuleFilePath;
105
+ }): Diagnostic;
@@ -0,0 +1,13 @@
1
+ export { main, createValLanguageServer, type ValSession } from "./server.js";
2
+ export { getLanguageServerVersion } from "./version.js";
3
+ export { createEditorFsHost, mapOpenDocuments, type OpenDocuments, } from "./EditorFsHost.js";
4
+ export { createValProject, defaultCoreResolver, type CoreResolver, type ValProject, type ValProjectInitError, type ValModuleContent, } from "./ValProject.js";
5
+ export { createValDiagnostics, createMissingModuleDiagnostic, createProjectErrorDiagnostic, severityFor, VAL_DIAGNOSTIC_SOURCE, VAL_DIAGNOSTIC_CODES, type ValDiagnosticCode, type ValDiagnosticData, } from "./diagnostics.js";
6
+ export { findRegisteredModuleSpecifiers, isModuleRegistered, } from "./valModulesRegistry.js";
7
+ export { createValCodeActions, isLocalFix, minimalTextEdit, } from "./codeActions.js";
8
+ export { createValCompletions, resolveValCompletion, type ValCompletionItemData, } from "./completions.js";
9
+ export { getValCompletionContext, type ValCompletionContext, type ValFileRefContext, type ValStringValueContext, } from "./completionContext.js";
10
+ export { createPublicValFiles, DEFAULT_FILES_DIRECTORY, type PublicValFile, type PublicValFiles, } from "./publicValFiles.js";
11
+ export { createModulePathMap, findModulePathAtPosition, getModulePathRange, type ModulePathMap, type ModulePathRange, type ModulePosition, } from "./modulePathMap.js";
12
+ export { isValModuleUri, pathToUri, toModuleFilePath, uriToPath } from "./uri.js";
13
+ export { PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, VAL_FEATURES, VAL_PICK_REQUEST, VAL_INPUT_REQUEST, negotiateProtocolVersion, type ProtocolVersionRange, type ProtocolNegotiationResult, type ValClientCapabilities, type ValClientInfo, type ValEnvOverrides, type ValFeature, type ValInitializationOptions, type ValInputParams, type ValInputResult, type ValPickItem, type ValPickParams, type ValPickResult, type ValServerCapabilities, } from "./protocol.js";
@@ -0,0 +1,60 @@
1
+ import ts from "typescript";
2
+ import { type ModulePath } from "@valbuild/core";
3
+ /**
4
+ * Maps Val module paths onto positions in the module's TypeScript source.
5
+ *
6
+ * Validation errors come back keyed by `SourcePath` (for example
7
+ * `/content/page.val.ts?p="hero"."image"`), but an editor needs a line/column
8
+ * range. This walks the source expression passed to `c.define(...)` and records
9
+ * where each addressable path lands in the file.
10
+ *
11
+ * This is version-sensitive by nature: it encodes how Val's module paths
12
+ * correspond to source syntax, which is why it lives with Val rather than in an
13
+ * editor extension.
14
+ *
15
+ * NOTE: `@valbuild/server` has a near-identical `modulePathMap.ts`, used for the
16
+ * CLI's code frames and the Val UI. This copy differs in locating the source
17
+ * expression via `analyzeValModule` and in exposing
18
+ * {@link findModulePathAtPosition}. Fix traversal bugs in both, or fold them
19
+ * together.
20
+ */
21
+ export type ModulePosition = {
22
+ line: number;
23
+ character: number;
24
+ };
25
+ export type ModulePathRange = {
26
+ start: ModulePosition;
27
+ end: ModulePosition;
28
+ };
29
+ export type ModulePathMap = {
30
+ [modulePath: string]: ModulePathRange & {
31
+ children: ModulePathMap;
32
+ };
33
+ };
34
+ /**
35
+ * Look up the source range for a module path.
36
+ *
37
+ * Returns `undefined` when the path cannot be resolved — an unparseable path, or
38
+ * one that does not correspond to any node. That happens legitimately when
39
+ * schema serialization failed upstream, so callers should treat a missing range
40
+ * as "report this diagnostic on the module instead" rather than as a bug.
41
+ */
42
+ export declare function getModulePathRange(modulePath: string, modulePathMap: ModulePathMap): ModulePathRange | undefined;
43
+ /**
44
+ * Find the module path of the innermost entry whose range contains `position`.
45
+ *
46
+ * The inverse of the map's normal use: given where the cursor is, work out which
47
+ * part of the module it addresses, so the schema there can be looked up. Used by
48
+ * schema-driven completions.
49
+ *
50
+ * Segments are encoded with `Internal.patchPathToModulePath`, so the result is
51
+ * addressable by the same functions that consume validation error paths.
52
+ */
53
+ export declare function findModulePathAtPosition(modulePathMap: ModulePathMap, position: ModulePosition): ModulePath | undefined;
54
+ /**
55
+ * Build a {@link ModulePathMap} for a Val module source file.
56
+ *
57
+ * Returns `undefined` when the file is not a recognisable Val module (no
58
+ * `export default c.define(...)`).
59
+ */
60
+ export declare function createModulePathMap(sourceFile: ts.SourceFile): ModulePathMap | undefined;
@@ -0,0 +1,200 @@
1
+ /**
2
+ * The Val language server protocol.
3
+ *
4
+ * This module is the contract between an editor client (for example the Val
5
+ * VS Code extension) and the language server that ships with the Val version
6
+ * installed in the user's project.
7
+ *
8
+ * The whole point of this package is that ONE editor client works against MANY
9
+ * versions of Val. That means:
10
+ *
11
+ * - Clients MUST NOT assume they can import this module. They resolve the
12
+ * server from the user's `node_modules` at runtime, and the constants below
13
+ * are intentionally small and pure so a client can vendor a copy of them.
14
+ * A client may take a type-only devDependency on this package for the types.
15
+ * - Anything added here must degrade gracefully. New capabilities are
16
+ * announced through `features` / `commands` so that a client which has never
17
+ * heard of them simply does not offer them, instead of breaking.
18
+ *
19
+ * Deliberately dependency-free: no `vscode-languageserver` import, no Val
20
+ * imports, no I/O.
21
+ */
22
+ /**
23
+ * The current protocol version.
24
+ *
25
+ * Bump this ONLY for a breaking change to the client/server contract — a
26
+ * removed or renamed request, a changed payload shape, or changed semantics
27
+ * that an older client would misinterpret. Additive changes (a new feature
28
+ * flag, a new command, a new optional field) do NOT need a bump: they are
29
+ * negotiated through `features` and `commands`.
30
+ *
31
+ * This is a hand-maintained literal on purpose. Deriving it from package.json
32
+ * (as `Internal.VERSION.core` does) breaks under bundling, and build-time
33
+ * string substitution (as `@valbuild/ui`'s VERSION does) is easy to get wrong.
34
+ */
35
+ export declare const PROTOCOL_VERSION = 1;
36
+ /**
37
+ * The range of protocol versions this server can speak. Kept separate from
38
+ * {@link PROTOCOL_VERSION} so that a future server can continue to serve older
39
+ * clients by lowering `min`.
40
+ */
41
+ export declare const SUPPORTED_PROTOCOL_VERSIONS: ProtocolVersionRange;
42
+ export type ProtocolVersionRange = {
43
+ min: number;
44
+ max: number;
45
+ };
46
+ export type ValClientInfo = {
47
+ /** For example `"vscode-val-build"`. Used for logging and telemetry only. */
48
+ name: string;
49
+ version: string | null;
50
+ };
51
+ /**
52
+ * The only environment variables a client may override. `initializationOptions`
53
+ * is untyped JSON at runtime, so this list is what the server enforces: without
54
+ * it a client could set `PATH` or `NODE_OPTIONS` on the server process.
55
+ */
56
+ export declare const VAL_ENV_OVERRIDE_KEYS: readonly ["VAL_CONTENT_URL", "VAL_REMOTE_HOST", "VAL_BUILD_URL"];
57
+ /**
58
+ * Environment overrides forwarded from the client. These mirror the
59
+ * `VAL_*` environment variables so that an editor can point a session at a
60
+ * non-production Val backend without the user having to restart their editor
61
+ * with a modified environment.
62
+ */
63
+ export type ValEnvOverrides = Partial<Record<(typeof VAL_ENV_OVERRIDE_KEYS)[number], string>>;
64
+ /**
65
+ * Sent by the client as `InitializeParams.initializationOptions`.
66
+ *
67
+ * One server instance serves exactly one Val root. A workspace containing
68
+ * several Val roots (a monorepo) gets one server per root, because different
69
+ * roots may pin different versions of Val.
70
+ */
71
+ export type ValInitializationOptions = {
72
+ client: ValClientInfo;
73
+ /** Protocol versions the client can speak. */
74
+ supportedProtocolVersions: ProtocolVersionRange;
75
+ /** Absolute path to the directory containing this project's `package.json`. */
76
+ valRoot: string;
77
+ env?: ValEnvOverrides;
78
+ };
79
+ /**
80
+ * Optional capabilities a client may implement, announced by the client under
81
+ * `capabilities.experimental.val`.
82
+ *
83
+ * The server uses this to decide whether it can offer flows that need user
84
+ * interaction. A client that implements neither still gets diagnostics and
85
+ * completions.
86
+ */
87
+ export type ValClientCapabilities = {
88
+ /** Client implements the {@link VAL_PICK_REQUEST} request. */
89
+ pick?: boolean;
90
+ /** Client implements the {@link VAL_INPUT_REQUEST} request. */
91
+ input?: boolean;
92
+ };
93
+ /**
94
+ * Feature flags announced by the server under
95
+ * `capabilities.experimental.val.features`.
96
+ *
97
+ * A client should treat an unknown string as "something this Val version can do
98
+ * that I do not know about" and ignore it, and a missing string as "not
99
+ * available in this Val version" and hide the corresponding UI.
100
+ */
101
+ export declare const VAL_FEATURES: readonly ["diagnostics", "diagnostics/gallery", "completions/route", "completions/keyOf", "completions/mediaPath", "completions/galleryKey", "completions/richtextLink", "fix/metadata", "fix/upload-remote", "fix/download-remote", "fix/missing-module", "fix/gallery", "login"];
102
+ export type ValFeature = (typeof VAL_FEATURES)[number];
103
+ /**
104
+ * Announced by the server as
105
+ * `InitializeResult.capabilities.experimental.val`.
106
+ */
107
+ export type ValServerCapabilities = {
108
+ /**
109
+ * The negotiated protocol version — within the client's range unless
110
+ * {@link ValServerCapabilities.incompatible} is set, in which case this is
111
+ * the highest version the server itself can speak.
112
+ */
113
+ protocolVersion: number;
114
+ /**
115
+ * Present only when version negotiation failed. `features` and `commands`
116
+ * are then empty; the client should stop the server and tell the user which
117
+ * side to update. Checking this first is what turns an "incompatible
118
+ * versions" dead end into an actionable message.
119
+ */
120
+ incompatible?: Exclude<ProtocolNegotiationResult, {
121
+ status: "ok";
122
+ }>;
123
+ versions: {
124
+ /** Version of `@valbuild/core` resolved in the user's project. */
125
+ core: string | null;
126
+ /** Version of this package. */
127
+ languageServer: string | null;
128
+ };
129
+ /** Echoed back so the client can label the session (status bar, logs). */
130
+ valRoot: string;
131
+ /**
132
+ * Features this server actually serves. Narrower than {@link VAL_FEATURES}
133
+ * when a capability could not be initialised for this project.
134
+ */
135
+ features: ValFeature[];
136
+ /** `workspace/executeCommand` names this server offers. */
137
+ commands: string[];
138
+ };
139
+ /** Ask the user to choose one of a list of options (a "quick pick"). */
140
+ export declare const VAL_PICK_REQUEST = "val/pick";
141
+ export type ValPickItem = {
142
+ label: string;
143
+ /** Rendered next to the label. */
144
+ description?: string;
145
+ /** Rendered below the label. */
146
+ detail?: string;
147
+ /** Opaque to the client; returned verbatim in {@link ValPickResult}. */
148
+ value: string;
149
+ };
150
+ export type ValPickParams = {
151
+ title: string;
152
+ placeholder?: string;
153
+ items: ValPickItem[];
154
+ };
155
+ /** `null` when the user dismissed the picker. */
156
+ export type ValPickResult = {
157
+ value: string;
158
+ } | null;
159
+ /** Ask the user to type a value (an "input box"). */
160
+ export declare const VAL_INPUT_REQUEST = "val/input";
161
+ export type ValInputParams = {
162
+ title: string;
163
+ prompt?: string;
164
+ /** Pre-filled value. */
165
+ value?: string;
166
+ placeholder?: string;
167
+ password?: boolean;
168
+ };
169
+ /** `null` when the user dismissed the input box. */
170
+ export type ValInputResult = {
171
+ value: string;
172
+ } | null;
173
+ export type ProtocolNegotiationResult = {
174
+ status: "ok";
175
+ /** Highest version both sides can speak. */
176
+ protocolVersion: number;
177
+ } | {
178
+ /**
179
+ * The server is newer than anything the client understands: the user
180
+ * should update their editor client.
181
+ */
182
+ status: "client-too-old";
183
+ server: ProtocolVersionRange;
184
+ client: ProtocolVersionRange;
185
+ } | {
186
+ /**
187
+ * The server is older than anything the client understands: the user
188
+ * should update Val in their project.
189
+ */
190
+ status: "server-too-old";
191
+ server: ProtocolVersionRange;
192
+ client: ProtocolVersionRange;
193
+ };
194
+ /**
195
+ * Pick the highest protocol version both sides can speak.
196
+ *
197
+ * Returns a *directional* failure so the client can tell the user which side to
198
+ * update, rather than showing a generic "incompatible versions" error.
199
+ */
200
+ export declare function negotiateProtocolVersion(client: ProtocolVersionRange, server?: ProtocolVersionRange): ProtocolNegotiationResult;