From 9967cdd21b95e187388dc840f8593a5c12c77c0b Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Fri, 17 Jul 2026 20:31:43 +0700 Subject: [PATCH 1/4] feat: add Registry Stack YAML navigation Signed-off-by: Jeremi Joslin --- .github/workflows/ci.yml | 43 + Cargo.lock | 84 ++ Cargo.toml | 5 + crates/registry-language-server/Cargo.toml | 22 + crates/registry-language-server/README.md | 36 + crates/registry-language-server/src/index.rs | 1260 +++++++++++++++++ crates/registry-language-server/src/lib.rs | 18 + crates/registry-language-server/src/main.rs | 6 + crates/registry-language-server/src/server.rs | 537 +++++++ .../tests/protocol.rs | 217 +++ crates/registryctl/CHANGELOG.md | 3 + crates/registryctl/Cargo.toml | 1 + crates/registryctl/src/main.rs | 17 + crates/registryctl/tests/language_server.rs | 92 ++ deny.toml | 3 + .../content/docs/reference/registryctl.mdx | 13 +- editors/README.md | 13 + editors/vscode/.gitignore | 3 + editors/vscode/.vscodeignore | 7 + editors/vscode/LICENSE | 201 +++ editors/vscode/README.md | 29 + editors/vscode/package-lock.json | 156 ++ editors/vscode/package.json | 64 + editors/vscode/src/extension.ts | 170 +++ editors/vscode/tsconfig.json | 20 + editors/zed/.gitignore | 1 + editors/zed/Cargo.lock | 817 +++++++++++ editors/zed/Cargo.toml | 14 + editors/zed/LICENSE | 201 +++ editors/zed/README.md | 21 + editors/zed/extension.toml | 11 + editors/zed/src/lib.rs | 38 + 32 files changed, 4121 insertions(+), 2 deletions(-) create mode 100644 crates/registry-language-server/Cargo.toml create mode 100644 crates/registry-language-server/README.md create mode 100644 crates/registry-language-server/src/index.rs create mode 100644 crates/registry-language-server/src/lib.rs create mode 100644 crates/registry-language-server/src/main.rs create mode 100644 crates/registry-language-server/src/server.rs create mode 100644 crates/registry-language-server/tests/protocol.rs create mode 100644 crates/registryctl/tests/language_server.rs create mode 100644 editors/README.md create mode 100644 editors/vscode/.gitignore create mode 100644 editors/vscode/.vscodeignore create mode 100644 editors/vscode/LICENSE create mode 100644 editors/vscode/README.md create mode 100644 editors/vscode/package-lock.json create mode 100644 editors/vscode/package.json create mode 100644 editors/vscode/src/extension.ts create mode 100644 editors/vscode/tsconfig.json create mode 100644 editors/zed/.gitignore create mode 100644 editors/zed/Cargo.lock create mode 100644 editors/zed/Cargo.toml create mode 100644 editors/zed/LICENSE create mode 100644 editors/zed/README.md create mode 100644 editors/zed/extension.toml create mode 100644 editors/zed/src/lib.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8252731cb..2733b8ec2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,7 @@ jobs: release_tool: ${{ steps.filter.outputs.release_tool }} release_source_proof: ${{ steps.filter.outputs.release_source_proof }} docs: ${{ steps.filter.outputs.docs }} + editors: ${{ steps.filter.outputs.editors }} registryctl_tutorial: ${{ steps.filter.outputs.registryctl_tutorial }} steps: - name: Checkout @@ -41,6 +42,7 @@ jobs: release_tool=false release_source_proof=false docs=false + editors=false registryctl_tutorial=false all=false @@ -60,6 +62,7 @@ jobs: release_tool=true release_source_proof=true docs=true + editors=true registryctl_tutorial=true } @@ -101,6 +104,11 @@ jobs: docs=true ;; esac + case "${path}" in + editors/*) + editors=true + ;; + esac case "${path}" in Cargo.toml|Cargo.lock|rust-toolchain*|LICENSE|docs/site/package.json|docs/site/package-lock.json|docs/site/scripts/check-registryctl-tutorials.sh|docs/site/scripts/registryctl-tutorial.*|docs/site/src/content/docs/tutorials/publish-spreadsheet-secured-registry-api.mdx|docs/site/src/content/docs/tutorials/verify-claim-registry-api.mdx|crates/registry-config-report/*|crates/registry-manifest-*/*|crates/registry-notary/*|crates/registry-notary-*/*|crates/registry-platform-*/*|crates/registry-relay/*|crates/registryctl/*|products/notary/*|release/docker/Dockerfile.registry-notary|release/docker/Dockerfile.registry-relay) registryctl_tutorial=true @@ -114,6 +122,7 @@ jobs: echo "release_tool=${release_tool}" echo "release_source_proof=${release_source_proof}" echo "docs=${docs}" + echo "editors=${editors}" echo "registryctl_tutorial=${registryctl_tutorial}" } >> "${GITHUB_OUTPUT}" @@ -377,3 +386,37 @@ jobs: - name: Check docs build working-directory: docs/site run: npm run check + + editor-extensions: + name: Editor extensions + needs: changes + if: needs.changes.outputs.editors == 'true' + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + with: + fetch-depth: 0 + submodules: false + + - name: Setup Node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + with: + node-version: 22.12.0 + cache: npm + cache-dependency-path: editors/vscode/package-lock.json + + - name: Check VS Code extension + working-directory: editors/vscode + run: | + npm ci + npm run check + cmp ../../LICENSE LICENSE + + - name: Install Zed extension target + run: rustup target add wasm32-wasip1 + + - name: Check Zed extension + run: | + cargo check --locked --target wasm32-wasip1 --manifest-path editors/zed/Cargo.toml + cmp LICENSE editors/zed/LICENSE diff --git a/Cargo.lock b/Cargo.lock index 09cfbc015..efba328d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3920,6 +3920,19 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "ls-types" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "896e16b8e17d8732b9efe4d5b66cb0cc162b3023a2d8122f2aea6f7f185e0a67" +dependencies = [ + "bitflags", + "fluent-uri", + "percent-encoding", + "serde", + "serde_json", +] + [[package]] name = "lz4_flex" version = "0.13.1" @@ -5186,6 +5199,19 @@ dependencies = [ "serde_json", ] +[[package]] +name = "registry-language-server" +version = "0.10.0" +dependencies = [ + "anyhow", + "serde_json", + "tempfile", + "tokio", + "tower-lsp-server", + "tree-sitter", + "tree-sitter-yaml", +] + [[package]] name = "registry-manifest-cli" version = "0.10.0" @@ -5716,6 +5742,7 @@ dependencies = [ "rcgen", "regex", "registry-config-report", + "registry-language-server", "registry-notary-core", "registry-notary-server", "registry-platform-authcommon", @@ -6221,6 +6248,7 @@ version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -6572,6 +6600,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + [[package]] name = "stringprep" version = "0.1.5" @@ -7028,6 +7062,26 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" +[[package]] +name = "tower-lsp-server" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0e711655c89181a6bc6a2cc348131fcd9680085f5b06b6af13427a393a6e72" +dependencies = [ + "bytes", + "dashmap", + "futures", + "httparse", + "ls-types", + "memchr", + "serde", + "serde_json", + "tokio", + "tokio-util", + "tower", + "tracing", +] + [[package]] name = "tower-service" version = "0.3.3" @@ -7109,6 +7163,36 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tree-sitter" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1c71c1c4cc0920b20d6b0f6572e7682cd07a6a2faec71067a31fa394c586df" +dependencies = [ + "cc", + "regex", + "regex-syntax 0.8.11", + "serde_json", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782" + +[[package]] +name = "tree-sitter-yaml" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c223db85f05e34794f065454843b0668ebc15d240ada63e2b5939f43ce7c97" +dependencies = [ + "cc", + "tree-sitter-language", +] + [[package]] name = "try-lock" version = "0.2.5" diff --git a/Cargo.toml b/Cargo.toml index 0a4e77fdc..896dcbfef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ members = [ "crates/registry-notary", "crates/registry-notary-worker-harness", "crates/registry-relay", + "crates/registry-language-server", "crates/registryctl", "products/notary/xtask", ] @@ -51,6 +52,7 @@ unsafe_code = "forbid" [workspace.dependencies] registry-config-report = { path = "crates/registry-config-report", version = "0.10.0" } +registry-language-server = { path = "crates/registry-language-server", version = "0.10.0" } registry-manifest-core = { path = "crates/registry-manifest-core", version = "0.10.0" } registry-notary-client = { path = "crates/registry-notary-client", version = "0.10.0" } registry-notary-core = { path = "crates/registry-notary-core", version = "0.10.0" } @@ -146,6 +148,9 @@ tokio-rustls = { version = "0.26", default-features = false, features = ["ring", tokio-util = { version = "0.7" } toml = { version = "1.1" } tower = { version = "0.5" } +tower-lsp-server = { version = "0.23" } +tree-sitter = { version = "0.26" } +tree-sitter-yaml = { version = "0.7.2" } tower-http = { version = "0.7", features = ["cors", "limit", "request-id", "set-header", "timeout", "trace"] } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", features = ["fmt", "json", "env-filter"] } diff --git a/crates/registry-language-server/Cargo.toml b/crates/registry-language-server/Cargo.toml new file mode 100644 index 000000000..81bff4685 --- /dev/null +++ b/crates/registry-language-server/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "registry-language-server" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +description = "Cross-file navigation and reference diagnostics for Registry Stack project YAML." +repository.workspace = true + +[dependencies] +anyhow.workspace = true +serde_json.workspace = true +tokio = { workspace = true, features = ["io-std"] } +tower-lsp-server.workspace = true +tree-sitter.workspace = true +tree-sitter-yaml.workspace = true + +[dev-dependencies] +tempfile.workspace = true + +[lints] +workspace = true diff --git a/crates/registry-language-server/README.md b/crates/registry-language-server/README.md new file mode 100644 index 000000000..65ca01af7 --- /dev/null +++ b/crates/registry-language-server/README.md @@ -0,0 +1,36 @@ +# Registry Stack language server + +`registry-language-server` adds Registry Stack project semantics to YAML editors through the +Language Server Protocol. It discovers the bounded authoring surface rooted at +`registry-stack.yaml` and indexes: + +- registry, integration, entity, service, consultation, claim, credential-profile, fixture, and + environment definitions; +- integration and entity aliases across the project manifest, their definition files, and + environment files; +- consultation integration references, records-service entity references, credential-profile + claim references, direct claim-output consultation references, and fixture expected-claim + references. + +It provides go to definition, find references, workspace symbols, document symbols, and errors for +missing, duplicate, or ambiguous references. It deliberately leaves syntax, schemas, completion, +hover, and formatting to the editor's YAML language server. + +## Run + +```console +cargo run -p registry-language-server +``` + +The same server is available from a release installation as: + +```console +registryctl authoring language-server +``` + +The server communicates over standard input and output and expects the opened workspace (or a +nested directory) to be inside a Registry Stack project. + +Only regular files in the documented Registry Stack project layout are indexed. Symbolic links, +files outside the project root, unrelated YAML files, and project documents larger than 1 MiB are +ignored. This keeps editor analysis inside the same bounded authoring surface as `registryctl`. diff --git a/crates/registry-language-server/src/index.rs b/crates/registry-language-server/src/index.rs new file mode 100644 index 000000000..123070369 --- /dev/null +++ b/crates/registry-language-server/src/index.rs @@ -0,0 +1,1260 @@ +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + collections::{BTreeMap, BTreeSet}, + fs, + path::{Component, Path, PathBuf}, +}; + +use anyhow::{Context, Result}; +use tower_lsp_server::ls_types::{DiagnosticSeverity, Position, Range, SymbolKind}; +use tree_sitter::{Node, Parser}; + +const PROJECT_FILE: &str = "registry-stack.yaml"; +const MAX_DOCUMENT_BYTES: u64 = 1024 * 1024; + +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum RegistrySymbolKind { + Registry, + Integration, + Entity, + Service, + Consultation, + Claim, + CredentialProfile, + Fixture, + Environment, +} + +impl RegistrySymbolKind { + pub fn label(self) -> &'static str { + match self { + Self::Registry => "registry", + Self::Integration => "integration", + Self::Entity => "entity", + Self::Service => "service", + Self::Consultation => "consultation", + Self::Claim => "claim", + Self::CredentialProfile => "credential profile", + Self::Fixture => "fixture", + Self::Environment => "environment", + } + } + + pub fn lsp_kind(self) -> SymbolKind { + match self { + Self::Registry => SymbolKind::NAMESPACE, + Self::Integration | Self::Entity => SymbolKind::MODULE, + Self::Service => SymbolKind::INTERFACE, + Self::Consultation => SymbolKind::FUNCTION, + Self::Claim => SymbolKind::PROPERTY, + Self::CredentialProfile => SymbolKind::OBJECT, + Self::Fixture => SymbolKind::EVENT, + Self::Environment => SymbolKind::PACKAGE, + } + } +} + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +struct SymbolKey { + kind: RegistrySymbolKind, + scope: Option, + name: String, +} + +impl SymbolKey { + fn global(kind: RegistrySymbolKind, name: impl Into) -> Self { + Self { + kind, + scope: None, + name: name.into(), + } + } + + fn scoped(kind: RegistrySymbolKind, scope: impl Into, name: impl Into) -> Self { + Self { + kind, + scope: Some(scope.into()), + name: name.into(), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct IndexedLocation { + pub path: PathBuf, + pub range: Range, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct IndexedSymbol { + pub name: String, + pub kind: RegistrySymbolKind, + pub container_name: Option, + pub location: IndexedLocation, + key: SymbolKey, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct IndexedDiagnostic { + pub path: PathBuf, + pub range: Range, + pub severity: DiagnosticSeverity, + pub message: String, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct SymbolQuery { + kind: RegistrySymbolKind, + scope: Option, + name: String, +} + +impl SymbolQuery { + fn global(kind: RegistrySymbolKind, name: impl Into) -> Self { + Self { + kind, + scope: None, + name: name.into(), + } + } + + fn scoped(kind: RegistrySymbolKind, scope: impl Into, name: impl Into) -> Self { + Self { + kind, + scope: Some(scope.into()), + name: name.into(), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct IndexedReference { + target: SymbolQuery, + location: IndexedLocation, +} + +#[derive(Debug, Default)] +pub struct ProjectIndex { + root: PathBuf, + symbols: Vec, + references: Vec, + diagnostics: Vec, + document_paths: BTreeSet, +} + +impl ProjectIndex { + pub fn load(root: &Path) -> Result { + let root = root + .canonicalize() + .with_context(|| format!("failed to resolve project root {}", root.display()))?; + let documents = load_project_documents(&root)?; + Ok(Self::from_documents(&root, &documents)) + } + + pub fn from_documents(root: &Path, documents: &BTreeMap) -> Self { + let root = root.to_path_buf(); + let parsed = documents + .iter() + .filter_map(|(path, source)| parse_yaml(source).ok().map(|value| (path.clone(), value))) + .collect::>(); + + let (symbols, references) = { + let mut builder = IndexBuilder { + root: &root, + parsed: &parsed, + symbols: Vec::new(), + references: Vec::new(), + }; + builder.build(); + (builder.symbols, builder.references) + }; + + let mut index = Self { + root, + symbols, + references, + diagnostics: Vec::new(), + document_paths: documents.keys().cloned().collect(), + }; + index.diagnostics = index.build_diagnostics(); + index + } + + pub fn root(&self) -> &Path { + &self.root + } + + pub fn symbols(&self) -> &[IndexedSymbol] { + &self.symbols + } + + pub fn document_symbols(&self, path: &Path) -> Vec<&IndexedSymbol> { + let path = normalize_lookup_path(path); + self.symbols + .iter() + .filter(|symbol| symbol.location.path == path) + .collect() + } + + pub fn workspace_symbols(&self, query: &str) -> Vec<&IndexedSymbol> { + let query = query.to_lowercase(); + self.symbols + .iter() + .filter(|symbol| { + query.is_empty() + || symbol.name.to_lowercase().contains(&query) + || symbol + .container_name + .as_ref() + .is_some_and(|container| container.to_lowercase().contains(&query)) + }) + .collect() + } + + pub fn definitions_at(&self, path: &Path, position: Position) -> Vec { + let path = normalize_lookup_path(path); + if let Some(reference) = self.reference_at(&path, position) { + return self + .definitions_for(&reference.target) + .into_iter() + .map(|symbol| symbol.location.clone()) + .collect(); + } + + self.symbol_at(&path, position) + .map(|symbol| vec![symbol.location.clone()]) + .unwrap_or_default() + } + + pub fn references_at( + &self, + path: &Path, + position: Position, + include_declaration: bool, + ) -> Vec { + let path = normalize_lookup_path(path); + let keys = if let Some(symbol) = self.symbol_at(&path, position) { + vec![symbol.key.clone()] + } else if let Some(reference) = self.reference_at(&path, position) { + self.definitions_for(&reference.target) + .into_iter() + .map(|symbol| symbol.key.clone()) + .collect() + } else { + Vec::new() + }; + + let mut locations = Vec::new(); + if include_declaration { + for symbol in &self.symbols { + if keys.contains(&symbol.key) { + locations.push(symbol.location.clone()); + } + } + } + for reference in &self.references { + if keys + .iter() + .any(|key| self.query_can_resolve_to(&reference.target, key)) + { + locations.push(reference.location.clone()); + } + } + locations.sort_by(location_cmp); + locations.dedup(); + locations + } + + pub fn diagnostics(&self) -> &[IndexedDiagnostic] { + &self.diagnostics + } + + pub fn document_paths(&self) -> impl Iterator { + self.document_paths.iter().map(PathBuf::as_path) + } + + fn symbol_at(&self, path: &Path, position: Position) -> Option<&IndexedSymbol> { + self.symbols.iter().find(|symbol| { + symbol.location.path == path && range_contains(symbol.location.range, position) + }) + } + + fn reference_at(&self, path: &Path, position: Position) -> Option<&IndexedReference> { + self.references.iter().find(|reference| { + reference.location.path == path && range_contains(reference.location.range, position) + }) + } + + fn definitions_for(&self, query: &SymbolQuery) -> Vec<&IndexedSymbol> { + self.symbols + .iter() + .filter(|symbol| self.query_can_resolve_to(query, &symbol.key)) + .collect() + } + + fn query_can_resolve_to(&self, query: &SymbolQuery, key: &SymbolKey) -> bool { + query.kind == key.kind + && query.name == key.name + && query + .scope + .as_ref() + .is_none_or(|scope| key.scope.as_ref() == Some(scope)) + } + + fn build_diagnostics(&self) -> Vec { + let mut diagnostics = Vec::new(); + let mut definitions: BTreeMap<&SymbolKey, Vec<&IndexedSymbol>> = BTreeMap::new(); + for symbol in &self.symbols { + definitions.entry(&symbol.key).or_default().push(symbol); + } + + for (key, duplicates) in definitions { + if duplicates.len() < 2 { + continue; + } + for symbol in duplicates { + diagnostics.push(IndexedDiagnostic { + path: symbol.location.path.clone(), + range: symbol.location.range, + severity: DiagnosticSeverity::ERROR, + message: format!( + "Duplicate {} definition '{}'{}", + key.kind.label(), + key.name, + key.scope + .as_ref() + .map(|scope| format!(" in service '{scope}'")) + .unwrap_or_default() + ), + }); + } + } + + for reference in &self.references { + let candidates = self.definitions_for(&reference.target); + let message = match candidates.len() { + 0 => Some(format!( + "Unknown {} reference '{}'{}", + reference.target.kind.label(), + reference.target.name, + reference + .target + .scope + .as_ref() + .map(|scope| format!(" in service '{scope}'")) + .unwrap_or_default() + )), + 1 => None, + count => Some(format!( + "Ambiguous {} reference '{}': found {count} definitions", + reference.target.kind.label(), + reference.target.name + )), + }; + if let Some(message) = message { + diagnostics.push(IndexedDiagnostic { + path: reference.location.path.clone(), + range: reference.location.range, + severity: DiagnosticSeverity::ERROR, + message, + }); + } + } + + diagnostics.sort_by(|left, right| { + left.path + .cmp(&right.path) + .then_with(|| range_cmp(left.range, right.range)) + .then_with(|| left.message.cmp(&right.message)) + }); + diagnostics + } +} + +pub fn is_valid_yaml(source: &str) -> bool { + parse_yaml(source).is_ok() +} + +pub fn is_project_document(root: &Path, path: &Path) -> bool { + let Ok(relative) = path.strip_prefix(root) else { + return false; + }; + let components = relative.components().collect::>(); + let normal = |component: &Component<'_>| matches!(component, Component::Normal(_)); + let extension_is_yaml = || { + relative + .extension() + .is_some_and(|extension| extension == "yaml") + }; + + match components.as_slice() { + [Component::Normal(file)] => file == &PROJECT_FILE, + [first, second] if normal(first) && normal(second) => { + matches!(first, Component::Normal(name) if *name == "entities" || *name == "environments") + && extension_is_yaml() + } + [Component::Normal(integrations), integration, Component::Normal(file)] => { + *integrations == "integrations" && normal(integration) && *file == "integration.yaml" + } + [Component::Normal(integrations), integration, Component::Normal(fixtures), fixture] => { + *integrations == "integrations" + && normal(integration) + && *fixtures == "fixtures" + && normal(fixture) + && extension_is_yaml() + } + _ => false, + } +} + +pub fn load_project_documents(root: &Path) -> Result> { + let mut candidates = vec![root.join(PROJECT_FILE)]; + add_yaml_files(&root.join("entities"), &mut candidates)?; + add_yaml_files(&root.join("environments"), &mut candidates)?; + + let integrations = root.join("integrations"); + if let Ok(entries) = fs::read_dir(&integrations) { + for entry in entries { + let entry = entry.with_context(|| { + format!( + "failed to inspect integrations under {}", + integrations.display() + ) + })?; + if entry.file_type()?.is_dir() { + let directory = entry.path(); + candidates.push(directory.join("integration.yaml")); + add_yaml_files(&directory.join("fixtures"), &mut candidates)?; + } + } + } + + candidates.sort(); + candidates.dedup(); + let mut documents = BTreeMap::new(); + for path in candidates { + let metadata = match fs::symlink_metadata(&path) { + Ok(metadata) => metadata, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue, + Err(error) => { + return Err(error).with_context(|| format!("failed to inspect {}", path.display())) + } + }; + if !metadata.file_type().is_file() || metadata.len() > MAX_DOCUMENT_BYTES { + continue; + } + let source = fs::read_to_string(&path) + .with_context(|| format!("failed to read {}", path.display()))?; + documents.insert(path, source); + } + Ok(documents) +} + +fn add_yaml_files(directory: &Path, candidates: &mut Vec) -> Result<()> { + let entries = match fs::read_dir(directory) { + Ok(entries) => entries, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(error) => { + return Err(error) + .with_context(|| format!("failed to inspect {}", directory.display())); + } + }; + for entry in entries { + let entry = entry + .with_context(|| format!("failed to inspect an entry in {}", directory.display()))?; + if entry.file_type()?.is_file() && entry.path().extension().is_some_and(|ext| ext == "yaml") + { + candidates.push(entry.path()); + } + } + Ok(()) +} + +struct IndexBuilder<'a> { + root: &'a Path, + parsed: &'a BTreeMap, + symbols: Vec, + references: Vec, +} + +impl IndexBuilder<'_> { + fn build(&mut self) { + let manifest_path = self.root.join(PROJECT_FILE); + let mut claimed_definition_files = BTreeSet::new(); + if let Some(manifest) = self.parsed.get(&manifest_path) { + self.extract_manifest(&manifest_path, manifest, &mut claimed_definition_files); + } + + for (path, document) in self.parsed { + if path == &manifest_path { + continue; + } + let Ok(relative) = path.strip_prefix(self.root) else { + continue; + }; + if is_fixture_path(relative) { + self.extract_fixture(path, document); + } else if is_environment_path(relative) { + self.extract_environment(path, relative, document); + } else if !claimed_definition_files.contains(path) { + if is_integration_path(relative) { + self.extract_orphan_definition(path, document, RegistrySymbolKind::Integration); + } else if is_entity_path(relative) { + self.extract_orphan_definition(path, document, RegistrySymbolKind::Entity); + } + } + } + } + + fn extract_manifest( + &mut self, + path: &Path, + manifest: &YamlValue, + claimed_definition_files: &mut BTreeSet, + ) { + if let Some(registry_id) = manifest + .get("registry") + .and_then(|registry| registry.get_scalar("id")) + { + self.add_symbol( + SymbolKey::global(RegistrySymbolKind::Registry, ®istry_id.value), + None, + path, + registry_id.range, + ); + } + + self.extract_aliases( + path, + manifest, + "integrations", + RegistrySymbolKind::Integration, + claimed_definition_files, + ); + self.extract_aliases( + path, + manifest, + "entities", + RegistrySymbolKind::Entity, + claimed_definition_files, + ); + + let Some(services) = manifest.get("services").and_then(YamlValue::as_mapping) else { + return; + }; + for service in services { + let service_name = service.key.value.clone(); + self.add_symbol( + SymbolKey::global(RegistrySymbolKind::Service, &service_name), + None, + path, + service.key.range, + ); + + if let Some(entity) = service.value.get_scalar("entity") { + self.add_reference( + SymbolQuery::global(RegistrySymbolKind::Entity, &entity.value), + path, + entity.range, + ); + } + + if let Some(consultations) = service + .value + .get("consultations") + .and_then(YamlValue::as_mapping) + { + for consultation in consultations { + self.add_symbol( + SymbolKey::scoped( + RegistrySymbolKind::Consultation, + &service_name, + &consultation.key.value, + ), + Some(service_name.clone()), + path, + consultation.key.range, + ); + if let Some(integration) = consultation.value.get_scalar("integration") { + self.add_reference( + SymbolQuery::global( + RegistrySymbolKind::Integration, + &integration.value, + ), + path, + integration.range, + ); + } + } + } + + if let Some(claims) = service.value.get("claims").and_then(YamlValue::as_mapping) { + for claim in claims { + self.add_symbol( + SymbolKey::scoped( + RegistrySymbolKind::Claim, + &service_name, + &claim.key.value, + ), + Some(service_name.clone()), + path, + claim.key.range, + ); + if let Some(output) = claim.value.get_scalar("output") { + if let Some((consultation, _)) = output.value.split_once('.') { + self.add_reference( + SymbolQuery::scoped( + RegistrySymbolKind::Consultation, + &service_name, + consultation, + ), + path, + scalar_prefix_range(output, consultation), + ); + } + } + } + } + + if let Some(profiles) = service + .value + .get("credential_profiles") + .and_then(YamlValue::as_mapping) + { + for profile in profiles { + self.add_symbol( + SymbolKey::scoped( + RegistrySymbolKind::CredentialProfile, + &service_name, + &profile.key.value, + ), + Some(service_name.clone()), + path, + profile.key.range, + ); + if let Some(claims) = + profile.value.get("claims").and_then(YamlValue::as_sequence) + { + for claim in claims.iter().filter_map(YamlValue::as_scalar) { + self.add_reference( + SymbolQuery::scoped( + RegistrySymbolKind::Claim, + &service_name, + &claim.value, + ), + path, + claim.range, + ); + } + } + } + } + } + } + + fn extract_aliases( + &mut self, + manifest_path: &Path, + manifest: &YamlValue, + field: &str, + kind: RegistrySymbolKind, + claimed_definition_files: &mut BTreeSet, + ) { + let Some(aliases) = manifest.get(field).and_then(YamlValue::as_mapping) else { + return; + }; + for alias in aliases { + let key = SymbolKey::global(kind, &alias.key.value); + let definition_path = alias + .value + .get_scalar("file") + .and_then(|file| safe_join(self.root, &file.value)); + let external_id = definition_path + .as_ref() + .and_then(|path| self.parsed.get(path).map(|document| (path, document))) + .and_then(|(path, document)| document.get_scalar("id").map(|id| (path, id))); + + if let Some((path, id)) = external_id { + claimed_definition_files.insert(path.clone()); + self.add_symbol(key, None, path, id.range); + self.add_reference( + SymbolQuery::global(kind, &alias.key.value), + manifest_path, + alias.key.range, + ); + } else { + self.add_symbol(key, None, manifest_path, alias.key.range); + } + } + } + + fn extract_orphan_definition( + &mut self, + path: &Path, + document: &YamlValue, + kind: RegistrySymbolKind, + ) { + if let Some(id) = document.get_scalar("id") { + self.add_symbol(SymbolKey::global(kind, &id.value), None, path, id.range); + } + } + + fn extract_fixture(&mut self, path: &Path, document: &YamlValue) { + if let Some(name) = document.get_scalar("name") { + self.add_symbol( + SymbolKey::global(RegistrySymbolKind::Fixture, &name.value), + None, + path, + name.range, + ); + } + if let Some(claims) = document + .get("expect") + .and_then(|expect| expect.get("claims")) + .and_then(YamlValue::as_mapping) + { + for claim in claims { + self.add_reference( + SymbolQuery::global(RegistrySymbolKind::Claim, &claim.key.value), + path, + claim.key.range, + ); + } + } + } + + fn extract_environment(&mut self, path: &Path, relative: &Path, document: &YamlValue) { + if let Some(name) = relative.file_stem().and_then(|name| name.to_str()) { + let range = Range::new(Position::new(0, 0), Position::new(0, 0)); + self.add_symbol( + SymbolKey::global(RegistrySymbolKind::Environment, name), + None, + path, + range, + ); + } + for (field, kind) in [ + ("integrations", RegistrySymbolKind::Integration), + ("entities", RegistrySymbolKind::Entity), + ] { + if let Some(entries) = document.get(field).and_then(YamlValue::as_mapping) { + for entry in entries { + self.add_reference( + SymbolQuery::global(kind, &entry.key.value), + path, + entry.key.range, + ); + } + } + } + } + + fn add_symbol( + &mut self, + key: SymbolKey, + container_name: Option, + path: &Path, + range: Range, + ) { + self.symbols.push(IndexedSymbol { + name: key.name.clone(), + kind: key.kind, + container_name, + location: IndexedLocation { + path: path.to_path_buf(), + range, + }, + key, + }); + } + + fn add_reference(&mut self, target: SymbolQuery, path: &Path, range: Range) { + self.references.push(IndexedReference { + target, + location: IndexedLocation { + path: path.to_path_buf(), + range, + }, + }); + } +} + +#[derive(Clone, Debug)] +struct YamlScalar { + value: String, + range: Range, +} + +#[derive(Clone, Debug)] +struct YamlPair { + key: YamlScalar, + value: YamlValue, +} + +#[derive(Clone, Debug)] +enum YamlValue { + Scalar(YamlScalar), + Mapping(Vec), + Sequence(Vec), + Other, +} + +impl YamlValue { + fn as_mapping(&self) -> Option<&[YamlPair]> { + match self { + Self::Mapping(entries) => Some(entries), + _ => None, + } + } + + fn as_sequence(&self) -> Option<&[YamlValue]> { + match self { + Self::Sequence(entries) => Some(entries), + _ => None, + } + } + + fn as_scalar(&self) -> Option<&YamlScalar> { + match self { + Self::Scalar(scalar) => Some(scalar), + _ => None, + } + } + + fn get(&self, key: &str) -> Option<&YamlValue> { + self.as_mapping()? + .iter() + .find(|entry| entry.key.value == key) + .map(|entry| &entry.value) + } + + fn get_scalar(&self, key: &str) -> Option<&YamlScalar> { + self.get(key)?.as_scalar() + } +} + +fn parse_yaml(source: &str) -> Result { + let mut parser = Parser::new(); + parser + .set_language(&tree_sitter_yaml::LANGUAGE.into()) + .context("failed to load the YAML parser")?; + let tree = parser + .parse(source, None) + .context("the YAML parser did not produce a syntax tree")?; + if tree.root_node().has_error() { + anyhow::bail!("invalid YAML syntax"); + } + let source_map = SourceMap::new(source); + Ok(value_from_node(tree.root_node(), source, &source_map)) +} + +fn value_from_node(node: Node<'_>, source: &str, source_map: &SourceMap<'_>) -> YamlValue { + match node.kind() { + "stream" + | "document" + | "block_node" + | "flow_node" + | "plain_scalar" + | "block_sequence_item" => meaningful_named_children(node) + .last() + .copied() + .map(|child| value_from_node(child, source, source_map)) + .unwrap_or(YamlValue::Other), + "block_mapping" | "flow_mapping" => { + let mut entries = Vec::new(); + let mut cursor = node.walk(); + for pair in node + .named_children(&mut cursor) + .filter(|child| matches!(child.kind(), "block_mapping_pair" | "flow_pair")) + { + let Some(key_node) = pair.child_by_field_name("key") else { + continue; + }; + let Some(key) = scalar_from_node(key_node, source, source_map) else { + continue; + }; + let value = pair + .child_by_field_name("value") + .map(|value| value_from_node(value, source, source_map)) + .unwrap_or(YamlValue::Other); + entries.push(YamlPair { key, value }); + } + YamlValue::Mapping(entries) + } + "block_sequence" | "flow_sequence" => { + let values = meaningful_named_children(node) + .into_iter() + .map(|child| value_from_node(child, source, source_map)) + .collect(); + YamlValue::Sequence(values) + } + kind if kind.ends_with("_scalar") => scalar_from_node(node, source, source_map) + .map(YamlValue::Scalar) + .unwrap_or(YamlValue::Other), + _ => YamlValue::Other, + } +} + +fn scalar_from_node( + node: Node<'_>, + source: &str, + source_map: &SourceMap<'_>, +) -> Option { + if matches!( + node.kind(), + "stream" | "document" | "block_node" | "flow_node" | "plain_scalar" | "block_sequence_item" + ) { + return meaningful_named_children(node) + .last() + .copied() + .and_then(|child| scalar_from_node(child, source, source_map)); + } + if !node.kind().ends_with("_scalar") { + return None; + } + + let raw = source.get(node.byte_range())?; + let (value, start_byte, end_byte) = match node.kind() { + "double_quote_scalar" => { + let value = serde_json::from_str::(raw) + .unwrap_or_else(|_| raw.trim_matches('"').to_owned()); + ( + value, + node.start_byte() + 1, + node.end_byte().saturating_sub(1), + ) + } + "single_quote_scalar" => ( + raw.trim_matches('\'').replace("''", "'"), + node.start_byte() + 1, + node.end_byte().saturating_sub(1), + ), + _ => (raw.to_owned(), node.start_byte(), node.end_byte()), + }; + Some(YamlScalar { + value, + range: source_map.range(start_byte, end_byte), + }) +} + +fn scalar_prefix_range(scalar: &YamlScalar, prefix: &str) -> Range { + let mut end = scalar.range.start; + end.character += prefix.encode_utf16().count() as u32; + Range::new(scalar.range.start, end) +} + +fn meaningful_named_children(node: Node<'_>) -> Vec> { + let mut cursor = node.walk(); + node.named_children(&mut cursor) + .filter(|child| !matches!(child.kind(), "comment" | "anchor" | "tag")) + .collect() +} + +struct SourceMap<'a> { + source: &'a str, + line_starts: Vec, +} + +impl<'a> SourceMap<'a> { + fn new(source: &'a str) -> Self { + let mut line_starts = vec![0]; + for (index, byte) in source.bytes().enumerate() { + if byte == b'\n' { + line_starts.push(index + 1); + } + } + Self { + source, + line_starts, + } + } + + fn range(&self, start: usize, end: usize) -> Range { + Range::new(self.position(start), self.position(end)) + } + + fn position(&self, byte: usize) -> Position { + let byte = byte.min(self.source.len()); + let line = self + .line_starts + .partition_point(|line_start| *line_start <= byte) + .saturating_sub(1); + let line_start = self.line_starts[line]; + let character = self.source[line_start..byte].encode_utf16().count(); + Position::new(line as u32, character as u32) + } +} + +fn safe_join(root: &Path, relative: &str) -> Option { + let path = Path::new(relative); + if path.is_absolute() + || path + .components() + .any(|component| !matches!(component, Component::Normal(_))) + { + return None; + } + Some(root.join(path)) +} + +fn normalize_lookup_path(path: &Path) -> PathBuf { + path.canonicalize().unwrap_or_else(|_| path.to_path_buf()) +} + +fn is_integration_path(relative: &Path) -> bool { + let components = relative.components().collect::>(); + matches!( + components.as_slice(), + [Component::Normal(integrations), Component::Normal(_), Component::Normal(file)] + if *integrations == "integrations" && *file == "integration.yaml" + ) +} + +fn is_entity_path(relative: &Path) -> bool { + let components = relative.components().collect::>(); + matches!( + components.as_slice(), + [Component::Normal(entities), Component::Normal(file)] + if *entities == "entities" && Path::new(file).extension().is_some_and(|ext| ext == "yaml") + ) +} + +fn is_environment_path(relative: &Path) -> bool { + let components = relative.components().collect::>(); + matches!( + components.as_slice(), + [Component::Normal(environments), Component::Normal(file)] + if *environments == "environments" && Path::new(file).extension().is_some_and(|ext| ext == "yaml") + ) +} + +fn is_fixture_path(relative: &Path) -> bool { + let components = relative.components().collect::>(); + matches!( + components.as_slice(), + [Component::Normal(integrations), Component::Normal(_), Component::Normal(fixtures), Component::Normal(file)] + if *integrations == "integrations" && *fixtures == "fixtures" && Path::new(file).extension().is_some_and(|ext| ext == "yaml") + ) +} + +fn range_contains(range: Range, position: Position) -> bool { + position_cmp(position, range.start).is_ge() && position_cmp(position, range.end).is_le() +} + +fn position_cmp(left: Position, right: Position) -> std::cmp::Ordering { + left.line + .cmp(&right.line) + .then_with(|| left.character.cmp(&right.character)) +} + +fn range_cmp(left: Range, right: Range) -> std::cmp::Ordering { + position_cmp(left.start, right.start).then_with(|| position_cmp(left.end, right.end)) +} + +fn location_cmp(left: &IndexedLocation, right: &IndexedLocation) -> std::cmp::Ordering { + left.path + .cmp(&right.path) + .then_with(|| range_cmp(left.range, right.range)) +} + +#[cfg(test)] +mod tests { + use std::fs; + + use super::*; + use tempfile::TempDir; + + fn write(root: &Path, relative: &str, contents: &str) { + let path = root.join(relative); + fs::create_dir_all(path.parent().expect("fixture path has parent")).unwrap(); + fs::write(path, contents).unwrap(); + } + + fn fixture_project() -> TempDir { + let temp = TempDir::new().unwrap(); + write( + temp.path(), + PROJECT_FILE, + r#"version: 1 +registry: { id: "fictional-😀-registry" } +integrations: + people: { file: integrations/people/integration.yaml } +entities: + residents: { file: entities/residents.yaml } +services: + person-check: + kind: evidence + consultations: + person_record: { integration: people } + claims: + active: { output: person_record.active, disclosure: predicate } + credential_profiles: + person-status: { claims: [active] } + records: + kind: records_api + entity: residents +"#, + ); + write( + temp.path(), + "integrations/people/integration.yaml", + "version: 1\nid: people-source\n", + ); + write( + temp.path(), + "entities/residents.yaml", + "version: 1\nid: resident-entity\n", + ); + write( + temp.path(), + "environments/local.yaml", + "version: 1\nintegrations: { people: { source: {} } }\nentities:\n residents: { provider: {} }\n", + ); + write( + temp.path(), + "integrations/people/fixtures/active.yaml", + "name: active-person\nexpect: { claims: { active: true } }\n", + ); + temp + } + + #[test] + fn indexes_block_and_flow_yaml_and_resolves_cross_file_definitions() { + let temp = fixture_project(); + let index = ProjectIndex::load(temp.path()).unwrap(); + + assert!(index.diagnostics().is_empty(), "{:?}", index.diagnostics()); + assert!(index.symbols().iter().any(|symbol| { + symbol.kind == RegistrySymbolKind::Integration + && symbol.name == "people" + && symbol.location.path.ends_with("integration.yaml") + })); + assert!(index.symbols().iter().any(|symbol| { + symbol.kind == RegistrySymbolKind::Entity + && symbol.name == "residents" + && symbol.location.path.ends_with("residents.yaml") + })); + + let manifest = temp.path().join(PROJECT_FILE); + let locations = index.definitions_at(&manifest, Position::new(10, 38)); + assert_eq!(locations.len(), 1); + assert!(locations[0].path.ends_with("integration.yaml")); + + let fixture = temp.path().join("integrations/people/fixtures/active.yaml"); + let claim_locations = index.definitions_at(&fixture, Position::new(1, 21)); + assert_eq!(claim_locations.len(), 1); + assert_eq!(claim_locations[0].path, normalize_lookup_path(&manifest)); + + let consultation_locations = index.definitions_at(&manifest, Position::new(12, 28)); + assert_eq!(consultation_locations.len(), 1); + assert_eq!( + consultation_locations[0].path, + normalize_lookup_path(&manifest) + ); + assert_eq!(consultation_locations[0].range.start, Position::new(10, 6)); + } + + #[test] + fn reports_missing_duplicate_and_ambiguous_references() { + let temp = fixture_project(); + write( + temp.path(), + PROJECT_FILE, + r#"version: 1 +registry: { id: demo } +integrations: + people: { file: integrations/people/integration.yaml } +services: + first: + consultations: + lookup: { integration: missing } + claims: + shared: { cel: true } + shared: { cel: false } + broken: { output: absent.value } + second: + claims: + shared: { cel: true } +"#, + ); + write( + temp.path(), + "integrations/people/fixtures/active.yaml", + "name: active-person\nexpect: { claims: { shared: true } }\n", + ); + let index = ProjectIndex::load(temp.path()).unwrap(); + let messages = index + .diagnostics() + .iter() + .map(|diagnostic| diagnostic.message.as_str()) + .collect::>(); + + assert!(messages + .iter() + .any(|message| message.contains("Unknown integration"))); + assert!(messages + .iter() + .any(|message| message.contains("Duplicate claim"))); + assert!(messages + .iter() + .any(|message| message.contains("Ambiguous claim"))); + assert!(messages + .iter() + .any(|message| message.contains("Unknown consultation"))); + } + + #[test] + fn converts_byte_offsets_to_utf16_positions() { + let value = parse_yaml("registry: { id: \"😀demo\" }\n").unwrap(); + let id = value + .get("registry") + .and_then(|registry| registry.get_scalar("id")) + .unwrap(); + assert_eq!(id.range.start, Position::new(0, 17)); + assert_eq!(id.range.end, Position::new(0, 23)); + } + + #[test] + fn rejects_unrelated_and_nested_project_documents() { + let root = Path::new("/project"); + assert!(is_project_document( + root, + Path::new("/project/registry-stack.yaml") + )); + assert!(is_project_document( + root, + Path::new("/project/integrations/people/integration.yaml") + )); + assert!(!is_project_document( + root, + Path::new("/project/integrations/people/fixtures/bodies/response.yaml") + )); + assert!(!is_project_document(root, Path::new("/project/other.yaml"))); + } + + #[test] + fn indexes_the_bundled_http_starter_without_reference_errors() { + let starter = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../registryctl/assets/project-starters/bounded-http"); + let index = ProjectIndex::load(&starter).unwrap(); + + assert!(index.diagnostics().is_empty(), "{:?}", index.diagnostics()); + for (kind, name) in [ + (RegistrySymbolKind::Registry, "fictional-citizen-registry"), + (RegistrySymbolKind::Integration, "person-record"), + (RegistrySymbolKind::Service, "person-verification"), + (RegistrySymbolKind::Claim, "person-active"), + (RegistrySymbolKind::Fixture, "active-person"), + (RegistrySymbolKind::Environment, "local"), + ] { + assert!( + index + .symbols() + .iter() + .any(|symbol| symbol.kind == kind && symbol.name == name), + "missing {kind:?} {name}" + ); + } + } +} diff --git a/crates/registry-language-server/src/lib.rs b/crates/registry-language-server/src/lib.rs new file mode 100644 index 000000000..b0ac1f584 --- /dev/null +++ b/crates/registry-language-server/src/lib.rs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +//! Cross-file navigation for Registry Stack project YAML. + +mod index; +mod server; + +pub use index::{ProjectIndex, RegistrySymbolKind}; +pub use server::Backend; + +/// Serve the Registry Stack language protocol over standard input and output. +pub async fn run_stdio() { + let stdin = tokio::io::stdin(); + let stdout = tokio::io::stdout(); + let (service, socket) = tower_lsp_server::LspService::new(Backend::new); + tower_lsp_server::Server::new(stdin, stdout, socket) + .serve(service) + .await; +} diff --git a/crates/registry-language-server/src/main.rs b/crates/registry-language-server/src/main.rs new file mode 100644 index 000000000..251f244c7 --- /dev/null +++ b/crates/registry-language-server/src/main.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 + +#[tokio::main] +async fn main() { + registry_language_server::run_stdio().await; +} diff --git a/crates/registry-language-server/src/server.rs b/crates/registry-language-server/src/server.rs new file mode 100644 index 000000000..c4f35f6ea --- /dev/null +++ b/crates/registry-language-server/src/server.rs @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + collections::{BTreeMap, BTreeSet}, + fs, + path::{Path, PathBuf}, +}; + +use tokio::sync::{Mutex, RwLock}; +use tower_lsp_server::{ + jsonrpc::Result, + ls_types::{ + Diagnostic, DidChangeTextDocumentParams, DidCloseTextDocumentParams, + DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentSymbol, DocumentSymbolParams, + DocumentSymbolResponse, GotoDefinitionParams, GotoDefinitionResponse, InitializeParams, + InitializeResult, InitializedParams, Location, MessageType, OneOf, PositionEncodingKind, + ReferenceParams, SaveOptions, ServerCapabilities, ServerInfo, SymbolInformation, + TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, Uri, + WorkspaceSymbolParams, WorkspaceSymbolResponse, + }, + Client, LanguageServer, +}; + +use crate::index::{ + is_project_document, is_valid_yaml, load_project_documents, IndexedLocation, IndexedSymbol, + ProjectIndex, +}; + +const SERVER_NAME: &str = "Registry Stack Language Server"; +const MAX_DOCUMENT_BYTES: usize = 1024 * 1024; + +#[derive(Debug)] +pub struct Backend { + client: Client, + state: RwLock>, + published_paths: Mutex>, +} + +impl Backend { + pub fn new(client: Client) -> Self { + Self { + client, + state: RwLock::new(None), + published_paths: Mutex::new(BTreeSet::new()), + } + } + + async fn publish_diagnostics(&self) { + let (mut by_path, versions) = { + let state = self.state.read().await; + let Some(state) = state.as_ref() else { + return; + }; + let mut by_path = state + .index + .document_paths() + .map(|path| (path.to_path_buf(), Vec::new())) + .collect::>>(); + for diagnostic in state.index.diagnostics() { + by_path + .entry(diagnostic.path.clone()) + .or_default() + .push(Diagnostic::new( + diagnostic.range, + Some(diagnostic.severity), + None, + Some("registry-stack".to_owned()), + diagnostic.message.clone(), + None, + None, + )); + } + (by_path, state.open_versions.clone()) + }; + + let mut published = self.published_paths.lock().await; + for stale_path in published.iter() { + by_path.entry(stale_path.clone()).or_default(); + } + let current_paths = by_path.keys().cloned().collect::>(); + + for (path, diagnostics) in by_path { + if let Some(uri) = Uri::from_file_path(&path) { + self.client + .publish_diagnostics(uri, diagnostics, versions.get(&path).copied()) + .await; + } + } + *published = current_paths; + } + + async fn update_document(&self, path: PathBuf, text: String, version: i32) { + let path = normalize_document_path(&path); + let mut state = self.state.write().await; + if state.is_none() { + *state = find_project_root(&path).and_then(|root| WorkspaceState::load(&root).ok()); + } + if let Some(state) = state.as_mut() { + state.update(path, text, version); + } + drop(state); + self.publish_diagnostics().await; + } + + async fn reload_closed_document(&self, path: PathBuf) { + let path = normalize_document_path(&path); + let mut state = self.state.write().await; + if let Some(state) = state.as_mut() { + state.close(&path); + } + drop(state); + self.publish_diagnostics().await; + } +} + +impl LanguageServer for Backend { + async fn initialize(&self, params: InitializeParams) -> Result { + if let Some(root) = project_root_from_initialize(¶ms) { + *self.state.write().await = WorkspaceState::load(&root).ok(); + } + + Ok(InitializeResult { + capabilities: ServerCapabilities { + position_encoding: Some(PositionEncodingKind::UTF16), + text_document_sync: Some(TextDocumentSyncCapability::Options( + TextDocumentSyncOptions { + open_close: Some(true), + change: Some(TextDocumentSyncKind::FULL), + save: Some( + SaveOptions { + include_text: Some(true), + } + .into(), + ), + ..TextDocumentSyncOptions::default() + }, + )), + definition_provider: Some(OneOf::Left(true)), + references_provider: Some(OneOf::Left(true)), + document_symbol_provider: Some(OneOf::Left(true)), + workspace_symbol_provider: Some(OneOf::Left(true)), + ..ServerCapabilities::default() + }, + server_info: Some(ServerInfo { + name: SERVER_NAME.to_owned(), + version: Some(env!("CARGO_PKG_VERSION").to_owned()), + }), + offset_encoding: None, + }) + } + + async fn initialized(&self, _params: InitializedParams) { + let message = { + let state = self.state.read().await; + state + .as_ref() + .map(|state| { + format!( + "Registry Stack project indexed at {}", + state.index.root().display() + ) + }) + .unwrap_or_else(|| { + "No registry-stack.yaml project found in the workspace".to_owned() + }) + }; + self.client.log_message(MessageType::INFO, message).await; + self.publish_diagnostics().await; + } + + async fn shutdown(&self) -> Result<()> { + Ok(()) + } + + async fn did_open(&self, params: DidOpenTextDocumentParams) { + let document = params.text_document; + if let Some(path) = document.uri.to_file_path() { + self.update_document(path.into_owned(), document.text, document.version) + .await; + } + } + + async fn did_change(&self, params: DidChangeTextDocumentParams) { + let Some(change) = params.content_changes.into_iter().last() else { + return; + }; + if change.range.is_some() { + self.client + .log_message( + MessageType::WARNING, + "Registry Stack language server received an incremental edit despite advertising full synchronization", + ) + .await; + return; + } + if let Some(path) = params.text_document.uri.to_file_path() { + self.update_document(path.into_owned(), change.text, params.text_document.version) + .await; + } + } + + async fn did_save(&self, params: DidSaveTextDocumentParams) { + let Some(path) = params.text_document.uri.to_file_path() else { + return; + }; + let path = path.into_owned(); + let text = params.text.or_else(|| fs::read_to_string(&path).ok()); + if let Some(text) = text { + let version = { + let state = self.state.read().await; + state + .as_ref() + .and_then(|state| state.open_versions.get(&normalize_document_path(&path))) + .copied() + .unwrap_or(0) + }; + self.update_document(path, text, version).await; + } + } + + async fn did_close(&self, params: DidCloseTextDocumentParams) { + if let Some(path) = params.text_document.uri.to_file_path() { + self.reload_closed_document(path.into_owned()).await; + } + } + + async fn goto_definition( + &self, + params: GotoDefinitionParams, + ) -> Result> { + let document = params.text_document_position_params; + let Some(path) = document.text_document.uri.to_file_path() else { + return Ok(None); + }; + let locations = { + let state = self.state.read().await; + state + .as_ref() + .map(|state| state.index.definitions_at(&path, document.position)) + .unwrap_or_default() + }; + let locations = locations + .into_iter() + .filter_map(to_lsp_location) + .collect::>(); + Ok((!locations.is_empty()).then_some(GotoDefinitionResponse::Array(locations))) + } + + async fn references(&self, params: ReferenceParams) -> Result>> { + let document = params.text_document_position; + let Some(path) = document.text_document.uri.to_file_path() else { + return Ok(None); + }; + let locations = { + let state = self.state.read().await; + state + .as_ref() + .map(|state| { + state.index.references_at( + &path, + document.position, + params.context.include_declaration, + ) + }) + .unwrap_or_default() + }; + let locations = locations + .into_iter() + .filter_map(to_lsp_location) + .collect::>(); + Ok(Some(locations)) + } + + async fn document_symbol( + &self, + params: DocumentSymbolParams, + ) -> Result> { + let Some(path) = params.text_document.uri.to_file_path() else { + return Ok(None); + }; + let symbols = { + let state = self.state.read().await; + state + .as_ref() + .map(|state| { + state + .index + .document_symbols(&path) + .into_iter() + .map(to_document_symbol) + .collect::>() + }) + .unwrap_or_default() + }; + Ok(Some(DocumentSymbolResponse::Nested(symbols))) + } + + async fn symbol( + &self, + params: WorkspaceSymbolParams, + ) -> Result> { + let symbols = { + let state = self.state.read().await; + state + .as_ref() + .map(|state| { + state + .index + .workspace_symbols(¶ms.query) + .into_iter() + .filter_map(to_symbol_information) + .collect::>() + }) + .unwrap_or_default() + }; + Ok(Some(WorkspaceSymbolResponse::Flat(symbols))) + } +} + +#[derive(Debug)] +struct WorkspaceState { + root: PathBuf, + documents: BTreeMap, + open_versions: BTreeMap, + index: ProjectIndex, +} + +impl WorkspaceState { + fn load(root: &Path) -> anyhow::Result { + let root = root.canonicalize()?; + let documents = load_project_documents(&root)?; + let index = ProjectIndex::from_documents(&root, &documents); + Ok(Self { + root, + documents, + open_versions: BTreeMap::new(), + index, + }) + } + + fn update(&mut self, path: PathBuf, text: String, version: i32) { + if !is_project_document(&self.root, &path) { + return; + } + self.open_versions.insert(path.clone(), version); + if text.len() <= MAX_DOCUMENT_BYTES && is_valid_yaml(&text) { + self.documents.insert(path, text); + self.rebuild(); + } + } + + fn close(&mut self, path: &Path) { + if !is_project_document(&self.root, path) { + return; + } + self.open_versions.remove(path); + match fs::symlink_metadata(path) { + Ok(metadata) + if metadata.file_type().is_file() + && metadata.len() <= MAX_DOCUMENT_BYTES as u64 => + { + if let Ok(text) = fs::read_to_string(path) { + if is_valid_yaml(&text) { + self.documents.insert(path.to_path_buf(), text); + } + } + } + Ok(_) => { + self.documents.remove(path); + } + Err(error) if error.kind() == std::io::ErrorKind::NotFound => { + self.documents.remove(path); + } + Err(_) => {} + } + self.rebuild(); + } + + fn rebuild(&mut self) { + self.index = ProjectIndex::from_documents(&self.root, &self.documents); + } +} + +fn project_root_from_initialize(params: &InitializeParams) -> Option { + if let Some(folders) = params.workspace_folders.as_ref() { + for folder in folders { + if let Some(path) = folder.uri.to_file_path() { + if let Some(root) = find_project_root(&path) { + return Some(root); + } + } + } + } + + #[allow(deprecated)] + if let Some(uri) = params.root_uri.as_ref() { + if let Some(path) = uri.to_file_path() { + if let Some(root) = find_project_root(&path) { + return Some(root); + } + } + } + + #[allow(deprecated)] + params + .root_path + .as_deref() + .and_then(|path| find_project_root(Path::new(path))) +} + +fn find_project_root(start: &Path) -> Option { + let start = if start.is_file() { + start.parent()? + } else { + start + }; + for candidate in start.ancestors() { + let manifest = candidate.join("registry-stack.yaml"); + if fs::symlink_metadata(&manifest).is_ok_and(|metadata| metadata.file_type().is_file()) { + return candidate.canonicalize().ok(); + } + } + None +} + +fn normalize_document_path(path: &Path) -> PathBuf { + if let Ok(path) = path.canonicalize() { + return path; + } + path.parent() + .and_then(|parent| parent.canonicalize().ok()) + .and_then(|parent| path.file_name().map(|name| parent.join(name))) + .unwrap_or_else(|| path.to_path_buf()) +} + +fn to_lsp_location(location: IndexedLocation) -> Option { + Some(Location::new( + Uri::from_file_path(location.path)?, + location.range, + )) +} + +#[allow(deprecated)] +fn to_document_symbol(symbol: &IndexedSymbol) -> DocumentSymbol { + DocumentSymbol { + name: symbol.name.clone(), + detail: Some(symbol.kind.label().to_owned()), + kind: symbol.kind.lsp_kind(), + tags: None, + deprecated: None, + range: symbol.location.range, + selection_range: symbol.location.range, + children: None, + } +} + +#[allow(deprecated)] +fn to_symbol_information(symbol: &IndexedSymbol) -> Option { + Some(SymbolInformation { + name: symbol.name.clone(), + kind: symbol.kind.lsp_kind(), + tags: None, + deprecated: None, + location: Location::new( + Uri::from_file_path(&symbol.location.path)?, + symbol.location.range, + ), + container_name: symbol.container_name.clone(), + }) +} + +#[cfg(test)] +mod tests { + use std::fs; + + use tempfile::TempDir; + use tower_lsp_server::ls_types::Position; + + use super::*; + + fn project() -> TempDir { + let temp = TempDir::new().unwrap(); + fs::write( + temp.path().join("registry-stack.yaml"), + "version: 1\nregistry: { id: demo }\nservices: {}\n", + ) + .unwrap(); + temp + } + + #[test] + fn finds_project_from_nested_directory() { + let temp = project(); + let nested = temp.path().join("integrations/people"); + fs::create_dir_all(&nested).unwrap(); + assert_eq!( + find_project_root(&nested), + Some(temp.path().canonicalize().unwrap()) + ); + } + + #[test] + fn retains_last_valid_index_during_invalid_edits() { + let temp = project(); + let mut state = WorkspaceState::load(temp.path()).unwrap(); + let manifest = temp + .path() + .join("registry-stack.yaml") + .canonicalize() + .unwrap(); + state.update( + manifest.clone(), + "version: 1\nregistry: { id: current }\nservices: {}\n".to_owned(), + 2, + ); + assert!(state + .index + .workspace_symbols("current") + .iter() + .any(|symbol| symbol.name == "current")); + + state.update(manifest.clone(), "registry: [\n".to_owned(), 3); + assert!(state + .index + .workspace_symbols("current") + .iter() + .any(|symbol| symbol.name == "current")); + assert_eq!(state.open_versions.get(&manifest), Some(&3)); + assert_eq!( + state + .index + .definitions_at(&manifest, Position::new(1, 20)) + .len(), + 1 + ); + } +} diff --git a/crates/registry-language-server/tests/protocol.rs b/crates/registry-language-server/tests/protocol.rs new file mode 100644 index 000000000..a3c5000cc --- /dev/null +++ b/crates/registry-language-server/tests/protocol.rs @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + fs, + io::{BufRead, BufReader, Read, Write}, + process::{ChildStdin, ChildStdout, Command, Stdio}, +}; + +use serde_json::{json, Value}; +use tempfile::TempDir; +use tower_lsp_server::ls_types::Uri; + +fn write_project() -> TempDir { + let temp = TempDir::new().unwrap(); + fs::create_dir_all(temp.path().join("integrations/people")).unwrap(); + fs::write( + temp.path().join("registry-stack.yaml"), + r#"version: 1 +registry: { id: demo } +integrations: + people: { file: integrations/people/integration.yaml } +services: + check: + consultations: + lookup: { integration: people } + claims: + active: { output: lookup.active } + credential_profiles: + status: { claims: [active, missing] } +"#, + ) + .unwrap(); + fs::write( + temp.path().join("integrations/people/integration.yaml"), + "version: 1\nid: upstream-people\n", + ) + .unwrap(); + temp +} + +fn send(stdin: &mut ChildStdin, message: Value) { + let body = serde_json::to_vec(&message).unwrap(); + write!(stdin, "Content-Length: {}\r\n\r\n", body.len()).unwrap(); + stdin.write_all(&body).unwrap(); + stdin.flush().unwrap(); +} + +fn receive(stdout: &mut BufReader) -> Value { + let mut content_length = None; + loop { + let mut header = String::new(); + stdout.read_line(&mut header).unwrap(); + assert!(!header.is_empty(), "language server closed stdout"); + if header == "\r\n" { + break; + } + if let Some(length) = header.strip_prefix("Content-Length:") { + content_length = Some(length.trim().parse::().unwrap()); + } + } + let mut body = vec![0; content_length.expect("response has Content-Length")]; + stdout.read_exact(&mut body).unwrap(); + serde_json::from_slice(&body).unwrap() +} + +fn receive_response(stdout: &mut BufReader, id: i64) -> Value { + for _ in 0..50 { + let message = receive(stdout); + if message.get("id").and_then(Value::as_i64) == Some(id) { + return message; + } + } + panic!("language server did not return response {id}"); +} + +#[test] +fn serves_definition_references_and_workspace_symbols_over_stdio() { + let project = write_project(); + let root_uri = Uri::from_file_path(project.path()).unwrap().to_string(); + let manifest_path = project + .path() + .join("registry-stack.yaml") + .canonicalize() + .unwrap(); + let manifest_uri = Uri::from_file_path(manifest_path).unwrap().to_string(); + let integration_path = project + .path() + .join("integrations/people/integration.yaml") + .canonicalize() + .unwrap(); + let integration_uri = Uri::from_file_path(integration_path).unwrap().to_string(); + + let mut child = Command::new(env!("CARGO_BIN_EXE_registry-language-server")) + .current_dir(project.path()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + let mut stdin = child.stdin.take().unwrap(); + let mut stdout = BufReader::new(child.stdout.take().unwrap()); + + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "processId": null, + "rootUri": root_uri, + "capabilities": {}, + "workspaceFolders": [{ "uri": root_uri, "name": "demo" }] + } + }), + ); + let initialize = receive_response(&mut stdout, 1); + assert_eq!( + initialize.pointer("/result/capabilities/definitionProvider"), + Some(&Value::Bool(true)) + ); + + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "method": "initialized", "params": {} }), + ); + let mut published_missing_reference = false; + for _ in 0..3 { + let notification = receive(&mut stdout); + if notification.get("method").and_then(Value::as_str) + == Some("textDocument/publishDiagnostics") + && notification.pointer("/params/uri").and_then(Value::as_str) + == Some(manifest_uri.as_str()) + { + published_missing_reference = notification + .pointer("/params/diagnostics") + .and_then(Value::as_array) + .is_some_and(|diagnostics| { + diagnostics.iter().any(|diagnostic| { + diagnostic + .get("message") + .and_then(Value::as_str) + .is_some_and(|message| { + message.contains("Unknown claim reference 'missing'") + }) + }) + }); + } + } + assert!(published_missing_reference); + + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": 2, + "method": "textDocument/definition", + "params": { + "textDocument": { "uri": manifest_uri }, + "position": { "line": 7, "character": 31 } + } + }), + ); + let definition = receive_response(&mut stdout, 2); + assert_eq!( + definition.pointer("/result/0/uri").and_then(Value::as_str), + Some(integration_uri.as_str()) + ); + + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": 3, + "method": "textDocument/references", + "params": { + "textDocument": { "uri": integration_uri }, + "position": { "line": 1, "character": 6 }, + "context": { "includeDeclaration": true } + } + }), + ); + let references = receive_response(&mut stdout, 3); + assert!( + references + .get("result") + .and_then(Value::as_array) + .is_some_and(|locations| locations.len() >= 3), + "{references}" + ); + + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": 4, + "method": "workspace/symbol", + "params": { "query": "active" } + }), + ); + let symbols = receive_response(&mut stdout, 4); + assert_eq!( + symbols.pointer("/result/0/name").and_then(Value::as_str), + Some("active") + ); + + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "id": 5, "method": "shutdown", "params": null }), + ); + receive_response(&mut stdout, 5); + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "method": "exit", "params": null }), + ); + drop(stdin); + assert!(child.wait().unwrap().success()); +} diff --git a/crates/registryctl/CHANGELOG.md b/crates/registryctl/CHANGELOG.md index 6453668b4..ddd4857ec 100644 --- a/crates/registryctl/CHANGELOG.md +++ b/crates/registryctl/CHANGELOG.md @@ -23,6 +23,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - `registryctl init --from` now installs deterministic project-local JSON Schemas and VS Code and Zed workspace mappings. `registryctl authoring editor` verifies or safely refreshes the same version-matched setup for an existing project. +- `registryctl authoring language-server` now provides bounded cross-file definitions, references, + symbols, and reference diagnostics for Registry Stack project YAML. Thin source extensions for + VS Code and Zed launch the same server alongside their existing YAML language servers. ### Changed diff --git a/crates/registryctl/Cargo.toml b/crates/registryctl/Cargo.toml index b3e0c541e..24df7af69 100644 --- a/crates/registryctl/Cargo.toml +++ b/crates/registryctl/Cargo.toml @@ -24,6 +24,7 @@ include_dir = "0.7" ipnet.workspace = true rcgen.workspace = true registry-config-report = { workspace = true } +registry-language-server.workspace = true registry-notary-core.workspace = true registry-notary-server = { workspace = true, features = ["registry-notary-cel"] } registry-platform-authcommon = { workspace = true } diff --git a/crates/registryctl/src/main.rs b/crates/registryctl/src/main.rs index 2b38fb29e..b0e76185e 100644 --- a/crates/registryctl/src/main.rs +++ b/crates/registryctl/src/main.rs @@ -163,6 +163,12 @@ fn main() -> Result<()> { project_directory: project_dir, })?, )?, + AuthoringCommand::LanguageServer => { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()? + .block_on(registry_language_server::run_stdio()); + } }, Commands::Build { project_dir, @@ -796,6 +802,8 @@ enum AuthoringCommand { #[arg(long, default_value = ".")] project_dir: PathBuf, }, + /// Run cross-file Registry Stack navigation over the Language Server Protocol. + LanguageServer, } #[derive(Debug, Parser)] @@ -1180,6 +1188,15 @@ mod tests { } if project_dir == std::path::Path::new(".") )); + let language_server = + Cli::try_parse_from(["registryctl", "authoring", "language-server"]).unwrap(); + assert!(matches!( + language_server.command, + Commands::Authoring { + command: AuthoringCommand::LanguageServer + } + )); + let build = Cli::try_parse_from([ "registryctl", "build", diff --git a/crates/registryctl/tests/language_server.rs b/crates/registryctl/tests/language_server.rs new file mode 100644 index 000000000..cdde6ef96 --- /dev/null +++ b/crates/registryctl/tests/language_server.rs @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + fs, + io::{BufRead, BufReader, Read, Write}, + process::{ChildStdin, ChildStdout, Command, Stdio}, +}; + +use serde_json::{json, Value}; + +fn send(stdin: &mut ChildStdin, message: Value) { + let body = serde_json::to_vec(&message).unwrap(); + write!(stdin, "Content-Length: {}\r\n\r\n", body.len()).unwrap(); + stdin.write_all(&body).unwrap(); + stdin.flush().unwrap(); +} + +fn receive(stdout: &mut BufReader) -> Value { + let mut content_length = None; + loop { + let mut header = String::new(); + stdout.read_line(&mut header).unwrap(); + assert!( + !header.is_empty(), + "registryctl closed language-server stdout" + ); + if header == "\r\n" { + break; + } + if let Some(length) = header.strip_prefix("Content-Length:") { + content_length = Some(length.trim().parse::().unwrap()); + } + } + let mut body = vec![0; content_length.expect("response has Content-Length")]; + stdout.read_exact(&mut body).unwrap(); + serde_json::from_slice(&body).unwrap() +} + +#[test] +fn authoring_language_server_speaks_lsp_without_cli_output() { + let project = tempfile::tempdir().unwrap(); + fs::write( + project.path().join("registry-stack.yaml"), + "version: 1\nregistry: { id: demo }\nservices: {}\n", + ) + .unwrap(); + let root_uri = format!("file://{}", project.path().display()); + + let mut child = Command::new(env!("CARGO_BIN_EXE_registryctl")) + .args(["authoring", "language-server"]) + .current_dir(project.path()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + let mut stdin = child.stdin.take().unwrap(); + let mut stdout = BufReader::new(child.stdout.take().unwrap()); + + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "processId": null, + "rootUri": root_uri, + "capabilities": {}, + "workspaceFolders": [{ "uri": root_uri, "name": "demo" }] + } + }), + ); + let initialize = receive(&mut stdout); + assert_eq!(initialize["id"], 1); + assert_eq!( + initialize["result"]["capabilities"]["definitionProvider"], + true + ); + + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "id": 2, "method": "shutdown", "params": null }), + ); + let shutdown = receive(&mut stdout); + assert_eq!(shutdown["id"], 2); + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "method": "exit", "params": null }), + ); + drop(stdin); + assert!(child.wait().unwrap().success()); +} diff --git a/deny.toml b/deny.toml index 3b511ddce..f08949690 100644 --- a/deny.toml +++ b/deny.toml @@ -47,6 +47,9 @@ allow = [ "CDLA-Permissive-2.0", "ISC", "MIT", + # MIT-0 removes MIT's attribution condition. It is used by `borrow-or-share`, + # a transitive dependency of the language server's URI implementation. + "MIT-0", # MPL-2.0 is weak, file-level copyleft: its obligations attach only to the # MPL-licensed files themselves, not to code that merely depends on them. # Allowed workspace-wide; the motivating dependency is `smartstring`, diff --git a/docs/site/src/content/docs/reference/registryctl.mdx b/docs/site/src/content/docs/reference/registryctl.mdx index 46f9b0c4a..c9397ba36 100644 --- a/docs/site/src/content/docs/reference/registryctl.mdx +++ b/docs/site/src/content/docs/reference/registryctl.mdx @@ -51,6 +51,7 @@ complete workflow and generated trust boundary. | `authoring xw --format editor` | Print generated editor metadata for the `xw.v1` function surface. | | `authoring schema --kind ` | Print a strict project, environment, integration, fixture, or entity JSON Schema. | | `authoring editor --project-dir ` | Create or verify version-matched VS Code and Zed schema configuration. Defaults to the current directory. | +| `authoring language-server` | Run Registry Stack navigation and reference diagnostics over the Language Server Protocol on standard input and output. | | `build --project-dir --environment ` | Emit deterministic unsigned Config Bundle input directories for the products in the selected topology. | | `build --against --anchor ` | Build against an explicitly verified signed baseline. Both flags are required together. | @@ -133,11 +134,19 @@ The generated `.vscode/settings.json` and `.zed/settings.json` reserve the five table. Open the Registry Stack project directory as the editor root. In VS Code, install or accept the workspace recommendation for Red Hat YAML. Zed includes YAML language-server support. +The source distribution also contains Registry Stack extensions for VS Code and Zed. Both launch +the same semantic server, either as `registry-language-server` or through +`registryctl authoring language-server`. The extensions add cross-file definitions, references, +workspace and document symbols, and missing, duplicate, or ambiguous reference diagnostics. They +run alongside the YAML language server, which continues to own syntax, schemas, completion, hover, +and formatting. + The shared YAML language server interprets configured paths as suffix matches at any depth. A file such as `notes.yaml` keeps its normal schema detection and formatting, but a nested copy such as `examples/environments/local.yaml` receives the Registry Stack environment schema. Do not reuse -the five reserved layouts for unrelated YAML inside the opened project. Exact worktree-root routing -requires the later editor extensions rather than portable 1.0 workspace settings. +the five reserved layouts for unrelated YAML inside the opened project. The Registry Stack +extensions restrict semantic indexing to the project root and documented authored-file layouts; +portable schema settings continue to use suffix matching. Completion, hover descriptions, the document outline, and inline validation then use the matching Registry Stack schemas. To smoke-test the association, add an unknown top-level key to an diff --git a/editors/README.md b/editors/README.md new file mode 100644 index 000000000..9c9f10f50 --- /dev/null +++ b/editors/README.md @@ -0,0 +1,13 @@ +# Registry Stack editor integrations + +The Registry Stack editor support is split into one reusable language server and thin editor +launchers: + +- `../crates/registry-language-server` owns project indexing, navigation, symbols, and Registry + Stack reference diagnostics. +- `vscode` launches the server through VS Code's language-client API. +- `zed` launches the same server through Zed's extension API. + +These integrations intentionally run alongside each editor's YAML language server. The generated +`.vscode/settings.json` and `.zed/settings.json` files continue to provide version-matched schema +validation and YAML completion without duplicating that behavior here. diff --git a/editors/vscode/.gitignore b/editors/vscode/.gitignore new file mode 100644 index 000000000..a08e1da2d --- /dev/null +++ b/editors/vscode/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +*.vsix diff --git a/editors/vscode/.vscodeignore b/editors/vscode/.vscodeignore new file mode 100644 index 000000000..6601f99da --- /dev/null +++ b/editors/vscode/.vscodeignore @@ -0,0 +1,7 @@ +.github/** +.gitignore +package-lock.json +src/** +tsconfig.json +node_modules/.cache/** +**/*.map diff --git a/editors/vscode/LICENSE b/editors/vscode/LICENSE new file mode 100644 index 000000000..0421f3c2d --- /dev/null +++ b/editors/vscode/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Jeremi Joslin + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/editors/vscode/README.md b/editors/vscode/README.md new file mode 100644 index 000000000..4a98ab1da --- /dev/null +++ b/editors/vscode/README.md @@ -0,0 +1,29 @@ +# Registry Stack for VS Code + +This extension starts `registry-language-server` for a workspace whose root contains +`registry-stack.yaml`. It adds cross-file definitions, references, workspace/document symbols, +and Registry Stack reference diagnostics. Red Hat YAML remains responsible for YAML syntax, +schema validation, completion, formatting, and ordinary hover information. + +## Development setup + +1. Build the server from the repository root: + + ```console + cargo build -p registry-language-server + ``` + +2. In this directory, install dependencies and compile the extension: + + ```console + npm ci + npm run compile + ``` + +3. Set `registryStack.languageServer.path` to the built executable, put + `registry-language-server` on `PATH`, or use a `registryctl` build that provides + `registryctl authoring language-server`. + +For a packaged extension, place the platform executable at +`bin/registry-language-server` (`bin/registry-language-server.exe` on Windows) before running +the VS Code extension packaging tool. diff --git a/editors/vscode/package-lock.json b/editors/vscode/package-lock.json new file mode 100644 index 000000000..08b118e10 --- /dev/null +++ b/editors/vscode/package-lock.json @@ -0,0 +1,156 @@ +{ + "name": "registry-stack", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "registry-stack", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "vscode-languageclient": "^10.1.0" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "@types/vscode": "^1.91.0", + "typescript": "^5.9.3" + }, + "engines": { + "vscode": "^1.91.0" + } + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/vscode": { + "version": "1.125.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.125.0.tgz", + "integrity": "sha512-0icm/ZQAaism87P0ekHqi4/Ju9du+Tm0RUW+y7vqRsxY2cY0FNRX1nAnaW7nT6npPt2tfHiheZ55Zm9UhqonFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vscode-jsonrpc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.1.tgz", + "integrity": "sha512-rfuA6T75H6m5EkbhtEPzre9pT0HPcDI2MMy4+nPFIBks5J8JBAUHD4tRYSgaBOijIEC7SRkC1kKyXTLqbmh9jw==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageclient": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.1.0.tgz", + "integrity": "sha512-XXRx6lqVitQy/oOLr9MfNYRG+MbQkhXkDaxbQMiKxEm8zZNfheRFUKNb8UYNh2stn9btl2wQM5wZFJjJvoc+jA==", + "license": "MIT", + "dependencies": { + "minimatch": "^10.2.5", + "semver": "^7.8.1", + "vscode-languageserver-protocol": "3.18.2", + "vscode-languageserver-textdocument": "1.0.13" + }, + "engines": { + "vscode": "^1.91.0" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.18.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.2.tgz", + "integrity": "sha512-XRyDbT0Pp3sSNti3JmxVEUMySWCSi1hhM+/KUlCy1hV1zmrqpM1OwO12EAki8blhmLuIMpaJrYbo0OzGVfK2Qg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "9.0.1", + "vscode-languageserver-types": "3.18.0" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.13.tgz", + "integrity": "sha512-nx0ZHwMGIsVkzFG3/VLeJYBLTaFBRuNdGDvevvjuoayU5EOS2fEYazOhtCM3PI9ClMMg5igc0uwXtAq4tJj+Dw==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.0.tgz", + "integrity": "sha512-8TsGPNMIMiiBdkORgRSvLjuiEIiAFtO+KssmYWxQ+uSVvlf7RjK8YKCOjPzZ+YA04jXEV7+7LvkSmHkhpNS99g==", + "license": "MIT" + } + } +} diff --git a/editors/vscode/package.json b/editors/vscode/package.json new file mode 100644 index 000000000..feab3ee62 --- /dev/null +++ b/editors/vscode/package.json @@ -0,0 +1,64 @@ +{ + "name": "registry-stack", + "displayName": "Registry Stack", + "description": "Cross-file navigation and reference diagnostics for Registry Stack YAML projects.", + "version": "0.1.0", + "publisher": "registrystack", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/registrystack/registry-stack" + }, + "engines": { + "vscode": "^1.91.0" + }, + "categories": [ + "Programming Languages", + "Linters" + ], + "keywords": [ + "registry-stack", + "yaml", + "language-server" + ], + "main": "./dist/extension.js", + "activationEvents": [ + "workspaceContains:registry-stack.yaml", + "onCommand:registryStack.restartLanguageServer" + ], + "extensionKind": [ + "workspace" + ], + "contributes": { + "commands": [ + { + "command": "registryStack.restartLanguageServer", + "title": "Registry Stack: Restart Language Server" + } + ], + "configuration": { + "title": "Registry Stack", + "properties": { + "registryStack.languageServer.path": { + "type": "string", + "default": "", + "scope": "machine-overridable", + "description": "Path to the registry-language-server executable. Relative paths resolve from the Registry Stack project root. When empty, the extension uses its bundled binary or searches PATH." + } + } + } + }, + "scripts": { + "compile": "tsc -p .", + "check": "tsc -p . --noEmit", + "vscode:prepublish": "npm run compile" + }, + "dependencies": { + "vscode-languageclient": "^10.1.0" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "@types/vscode": "^1.91.0", + "typescript": "^5.9.3" + } +} diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts new file mode 100644 index 000000000..3d9f248ce --- /dev/null +++ b/editors/vscode/src/extension.ts @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: Apache-2.0 + +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +import * as vscode from 'vscode'; +import { + LanguageClient, + LanguageClientOptions, + ServerOptions, + TransportKind, +} from 'vscode-languageclient/node'; + +let client: LanguageClient | undefined; + +export async function activate(context: vscode.ExtensionContext): Promise { + context.subscriptions.push( + vscode.commands.registerCommand('registryStack.restartLanguageServer', async () => { + await restart(context); + }), + vscode.workspace.onDidChangeConfiguration(async (event) => { + if (event.affectsConfiguration('registryStack.languageServer.path')) { + await restart(context); + } + }), + ); + + await start(context); +} + +export async function deactivate(): Promise { + await stop(); +} + +async function restart(context: vscode.ExtensionContext): Promise { + await stop(); + await start(context); +} + +async function start(context: vscode.ExtensionContext): Promise { + if (client !== undefined) { + return; + } + + const projectFolder = findProjectFolder(); + if (projectFolder === undefined) { + return; + } + + try { + const server = resolveServerCommand(context, projectFolder); + const serverOptions: ServerOptions = { + run: { + command: server.command, + args: server.args, + transport: TransportKind.stdio, + options: { cwd: projectFolder.uri.fsPath }, + }, + debug: { + command: server.command, + args: server.args, + transport: TransportKind.stdio, + options: { cwd: projectFolder.uri.fsPath }, + }, + }; + const clientOptions: LanguageClientOptions = { + documentSelector: [ + { + scheme: 'file', + language: 'yaml', + pattern: { + baseUri: projectFolder.uri.toString(), + pattern: '**/*.yaml', + }, + }, + ], + workspaceFolder: projectFolder, + outputChannelName: 'Registry Stack Language Server', + }; + + client = new LanguageClient( + 'registry-stack', + 'Registry Stack Language Server', + serverOptions, + clientOptions, + ); + await client.start(); + } catch (error) { + client = undefined; + const detail = error instanceof Error ? error.message : String(error); + void vscode.window.showErrorMessage(`Registry Stack language server failed to start: ${detail}`); + } +} + +async function stop(): Promise { + const activeClient = client; + client = undefined; + await activeClient?.dispose(); +} + +function findProjectFolder(): vscode.WorkspaceFolder | undefined { + return vscode.workspace.workspaceFolders?.find((folder) => + fs.existsSync(path.join(folder.uri.fsPath, 'registry-stack.yaml')), + ); +} + +function resolveServerCommand( + context: vscode.ExtensionContext, + projectFolder: vscode.WorkspaceFolder, +): { command: string; args: string[] } { + const configured = vscode.workspace + .getConfiguration('registryStack', projectFolder.uri) + .get('languageServer.path', '') + .trim(); + if (configured !== '') { + const resolved = path.isAbsolute(configured) + ? configured + : path.resolve(projectFolder.uri.fsPath, configured); + if (!isExecutableFile(resolved)) { + throw new Error(`configured executable does not exist or is not a file: ${resolved}`); + } + return { command: resolved, args: [] }; + } + + const executable = process.platform === 'win32' + ? 'registry-language-server.exe' + : 'registry-language-server'; + const bundled = context.asAbsolutePath(path.join('bin', executable)); + if (isExecutableFile(bundled)) { + return { command: bundled, args: [] }; + } + const standalone = findExecutableOnPath(executable); + if (standalone !== undefined) { + return { command: standalone, args: [] }; + } + const registryctl = findExecutableOnPath( + process.platform === 'win32' ? 'registryctl.exe' : 'registryctl', + ); + if (registryctl !== undefined) { + return { command: registryctl, args: ['authoring', 'language-server'] }; + } + throw new Error( + 'neither registry-language-server nor registryctl was found on PATH; install Registry Stack or configure registryStack.languageServer.path', + ); +} + +function isExecutableFile(candidate: string): boolean { + try { + if (!fs.statSync(candidate).isFile()) { + return false; + } + if (process.platform !== 'win32') { + fs.accessSync(candidate, fs.constants.X_OK); + } + return true; + } catch { + return false; + } +} + +function findExecutableOnPath(executable: string): string | undefined { + const pathEntries = process.env.PATH?.split(path.delimiter) ?? []; + for (const entry of pathEntries) { + const candidate = path.join(entry === '' ? process.cwd() : entry, executable); + if (isExecutableFile(candidate)) { + return candidate; + } + } + return undefined; +} diff --git a/editors/vscode/tsconfig.json b/editors/vscode/tsconfig.json new file mode 100644 index 000000000..7e5eaf3a0 --- /dev/null +++ b/editors/vscode/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "lib": [ + "ES2022" + ], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "sourceMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/editors/zed/.gitignore b/editors/zed/.gitignore new file mode 100644 index 000000000..2f7896d1d --- /dev/null +++ b/editors/zed/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/editors/zed/Cargo.lock b/editors/zed/Cargo.lock new file mode 100644 index 000000000..27fa3b702 --- /dev/null +++ b/editors/zed/Cargo.lock @@ -0,0 +1,817 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "auditable-serde" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7bf8143dfc3c0258df908843e169b5cc5fcf76c7718bd66135ef4a9cd558c5" +dependencies = [ + "semver", + "serde", + "serde_json", + "topological-sort", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "registry-stack-zed" +version = "0.1.0" +dependencies = [ + "zed_extension_api", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "spdx" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e17e880bafaeb362a7b751ec46bdc5b61445a188f80e0606e68167cd540fa3" +dependencies = [ + "smallvec", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "topological-sort" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "wasm-encoder" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bb72f02e7fbf07183443b27b0f3d4144abf8c114189f2e088ed95b696a7822" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1ef0faabbbba6674e97a56bee857ccddf942785a336c8b47b42373c922a91d" +dependencies = [ + "anyhow", + "auditable-serde", + "flate2", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "url", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10fb6648689b3929d56bbc7eb1acf70c9a42a29eb5358c67c10f54dbd5d695de" +dependencies = [ + "wit-bindgen-rt", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92fa781d4f2ff6d3f27f3cc9b74a73327b31ca0dc4a3ef25a0ce2983e0e5af9b" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db52a11d4dfb0a59f194c064055794ee6564eb1ced88c25da2cf76e50c5621" +dependencies = [ + "bitflags", + "futures", + "once_cell", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0809dc5ba19e2e98661bf32fc0addc5a3ca5bf3a6a7083aa6ba484085ff3ce" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad19eec017904e04c60719592a803ee5da76cb51c81e3f6fbf9457f59db49799" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "635c3adc595422cbf2341a17fb73a319669cc8d33deed3a48368a841df86b676" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.227.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf445ed5157046e4baf56f9138c124a0824d4d1657e7204d71886ad8ce2fc11" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zed_extension_api" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0729d50b4ca0a7e28e590bbe32e3ca0194d97ef654961451a424c661a366fca0" +dependencies = [ + "serde", + "serde_json", + "wit-bindgen", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/editors/zed/Cargo.toml b/editors/zed/Cargo.toml new file mode 100644 index 000000000..75c48ea5e --- /dev/null +++ b/editors/zed/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "registry-stack-zed" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +zed_extension_api = "0.7.0" + +[workspace] diff --git a/editors/zed/LICENSE b/editors/zed/LICENSE new file mode 100644 index 000000000..0421f3c2d --- /dev/null +++ b/editors/zed/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Jeremi Joslin + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/editors/zed/README.md b/editors/zed/README.md new file mode 100644 index 000000000..e2231c80c --- /dev/null +++ b/editors/zed/README.md @@ -0,0 +1,21 @@ +# Registry Stack for Zed + +This extension attaches `registry-language-server` to Zed's built-in YAML language. It adds +cross-file definitions, references, workspace/document symbols, and Registry Stack reference +diagnostics. Zed's YAML language server remains responsible for YAML syntax, schema validation, +completion, formatting, and ordinary hover information. + +## Development setup + +1. Build the server from the repository root and put it on `PATH`: + + ```console + cargo build -p registry-language-server + export PATH="$PWD/target/debug:$PATH" + ``` + +2. In Zed, run `zed: install dev extension` and select this directory. + +Zed extensions cannot bundle an external language server. This launcher first looks for +`registry-language-server`, then falls back to `registryctl authoring language-server`. A future +marketplace package can also download a matching signed Registry Stack release asset. diff --git a/editors/zed/extension.toml b/editors/zed/extension.toml new file mode 100644 index 000000000..9c3b96621 --- /dev/null +++ b/editors/zed/extension.toml @@ -0,0 +1,11 @@ +id = "registry-stack" +name = "Registry Stack" +version = "0.1.0" +schema_version = 1 +authors = ["Registry Stack contributors"] +description = "Cross-file navigation and reference diagnostics for Registry Stack YAML projects." +repository = "https://github.com/registrystack/registry-stack" + +[language_servers.registry-stack] +name = "Registry Stack Language Server" +languages = ["YAML"] diff --git a/editors/zed/src/lib.rs b/editors/zed/src/lib.rs new file mode 100644 index 000000000..be65d883e --- /dev/null +++ b/editors/zed/src/lib.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 + +use zed_extension_api as zed; + +struct RegistryStackExtension; + +impl zed::Extension for RegistryStackExtension { + fn new() -> Self { + Self + } + + fn language_server_command( + &mut self, + _language_server_id: &zed::LanguageServerId, + worktree: &zed::Worktree, + ) -> zed::Result { + let (command, args) = if let Some(command) = worktree.which("registry-language-server") { + (command, Vec::new()) + } else if let Some(command) = worktree.which("registryctl") { + ( + command, + vec!["authoring".to_owned(), "language-server".to_owned()], + ) + } else { + return Err( + "neither registry-language-server nor registryctl was found on PATH; install Registry Stack before enabling this extension" + .to_owned(), + ); + }; + Ok(zed::Command { + command, + args, + env: worktree.shell_env(), + }) + } +} + +zed::register_extension!(RegistryStackExtension); From 557268411e3e4dd9d6bf620189c420058fd930e2 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Fri, 17 Jul 2026 20:56:35 +0700 Subject: [PATCH 2/4] fix(vscode): package runtime dependencies Signed-off-by: Jeremi Joslin --- .github/workflows/ci.yml | 2 +- editors/README.md | 60 ++++ editors/vscode/README.md | 71 ++++- editors/vscode/package-lock.json | 493 ++++++++++++++++++++++++++++++- editors/vscode/package.json | 6 +- editors/zed/README.md | 55 +++- 6 files changed, 660 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2733b8ec2..e3fd62345 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -410,7 +410,7 @@ jobs: working-directory: editors/vscode run: | npm ci - npm run check + npm run package:dev cmp ../../LICENSE LICENSE - name: Install Zed extension target diff --git a/editors/README.md b/editors/README.md index 9c9f10f50..896432413 100644 --- a/editors/README.md +++ b/editors/README.md @@ -11,3 +11,63 @@ launchers: These integrations intentionally run alongside each editor's YAML language server. The generated `.vscode/settings.json` and `.zed/settings.json` files continue to provide version-matched schema validation and YAML completion without duplicating that behavior here. + +## Local end-to-end smoke test + +Run the commands in this section from the repository root. They build both server entry points and +create a disposable HTTP starter outside the checkout, so the diagnostic checks below cannot +modify a tracked golden project. + +```console +export REGISTRY_STACK_REPO="$(pwd)" +export REGISTRY_STACK_SMOKE_ROOT="$(mktemp -d)" +export REGISTRY_STACK_SMOKE_PROJECT="$REGISTRY_STACK_SMOKE_ROOT/project" +cargo build --locked -p registry-language-server -p registryctl +cargo run --locked -p registryctl -- init --from http --project-dir "$REGISTRY_STACK_SMOKE_PROJECT" +``` + +Keep that terminal open so the three variables remain available. Then follow the editor-specific +installation and launch instructions: + +- [VS Code](vscode/README.md#package-install-and-launch) +- [Zed](zed/README.md#install-and-launch) + +### Expected behavior + +Use the following checks in either editor: + +1. Confirm the Registry Stack language-server output or log says that the project was indexed. +2. In `registry-stack.yaml`, invoke **Go to Definition** on the value in + `integration: person-record`. It must open the `id` in + `integrations/person-record/integration.yaml`. +3. Invoke **Go to Definition** on `person_record` in `output: person_record.active`. It must jump + to the `consultations.person_record` key in the same manifest. +4. Invoke **Go to Definition** on `person-active` in the `person-status` profile's `claims` list. + It must jump to the `claims.person-active` definition. +5. Open `integrations/person-record/fixtures/active.yaml` and invoke **Go to Definition** on + `person-active` under `expect.claims`. It must jump back to the manifest claim. +6. Open `environments/local.yaml` and invoke **Go to Definition** on the `person-record` + integration key. It must open the integration definition. +7. Invoke **Find References** on the integration definition. Results must include the manifest + alias, consultation reference, and environment binding. +8. Search workspace symbols for `person`. Results must include the registry, integration, service, + consultation, claims, credential profile, and fixture symbols. The document outline for + `registry-stack.yaml` must list its Registry Stack symbols. +9. Temporarily change `integration: person-record` to `integration: missing-source`. The editor + must report `Unknown integration reference 'missing-source'`. Restore `person-record` and + confirm that the diagnostic clears. + +The YAML language server may report additional schema or syntax diagnostics. Those are expected +and are separate from diagnostics whose source is `registry-stack`. + +### Automated checks + +The same core behavior has non-GUI coverage: + +```console +cargo test --locked -p registry-language-server +cargo test --locked -p registryctl --test language_server +``` + +When finished, close the smoke project and remove the temporary directory shown by +`$REGISTRY_STACK_SMOKE_ROOT` after checking that it is the directory created by `mktemp` above. diff --git a/editors/vscode/README.md b/editors/vscode/README.md index 4a98ab1da..02aeebb3c 100644 --- a/editors/vscode/README.md +++ b/editors/vscode/README.md @@ -5,25 +5,72 @@ This extension starts `registry-language-server` for a workspace whose root cont and Registry Stack reference diagnostics. Red Hat YAML remains responsible for YAML syntax, schema validation, completion, formatting, and ordinary hover information. -## Development setup +## Package, install, and launch -1. Build the server from the repository root: +Prerequisites are Node.js 22, the `code` command-line tool, and the Rust toolchain used by the +repository. First complete the [shared smoke-project setup](../README.md#local-end-to-end-smoke-test). + +1. Package and install the extension from this directory: ```console - cargo build -p registry-language-server + cd "$REGISTRY_STACK_REPO/editors/vscode" + npm ci + npm run package:dev + code --install-extension ./registry-stack-dev.vsix --force ``` -2. In this directory, install dependencies and compile the extension: + `package:dev` type-checks the source, bundles its runtime dependencies, and creates an + installable VSIX. The explicit install affects the active VS Code profile. + +2. Open the smoke project as the workspace root: ```console - npm ci - npm run compile + code --new-window "$REGISTRY_STACK_SMOKE_PROJECT" + ``` + +3. Run **Preferences: Open Workspace Settings (JSON)** and add this property to the existing + generated settings object, replacing the example with the absolute value of + `$REGISTRY_STACK_REPO`: + + ```json + "registryStack.languageServer.path": "/absolute/path/to/registry-stack/target/debug/registry-language-server" ``` -3. Set `registryStack.languageServer.path` to the built executable, put - `registry-language-server` on `PATH`, or use a `registryctl` build that provides - `registryctl authoring language-server`. +4. Run **Registry Stack: Restart Language Server**. Open **View: Toggle Output**, select + **Registry Stack Language Server**, and confirm it reports the smoke project as indexed. +5. Complete the [shared expected-behavior checklist](../README.md#expected-behavior). VS Code uses + `F12` for definitions, `Shift+F12` for references, `Cmd+Shift+O`/`Ctrl+Shift+O` for document + symbols, and `Cmd+T`/`Ctrl+T` for workspace symbols. + +The source VSIX does not contain a platform server binary. Instead of the explicit setting, the +extension can find `registry-language-server` on `PATH`, then fall back to +`registryctl authoring language-server` when `registryctl` is on `PATH`. + +## Iterate + +- After changing the Rust server, rebuild it from the repository root with + `cargo build --locked -p registry-language-server`, then run + **Registry Stack: Restart Language Server**. +- After changing the extension, rerun `npm run package:dev`, reinstall the VSIX with `--force`, + and run **Developer: Reload Window**. + +## Troubleshooting + +- If activation does not occur, confirm the opened workspace root itself contains + `registry-stack.yaml`. Opening only a YAML file or a parent directory does not activate it. +- If startup reports that no executable was found, verify the workspace setting is an absolute + path to an executable regular file. +- If navigation is absent, confirm the file's VS Code language mode is YAML and inspect the + **Registry Stack Language Server** output channel. +- Red Hat YAML still owns schema validation, completion, hover, formatting, and syntax errors. Its + diagnostics do not indicate a Registry Stack language-server failure. + +## Remove the development extension + +```console +code --uninstall-extension registrystack.registry-stack +``` -For a packaged extension, place the platform executable at -`bin/registry-language-server` (`bin/registry-language-server.exe` on Windows) before running -the VS Code extension packaging tool. +VS Code also supports installing the VSIX through **Extensions: Install from VSIX**. See the +[official VSIX instructions](https://code.visualstudio.com/docs/configure/extensions/extension-marketplace#_install-from-a-vsix) +for profile and command-line alternatives. diff --git a/editors/vscode/package-lock.json b/editors/vscode/package-lock.json index 08b118e10..ad8ef8ec0 100644 --- a/editors/vscode/package-lock.json +++ b/editors/vscode/package-lock.json @@ -13,13 +13,456 @@ }, "devDependencies": { "@types/node": "^24.0.0", - "@types/vscode": "^1.91.0", + "@types/vscode": "1.91.0", + "esbuild": "0.28.1", "typescript": "^5.9.3" }, "engines": { "vscode": "^1.91.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@types/node": { "version": "24.13.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", @@ -31,9 +474,9 @@ } }, "node_modules/@types/vscode": { - "version": "1.125.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.125.0.tgz", - "integrity": "sha512-0icm/ZQAaism87P0ekHqi4/Ju9du+Tm0RUW+y7vqRsxY2cY0FNRX1nAnaW7nT6npPt2tfHiheZ55Zm9UhqonFA==", + "version": "1.91.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.91.0.tgz", + "integrity": "sha512-PgPr+bUODjG3y+ozWUCyzttqR9EHny9sPAfJagddQjDwdtf66y2sDKJMnFZRuzBA2YtBGASqJGPil8VDUPvO6A==", "dev": true, "license": "MIT" }, @@ -58,6 +501,48 @@ "node": "18 || 20 || >=22" } }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", diff --git a/editors/vscode/package.json b/editors/vscode/package.json index feab3ee62..e32d9cdcd 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -49,8 +49,9 @@ } }, "scripts": { - "compile": "tsc -p .", + "compile": "esbuild src/extension.ts --bundle --platform=node --target=node20 --external:vscode --outfile=dist/extension.js --sourcemap", "check": "tsc -p . --noEmit", + "package:dev": "npm run check && npx --yes @vscode/vsce@3.9.2 package --no-dependencies --out registry-stack-dev.vsix", "vscode:prepublish": "npm run compile" }, "dependencies": { @@ -58,7 +59,8 @@ }, "devDependencies": { "@types/node": "^24.0.0", - "@types/vscode": "^1.91.0", + "@types/vscode": "1.91.0", + "esbuild": "0.28.1", "typescript": "^5.9.3" } } diff --git a/editors/zed/README.md b/editors/zed/README.md index e2231c80c..29827a60b 100644 --- a/editors/zed/README.md +++ b/editors/zed/README.md @@ -5,17 +5,56 @@ cross-file definitions, references, workspace/document symbols, and Registry Sta diagnostics. Zed's YAML language server remains responsible for YAML syntax, schema validation, completion, formatting, and ordinary hover information. -## Development setup +## Install and launch -1. Build the server from the repository root and put it on `PATH`: +Zed requires Rust installed through `rustup` to compile development extensions. First complete the +[shared smoke-project setup](../README.md#local-end-to-end-smoke-test), then verify the required Zed +WebAssembly target from the repository root: + +```console +rustup target add wasm32-wasip1 +cargo check --locked --target wasm32-wasip1 --manifest-path editors/zed/Cargo.toml +``` + +1. Put the freshly built language server on the environment inherited by Zed, then open the smoke + project from the same terminal: ```console - cargo build -p registry-language-server - export PATH="$PWD/target/debug:$PATH" + export PATH="$REGISTRY_STACK_REPO/target/debug:$PATH" + zed "$REGISTRY_STACK_SMOKE_PROJECT" ``` -2. In Zed, run `zed: install dev extension` and select this directory. +2. Run `zed: install dev extension` from the command palette and select + `$REGISTRY_STACK_REPO/editors/zed`. Zed compiles and installs the WebAssembly extension. +3. Run `editor: restart language server`, then `zed: open log`. Confirm the log reports that the + Registry Stack project was indexed. +4. Complete the [shared expected-behavior checklist](../README.md#expected-behavior). Zed uses + `F12` for definitions, `Alt+Shift+F12` for references, `Cmd+Shift+O`/`Ctrl+Shift+O` for document + symbols, and `Cmd+T`/`Ctrl+T` for workspace symbols. + +## Iterate + +- After changing the Rust server, run `cargo build --locked -p registry-language-server`, then + `editor: restart language server` in Zed. +- After changing the Zed launcher, install the development extension again from the same directory + and restart the language server. + +## Troubleshooting + +- If the development extension does not compile, confirm `rustup` owns the active Rust installation + and that `cargo check` for `wasm32-wasip1` passes. +- If Zed cannot find the server, close it, export the updated `PATH`, and relaunch it from that + terminal. The launcher looks for `registry-language-server`, then `registryctl`. +- Use `dev: open language server logs` to inspect how the server was launched. Use + `zed: open log` for extension errors. For verbose extension output, close Zed and relaunch it with + `zed --foreground "$REGISTRY_STACK_SMOKE_PROJECT"`. +- Confirm the project root contains `registry-stack.yaml` and the active file language is YAML. + +The Extensions page identifies a successful local install as a development extension. Remove it +from that page after the smoke test if you do not want the override to remain active. -Zed extensions cannot bundle an external language server. This launcher first looks for -`registry-language-server`, then falls back to `registryctl authoring language-server`. A future -marketplace package can also download a matching signed Registry Stack release asset. +Zed does not permit shipping an external language server inside the extension. This launcher first +looks on `PATH` for `registry-language-server`, then falls back to +`registryctl authoring language-server`. See Zed's official +[development-extension instructions](https://zed.dev/docs/extensions/developing-extensions#developing-an-extension-locally) +for the current installation and logging workflow. From 1d9c7423bd5916fd8f3652cbffee2b078404ac05 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Fri, 17 Jul 2026 21:36:49 +0700 Subject: [PATCH 3/4] feat(editors): harden editor integrations Signed-off-by: Jeremi Joslin --- .github/workflows/ci.yml | 6 +- crates/registry-language-server/src/server.rs | 177 +- .../tests/protocol.rs | 77 +- editors/README.md | 10 + editors/vscode/.gitignore | 2 + editors/vscode/.vscode-test.cjs | 49 + editors/vscode/.vscodeignore | 4 + editors/vscode/README.md | 22 +- editors/vscode/package-lock.json | 5624 ++++++++++++++++- editors/vscode/package.json | 26 +- editors/vscode/src/extension.ts | 99 +- editors/vscode/test/trusted.test.ts | 102 + editors/vscode/tsconfig.test.json | 15 + editors/zed/.gitignore | 1 + editors/zed/README.md | 11 +- 15 files changed, 6054 insertions(+), 171 deletions(-) create mode 100644 editors/vscode/.vscode-test.cjs create mode 100644 editors/vscode/test/trusted.test.ts create mode 100644 editors/vscode/tsconfig.test.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3fd62345..18ff8b07b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -411,12 +411,14 @@ jobs: run: | npm ci npm run package:dev + cargo build --locked -p registry-language-server + xvfb-run -a npm test cmp ../../LICENSE LICENSE - name: Install Zed extension target - run: rustup target add wasm32-wasip1 + run: rustup target add wasm32-wasip2 - name: Check Zed extension run: | - cargo check --locked --target wasm32-wasip1 --manifest-path editors/zed/Cargo.toml + cargo check --locked --target wasm32-wasip2 --manifest-path editors/zed/Cargo.toml cmp LICENSE editors/zed/LICENSE diff --git a/crates/registry-language-server/src/server.rs b/crates/registry-language-server/src/server.rs index c4f35f6ea..b3cccb57a 100644 --- a/crates/registry-language-server/src/server.rs +++ b/crates/registry-language-server/src/server.rs @@ -4,19 +4,22 @@ use std::{ collections::{BTreeMap, BTreeSet}, fs, path::{Path, PathBuf}, + sync::atomic::{AtomicBool, Ordering}, }; use tokio::sync::{Mutex, RwLock}; use tower_lsp_server::{ jsonrpc::Result, ls_types::{ - Diagnostic, DidChangeTextDocumentParams, DidCloseTextDocumentParams, + Diagnostic, DidChangeTextDocumentParams, DidChangeWatchedFilesParams, + DidChangeWatchedFilesRegistrationOptions, DidCloseTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentSymbol, DocumentSymbolParams, - DocumentSymbolResponse, GotoDefinitionParams, GotoDefinitionResponse, InitializeParams, - InitializeResult, InitializedParams, Location, MessageType, OneOf, PositionEncodingKind, - ReferenceParams, SaveOptions, ServerCapabilities, ServerInfo, SymbolInformation, - TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, Uri, - WorkspaceSymbolParams, WorkspaceSymbolResponse, + DocumentSymbolResponse, FileSystemWatcher, GlobPattern, GotoDefinitionParams, + GotoDefinitionResponse, InitializeParams, InitializeResult, InitializedParams, Location, + MessageType, OneOf, PositionEncodingKind, ReferenceParams, Registration, SaveOptions, + ServerCapabilities, ServerInfo, SymbolInformation, TextDocumentSyncCapability, + TextDocumentSyncKind, TextDocumentSyncOptions, Uri, WorkspaceSymbolParams, + WorkspaceSymbolResponse, }, Client, LanguageServer, }; @@ -34,6 +37,7 @@ pub struct Backend { client: Client, state: RwLock>, published_paths: Mutex>, + supports_dynamic_file_watching: AtomicBool, } impl Backend { @@ -42,6 +46,7 @@ impl Backend { client, state: RwLock::new(None), published_paths: Mutex::new(BTreeSet::new()), + supports_dynamic_file_watching: AtomicBool::new(false), } } @@ -74,10 +79,10 @@ impl Backend { }; let mut published = self.published_paths.lock().await; + let current_paths = by_path.keys().cloned().collect::>(); for stale_path in published.iter() { by_path.entry(stale_path.clone()).or_default(); } - let current_paths = by_path.keys().cloned().collect::>(); for (path, diagnostics) in by_path { if let Some(uri) = Uri::from_file_path(&path) { @@ -111,10 +116,42 @@ impl Backend { drop(state); self.publish_diagnostics().await; } + + async fn reload_watched_documents(&self, paths: Vec) { + let mut paths = paths + .into_iter() + .map(|path| normalize_document_path(&path)) + .collect::>(); + paths.sort(); + paths.dedup(); + let mut state = self.state.write().await; + if state.is_none() { + *state = paths + .iter() + .find_map(|path| find_project_root(path)) + .and_then(|root| WorkspaceState::load(&root).ok()); + } + if let Some(state) = state.as_mut() { + for path in paths { + state.reload_from_disk(&path); + } + } + drop(state); + self.publish_diagnostics().await; + } } impl LanguageServer for Backend { async fn initialize(&self, params: InitializeParams) -> Result { + let supports_dynamic_file_watching = params + .capabilities + .workspace + .as_ref() + .and_then(|workspace| workspace.did_change_watched_files.as_ref()) + .and_then(|capability| capability.dynamic_registration) + .unwrap_or(false); + self.supports_dynamic_file_watching + .store(supports_dynamic_file_watching, Ordering::Relaxed); if let Some(root) = project_root_from_initialize(¶ms) { *self.state.write().await = WorkspaceState::load(&root).ok(); } @@ -150,6 +187,32 @@ impl LanguageServer for Backend { } async fn initialized(&self, _params: InitializedParams) { + if self.supports_dynamic_file_watching.load(Ordering::Relaxed) { + let register_options = serde_json::to_value(DidChangeWatchedFilesRegistrationOptions { + watchers: vec![FileSystemWatcher { + glob_pattern: GlobPattern::String("**/*.yaml".to_owned()), + kind: None, + }], + }) + .expect("watched-file registration options serialize"); + if let Err(error) = self + .client + .register_capability(vec![Registration { + id: "registry-stack-yaml-files".to_owned(), + method: "workspace/didChangeWatchedFiles".to_owned(), + register_options: Some(register_options), + }]) + .await + { + self.client + .log_message( + MessageType::WARNING, + format!("Could not watch Registry Stack YAML files: {error}"), + ) + .await; + } + } + let message = { let state = self.state.read().await; state @@ -224,6 +287,17 @@ impl LanguageServer for Backend { } } + async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) { + let paths = params + .changes + .into_iter() + .filter_map(|change| change.uri.to_file_path().map(|path| path.into_owned())) + .collect::>(); + if !paths.is_empty() { + self.reload_watched_documents(paths).await; + } + } + async fn goto_definition( &self, params: GotoDefinitionParams, @@ -354,6 +428,13 @@ impl WorkspaceState { return; } self.open_versions.remove(path); + self.reload_from_disk(path); + } + + fn reload_from_disk(&mut self, path: &Path) { + if !is_project_document(&self.root, path) || self.open_versions.contains_key(path) { + return; + } match fs::symlink_metadata(path) { Ok(metadata) if metadata.file_type().is_file() @@ -534,4 +615,86 @@ mod tests { 1 ); } + + #[test] + fn reloads_external_changes_from_disk() { + let temp = project(); + let manifest = temp + .path() + .join("registry-stack.yaml") + .canonicalize() + .unwrap(); + let mut state = WorkspaceState::load(temp.path()).unwrap(); + + fs::write( + &manifest, + "version: 1\nregistry: { id: external }\nservices: {}\n", + ) + .unwrap(); + state.reload_from_disk(&manifest); + + assert!(state + .index + .workspace_symbols("external") + .iter() + .any(|symbol| symbol.name == "external")); + } + + #[test] + fn adds_and_removes_external_project_documents() { + let temp = project(); + let mut state = WorkspaceState::load(temp.path()).unwrap(); + let entities = temp.path().join("entities"); + fs::create_dir(&entities).unwrap(); + let entity = entities.join("person.yaml"); + fs::write(&entity, "version: 1\nid: person\n").unwrap(); + let entity = entity.canonicalize().unwrap(); + + state.reload_from_disk(&entity); + assert!(state + .index + .workspace_symbols("person") + .iter() + .any(|symbol| symbol.name == "person")); + + fs::remove_file(&entity).unwrap(); + state.reload_from_disk(&entity); + assert!(state.index.workspace_symbols("person").is_empty()); + } + + #[test] + fn external_changes_do_not_replace_an_open_document() { + let temp = project(); + let manifest = temp + .path() + .join("registry-stack.yaml") + .canonicalize() + .unwrap(); + let mut state = WorkspaceState::load(temp.path()).unwrap(); + state.update( + manifest.clone(), + "version: 1\nregistry: { id: unsaved }\nservices: {}\n".to_owned(), + 2, + ); + + fs::write( + &manifest, + "version: 1\nregistry: { id: external }\nservices: {}\n", + ) + .unwrap(); + state.reload_from_disk(&manifest); + assert!(state + .index + .workspace_symbols("unsaved") + .iter() + .any(|symbol| symbol.name == "unsaved")); + assert!(state.index.workspace_symbols("external").is_empty()); + + state.close(&manifest); + assert!(state + .index + .workspace_symbols("external") + .iter() + .any(|symbol| symbol.name == "external")); + } } diff --git a/crates/registry-language-server/tests/protocol.rs b/crates/registry-language-server/tests/protocol.rs index a3c5000cc..0d98e58eb 100644 --- a/crates/registry-language-server/tests/protocol.rs +++ b/crates/registry-language-server/tests/protocol.rs @@ -82,13 +82,13 @@ fn serves_definition_references_and_workspace_symbols_over_stdio() { .join("registry-stack.yaml") .canonicalize() .unwrap(); - let manifest_uri = Uri::from_file_path(manifest_path).unwrap().to_string(); + let manifest_uri = Uri::from_file_path(&manifest_path).unwrap().to_string(); let integration_path = project .path() .join("integrations/people/integration.yaml") .canonicalize() .unwrap(); - let integration_uri = Uri::from_file_path(integration_path).unwrap().to_string(); + let integration_uri = Uri::from_file_path(&integration_path).unwrap().to_string(); let mut child = Command::new(env!("CARGO_BIN_EXE_registry-language-server")) .current_dir(project.path()) @@ -108,7 +108,11 @@ fn serves_definition_references_and_workspace_symbols_over_stdio() { "params": { "processId": null, "rootUri": root_uri, - "capabilities": {}, + "capabilities": { + "workspace": { + "didChangeWatchedFiles": { "dynamicRegistration": true } + } + }, "workspaceFolders": [{ "uri": root_uri, "name": "demo" }] } }), @@ -123,6 +127,31 @@ fn serves_definition_references_and_workspace_symbols_over_stdio() { &mut stdin, json!({ "jsonrpc": "2.0", "method": "initialized", "params": {} }), ); + let registration = receive(&mut stdout); + assert_eq!( + registration.get("method").and_then(Value::as_str), + Some("client/registerCapability") + ); + assert_eq!( + registration + .pointer("/params/registrations/0/method") + .and_then(Value::as_str), + Some("workspace/didChangeWatchedFiles") + ); + assert_eq!( + registration + .pointer("/params/registrations/0/registerOptions/watchers/0/globPattern") + .and_then(Value::as_str), + Some("**/*.yaml") + ); + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": registration.get("id").unwrap(), + "result": null + }), + ); let mut published_missing_reference = false; for _ in 0..3 { let notification = receive(&mut stdout); @@ -203,11 +232,49 @@ fn serves_definition_references_and_workspace_symbols_over_stdio() { Some("active") ); + let changed_manifest = fs::read_to_string(&manifest_path) + .unwrap() + .replace("registry: { id: demo }", "registry: { id: external-demo }"); + fs::write(&manifest_path, changed_manifest).unwrap(); + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "method": "workspace/didChangeWatchedFiles", + "params": { + "changes": [{ "uri": manifest_uri, "type": 2 }] + } + }), + ); + let mut observed_external_change = false; + for id in 5..15 { + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": id, + "method": "workspace/symbol", + "params": { "query": "external-demo" } + }), + ); + let reloaded_symbols = receive_response(&mut stdout, id); + if reloaded_symbols + .pointer("/result/0/name") + .and_then(Value::as_str) + == Some("external-demo") + { + observed_external_change = true; + break; + } + std::thread::sleep(std::time::Duration::from_millis(20)); + } + assert!(observed_external_change); + send( &mut stdin, - json!({ "jsonrpc": "2.0", "id": 5, "method": "shutdown", "params": null }), + json!({ "jsonrpc": "2.0", "id": 15, "method": "shutdown", "params": null }), ); - receive_response(&mut stdout, 5); + receive_response(&mut stdout, 15); send( &mut stdin, json!({ "jsonrpc": "2.0", "method": "exit", "params": null }), diff --git a/editors/README.md b/editors/README.md index 896432413..96cb4b73c 100644 --- a/editors/README.md +++ b/editors/README.md @@ -12,6 +12,10 @@ These integrations intentionally run alongside each editor's YAML language serve `.vscode/settings.json` and `.zed/settings.json` files continue to provide version-matched schema validation and YAML completion without duplicating that behavior here. +The language server watches Registry Stack YAML paths for changes made by generators, Git, or +other tools. An open editor buffer remains authoritative until it is closed, so a filesystem event +cannot replace unsaved content. + ## Local end-to-end smoke test Run the commands in this section from the repository root. They build both server entry points and @@ -67,7 +71,13 @@ The same core behavior has non-GUI coverage: ```console cargo test --locked -p registry-language-server cargo test --locked -p registryctl --test language_server +cd editors/vscode && npm ci && npm test ``` +The VS Code test launches the minimum supported VS Code release line in an Extension Host. It +checks activation, the trust and virtual-workspace declarations, external file reloads, and the +addition and removal of Registry Stack folders in a multi-root workspace. On headless Linux, run +it as `xvfb-run -a npm test`, matching CI. + When finished, close the smoke project and remove the temporary directory shown by `$REGISTRY_STACK_SMOKE_ROOT` after checking that it is the directory created by `mktemp` above. diff --git a/editors/vscode/.gitignore b/editors/vscode/.gitignore index a08e1da2d..64c3490dc 100644 --- a/editors/vscode/.gitignore +++ b/editors/vscode/.gitignore @@ -1,3 +1,5 @@ node_modules/ dist/ +out/ +.vscode-test/ *.vsix diff --git a/editors/vscode/.vscode-test.cjs b/editors/vscode/.vscode-test.cjs new file mode 100644 index 000000000..39e137564 --- /dev/null +++ b/editors/vscode/.vscode-test.cjs @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 + +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); + +const { defineConfig } = require('@vscode/test-cli'); + +const testRunDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'registry-stack-vscode-')); +const projectAlpha = path.join(testRunDirectory, 'project-alpha'); +const projectBeta = path.join(testRunDirectory, 'project-beta'); +const workspaceFolder = path.join(testRunDirectory, 'multi-root.code-workspace'); +fs.mkdirSync(projectAlpha, { recursive: true }); +fs.mkdirSync(projectBeta, { recursive: true }); +fs.writeFileSync( + path.join(projectAlpha, 'registry-stack.yaml'), + 'version: 1\nregistry: { id: alpha-registry }\nservices: {}\n', +); +fs.writeFileSync( + path.join(projectBeta, 'registry-stack.yaml'), + 'version: 1\nregistry: { id: beta-registry }\nservices: {}\n', +); +fs.writeFileSync( + workspaceFolder, + JSON.stringify({ + folders: [ + { name: 'alpha', path: projectAlpha }, + { name: 'beta', path: projectBeta }, + ], + settings: { + 'registryStack.languageServer.path': path.resolve( + __dirname, + '../../target/debug/registry-language-server', + ), + }, + }), +); + +module.exports = defineConfig({ + files: 'out/test/trusted.test.js', + version: '1.91.1', + workspaceFolder, + launchArgs: [ + '--disable-extensions', + '--user-data-dir', + path.join(testRunDirectory, 'trusted'), + ], + mocha: { timeout: 30_000 }, +}); diff --git a/editors/vscode/.vscodeignore b/editors/vscode/.vscodeignore index 6601f99da..6fb7ecf47 100644 --- a/editors/vscode/.vscodeignore +++ b/editors/vscode/.vscodeignore @@ -1,7 +1,11 @@ .github/** +.vscode-test.* .gitignore package-lock.json src/** +test/** +out/** tsconfig.json +tsconfig.test.json node_modules/.cache/** **/*.map diff --git a/editors/vscode/README.md b/editors/vscode/README.md index 02aeebb3c..63b1136bf 100644 --- a/editors/vscode/README.md +++ b/editors/vscode/README.md @@ -5,6 +5,11 @@ This extension starts `registry-language-server` for a workspace whose root cont and Registry Stack reference diagnostics. Red Hat YAML remains responsible for YAML syntax, schema validation, completion, formatting, and ordinary hover information. +Multi-root workspaces are supported. The extension starts one isolated language-server process for +each folder whose root contains `registry-stack.yaml`, and it responds when workspace folders are +added or removed. Because the server executes a local binary and reads local files, the extension +is disabled in untrusted and virtual workspaces. + ## Package, install, and launch Prerequisites are Node.js 22, the `code` command-line tool, and the Rust toolchain used by the @@ -36,8 +41,9 @@ repository. First complete the [shared smoke-project setup](../README.md#local-e "registryStack.languageServer.path": "/absolute/path/to/registry-stack/target/debug/registry-language-server" ``` -4. Run **Registry Stack: Restart Language Server**. Open **View: Toggle Output**, select - **Registry Stack Language Server**, and confirm it reports the smoke project as indexed. +4. Run **Registry Stack: Restart Language Server**. Open **View: Toggle Output**, select the + **Registry Stack Language Server (project)** channel, and confirm it reports the smoke project + as indexed. 5. Complete the [shared expected-behavior checklist](../README.md#expected-behavior). VS Code uses `F12` for definitions, `Shift+F12` for references, `Cmd+Shift+O`/`Ctrl+Shift+O` for document symbols, and `Cmd+T`/`Ctrl+T` for workspace symbols. @@ -53,15 +59,19 @@ extension can find `registry-language-server` on `PATH`, then fall back to **Registry Stack: Restart Language Server**. - After changing the extension, rerun `npm run package:dev`, reinstall the VSIX with `--force`, and run **Developer: Reload Window**. +- Run `npm test` after building `registry-language-server` to launch the Extension Host test for + multi-root behavior and declared workspace capabilities. On headless Linux, use + `xvfb-run -a npm test`. ## Troubleshooting -- If activation does not occur, confirm the opened workspace root itself contains - `registry-stack.yaml`. Opening only a YAML file or a parent directory does not activate it. +- If activation does not occur, confirm each intended workspace folder root itself contains + `registry-stack.yaml` and that VS Code trusts the workspace. Opening only a YAML file or a parent + directory does not activate it. - If startup reports that no executable was found, verify the workspace setting is an absolute path to an executable regular file. -- If navigation is absent, confirm the file's VS Code language mode is YAML and inspect the - **Registry Stack Language Server** output channel. +- If navigation is absent, confirm the file's VS Code language mode is YAML and inspect the output + channel named for that workspace folder. - Red Hat YAML still owns schema validation, completion, hover, formatting, and syntax errors. Its diagnostics do not indicate a Registry Stack language-server failure. diff --git a/editors/vscode/package-lock.json b/editors/vscode/package-lock.json index ad8ef8ec0..339506e94 100644 --- a/editors/vscode/package-lock.json +++ b/editors/vscode/package-lock.json @@ -12,8 +12,12 @@ "vscode-languageclient": "^10.1.0" }, "devDependencies": { + "@types/mocha": "10.0.10", "@types/node": "^24.0.0", "@types/vscode": "1.91.0", + "@vscode/test-cli": "0.0.15", + "@vscode/test-electron": "3.0.0", + "@vscode/vsce": "3.9.2", "esbuild": "0.28.1", "typescript": "^5.9.3" }, @@ -21,6 +25,226 @@ "vscode": "^1.91.0" } }, + "node_modules/@azu/format-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", + "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@azu/style-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", + "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "@azu/format-text": "^1.0.1" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.2.0.tgz", + "integrity": "sha512-fNAjWnA/nZ2jz31kxR/AqRaUT8ewHBw/WuBIosK0moMy1C9e5ValbDfFdIxJzVOOYaYkV/b2F1S4H/aHiqfVQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.11.0.tgz", + "integrity": "sha512-IUZydyTUkDnYdstOW9pFOOUQlBjAepK5teihDE3x6yxsPJs/hsAaaYpeGxdxrgtOiJbBKSjKW7MDk7AEhb4LRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.11.0.tgz", + "integrity": "sha512-JjQWO6akOck45PH/XBrxzsQGAiKrfFl4m5iggJ0ItMIz5omRufOXWpqCPpdjKN3vKDzlSUvFjaMb7Zwf0gvAdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.25.0.tgz", + "integrity": "sha512-bMs8ekJLjX8wPV+9IPBges1SLPyuDtE9g5gLDWOpxzKcoOFQnpLGkbcT1tdw3FaAmDS1gnPmMmJ6y/T5B96kIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.4.0.tgz", + "integrity": "sha512-eGwxD0AtncrxeBM4tG8R55Pc3rdX1hNW2WibJAgYpCVA6E93mvvVH+LcssoVjOBrSKWS55yEIHsk0X8ctHmfOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.14.0.tgz", + "integrity": "sha512-9n2pWK61veAuN0V20t9lOuoV4CFMdyAZ1ygZzvBGk/pBBJRib/PjL9PLXa/aI2CcPpyHfqVsxxqLCYl6uZlfDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", + "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.4.0.tgz", + "integrity": "sha512-rbAE25KUfjU/s3XHUdJgceoCP5dEOpMx85J04kF+QMdta73XkuG9JGHHinch+XIoKpBdqljin+KqURpJriSzLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.17.1.tgz", + "integrity": "sha512-zBhRGzABKSI7hfWh5EaZmril5ybZ7imBN1qEZl5sDTaelr+l8SnPjZO50Q4dnKnm347YPIlBMSnXKZyh3Yu5DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.11.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.11.2", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.11.2.tgz", + "integrity": "sha512-yDhtBOGDCdK9ipQ9g3+wmlMEPnZx2pXaDicDd9jYyR1L+7lEbvEohTDmF5qejZDutZY3m9pWPxeYxzNC701A2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.4.1.tgz", + "integrity": "sha512-yqgoyOIMCH7TNaSLMBTP+4LUlbMMf1zgC8nzOFG95lmW82CmsAEtUT0J93e4BdqDcnX5qle/9X+yb7A8Mw9M0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.11.2", + "jsonwebtoken": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", @@ -463,171 +687,5065 @@ "node": ">=18" } }, - "node_modules/@types/node": { - "version": "24.13.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", - "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "undici-types": "~7.18.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@types/vscode": { - "version": "1.91.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.91.0.tgz", - "integrity": "sha512-PgPr+bUODjG3y+ozWUCyzttqR9EHny9sPAfJagddQjDwdtf66y2sDKJMnFZRuzBA2YtBGASqJGPil8VDUPvO6A==", + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "license": "MIT", "engines": { - "node": "18 || 20 || >=22" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=18" + "node": ">=12" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "license": "BlueOak-1.0.0", + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^5.0.5" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 8" } }, - "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 8" } }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" }, "engines": { - "node": ">=14.17" + "node": ">= 8" } }, - "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, - "license": "MIT" - }, - "node_modules/vscode-jsonrpc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.1.tgz", - "integrity": "sha512-rfuA6T75H6m5EkbhtEPzre9pT0HPcDI2MMy4+nPFIBks5J8JBAUHD4tRYSgaBOijIEC7SRkC1kKyXTLqbmh9jw==", "license": "MIT", + "optional": true, "engines": { - "node": ">=14.0.0" + "node": ">=14" } }, - "node_modules/vscode-languageclient": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.1.0.tgz", - "integrity": "sha512-XXRx6lqVitQy/oOLr9MfNYRG+MbQkhXkDaxbQMiKxEm8zZNfheRFUKNb8UYNh2stn9btl2wQM5wZFJjJvoc+jA==", + "node_modules/@secretlint/config-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", + "dev": true, "license": "MIT", "dependencies": { - "minimatch": "^10.2.5", - "semver": "^7.8.1", - "vscode-languageserver-protocol": "3.18.2", - "vscode-languageserver-textdocument": "1.0.13" + "@secretlint/types": "^10.2.2" }, "engines": { - "vscode": "^1.91.0" + "node": ">=20.0.0" } }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.18.2", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.2.tgz", - "integrity": "sha512-XRyDbT0Pp3sSNti3JmxVEUMySWCSi1hhM+/KUlCy1hV1zmrqpM1OwO12EAki8blhmLuIMpaJrYbo0OzGVfK2Qg==", + "node_modules/@secretlint/config-loader": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", + "dev": true, "license": "MIT", "dependencies": { - "vscode-jsonrpc": "9.0.1", - "vscode-languageserver-types": "3.18.0" + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.13.tgz", + "node_modules/@secretlint/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@secretlint/node": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/profiler": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/resolver": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-sarif-builder": "^3.2.0" + } + }, + "node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/source-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@textlint/ast-node-types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.7.1.tgz", + "integrity": "sha512-Wii5UgUKFEh9Uv6wbq1zr4/Kf+dtjiUuzPrrXzKp8H+ifkvKNzi23V4Nz+6wVyHQn5T28AFuc8VH8OtzvGYecA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.7.1.tgz", + "integrity": "sha512-TdwZ/debWYFD05K3CcoHtwvnCrza29wZxD+BjDTk/V5N7iRqkK1dTTHSD4A8AIgROLiDkHJmIKQbasbmsg8AvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.7.1", + "@textlint/resolver": "15.7.1", + "@textlint/types": "15.7.1", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "lodash": "^4.18.1", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/module-interop": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.7.1.tgz", + "integrity": "sha512-Jg+sQW2L/cRJypk59wtcMUVVpt8vmit5ZMT3gUnFwevP3A6Qp1HfOtUy9ObT4hBX3lOSGT/ekcCDxR1pL7uH1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/resolver": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.7.1.tgz", + "integrity": "sha512-8XnO0pgF6mXnm41VvWmBbEIdGPhiCUt31uLZkOis1ECeg/1SoUcIT6Mx/F0e1rukq8l0UlOSeY9a31CsvRMK0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.7.1.tgz", + "integrity": "sha512-Vye/GmFNBTgVzZFtIFJTmLB+s2A7oIADxNG6r9UhfPuY+Czv0z5G3xeyFZZudPlfxURsKUyPIU5XsjOFqVp33A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "15.7.1" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mocha": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/vscode": { + "version": "1.91.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.91.0.tgz", + "integrity": "sha512-PgPr+bUODjG3y+ozWUCyzttqR9EHny9sPAfJagddQjDwdtf66y2sDKJMnFZRuzBA2YtBGASqJGPil8VDUPvO6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.7.tgz", + "integrity": "sha512-JVUD8X2tfDMWjcjLs4yVxxVrS8yR5vnh386GAXT9Qj79nBxxXSaHFQZg5FweLmT8HlPQ3kii6noUB+Z9RN7DvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@vscode/test-cli": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.15.tgz", + "integrity": "sha512-nAxk2X79wuXS7aOhyFFhFcCqd7EBUoMesu7ZgsYE/4eFjyBMuyIweVE94BxdKH1RieN8eOz2SIrljrZt6Lk9fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mocha": "^10.0.10", + "c8": "^11.0.0", + "chokidar": "^5.0.0", + "enhanced-resolve": "^5.24.0", + "glob": "^13.0.6", + "minimatch": "^10.2.5", + "mocha": "^11.7.6", + "supports-color": "^10.2.2", + "yargs": "^18.0.0" + }, + "bin": { + "vscode-test": "out/bin.mjs" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@vscode/test-cli/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@vscode/test-cli/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@vscode/test-cli/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/test-cli/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-cli/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@vscode/test-cli/node_modules/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/@vscode/test-cli/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/@vscode/test-electron": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-3.0.0.tgz", + "integrity": "sha512-TY5mC7aAjxSLDXsyjhrG8cJHgc/HLdiE5lvtW7hABYQrY24Qwozzr5UoO3HiuAM4Hzz4b7K/eZlwrCILj94CcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "jszip": "^3.10.1", + "ora": "^8.1.0", + "semver": "^7.6.2" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@vscode/vsce": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.2.tgz", + "integrity": "sha512-XSxMosEEDO6vLxELAHVkwmhC0qe0ijZni2jB9Rcs8kQsW4lhTDQ/wMzmwFs/buotAWSnpmUp/dRWD2ufG3UYKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "^4.0.0", + "glob": "^13.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^10.2.2", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.2", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^3.2.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.9.tgz", + "integrity": "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.6", + "@vscode/vsce-sign-alpine-x64": "2.0.6", + "@vscode/vsce-sign-darwin-arm64": "2.0.6", + "@vscode/vsce-sign-darwin-x64": "2.0.6", + "@vscode/vsce-sign-linux-arm": "2.0.6", + "@vscode/vsce-sign-linux-arm64": "2.0.6", + "@vscode/vsce-sign-linux-x64": "2.0.6", + "@vscode/vsce-sign-win32-arm64": "2.0.6", + "@vscode/vsce-sign-win32-x64": "2.0.6" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz", + "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz", + "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz", + "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz", + "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz", + "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz", + "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz", + "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz", + "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/c8": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz", + "integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^8.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": "20 || >=22" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cockatiel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", + "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.24.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz", + "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/fs-extra": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/linkify-it": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-it": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", + "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/mocha": { + "version": "11.7.6", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.6.tgz", + "integrity": "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mocha/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-abi": { + "version": "3.94.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz", + "integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ora/node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.5.tgz", + "integrity": "sha512-e8vJF4XdVkzqqSHguEMz41mQO1wKwxKm5ENrUJQUu9kLDCtn83cxbyHZcszr4QC5zEA7WffRRC4gsTecC7J9oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.1.0" + } + }, + "node_modules/parse-semver/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-config-loader": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "^2.2.3", + "require-from-string": "^2.0.2" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/secretlint": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.7.tgz", + "integrity": "sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar-fs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/terminal-link": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/test-exclude": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^13.0.6", + "minimatch": "^10.2.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.1.tgz", + "integrity": "sha512-rfuA6T75H6m5EkbhtEPzre9pT0HPcDI2MMy4+nPFIBks5J8JBAUHD4tRYSgaBOijIEC7SRkC1kKyXTLqbmh9jw==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageclient": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.1.0.tgz", + "integrity": "sha512-XXRx6lqVitQy/oOLr9MfNYRG+MbQkhXkDaxbQMiKxEm8zZNfheRFUKNb8UYNh2stn9btl2wQM5wZFJjJvoc+jA==", + "license": "MIT", + "dependencies": { + "minimatch": "^10.2.5", + "semver": "^7.8.1", + "vscode-languageserver-protocol": "3.18.2", + "vscode-languageserver-textdocument": "1.0.13" + }, + "engines": { + "vscode": "^1.91.0" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.18.2", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.18.2.tgz", + "integrity": "sha512-XRyDbT0Pp3sSNti3JmxVEUMySWCSi1hhM+/KUlCy1hV1zmrqpM1OwO12EAki8blhmLuIMpaJrYbo0OzGVfK2Qg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "9.0.1", + "vscode-languageserver-types": "3.18.0" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.13.tgz", "integrity": "sha512-nx0ZHwMGIsVkzFG3/VLeJYBLTaFBRuNdGDvevvjuoayU5EOS2fEYazOhtCM3PI9ClMMg5igc0uwXtAq4tJj+Dw==", "license": "MIT" }, @@ -636,6 +5754,282 @@ "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.18.0.tgz", "integrity": "sha512-8TsGPNMIMiiBdkORgRSvLjuiEIiAFtO+KssmYWxQ+uSVvlf7RjK8YKCOjPzZ+YA04jXEV7+7LvkSmHkhpNS99g==", "license": "MIT" + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", + "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/editors/vscode/package.json b/editors/vscode/package.json index e32d9cdcd..741145b56 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -29,6 +29,15 @@ "extensionKind": [ "workspace" ], + "capabilities": { + "untrustedWorkspaces": { + "supported": false, + "description": "Registry Stack starts a local executable and reads project files from disk. Trust the workspace before enabling it." + }, + "virtualWorkspaces": { + "supported": false + } + }, "contributes": { "commands": [ { @@ -49,18 +58,29 @@ } }, "scripts": { - "compile": "esbuild src/extension.ts --bundle --platform=node --target=node20 --external:vscode --outfile=dist/extension.js --sourcemap", - "check": "tsc -p . --noEmit", - "package:dev": "npm run check && npx --yes @vscode/vsce@3.9.2 package --no-dependencies --out registry-stack-dev.vsix", + "compile": "esbuild src/extension.ts --bundle --platform=node --target=node20 --external:vscode --outfile=dist/extension.js --sourcemap --minify", + "compile-tests": "tsc -p tsconfig.test.json", + "check": "tsc -p . --noEmit && tsc -p tsconfig.test.json --noEmit", + "package:dev": "npm run check && vsce package --no-dependencies --out registry-stack-dev.vsix", + "pretest": "npm run compile && npm run compile-tests", + "test": "vscode-test", "vscode:prepublish": "npm run compile" }, "dependencies": { "vscode-languageclient": "^10.1.0" }, "devDependencies": { + "@types/mocha": "10.0.10", "@types/node": "^24.0.0", "@types/vscode": "1.91.0", + "@vscode/test-cli": "0.0.15", + "@vscode/test-electron": "3.0.0", + "@vscode/vsce": "3.9.2", "esbuild": "0.28.1", "typescript": "^5.9.3" + }, + "overrides": { + "diff": "8.0.3", + "serialize-javascript": "7.0.7" } } diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts index 3d9f248ce..635da454c 100644 --- a/editors/vscode/src/extension.ts +++ b/editors/vscode/src/extension.ts @@ -11,42 +11,62 @@ import { TransportKind, } from 'vscode-languageclient/node'; -let client: LanguageClient | undefined; +const clients = new Map(); +let lifecycle = Promise.resolve(); export async function activate(context: vscode.ExtensionContext): Promise { context.subscriptions.push( vscode.commands.registerCommand('registryStack.restartLanguageServer', async () => { - await restart(context); + await enqueueLifecycle(() => restart(context)); }), vscode.workspace.onDidChangeConfiguration(async (event) => { if (event.affectsConfiguration('registryStack.languageServer.path')) { - await restart(context); + await enqueueLifecycle(() => restart(context)); } }), + vscode.workspace.onDidChangeWorkspaceFolders(async () => { + await enqueueLifecycle(() => reconcileClients(context)); + }), ); - await start(context); + await enqueueLifecycle(() => reconcileClients(context)); } export async function deactivate(): Promise { - await stop(); + await enqueueLifecycle(stopAll); } async function restart(context: vscode.ExtensionContext): Promise { - await stop(); - await start(context); + await stopAll(); + await reconcileClients(context); +} + +function enqueueLifecycle(operation: () => Promise): Promise { + lifecycle = lifecycle.then(operation, operation); + return lifecycle; } -async function start(context: vscode.ExtensionContext): Promise { - if (client !== undefined) { - return; +async function reconcileClients(context: vscode.ExtensionContext): Promise { + const projectFolders = findProjectFolders(); + const desiredKeys = new Set(projectFolders.map(folderKey)); + for (const key of clients.keys()) { + if (!desiredKeys.has(key)) { + await stopClient(key); + } } - const projectFolder = findProjectFolder(); - if (projectFolder === undefined) { - return; + for (const projectFolder of projectFolders) { + if (!clients.has(folderKey(projectFolder))) { + await startClient(context, projectFolder); + } } +} +async function startClient( + context: vscode.ExtensionContext, + projectFolder: vscode.WorkspaceFolder, +): Promise { + const key = folderKey(projectFolder); try { const server = resolveServerCommand(context, projectFolder); const serverOptions: ServerOptions = { @@ -75,33 +95,53 @@ async function start(context: vscode.ExtensionContext): Promise { }, ], workspaceFolder: projectFolder, - outputChannelName: 'Registry Stack Language Server', + outputChannelName: `Registry Stack Language Server (${projectFolder.name})`, }; - client = new LanguageClient( - 'registry-stack', - 'Registry Stack Language Server', + const client = new LanguageClient( + `registry-stack:${key}`, + `Registry Stack Language Server (${projectFolder.name})`, serverOptions, clientOptions, ); + clients.set(key, client); await client.start(); } catch (error) { - client = undefined; + clients.delete(key); const detail = error instanceof Error ? error.message : String(error); - void vscode.window.showErrorMessage(`Registry Stack language server failed to start: ${detail}`); + void vscode.window.showErrorMessage( + `Registry Stack language server failed to start for ${projectFolder.name}: ${detail}`, + ); } } -async function stop(): Promise { - const activeClient = client; - client = undefined; - await activeClient?.dispose(); +async function stopClient(key: string): Promise { + const client = clients.get(key); + clients.delete(key); + await client?.dispose(); } -function findProjectFolder(): vscode.WorkspaceFolder | undefined { - return vscode.workspace.workspaceFolders?.find((folder) => - fs.existsSync(path.join(folder.uri.fsPath, 'registry-stack.yaml')), - ); +async function stopAll(): Promise { + const activeClients = [...clients.values()]; + clients.clear(); + await Promise.all(activeClients.map(async (client) => client.dispose())); +} + +function findProjectFolders(): vscode.WorkspaceFolder[] { + return (vscode.workspace.workspaceFolders ?? []).filter((folder) => { + if (folder.uri.scheme !== 'file') { + return false; + } + try { + return fs.statSync(path.join(folder.uri.fsPath, 'registry-stack.yaml')).isFile(); + } catch { + return false; + } + }); +} + +function folderKey(folder: vscode.WorkspaceFolder): string { + return folder.uri.toString(); } function resolveServerCommand( @@ -161,7 +201,10 @@ function isExecutableFile(candidate: string): boolean { function findExecutableOnPath(executable: string): string | undefined { const pathEntries = process.env.PATH?.split(path.delimiter) ?? []; for (const entry of pathEntries) { - const candidate = path.join(entry === '' ? process.cwd() : entry, executable); + if (entry === '') { + continue; + } + const candidate = path.join(entry, executable); if (isExecutableFile(candidate)) { return candidate; } diff --git a/editors/vscode/test/trusted.test.ts b/editors/vscode/test/trusted.test.ts new file mode 100644 index 000000000..78672aa44 --- /dev/null +++ b/editors/vscode/test/trusted.test.ts @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 + +import * as assert from 'node:assert'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +import * as vscode from 'vscode'; + +suite('Registry Stack extension', () => { + test('starts a language server for every Registry Stack workspace folder', async () => { + assert.strictEqual(vscode.workspace.isTrusted, true); + assert.strictEqual(vscode.workspace.workspaceFolders?.length, 2); + + const extension = vscode.extensions.getExtension('registrystack.registry-stack'); + assert.ok(extension, 'Registry Stack extension is available in the Extension Host'); + assert.strictEqual( + extension.packageJSON.capabilities?.untrustedWorkspaces?.supported, + false, + ); + assert.strictEqual(extension.packageJSON.capabilities?.virtualWorkspaces?.supported, false); + await assertExtensionActivated(extension); + + await assertWorkspaceSymbol('alpha-registry'); + await assertWorkspaceSymbol('beta-registry'); + + const alphaFolder = vscode.workspace.workspaceFolders?.find((folder) => folder.name === 'alpha'); + assert.ok(alphaFolder, 'alpha workspace folder is available'); + fs.writeFileSync( + path.join(alphaFolder.uri.fsPath, 'registry-stack.yaml'), + 'version: 1\nregistry: { id: alpha-reloaded }\nservices: {}\n', + ); + await assertWorkspaceSymbol('alpha-reloaded'); + + const gammaPath = path.join(path.dirname(alphaFolder.uri.fsPath), 'project-gamma'); + fs.mkdirSync(gammaPath); + fs.writeFileSync( + path.join(gammaPath, 'registry-stack.yaml'), + 'version: 1\nregistry: { id: gamma-registry }\nservices: {}\n', + ); + assert.strictEqual( + vscode.workspace.updateWorkspaceFolders(2, 0, { + uri: vscode.Uri.file(gammaPath), + name: 'gamma', + }), + true, + ); + await assertWorkspaceFolderCount(3); + await assertWorkspaceSymbol('gamma-registry'); + + assert.strictEqual(vscode.workspace.updateWorkspaceFolders(2, 1), true); + await assertWorkspaceFolderCount(2); + await assertWorkspaceSymbolAbsent('gamma-registry'); + }); +}); + +async function assertExtensionActivated(extension: vscode.Extension): Promise { + for (let attempt = 0; attempt < 50; attempt += 1) { + if (extension.isActive) { + return; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } + assert.fail('Registry Stack extension did not activate for the workspace manifest'); +} + +async function assertWorkspaceFolderCount(expected: number): Promise { + for (let attempt = 0; attempt < 50; attempt += 1) { + if (vscode.workspace.workspaceFolders?.length === expected) { + return; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } + assert.fail(`workspace folder count did not become ${expected}`); +} + +async function assertWorkspaceSymbol(expected: string): Promise { + for (let attempt = 0; attempt < 50; attempt += 1) { + const symbols = await vscode.commands.executeCommand( + 'vscode.executeWorkspaceSymbolProvider', + expected, + ); + if (symbols?.some((symbol) => symbol.name === expected)) { + return; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } + assert.fail(`workspace symbol ${expected} was not provided`); +} + +async function assertWorkspaceSymbolAbsent(unexpected: string): Promise { + for (let attempt = 0; attempt < 50; attempt += 1) { + const symbols = await vscode.commands.executeCommand( + 'vscode.executeWorkspaceSymbolProvider', + unexpected, + ); + if (!symbols?.some((symbol) => symbol.name === unexpected)) { + return; + } + await new Promise((resolve) => setTimeout(resolve, 100)); + } + assert.fail(`workspace symbol ${unexpected} remained after its folder was removed`); +} diff --git a/editors/vscode/tsconfig.test.json b/editors/vscode/tsconfig.test.json new file mode 100644 index 000000000..3ecda7c03 --- /dev/null +++ b/editors/vscode/tsconfig.test.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out/test", + "rootDir": "./test", + "types": [ + "mocha", + "node", + "vscode" + ] + }, + "include": [ + "test/**/*.ts" + ] +} diff --git a/editors/zed/.gitignore b/editors/zed/.gitignore index 2f7896d1d..bd754f7ef 100644 --- a/editors/zed/.gitignore +++ b/editors/zed/.gitignore @@ -1 +1,2 @@ target/ +extension.wasm diff --git a/editors/zed/README.md b/editors/zed/README.md index 29827a60b..92dbaa25b 100644 --- a/editors/zed/README.md +++ b/editors/zed/README.md @@ -12,8 +12,8 @@ Zed requires Rust installed through `rustup` to compile development extensions. WebAssembly target from the repository root: ```console -rustup target add wasm32-wasip1 -cargo check --locked --target wasm32-wasip1 --manifest-path editors/zed/Cargo.toml +rustup target add wasm32-wasip2 +cargo check --locked --target wasm32-wasip2 --manifest-path editors/zed/Cargo.toml ``` 1. Put the freshly built language server on the environment inherited by Zed, then open the smoke @@ -26,8 +26,9 @@ cargo check --locked --target wasm32-wasip1 --manifest-path editors/zed/Cargo.to 2. Run `zed: install dev extension` from the command palette and select `$REGISTRY_STACK_REPO/editors/zed`. Zed compiles and installs the WebAssembly extension. -3. Run `editor: restart language server`, then `zed: open log`. Confirm the log reports that the - Registry Stack project was indexed. +3. Run `editor: restart language server`, then `dev: open language server logs`. Select + `registry-stack` for the smoke project and confirm the server log reports that the project was + indexed. Use `zed: open log` instead for extension compilation or launcher failures. 4. Complete the [shared expected-behavior checklist](../README.md#expected-behavior). Zed uses `F12` for definitions, `Alt+Shift+F12` for references, `Cmd+Shift+O`/`Ctrl+Shift+O` for document symbols, and `Cmd+T`/`Ctrl+T` for workspace symbols. @@ -42,7 +43,7 @@ cargo check --locked --target wasm32-wasip1 --manifest-path editors/zed/Cargo.to ## Troubleshooting - If the development extension does not compile, confirm `rustup` owns the active Rust installation - and that `cargo check` for `wasm32-wasip1` passes. + and that `cargo check` for `wasm32-wasip2` passes. - If Zed cannot find the server, close it, export the updated `PATH`, and relaunch it from that terminal. The launcher looks for `registry-language-server`, then `registryctl`. - Use `dev: open language server logs` to inspect how the server was launched. Use From a710c85d283a5298c0384940f5a40d92c6656c17 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Sat, 18 Jul 2026 07:19:25 +0700 Subject: [PATCH 4/4] fix(editors): harden project navigation preview Signed-off-by: Jeremi Joslin --- .github/workflows/ci.yml | 2 +- crates/registry-language-server/src/index.rs | 647 ++++++++++++++++-- crates/registry-language-server/src/server.rs | 183 +++-- .../tests/protocol.rs | 376 ++++++++++ crates/registryctl/CHANGELOG.md | 6 +- .../content/docs/reference/registryctl.mdx | 18 +- editors/README.md | 16 +- editors/vscode/.vscode-test.cjs | 7 +- editors/vscode/.vscodeignore | 1 + editors/vscode/README.md | 36 +- editors/vscode/package.json | 5 +- editors/vscode/scripts/verify-vsix.cjs | 26 + editors/vscode/src/extension.ts | 20 +- editors/vscode/test/trusted.test.ts | 2 +- editors/zed/README.md | 24 +- 15 files changed, 1197 insertions(+), 172 deletions(-) create mode 100644 editors/vscode/scripts/verify-vsix.cjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18ff8b07b..7f27e8aea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -406,7 +406,7 @@ jobs: cache: npm cache-dependency-path: editors/vscode/package-lock.json - - name: Check VS Code extension + - name: Package and test VS Code developer preview working-directory: editors/vscode run: | npm ci diff --git a/crates/registry-language-server/src/index.rs b/crates/registry-language-server/src/index.rs index 123070369..633ec522d 100644 --- a/crates/registry-language-server/src/index.rs +++ b/crates/registry-language-server/src/index.rs @@ -93,6 +93,7 @@ pub struct IndexedSymbol { pub container_name: Option, pub location: IndexedLocation, key: SymbolKey, + resolvable: bool, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -148,26 +149,48 @@ impl ProjectIndex { let root = root .canonicalize() .with_context(|| format!("failed to resolve project root {}", root.display()))?; - let documents = load_project_documents(&root)?; - Ok(Self::from_documents(&root, &documents)) + let loaded = load_project_documents(&root)?; + Ok(Self::from_documents_with_diagnostics( + &root, + &loaded.documents, + loaded.diagnostics, + )) } pub fn from_documents(root: &Path, documents: &BTreeMap) -> Self { + Self::from_documents_with_diagnostics(root, documents, Vec::new()) + } + + pub(crate) fn from_documents_with_diagnostics( + root: &Path, + documents: &BTreeMap, + mut diagnostics: Vec, + ) -> Self { let root = root.to_path_buf(); - let parsed = documents - .iter() - .filter_map(|(path, source)| parse_yaml(source).ok().map(|value| (path.clone(), value))) - .collect::>(); + let mut parsed = BTreeMap::new(); + for (path, source) in documents { + match parse_yaml(source) { + Ok(value) => { + parsed.insert(path.clone(), value); + } + Err(_) => diagnostics.push(document_diagnostic( + path, + "Invalid YAML syntax; fix this project document before it can be indexed", + )), + } + } - let (symbols, references) = { + let (symbols, references, semantic_diagnostics) = { let mut builder = IndexBuilder { root: &root, + documents, parsed: &parsed, symbols: Vec::new(), references: Vec::new(), + diagnostics: Vec::new(), }; builder.build(); - (builder.symbols, builder.references) + (builder.symbols, builder.references, builder.diagnostics) }; let mut index = Self { @@ -177,7 +200,11 @@ impl ProjectIndex { diagnostics: Vec::new(), document_paths: documents.keys().cloned().collect(), }; - index.diagnostics = index.build_diagnostics(); + diagnostics.extend(semantic_diagnostics); + diagnostics.extend(index.build_diagnostics()); + diagnostics.sort_by(diagnostic_cmp); + diagnostics.dedup(); + index.diagnostics = diagnostics; index } @@ -234,7 +261,10 @@ impl ProjectIndex { include_declaration: bool, ) -> Vec { let path = normalize_lookup_path(path); - let keys = if let Some(symbol) = self.symbol_at(&path, position) { + let keys = if let Some(symbol) = self + .symbol_at(&path, position) + .filter(|symbol| symbol.resolvable) + { vec![symbol.key.clone()] } else if let Some(reference) = self.reference_at(&path, position) { self.definitions_for(&reference.target) @@ -289,7 +319,7 @@ impl ProjectIndex { fn definitions_for(&self, query: &SymbolQuery) -> Vec<&IndexedSymbol> { self.symbols .iter() - .filter(|symbol| self.query_can_resolve_to(query, &symbol.key)) + .filter(|symbol| symbol.resolvable && self.query_can_resolve_to(query, &symbol.key)) .collect() } @@ -305,7 +335,7 @@ impl ProjectIndex { fn build_diagnostics(&self) -> Vec { let mut diagnostics = Vec::new(); let mut definitions: BTreeMap<&SymbolKey, Vec<&IndexedSymbol>> = BTreeMap::new(); - for symbol in &self.symbols { + for symbol in self.symbols.iter().filter(|symbol| symbol.resolvable) { definitions.entry(&symbol.key).or_default().push(symbol); } @@ -321,10 +351,10 @@ impl ProjectIndex { message: format!( "Duplicate {} definition '{}'{}", key.kind.label(), - key.name, + bounded_value(&key.name), key.scope .as_ref() - .map(|scope| format!(" in service '{scope}'")) + .map(|scope| format!(" in service '{}'", bounded_value(scope))) .unwrap_or_default() ), }); @@ -337,19 +367,19 @@ impl ProjectIndex { 0 => Some(format!( "Unknown {} reference '{}'{}", reference.target.kind.label(), - reference.target.name, + bounded_value(&reference.target.name), reference .target .scope .as_ref() - .map(|scope| format!(" in service '{scope}'")) + .map(|scope| format!(" in service '{}'", bounded_value(scope))) .unwrap_or_default() )), 1 => None, count => Some(format!( "Ambiguous {} reference '{}': found {count} definitions", reference.target.kind.label(), - reference.target.name + bounded_value(&reference.target.name) )), }; if let Some(message) = message { @@ -362,12 +392,7 @@ impl ProjectIndex { } } - diagnostics.sort_by(|left, right| { - left.path - .cmp(&right.path) - .then_with(|| range_cmp(left.range, right.range)) - .then_with(|| left.message.cmp(&right.message)) - }); + diagnostics.sort_by(diagnostic_cmp); diagnostics } } @@ -408,24 +433,29 @@ pub fn is_project_document(root: &Path, path: &Path) -> bool { } } -pub fn load_project_documents(root: &Path) -> Result> { +#[derive(Debug)] +pub struct LoadedProjectDocuments { + pub documents: BTreeMap, + pub diagnostics: Vec, +} + +pub fn load_project_documents(root: &Path) -> Result { let mut candidates = vec![root.join(PROJECT_FILE)]; - add_yaml_files(&root.join("entities"), &mut candidates)?; - add_yaml_files(&root.join("environments"), &mut candidates)?; + add_yaml_files(root, &root.join("entities"), &mut candidates)?; + add_yaml_files(root, &root.join("environments"), &mut candidates)?; let integrations = root.join("integrations"); - if let Ok(entries) = fs::read_dir(&integrations) { + if secure_directory(root, &integrations)? { + let entries = fs::read_dir(&integrations) + .with_context(|| format!("failed to inspect integrations under {}", root.display()))?; for entry in entries { let entry = entry.with_context(|| { - format!( - "failed to inspect integrations under {}", - integrations.display() - ) + format!("failed to inspect integrations under {}", root.display()) })?; - if entry.file_type()?.is_dir() { - let directory = entry.path(); + let directory = entry.path(); + if secure_directory(root, &directory)? { candidates.push(directory.join("integration.yaml")); - add_yaml_files(&directory.join("fixtures"), &mut candidates)?; + add_yaml_files(root, &directory.join("fixtures"), &mut candidates)?; } } } @@ -433,37 +463,65 @@ pub fn load_project_documents(root: &Path) -> Result> candidates.sort(); candidates.dedup(); let mut documents = BTreeMap::new(); + let mut diagnostics = Vec::new(); for path in candidates { - let metadata = match fs::symlink_metadata(&path) { - Ok(metadata) => metadata, - Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue, - Err(error) => { - return Err(error).with_context(|| format!("failed to inspect {}", path.display())) - } + let Some(metadata) = secure_regular_file(root, &path)? else { + continue; }; - if !metadata.file_type().is_file() || metadata.len() > MAX_DOCUMENT_BYTES { + if metadata.len() > MAX_DOCUMENT_BYTES { + diagnostics.push(document_diagnostic( + &path, + "Project document exceeds the 1 MiB indexing limit", + )); continue; } - let source = fs::read_to_string(&path) - .with_context(|| format!("failed to read {}", path.display()))?; - documents.insert(path, source); + match fs::read(&path) { + Ok(bytes) => match String::from_utf8(bytes) { + Ok(source) => { + documents.insert(path, source); + } + Err(_) => diagnostics.push(document_diagnostic( + &path, + "Project document is not valid UTF-8 and cannot be indexed", + )), + }, + Err(error) if path.ends_with(PROJECT_FILE) => { + return Err(error).context("failed to read registry-stack.yaml") + } + Err(_) => diagnostics.push(document_diagnostic( + &path, + "Project document could not be read; check its permissions", + )), + } } - Ok(documents) + if !documents.contains_key(&root.join(PROJECT_FILE)) { + anyhow::bail!("registry-stack.yaml is missing, unsafe, oversized, or not valid UTF-8"); + } + Ok(LoadedProjectDocuments { + documents, + diagnostics, + }) } -fn add_yaml_files(directory: &Path, candidates: &mut Vec) -> Result<()> { - let entries = match fs::read_dir(directory) { - Ok(entries) => entries, - Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()), - Err(error) => { - return Err(error) - .with_context(|| format!("failed to inspect {}", directory.display())); - } - }; +fn add_yaml_files(root: &Path, directory: &Path, candidates: &mut Vec) -> Result<()> { + if !secure_directory(root, directory)? { + return Ok(()); + } + let entries = fs::read_dir(directory).with_context(|| { + format!( + "failed to inspect a project directory under {}", + root.display() + ) + })?; for entry in entries { - let entry = entry - .with_context(|| format!("failed to inspect an entry in {}", directory.display()))?; - if entry.file_type()?.is_file() && entry.path().extension().is_some_and(|ext| ext == "yaml") + let entry = entry.with_context(|| { + format!( + "failed to inspect an entry in a project directory under {}", + root.display() + ) + })?; + if entry.path().extension().is_some_and(|ext| ext == "yaml") + && secure_regular_file(root, &entry.path())?.is_some() { candidates.push(entry.path()); } @@ -471,11 +529,60 @@ fn add_yaml_files(directory: &Path, candidates: &mut Vec) -> Result<()> Ok(()) } +fn secure_directory(root: &Path, path: &Path) -> Result { + Ok(secure_path_metadata(root, path)?.is_some_and(|metadata| metadata.is_dir())) +} + +fn secure_regular_file(root: &Path, path: &Path) -> Result> { + Ok(secure_path_metadata(root, path)?.filter(|metadata| metadata.file_type().is_file())) +} + +pub(crate) fn is_safe_authored_file(root: &Path, path: &Path) -> bool { + secure_regular_file(root, path).is_ok_and(|metadata| metadata.is_some()) +} + +fn secure_path_metadata(root: &Path, path: &Path) -> Result> { + let Ok(relative) = path.strip_prefix(root) else { + return Ok(None); + }; + if relative + .components() + .any(|component| !matches!(component, Component::Normal(_))) + { + return Ok(None); + } + + let mut candidate = root.to_path_buf(); + let mut metadata = fs::symlink_metadata(root) + .with_context(|| format!("failed to inspect project root {}", root.display()))?; + for component in relative.components() { + candidate.push(component.as_os_str()); + metadata = match fs::symlink_metadata(&candidate) { + Ok(metadata) => metadata, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None), + Err(error) => return Err(error).context("failed to inspect a project path"), + }; + if metadata.file_type().is_symlink() { + return Ok(None); + } + } + + let canonical = candidate + .canonicalize() + .context("failed to prove project path containment")?; + if !canonical.starts_with(root) || canonical != candidate { + return Ok(None); + } + Ok(Some(metadata)) +} + struct IndexBuilder<'a> { root: &'a Path, + documents: &'a BTreeMap, parsed: &'a BTreeMap, symbols: Vec, references: Vec, + diagnostics: Vec, } impl IndexBuilder<'_> { @@ -517,7 +624,7 @@ impl IndexBuilder<'_> { .get("registry") .and_then(|registry| registry.get_scalar("id")) { - self.add_symbol( + self.add_resolvable_symbol( SymbolKey::global(RegistrySymbolKind::Registry, ®istry_id.value), None, path, @@ -545,7 +652,7 @@ impl IndexBuilder<'_> { }; for service in services { let service_name = service.key.value.clone(); - self.add_symbol( + self.add_resolvable_symbol( SymbolKey::global(RegistrySymbolKind::Service, &service_name), None, path, @@ -566,7 +673,7 @@ impl IndexBuilder<'_> { .and_then(YamlValue::as_mapping) { for consultation in consultations { - self.add_symbol( + self.add_resolvable_symbol( SymbolKey::scoped( RegistrySymbolKind::Consultation, &service_name, @@ -591,7 +698,7 @@ impl IndexBuilder<'_> { if let Some(claims) = service.value.get("claims").and_then(YamlValue::as_mapping) { for claim in claims { - self.add_symbol( + self.add_resolvable_symbol( SymbolKey::scoped( RegistrySymbolKind::Claim, &service_name, @@ -623,7 +730,7 @@ impl IndexBuilder<'_> { .and_then(YamlValue::as_mapping) { for profile in profiles { - self.add_symbol( + self.add_resolvable_symbol( SymbolKey::scoped( RegistrySymbolKind::CredentialProfile, &service_name, @@ -666,10 +773,9 @@ impl IndexBuilder<'_> { }; for alias in aliases { let key = SymbolKey::global(kind, &alias.key.value); - let definition_path = alias - .value - .get_scalar("file") - .and_then(|file| safe_join(self.root, &file.value)); + let file = alias.value.get_scalar("file"); + let definition_path = + file.and_then(|file| safe_definition_path(self.root, &file.value, kind)); let external_id = definition_path .as_ref() .and_then(|path| self.parsed.get(path).map(|document| (path, document))) @@ -677,14 +783,42 @@ impl IndexBuilder<'_> { if let Some((path, id)) = external_id { claimed_definition_files.insert(path.clone()); - self.add_symbol(key, None, path, id.range); + self.add_resolvable_symbol(key, None, path, id.range); self.add_reference( SymbolQuery::global(kind, &alias.key.value), manifest_path, alias.key.range, ); - } else { - self.add_symbol(key, None, manifest_path, alias.key.range); + continue; + } + + let problem = match (file, definition_path.as_ref()) { + (None, _) => "does not declare a file", + (Some(_), None) => "declares a file outside the supported project layout", + (Some(_), Some(path)) if self.parsed.contains_key(path) => { + "targets a document without a scalar id" + } + (Some(_), Some(path)) if self.documents.contains_key(path) => { + "targets invalid YAML" + } + (Some(_), Some(_)) => { + "targets a missing, unreadable, unsafe, oversized, or non-UTF-8 file" + } + }; + self.diagnostics.push(IndexedDiagnostic { + path: manifest_path.to_path_buf(), + range: file.map_or(alias.key.range, |file| file.range), + severity: DiagnosticSeverity::ERROR, + message: format!( + "Declared {} '{}' {problem}; use a regular UTF-8 YAML file inside the documented project layout", + kind.label(), + bounded_value(&alias.key.value), + ), + }); + if let Some(path) = definition_path { + if self.parsed.contains_key(&path) { + claimed_definition_files.insert(path); + } } } } @@ -696,13 +830,13 @@ impl IndexBuilder<'_> { kind: RegistrySymbolKind, ) { if let Some(id) = document.get_scalar("id") { - self.add_symbol(SymbolKey::global(kind, &id.value), None, path, id.range); + self.add_non_resolving_symbol(SymbolKey::global(kind, &id.value), None, path, id.range); } } fn extract_fixture(&mut self, path: &Path, document: &YamlValue) { if let Some(name) = document.get_scalar("name") { - self.add_symbol( + self.add_resolvable_symbol( SymbolKey::global(RegistrySymbolKind::Fixture, &name.value), None, path, @@ -727,7 +861,7 @@ impl IndexBuilder<'_> { fn extract_environment(&mut self, path: &Path, relative: &Path, document: &YamlValue) { if let Some(name) = relative.file_stem().and_then(|name| name.to_str()) { let range = Range::new(Position::new(0, 0), Position::new(0, 0)); - self.add_symbol( + self.add_resolvable_symbol( SymbolKey::global(RegistrySymbolKind::Environment, name), None, path, @@ -750,12 +884,33 @@ impl IndexBuilder<'_> { } } + fn add_resolvable_symbol( + &mut self, + key: SymbolKey, + container_name: Option, + path: &Path, + range: Range, + ) { + self.add_symbol(key, container_name, path, range, true); + } + + fn add_non_resolving_symbol( + &mut self, + key: SymbolKey, + container_name: Option, + path: &Path, + range: Range, + ) { + self.add_symbol(key, container_name, path, range, false); + } + fn add_symbol( &mut self, key: SymbolKey, container_name: Option, path: &Path, range: Range, + resolvable: bool, ) { self.symbols.push(IndexedSymbol { name: key.name.clone(), @@ -766,6 +921,7 @@ impl IndexBuilder<'_> { range, }, key, + resolvable, }); } @@ -986,7 +1142,7 @@ impl<'a> SourceMap<'a> { } } -fn safe_join(root: &Path, relative: &str) -> Option { +fn safe_definition_path(root: &Path, relative: &str, kind: RegistrySymbolKind) -> Option { let path = Path::new(relative); if path.is_absolute() || path @@ -995,7 +1151,48 @@ fn safe_join(root: &Path, relative: &str) -> Option { { return None; } - Some(root.join(path)) + let candidate = root.join(path); + let supported = match kind { + RegistrySymbolKind::Integration => is_integration_path(path), + RegistrySymbolKind::Entity => is_entity_path(path), + _ => false, + }; + supported.then_some(candidate) +} + +pub(crate) fn document_diagnostic(path: &Path, message: &str) -> IndexedDiagnostic { + IndexedDiagnostic { + path: path.to_path_buf(), + range: Range::new(Position::new(0, 0), Position::new(0, 0)), + severity: DiagnosticSeverity::ERROR, + message: message.to_owned(), + } +} + +fn diagnostic_cmp(left: &IndexedDiagnostic, right: &IndexedDiagnostic) -> std::cmp::Ordering { + left.path + .cmp(&right.path) + .then_with(|| range_cmp(left.range, right.range)) + .then_with(|| left.message.cmp(&right.message)) +} + +fn bounded_value(value: &str) -> String { + const MAX_CHARS: usize = 120; + let mut bounded = value + .chars() + .take(MAX_CHARS) + .map(|character| { + if character.is_control() { + '�' + } else { + character + } + }) + .collect::(); + if value.chars().count() > MAX_CHARS { + bounded.push('…'); + } + bounded } fn normalize_lookup_path(path: &Path) -> PathBuf { @@ -1173,6 +1370,8 @@ services: shared: { cel: true } shared: { cel: false } broken: { output: absent.value } + credential_profiles: + broken: { claims: [absent-claim] } second: claims: shared: { cel: true } @@ -1181,7 +1380,7 @@ services: write( temp.path(), "integrations/people/fixtures/active.yaml", - "name: active-person\nexpect: { claims: { shared: true } }\n", + "name: active-person\nexpect: { claims: { shared: true, absent-fixture-claim: true } }\n", ); let index = ProjectIndex::load(temp.path()).unwrap(); let messages = index @@ -1202,6 +1401,144 @@ services: assert!(messages .iter() .any(|message| message.contains("Unknown consultation"))); + assert!(messages + .iter() + .any(|message| message.contains("Unknown claim reference 'absent-claim'"))); + assert!(messages + .iter() + .any(|message| message.contains("Unknown claim reference 'absent-fixture-claim'"))); + } + + #[test] + fn orphan_files_never_satisfy_manifest_or_environment_references() { + let temp = TempDir::new().unwrap(); + write( + temp.path(), + PROJECT_FILE, + r#"version: 1 +registry: { id: demo } +services: + evidence: + consultations: + lookup: { integration: orphan-integration } + records: + entity: orphan-entity +"#, + ); + write( + temp.path(), + "integrations/orphan/integration.yaml", + "version: 1\nid: orphan-integration\n", + ); + write( + temp.path(), + "entities/orphan.yaml", + "version: 1\nid: orphan-entity\n", + ); + write( + temp.path(), + "environments/local.yaml", + "version: 1\nintegrations: { orphan-integration: {} }\nentities: { orphan-entity: {} }\n", + ); + + let index = ProjectIndex::load(temp.path()).unwrap(); + let messages = index + .diagnostics() + .iter() + .map(|diagnostic| diagnostic.message.as_str()) + .collect::>(); + assert_eq!( + messages + .iter() + .filter(|message| message.contains("Unknown integration reference")) + .count(), + 2 + ); + assert_eq!( + messages + .iter() + .filter(|message| message.contains("Unknown entity reference")) + .count(), + 2 + ); + assert!(index + .workspace_symbols("orphan-integration") + .iter() + .any(|symbol| symbol.location.path.ends_with("integration.yaml"))); + assert!(index + .workspace_symbols("orphan-entity") + .iter() + .any(|symbol| symbol.location.path.ends_with("orphan.yaml"))); + assert!(index + .definitions_at(&temp.path().join(PROJECT_FILE), Position::new(6, 38),) + .is_empty()); + assert!(index + .references_at( + &temp.path().join("integrations/orphan/integration.yaml"), + Position::new(1, 5), + true, + ) + .is_empty()); + } + + #[test] + fn declared_alias_targets_must_be_valid_indexable_documents() { + let temp = TempDir::new().unwrap(); + write( + temp.path(), + PROJECT_FILE, + r#"version: 1 +registry: { id: demo } +integrations: + missing: { file: integrations/missing/integration.yaml } + malformed: { file: integrations/malformed/integration.yaml } + non-utf8: { file: integrations/non-utf8/integration.yaml } +entities: + missing-entity: { file: entities/missing.yaml } +services: + evidence: + consultations: + one: { integration: missing } + two: { integration: malformed } + three: { integration: non-utf8 } + records: + entity: missing-entity +"#, + ); + write( + temp.path(), + "integrations/malformed/integration.yaml", + "id: [\n", + ); + let non_utf8 = temp.path().join("integrations/non-utf8/integration.yaml"); + fs::create_dir_all(non_utf8.parent().unwrap()).unwrap(); + fs::write(&non_utf8, [0xff, 0xfe]).unwrap(); + + let index = ProjectIndex::load(temp.path()).unwrap(); + for alias in ["missing", "malformed", "non-utf8"] { + assert!(index.diagnostics().iter().any(|diagnostic| { + diagnostic.message.contains("Declared integration") + && diagnostic.message.contains(alias) + })); + assert!(index.diagnostics().iter().any(|diagnostic| { + diagnostic + .message + .contains(&format!("Unknown integration reference '{alias}'")) + })); + } + assert!(index.diagnostics().iter().any(|diagnostic| diagnostic + .message + .contains("Declared entity 'missing-entity'"))); + assert!(index.diagnostics().iter().any(|diagnostic| diagnostic + .message + .contains("Unknown entity reference 'missing-entity'"))); + assert!(index.diagnostics().iter().any(|diagnostic| diagnostic + .message + .contains("Project document is not valid UTF-8"))); + assert!(index + .diagnostics() + .iter() + .any(|diagnostic| diagnostic.message.contains("Invalid YAML syntax"))); } #[test] @@ -1257,4 +1594,164 @@ services: ); } } + + #[test] + fn maintained_authoring_catalog_workspaces_have_no_reference_diagnostics() { + let repository_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let catalog_path = repository_root + .join("crates/registryctl/tests/fixtures/project-authoring-journeys.yaml"); + let catalog = parse_yaml(&fs::read_to_string(catalog_path).unwrap()).unwrap(); + let workspaces = catalog + .get("workspaces") + .and_then(YamlValue::as_sequence) + .unwrap(); + let mut maintained = 0; + for workspace in workspaces { + if workspace + .get_scalar("classification") + .is_none_or(|classification| classification.value != "maintained") + { + continue; + } + maintained += 1; + let id = &workspace.get_scalar("id").unwrap().value; + let source = &workspace.get_scalar("source").unwrap().value; + let index = ProjectIndex::load(&repository_root.join(source)).unwrap(); + let reference_diagnostics = index + .diagnostics() + .iter() + .filter(|diagnostic| { + diagnostic.message.starts_with("Unknown ") + || diagnostic.message.starts_with("Ambiguous ") + }) + .collect::>(); + assert!( + reference_diagnostics.is_empty(), + "{id} has false reference diagnostics: {reference_diagnostics:?}" + ); + } + assert_eq!(maintained, 12, "catalog maintenance coverage changed"); + } + + #[cfg(unix)] + #[test] + fn rejects_symlinks_at_every_authored_directory_layer() { + use std::os::unix::fs::symlink; + + let temp = TempDir::new().unwrap(); + let outside = TempDir::new().unwrap(); + write( + temp.path(), + PROJECT_FILE, + r#"version: 1 +registry: { id: demo } +integrations: + linked-file: { file: integrations/real/integration.yaml } +services: + check: + consultations: + linked: { integration: linked-file } +"#, + ); + + write(outside.path(), "entity.yaml", "id: outside-entity\n"); + write( + outside.path(), + "environment.yaml", + "id: outside-environment\n", + ); + write( + outside.path(), + "integration.yaml", + "id: outside-integration\n", + ); + write(outside.path(), "fixture.yaml", "name: outside-fixture\n"); + + fs::create_dir_all(temp.path().join("entities")).unwrap(); + symlink( + outside.path().join("entity.yaml"), + temp.path().join("entities/linked.yaml"), + ) + .unwrap(); + fs::create_dir_all(temp.path().join("environments")).unwrap(); + symlink( + outside.path().join("environment.yaml"), + temp.path().join("environments/linked.yaml"), + ) + .unwrap(); + fs::create_dir_all(temp.path().join("integrations/real/fixtures")).unwrap(); + symlink( + outside.path().join("integration.yaml"), + temp.path().join("integrations/real/integration.yaml"), + ) + .unwrap(); + symlink( + outside.path().join("fixture.yaml"), + temp.path().join("integrations/real/fixtures/linked.yaml"), + ) + .unwrap(); + + let index = ProjectIndex::load(temp.path()).unwrap(); + for outside_name in [ + "outside-entity", + "outside-environment", + "outside-integration", + "outside-fixture", + ] { + assert!(index.workspace_symbols(outside_name).is_empty()); + } + assert!(index.diagnostics().iter().any(|diagnostic| diagnostic + .message + .contains("Declared integration 'linked-file'"))); + + let nested_project = TempDir::new().unwrap(); + write( + nested_project.path(), + PROJECT_FILE, + "version: 1\nregistry: { id: nested }\nservices: {}\n", + ); + symlink(outside.path(), nested_project.path().join("entities")).unwrap(); + symlink(outside.path(), nested_project.path().join("environments")).unwrap(); + symlink(outside.path(), nested_project.path().join("integrations")).unwrap(); + let nested_index = ProjectIndex::load(nested_project.path()).unwrap(); + assert_eq!(nested_index.symbols().len(), 1); + + let integration_directory_project = TempDir::new().unwrap(); + write( + integration_directory_project.path(), + PROJECT_FILE, + "version: 1\nregistry: { id: nested-integration }\nservices: {}\n", + ); + fs::create_dir(integration_directory_project.path().join("integrations")).unwrap(); + symlink( + outside.path(), + integration_directory_project + .path() + .join("integrations/linked"), + ) + .unwrap(); + let nested_index = ProjectIndex::load(integration_directory_project.path()).unwrap(); + assert_eq!(nested_index.symbols().len(), 1); + + let fixture_directory_project = TempDir::new().unwrap(); + write( + fixture_directory_project.path(), + PROJECT_FILE, + "version: 1\nregistry: { id: nested-fixture }\nservices: {}\n", + ); + write( + fixture_directory_project.path(), + "integrations/real/integration.yaml", + "id: unclaimed\n", + ); + symlink( + outside.path(), + fixture_directory_project + .path() + .join("integrations/real/fixtures"), + ) + .unwrap(); + let nested_index = ProjectIndex::load(fixture_directory_project.path()).unwrap(); + assert!(nested_index.workspace_symbols("outside-fixture").is_empty()); + } } diff --git a/crates/registry-language-server/src/server.rs b/crates/registry-language-server/src/server.rs index b3cccb57a..4d5886d14 100644 --- a/crates/registry-language-server/src/server.rs +++ b/crates/registry-language-server/src/server.rs @@ -25,8 +25,8 @@ use tower_lsp_server::{ }; use crate::index::{ - is_project_document, is_valid_yaml, load_project_documents, IndexedLocation, IndexedSymbol, - ProjectIndex, + document_diagnostic, is_project_document, is_safe_authored_file, is_valid_yaml, + load_project_documents, IndexedDiagnostic, IndexedLocation, IndexedSymbol, ProjectIndex, }; const SERVER_NAME: &str = "Registry Stack Language Server"; @@ -36,6 +36,7 @@ const MAX_DOCUMENT_BYTES: usize = 1024 * 1024; pub struct Backend { client: Client, state: RwLock>, + load_error: RwLock>, published_paths: Mutex>, supports_dynamic_file_watching: AtomicBool, } @@ -45,6 +46,7 @@ impl Backend { Self { client, state: RwLock::new(None), + load_error: RwLock::new(None), published_paths: Mutex::new(BTreeSet::new()), supports_dynamic_file_watching: AtomicBool::new(false), } @@ -97,13 +99,26 @@ impl Backend { async fn update_document(&self, path: PathBuf, text: String, version: i32) { let path = normalize_document_path(&path); let mut state = self.state.write().await; + let mut load_error = None; if state.is_none() { - *state = find_project_root(&path).and_then(|root| WorkspaceState::load(&root).ok()); + if let Some(root) = find_project_root(&path) { + match WorkspaceState::load(&root) { + Ok(loaded) => { + *state = Some(loaded); + *self.load_error.write().await = None; + } + Err(error) => load_error = Some(bounded_load_error(&error)), + } + } } if let Some(state) = state.as_mut() { state.update(path, text, version); } drop(state); + if let Some(error) = load_error { + *self.load_error.write().await = Some(error.clone()); + self.client.log_message(MessageType::ERROR, error).await; + } self.publish_diagnostics().await; } @@ -125,11 +140,17 @@ impl Backend { paths.sort(); paths.dedup(); let mut state = self.state.write().await; + let mut load_error = None; if state.is_none() { - *state = paths - .iter() - .find_map(|path| find_project_root(path)) - .and_then(|root| WorkspaceState::load(&root).ok()); + if let Some(root) = paths.iter().find_map(|path| find_project_root(path)) { + match WorkspaceState::load(&root) { + Ok(loaded) => { + *state = Some(loaded); + *self.load_error.write().await = None; + } + Err(error) => load_error = Some(bounded_load_error(&error)), + } + } } if let Some(state) = state.as_mut() { for path in paths { @@ -137,6 +158,10 @@ impl Backend { } } drop(state); + if let Some(error) = load_error { + *self.load_error.write().await = Some(error.clone()); + self.client.log_message(MessageType::ERROR, error).await; + } self.publish_diagnostics().await; } } @@ -153,7 +178,13 @@ impl LanguageServer for Backend { self.supports_dynamic_file_watching .store(supports_dynamic_file_watching, Ordering::Relaxed); if let Some(root) = project_root_from_initialize(¶ms) { - *self.state.write().await = WorkspaceState::load(&root).ok(); + match WorkspaceState::load(&root) { + Ok(state) => { + *self.state.write().await = Some(state); + *self.load_error.write().await = None; + } + Err(error) => *self.load_error.write().await = Some(bounded_load_error(&error)), + } } Ok(InitializeResult { @@ -213,21 +244,26 @@ impl LanguageServer for Backend { } } - let message = { + let (message_type, message) = { let state = self.state.read().await; - state - .as_ref() - .map(|state| { + if let Some(state) = state.as_ref() { + ( + MessageType::INFO, format!( "Registry Stack project indexed at {}", state.index.root().display() - ) - }) - .unwrap_or_else(|| { - "No registry-stack.yaml project found in the workspace".to_owned() - }) + ), + ) + } else if let Some(error) = self.load_error.read().await.clone() { + (MessageType::ERROR, error) + } else { + ( + MessageType::INFO, + "No registry-stack.yaml project found in the workspace".to_owned(), + ) + } }; - self.client.log_message(MessageType::INFO, message).await; + self.client.log_message(message_type, message).await; self.publish_diagnostics().await; } @@ -263,22 +299,22 @@ impl LanguageServer for Backend { } async fn did_save(&self, params: DidSaveTextDocumentParams) { + let Some(text) = params.text else { + return; + }; let Some(path) = params.text_document.uri.to_file_path() else { return; }; let path = path.into_owned(); - let text = params.text.or_else(|| fs::read_to_string(&path).ok()); - if let Some(text) = text { - let version = { - let state = self.state.read().await; - state - .as_ref() - .and_then(|state| state.open_versions.get(&normalize_document_path(&path))) - .copied() - .unwrap_or(0) - }; - self.update_document(path, text, version).await; - } + let version = { + let state = self.state.read().await; + state + .as_ref() + .and_then(|state| state.open_versions.get(&normalize_document_path(&path))) + .copied() + .unwrap_or(0) + }; + self.update_document(path, text, version).await; } async fn did_close(&self, params: DidCloseTextDocumentParams) { @@ -396,18 +432,24 @@ struct WorkspaceState { root: PathBuf, documents: BTreeMap, open_versions: BTreeMap, + disk_diagnostics: Vec, index: ProjectIndex, } impl WorkspaceState { fn load(root: &Path) -> anyhow::Result { let root = root.canonicalize()?; - let documents = load_project_documents(&root)?; - let index = ProjectIndex::from_documents(&root, &documents); + let loaded = load_project_documents(&root)?; + let index = ProjectIndex::from_documents_with_diagnostics( + &root, + &loaded.documents, + loaded.diagnostics.clone(), + ); Ok(Self { root, - documents, + documents: loaded.documents, open_versions: BTreeMap::new(), + disk_diagnostics: loaded.diagnostics, index, }) } @@ -418,6 +460,8 @@ impl WorkspaceState { } self.open_versions.insert(path.clone(), version); if text.len() <= MAX_DOCUMENT_BYTES && is_valid_yaml(&text) { + self.disk_diagnostics + .retain(|diagnostic| diagnostic.path != path); self.documents.insert(path, text); self.rebuild(); } @@ -435,33 +479,76 @@ impl WorkspaceState { if !is_project_document(&self.root, path) || self.open_versions.contains_key(path) { return; } - match fs::symlink_metadata(path) { - Ok(metadata) - if metadata.file_type().is_file() - && metadata.len() <= MAX_DOCUMENT_BYTES as u64 => - { - if let Ok(text) = fs::read_to_string(path) { - if is_valid_yaml(&text) { - self.documents.insert(path.to_path_buf(), text); - } - } - } - Ok(_) => { + self.disk_diagnostics + .retain(|diagnostic| diagnostic.path != path); + if !is_safe_authored_file(&self.root, path) { + self.documents.remove(path); + self.rebuild(); + return; + } + match fs::read(path) { + Ok(bytes) if bytes.len() > MAX_DOCUMENT_BYTES => { self.documents.remove(path); + self.disk_diagnostics.push(document_diagnostic( + path, + "Project document exceeds the 1 MiB indexing limit", + )); } - Err(error) if error.kind() == std::io::ErrorKind::NotFound => { + Ok(bytes) => match String::from_utf8(bytes) { + Ok(text) if is_valid_yaml(&text) => { + self.documents.insert(path.to_path_buf(), text); + } + Ok(_) => { + self.documents.remove(path); + self.disk_diagnostics.push(document_diagnostic( + path, + "Invalid YAML syntax; fix this project document before it can be indexed", + )); + } + Err(_) => { + self.documents.remove(path); + self.disk_diagnostics.push(document_diagnostic( + path, + "Project document is not valid UTF-8 and cannot be indexed", + )); + } + }, + Err(_) => { self.documents.remove(path); + self.disk_diagnostics.push(document_diagnostic( + path, + "Project document could not be read; check its permissions", + )); } - Err(_) => {} } self.rebuild(); } fn rebuild(&mut self) { - self.index = ProjectIndex::from_documents(&self.root, &self.documents); + self.index = ProjectIndex::from_documents_with_diagnostics( + &self.root, + &self.documents, + self.disk_diagnostics.clone(), + ); } } +fn bounded_load_error(error: &anyhow::Error) -> String { + const MAX_CHARS: usize = 500; + let detail = format!("{error:#}") + .chars() + .take(MAX_CHARS) + .map(|character| { + if character.is_control() { + ' ' + } else { + character + } + }) + .collect::(); + format!("Could not index Registry Stack project: {detail}") +} + fn project_root_from_initialize(params: &InitializeParams) -> Option { if let Some(folders) = params.workspace_folders.as_ref() { for folder in folders { diff --git a/crates/registry-language-server/tests/protocol.rs b/crates/registry-language-server/tests/protocol.rs index 0d98e58eb..170b8fc04 100644 --- a/crates/registry-language-server/tests/protocol.rs +++ b/crates/registry-language-server/tests/protocol.rs @@ -73,6 +73,16 @@ fn receive_response(stdout: &mut BufReader, id: i64) -> Value { panic!("language server did not return response {id}"); } +fn receive_method(stdout: &mut BufReader, method: &str) -> Value { + for _ in 0..50 { + let message = receive(stdout); + if message.get("method").and_then(Value::as_str) == Some(method) { + return message; + } + } + panic!("language server did not send {method}"); +} + #[test] fn serves_definition_references_and_workspace_symbols_over_stdio() { let project = write_project(); @@ -282,3 +292,369 @@ fn serves_definition_references_and_workspace_symbols_over_stdio() { drop(stdin); assert!(child.wait().unwrap().success()); } + +#[test] +fn reports_initial_and_lazy_project_load_failures_over_lsp() { + for lazy in [false, true] { + let project = TempDir::new().unwrap(); + let manifest = project.path().join("registry-stack.yaml"); + if !lazy { + fs::write(&manifest, [0xff, 0xfe]).unwrap(); + } + let root_uri = Uri::from_file_path(project.path()).unwrap().to_string(); + let manifest_uri = Uri::from_file_path(&manifest).unwrap().to_string(); + let mut child = Command::new(env!("CARGO_BIN_EXE_registry-language-server")) + .current_dir(project.path()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + let mut stdin = child.stdin.take().unwrap(); + let mut stdout = BufReader::new(child.stdout.take().unwrap()); + + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "processId": null, + "rootUri": root_uri, + "capabilities": {}, + "workspaceFolders": [{ "uri": root_uri, "name": "broken" }] + } + }), + ); + receive_response(&mut stdout, 1); + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "method": "initialized", "params": {} }), + ); + + if lazy { + let initial_log = receive_method(&mut stdout, "window/logMessage"); + assert_eq!( + initial_log + .pointer("/params/message") + .and_then(Value::as_str), + Some("No registry-stack.yaml project found in the workspace") + ); + fs::write(&manifest, [0xff, 0xfe]).unwrap(); + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "method": "textDocument/didOpen", + "params": { + "textDocument": { + "uri": manifest_uri, + "languageId": "yaml", + "version": 1, + "text": "version: 1\nregistry: { id: unsaved }\nservices: {}\n" + } + } + }), + ); + } + + let error_log = receive_method(&mut stdout, "window/logMessage"); + assert_eq!( + error_log.pointer("/params/type").and_then(Value::as_i64), + Some(1), + "{error_log}" + ); + let message = error_log + .pointer("/params/message") + .and_then(Value::as_str) + .unwrap(); + assert!(message.starts_with("Could not index Registry Stack project:")); + assert!(!message.contains("No registry-stack.yaml project found")); + assert!( + message.len() <= 560, + "load error was not bounded: {message}" + ); + + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "id": 2, "method": "shutdown", "params": null }), + ); + receive_response(&mut stdout, 2); + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "method": "exit", "params": null }), + ); + drop(stdin); + assert!(child.wait().unwrap().success()); + } +} + +#[test] +fn publishes_malformed_project_document_diagnostics() { + let project = TempDir::new().unwrap(); + let manifest = project.path().join("registry-stack.yaml"); + fs::write(&manifest, "registry: [\n").unwrap(); + let root_uri = Uri::from_file_path(project.path()).unwrap().to_string(); + let manifest_uri = Uri::from_file_path(manifest.canonicalize().unwrap()) + .unwrap() + .to_string(); + let mut child = Command::new(env!("CARGO_BIN_EXE_registry-language-server")) + .current_dir(project.path()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + let mut stdin = child.stdin.take().unwrap(); + let mut stdout = BufReader::new(child.stdout.take().unwrap()); + + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "processId": null, + "rootUri": root_uri, + "capabilities": {}, + "workspaceFolders": [{ "uri": root_uri, "name": "malformed" }] + } + }), + ); + receive_response(&mut stdout, 1); + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "method": "initialized", "params": {} }), + ); + let diagnostics = receive_method(&mut stdout, "textDocument/publishDiagnostics"); + assert_eq!( + diagnostics.pointer("/params/uri").and_then(Value::as_str), + Some(manifest_uri.as_str()) + ); + assert!(diagnostics + .pointer("/params/diagnostics") + .and_then(Value::as_array) + .is_some_and(|diagnostics| diagnostics.iter().any(|diagnostic| diagnostic + .pointer("/message") + .and_then(Value::as_str) + .is_some_and(|message| message.contains("Invalid YAML syntax"))))); + + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "id": 2, "method": "shutdown", "params": null }), + ); + receive_response(&mut stdout, 2); + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "method": "exit", "params": null }), + ); + drop(stdin); + assert!(child.wait().unwrap().success()); +} + +#[cfg(unix)] +#[test] +fn did_save_only_indexes_included_text_and_never_reads_uri_paths() { + use std::{ + fs::FileTimes, + os::unix::fs::symlink, + time::{Duration, UNIX_EPOCH}, + }; + + fn reset_access_time(path: &std::path::Path) -> std::time::SystemTime { + let old = UNIX_EPOCH + Duration::from_secs(24 * 60 * 60); + fs::OpenOptions::new() + .write(true) + .open(path) + .unwrap() + .set_times(FileTimes::new().set_accessed(old)) + .unwrap(); + fs::metadata(path).unwrap().accessed().unwrap() + } + + let project = TempDir::new().unwrap(); + fs::write( + project.path().join("registry-stack.yaml"), + "version: 1\nregistry: { id: initial }\nservices: {}\n", + ) + .unwrap(); + fs::create_dir(project.path().join("entities")).unwrap(); + + let outside = TempDir::new().unwrap(); + let arbitrary_outside = outside.path().join("arbitrary.yaml"); + fs::write(&arbitrary_outside, "id: outside-save-content\n").unwrap(); + let symlink_target = outside.path().join("symlink-target.yaml"); + fs::write(&symlink_target, "id: symlink-save-content\n").unwrap(); + let symlink_path = project.path().join("entities/linked.yaml"); + symlink(&symlink_target, &symlink_path).unwrap(); + + let oversized_path = project.path().join("entities/oversized.yaml"); + let mut oversized = b"id: oversized-save-content\n".to_vec(); + oversized.resize(1024 * 1024 + 1, b' '); + fs::write(&oversized_path, oversized).unwrap(); + + let root_uri = Uri::from_file_path(project.path()).unwrap().to_string(); + let manifest_path = project + .path() + .join("registry-stack.yaml") + .canonicalize() + .unwrap(); + let manifest_uri = Uri::from_file_path(&manifest_path).unwrap().to_string(); + let mut child = Command::new(env!("CARGO_BIN_EXE_registry-language-server")) + .current_dir(project.path()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + let mut stdin = child.stdin.take().unwrap(); + let mut stdout = BufReader::new(child.stdout.take().unwrap()); + + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "processId": null, + "rootUri": root_uri, + "capabilities": {}, + "workspaceFolders": [{ "uri": root_uri, "name": "save-safety" }] + } + }), + ); + let initialize = receive_response(&mut stdout, 1); + assert_eq!( + initialize + .pointer("/result/capabilities/textDocumentSync/save/includeText") + .and_then(Value::as_bool), + Some(true) + ); + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "method": "initialized", "params": {} }), + ); + + let outside_accessed = reset_access_time(&arbitrary_outside); + let symlink_target_accessed = reset_access_time(&symlink_target); + let oversized_accessed = reset_access_time(&oversized_path); + for path in [&arbitrary_outside, &symlink_path, &oversized_path] { + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "method": "textDocument/didSave", + "params": { "textDocument": { "uri": Uri::from_file_path(path).unwrap().to_string() } } + }), + ); + } + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": 2, + "method": "workspace/symbol", + "params": { "query": "save-content" } + }), + ); + let symbols = receive_response(&mut stdout, 2); + assert_eq!( + symbols.pointer("/result").and_then(Value::as_array), + Some(&vec![]) + ); + std::thread::sleep(Duration::from_millis(50)); + assert_eq!( + fs::metadata(&arbitrary_outside) + .unwrap() + .accessed() + .unwrap(), + outside_accessed, + "didSave without text read an arbitrary outside URI" + ); + assert_eq!( + fs::metadata(&symlink_target).unwrap().accessed().unwrap(), + symlink_target_accessed, + "didSave without text followed a symlinked project-layout URI" + ); + assert_eq!( + fs::metadata(&oversized_path).unwrap().accessed().unwrap(), + oversized_accessed, + "didSave without text read an oversized project document" + ); + + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "method": "textDocument/didOpen", + "params": { + "textDocument": { + "uri": manifest_uri, + "languageId": "yaml", + "version": 7, + "text": "version: 1\nregistry: { id: initial }\nservices: {}\n" + } + } + }), + ); + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "method": "textDocument/didSave", + "params": { + "textDocument": { "uri": manifest_uri }, + "text": "version: 1\nregistry: { id: included-save-content }\nservices: {}\n" + } + }), + ); + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": 3, + "method": "workspace/symbol", + "params": { "query": "included-save-content" } + }), + ); + let symbols = receive_response(&mut stdout, 3); + assert_eq!( + symbols.pointer("/result/0/name").and_then(Value::as_str), + Some("included-save-content") + ); + + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "method": "textDocument/didClose", + "params": { "textDocument": { "uri": manifest_uri } } + }), + ); + send( + &mut stdin, + json!({ + "jsonrpc": "2.0", + "id": 4, + "method": "workspace/symbol", + "params": { "query": "initial" } + }), + ); + let reloaded = receive_response(&mut stdout, 4); + assert_eq!( + reloaded.pointer("/result/0/name").and_then(Value::as_str), + Some("initial") + ); + + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "id": 5, "method": "shutdown", "params": null }), + ); + receive_response(&mut stdout, 5); + send( + &mut stdin, + json!({ "jsonrpc": "2.0", "method": "exit", "params": null }), + ); + drop(stdin); + assert!(child.wait().unwrap().success()); +} diff --git a/crates/registryctl/CHANGELOG.md b/crates/registryctl/CHANGELOG.md index ddd4857ec..fc6dc5801 100644 --- a/crates/registryctl/CHANGELOG.md +++ b/crates/registryctl/CHANGELOG.md @@ -24,8 +24,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Zed workspace mappings. `registryctl authoring editor` verifies or safely refreshes the same version-matched setup for an existing project. - `registryctl authoring language-server` now provides bounded cross-file definitions, references, - symbols, and reference diagnostics for Registry Stack project YAML. Thin source extensions for - VS Code and Zed launch the same server alongside their existing YAML language servers. + symbols, and reference diagnostics for Registry Stack project YAML. Optional, source-installable + developer previews for VS Code and Zed launch the same server alongside their existing YAML + language servers. They are not marketplace extensions or release artifacts; generated editor + schema setup remains the stable beta path for YAML validation, completion, hover, and formatting. ### Changed diff --git a/docs/site/src/content/docs/reference/registryctl.mdx b/docs/site/src/content/docs/reference/registryctl.mdx index c9397ba36..2a74954a2 100644 --- a/docs/site/src/content/docs/reference/registryctl.mdx +++ b/docs/site/src/content/docs/reference/registryctl.mdx @@ -134,12 +134,18 @@ The generated `.vscode/settings.json` and `.zed/settings.json` reserve the five table. Open the Registry Stack project directory as the editor root. In VS Code, install or accept the workspace recommendation for Red Hat YAML. Zed includes YAML language-server support. -The source distribution also contains Registry Stack extensions for VS Code and Zed. Both launch -the same semantic server, either as `registry-language-server` or through -`registryctl authoring language-server`. The extensions add cross-file definitions, references, -workspace and document symbols, and missing, duplicate, or ambiguous reference diagnostics. They -run alongside the YAML language server, which continues to own syntax, schemas, completion, hover, -and formatting. +The generated schema setup is the stable beta editor path. +It provides YAML validation, completion, hover, and formatting without a source checkout. + +VS Code and Zed semantic navigation are optional, source-installable developer previews for this +beta. They are not marketplace extensions and are not available as release assets. +Build and install them from the Registry Stack source tree only when you want cross-file +definitions, references, workspace and document symbols, and missing, duplicate, or ambiguous +reference diagnostics. +Both previews launch the same semantic server, either as `registry-language-server` or through +`registryctl authoring language-server`, alongside the YAML language server. +The YAML language server continues to own syntax, schemas, completion, hover, and formatting. +Follow `editors/vscode/README.md` or `editors/zed/README.md` in the same source checkout. The shared YAML language server interprets configured paths as suffix matches at any depth. A file such as `notes.yaml` keeps its normal schema detection and formatting, but a nested copy such as diff --git a/editors/README.md b/editors/README.md index 96cb4b73c..b16838fe7 100644 --- a/editors/README.md +++ b/editors/README.md @@ -1,5 +1,11 @@ # Registry Stack editor integrations +VS Code and Zed semantic navigation are source-installable developer previews for this beta. +They are not marketplace extensions or release assets. +Use `registryctl init --from ` and its generated editor schema setup as the stable beta +path for YAML validation, completion, hover, and formatting. +Install an editor preview only when you also want optional cross-file semantic navigation. + The Registry Stack editor support is split into one reusable language server and thin editor launchers: @@ -8,9 +14,9 @@ launchers: - `vscode` launches the server through VS Code's language-client API. - `zed` launches the same server through Zed's extension API. -These integrations intentionally run alongside each editor's YAML language server. The generated -`.vscode/settings.json` and `.zed/settings.json` files continue to provide version-matched schema -validation and YAML completion without duplicating that behavior here. +These integrations intentionally run alongside each editor's YAML language server. +The generated `.vscode/settings.json` and `.zed/settings.json` files continue to provide +version-matched schema validation and YAML completion without duplicating that behavior here. The language server watches Registry Stack YAML paths for changes made by generators, Git, or other tools. An open editor buffer remains authoritative until it is closed, so a filesystem event @@ -33,8 +39,8 @@ cargo run --locked -p registryctl -- init --from http --project-dir "$REGISTRY_S Keep that terminal open so the three variables remain available. Then follow the editor-specific installation and launch instructions: -- [VS Code](vscode/README.md#package-install-and-launch) -- [Zed](zed/README.md#install-and-launch) +- [VS Code developer preview](vscode/README.md#package-install-and-launch) +- [Zed developer preview](zed/README.md#install-and-launch) ### Expected behavior diff --git a/editors/vscode/.vscode-test.cjs b/editors/vscode/.vscode-test.cjs index 39e137564..84472542d 100644 --- a/editors/vscode/.vscode-test.cjs +++ b/editors/vscode/.vscode-test.cjs @@ -7,6 +7,7 @@ const path = require('node:path'); const { defineConfig } = require('@vscode/test-cli'); const testRunDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'registry-stack-vscode-')); +const trustedUserData = path.join(testRunDirectory, 'trusted-user-data'); const projectAlpha = path.join(testRunDirectory, 'project-alpha'); const projectBeta = path.join(testRunDirectory, 'project-beta'); const workspaceFolder = path.join(testRunDirectory, 'multi-root.code-workspace'); @@ -40,10 +41,6 @@ module.exports = defineConfig({ files: 'out/test/trusted.test.js', version: '1.91.1', workspaceFolder, - launchArgs: [ - '--disable-extensions', - '--user-data-dir', - path.join(testRunDirectory, 'trusted'), - ], + launchArgs: ['--disable-extensions', '--user-data-dir', trustedUserData], mocha: { timeout: 30_000 }, }); diff --git a/editors/vscode/.vscodeignore b/editors/vscode/.vscodeignore index 6fb7ecf47..93eca0c4f 100644 --- a/editors/vscode/.vscodeignore +++ b/editors/vscode/.vscodeignore @@ -4,6 +4,7 @@ package-lock.json src/** test/** +scripts/** out/** tsconfig.json tsconfig.test.json diff --git a/editors/vscode/README.md b/editors/vscode/README.md index 63b1136bf..881caab22 100644 --- a/editors/vscode/README.md +++ b/editors/vscode/README.md @@ -1,5 +1,11 @@ # Registry Stack for VS Code +This is a source-installable developer preview for the current beta. +It is not published to the VS Code Marketplace and no release VSIX is provided. +For the stable beta path, run `registryctl init --from ` or +`registryctl authoring editor --project-dir ` and use the generated YAML schema settings. +Install this preview only for optional semantic navigation. + This extension starts `registry-language-server` for a workspace whose root contains `registry-stack.yaml`. It adds cross-file definitions, references, workspace/document symbols, and Registry Stack reference diagnostics. Red Hat YAML remains responsible for YAML syntax, @@ -24,8 +30,9 @@ repository. First complete the [shared smoke-project setup](../README.md#local-e code --install-extension ./registry-stack-dev.vsix --force ``` - `package:dev` type-checks the source, bundles its runtime dependencies, and creates an - installable VSIX. The explicit install affects the active VS Code profile. + `package:dev` type-checks the source, bundles its runtime dependencies into + `dist/extension.js`, and verifies that the VSIX contains no external `node_modules` runtime. + The explicit install affects the active VS Code profile. 2. Open the smoke project as the workspace root: @@ -33,9 +40,10 @@ repository. First complete the [shared smoke-project setup](../README.md#local-e code --new-window "$REGISTRY_STACK_SMOKE_PROJECT" ``` -3. Run **Preferences: Open Workspace Settings (JSON)** and add this property to the existing - generated settings object, replacing the example with the absolute value of - `$REGISTRY_STACK_REPO`: +3. Trust the opened workspace. This preview runs a local executable and is disabled in Restricted + Mode and virtual workspaces. Then run **Preferences: Open Workspace Settings (JSON)** and add + this property to the existing generated settings object, replacing the example with the + absolute value of `$REGISTRY_STACK_REPO`: ```json "registryStack.languageServer.path": "/absolute/path/to/registry-stack/target/debug/registry-language-server" @@ -48,9 +56,12 @@ repository. First complete the [shared smoke-project setup](../README.md#local-e `F12` for definitions, `Shift+F12` for references, `Cmd+Shift+O`/`Ctrl+Shift+O` for document symbols, and `Cmd+T`/`Ctrl+T` for workspace symbols. -The source VSIX does not contain a platform server binary. Instead of the explicit setting, the -extension can find `registry-language-server` on `PATH`, then fall back to -`registryctl authoring language-server` when `registryctl` is on `PATH`. +The source VSIX contains the extension runtime, not a platform server binary. +Its server discovery order is: the explicit `registryStack.languageServer.path` setting, +`registry-language-server` on `PATH`, then a matching `registryctl` on `PATH` running +`registryctl authoring language-server`. +The explicit setting is the shortest reliable preview path because it selects the binary built +from the same checkout. ## Iterate @@ -67,9 +78,12 @@ extension can find `registry-language-server` on `PATH`, then fall back to - If activation does not occur, confirm each intended workspace folder root itself contains `registry-stack.yaml` and that VS Code trusts the workspace. Opening only a YAML file or a parent - directory does not activate it. -- If startup reports that no executable was found, verify the workspace setting is an absolute - path to an executable regular file. + directory does not activate it. Select **Workspaces: Manage Workspace Trust**, trust the reviewed + project, and run **Registry Stack: Restart Language Server**. +- If startup reports that no server was found, set `registryStack.languageServer.path` to the + executable built in step 3. Otherwise, add `registry-language-server` to `PATH`, or add the + matching `registryctl` to `PATH` and restart the language server. The output message names the + project folder that failed. - If navigation is absent, confirm the file's VS Code language mode is YAML and inspect the output channel named for that workspace folder. - Red Hat YAML still owns schema validation, completion, hover, formatting, and syntax errors. Its diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 741145b56..a68197b65 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -52,7 +52,7 @@ "type": "string", "default": "", "scope": "machine-overridable", - "description": "Path to the registry-language-server executable. Relative paths resolve from the Registry Stack project root. When empty, the extension uses its bundled binary or searches PATH." + "description": "Path to the registry-language-server executable. Relative paths resolve from the Registry Stack project root. When empty, the extension searches PATH, then runs `registryctl authoring language-server` from PATH." } } } @@ -61,7 +61,8 @@ "compile": "esbuild src/extension.ts --bundle --platform=node --target=node20 --external:vscode --outfile=dist/extension.js --sourcemap --minify", "compile-tests": "tsc -p tsconfig.test.json", "check": "tsc -p . --noEmit && tsc -p tsconfig.test.json --noEmit", - "package:dev": "npm run check && vsce package --no-dependencies --out registry-stack-dev.vsix", + "package:dev": "npm run check && npm run compile && vsce package --no-dependencies --out registry-stack-dev.vsix && npm run verify:package", + "verify:package": "node scripts/verify-vsix.cjs registry-stack-dev.vsix", "pretest": "npm run compile && npm run compile-tests", "test": "vscode-test", "vscode:prepublish": "npm run compile" diff --git a/editors/vscode/scripts/verify-vsix.cjs b/editors/vscode/scripts/verify-vsix.cjs new file mode 100644 index 000000000..91c27807d --- /dev/null +++ b/editors/vscode/scripts/verify-vsix.cjs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 + +const { execFileSync } = require('node:child_process'); + +const vsixPath = process.argv[2]; +if (typeof vsixPath !== 'string' || vsixPath.length === 0) { + throw new Error('usage: node scripts/verify-vsix.cjs '); +} + +const entries = execFileSync('unzip', ['-Z1', vsixPath], { encoding: 'utf8' }).split('\n'); +const requiredEntries = ['extension/package.json', 'extension/dist/extension.js']; +for (const entry of requiredEntries) { + if (!entries.includes(entry)) { + throw new Error(`VSIX is missing required runtime entry: ${entry}`); + } +} +if (entries.some((entry) => entry.startsWith('extension/node_modules/'))) { + throw new Error('VSIX contains node_modules; runtime dependencies must be bundled in dist/extension.js'); +} + +const bundle = execFileSync('unzip', ['-p', vsixPath, 'extension/dist/extension.js'], { + encoding: 'utf8', +}); +if (bundle.includes('require("vscode-languageclient') || bundle.includes("require('vscode-languageclient")) { + throw new Error('VSIX leaves vscode-languageclient as an external runtime dependency'); +} diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts index 635da454c..ca23ed47e 100644 --- a/editors/vscode/src/extension.ts +++ b/editors/vscode/src/extension.ts @@ -68,7 +68,7 @@ async function startClient( ): Promise { const key = folderKey(projectFolder); try { - const server = resolveServerCommand(context, projectFolder); + const server = resolveServerCommand(projectFolder); const serverOptions: ServerOptions = { run: { command: server.command, @@ -144,10 +144,10 @@ function folderKey(folder: vscode.WorkspaceFolder): string { return folder.uri.toString(); } -function resolveServerCommand( - context: vscode.ExtensionContext, - projectFolder: vscode.WorkspaceFolder, -): { command: string; args: string[] } { +function resolveServerCommand(projectFolder: vscode.WorkspaceFolder): { + command: string; + args: string[]; +} { const configured = vscode.workspace .getConfiguration('registryStack', projectFolder.uri) .get('languageServer.path', '') @@ -157,7 +157,9 @@ function resolveServerCommand( ? configured : path.resolve(projectFolder.uri.fsPath, configured); if (!isExecutableFile(resolved)) { - throw new Error(`configured executable does not exist or is not a file: ${resolved}`); + throw new Error( + `Configured registryStack.languageServer.path is not an executable file: ${resolved}. Update the setting, or clear it to search PATH.`, + ); } return { command: resolved, args: [] }; } @@ -165,10 +167,6 @@ function resolveServerCommand( const executable = process.platform === 'win32' ? 'registry-language-server.exe' : 'registry-language-server'; - const bundled = context.asAbsolutePath(path.join('bin', executable)); - if (isExecutableFile(bundled)) { - return { command: bundled, args: [] }; - } const standalone = findExecutableOnPath(executable); if (standalone !== undefined) { return { command: standalone, args: [] }; @@ -180,7 +178,7 @@ function resolveServerCommand( return { command: registryctl, args: ['authoring', 'language-server'] }; } throw new Error( - 'neither registry-language-server nor registryctl was found on PATH; install Registry Stack or configure registryStack.languageServer.path', + 'No Registry Stack language server was found. Set registryStack.languageServer.path to an executable, add registry-language-server to PATH, or add a matching registryctl to PATH so it can run "registryctl authoring language-server".', ); } diff --git a/editors/vscode/test/trusted.test.ts b/editors/vscode/test/trusted.test.ts index 78672aa44..eb808f181 100644 --- a/editors/vscode/test/trusted.test.ts +++ b/editors/vscode/test/trusted.test.ts @@ -7,7 +7,7 @@ import * as path from 'node:path'; import * as vscode from 'vscode'; suite('Registry Stack extension', () => { - test('starts a language server for every Registry Stack workspace folder', async () => { + test('starts a language server for every trusted Registry Stack workspace folder', async () => { assert.strictEqual(vscode.workspace.isTrusted, true); assert.strictEqual(vscode.workspace.workspaceFolders?.length, 2); diff --git a/editors/zed/README.md b/editors/zed/README.md index 92dbaa25b..ecff84b0d 100644 --- a/editors/zed/README.md +++ b/editors/zed/README.md @@ -1,5 +1,11 @@ # Registry Stack for Zed +This is a source-installable developer preview for the current beta. +It is not listed in Zed Extensions and no release artifact is provided. +For the stable beta path, run `registryctl init --from ` or +`registryctl authoring editor --project-dir ` and use the generated YAML schema settings. +Install this preview only for optional semantic navigation. + This extension attaches `registry-language-server` to Zed's built-in YAML language. It adds cross-file definitions, references, workspace/document symbols, and Registry Stack reference diagnostics. Zed's YAML language server remains responsible for YAML syntax, schema validation, @@ -24,7 +30,7 @@ cargo check --locked --target wasm32-wasip2 --manifest-path editors/zed/Cargo.to zed "$REGISTRY_STACK_SMOKE_PROJECT" ``` -2. Run `zed: install dev extension` from the command palette and select +2. Run **Zed: Install Dev Extension** from the command palette and select `$REGISTRY_STACK_REPO/editors/zed`. Zed compiles and installs the WebAssembly extension. 3. Run `editor: restart language server`, then `dev: open language server logs`. Select `registry-stack` for the smoke project and confirm the server log reports that the project was @@ -45,7 +51,9 @@ cargo check --locked --target wasm32-wasip2 --manifest-path editors/zed/Cargo.to - If the development extension does not compile, confirm `rustup` owns the active Rust installation and that `cargo check` for `wasm32-wasip2` passes. - If Zed cannot find the server, close it, export the updated `PATH`, and relaunch it from that - terminal. The launcher looks for `registry-language-server`, then `registryctl`. + terminal. The launcher first looks for `registry-language-server`, then runs + `registryctl authoring language-server` when `registryctl` is on `PATH`. The two executables must + come from the same checkout or beta build that you are previewing. - Use `dev: open language server logs` to inspect how the server was launched. Use `zed: open log` for extension errors. For verbose extension output, close Zed and relaunch it with `zed --foreground "$REGISTRY_STACK_SMOKE_PROJECT"`. @@ -54,8 +62,14 @@ cargo check --locked --target wasm32-wasip2 --manifest-path editors/zed/Cargo.to The Extensions page identifies a successful local install as a development extension. Remove it from that page after the smoke test if you do not want the override to remain active. -Zed does not permit shipping an external language server inside the extension. This launcher first -looks on `PATH` for `registry-language-server`, then falls back to -`registryctl authoring language-server`. See Zed's official +Zed does not permit shipping an external language server inside the extension. +The current Zed extension API registers a language server against a language name, but has no +worktree-root predicate for `registry-stack.yaml`. +The preview therefore attaches to YAML while the development extension remains installed. +It has no Registry Stack behavior without a server binary, but Zed can log a missing-server error +when you open unrelated YAML in another worktree. +Keep the development extension installed only while previewing a Registry Stack project, and remove +it afterwards to avoid that noise. +See Zed's official [development-extension instructions](https://zed.dev/docs/extensions/developing-extensions#developing-an-extension-locally) for the current installation and logging workflow.