release: prepare WRNexusJS 0.8.8
Quality / quality (ubuntu-latest) (push) Failing after 9m49s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-11 13:40:03 +05:30
parent 816594a58b
commit feff30a2b5
97 changed files with 769 additions and 10487 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
"license": "MIT", "license": "MIT",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/authz — part of the WrNexus framework.", "description": "@wrnexus/authz — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -41,8 +41,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7", "@wrnexus/core": "^0.8.8",
"@wrnexus/db": "^0.8.7" "@wrnexus/db": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+18 -18
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/cli — part of the WrNexus framework.", "description": "@wrnexus/cli — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -44,23 +44,23 @@
"wrnexus": "./dist/index.js" "wrnexus": "./dist/index.js"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7", "@wrnexus/core": "^0.8.8",
"@wrnexus/router": "^0.8.7", "@wrnexus/router": "^0.8.8",
"@wrnexus/csr": "^0.8.7", "@wrnexus/csr": "^0.8.8",
"@wrnexus/compiler": "^0.8.7", "@wrnexus/compiler": "^0.8.8",
"@wrnexus/styles": "^0.8.7", "@wrnexus/styles": "^0.8.8",
"@wrnexus/dev-server": "^0.8.7", "@wrnexus/dev-server": "^0.8.8",
"@wrnexus/ui": "^0.8.7", "@wrnexus/ui": "^0.8.8",
"@wrnexus/validation": "^0.8.7", "@wrnexus/validation": "^0.8.8",
"@wrnexus/i18n": "^0.8.7", "@wrnexus/i18n": "^0.8.8",
"@wrnexus/mcp": "^0.8.7", "@wrnexus/mcp": "^0.8.8",
"@wrnexus/playground": "^0.8.7", "@wrnexus/playground": "^0.8.8",
"@wrnexus/db": "^0.8.7", "@wrnexus/db": "^0.8.8",
"@wrnexus/authz": "^0.8.7", "@wrnexus/authz": "^0.8.8",
"@wrnexus/plugin": "^0.8.7", "@wrnexus/plugin": "^0.8.8",
"@wrnexus/syntax": "^0.8.7", "@wrnexus/syntax": "^0.8.8",
"@wrnexus/typecheck": "^0.8.7", "@wrnexus/typecheck": "^0.8.8",
"@wrnexus/security": "^0.8.7", "@wrnexus/security": "^0.8.8",
"selfsigned": "^5.5.0" "selfsigned": "^5.5.0"
}, },
"files": [ "files": [
+5 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/compiler — part of the WrNexus framework.", "description": "@wrnexus/compiler — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,10 +37,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/csr": "^0.8.7", "@wrnexus/csr": "^0.8.8",
"@wrnexus/syntax": "^0.8.7", "@wrnexus/syntax": "^0.8.8",
"@wrnexus/store": "^0.8.7", "@wrnexus/store": "^0.8.8",
"@wrnexus/validation": "^0.8.7" "@wrnexus/validation": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/core — part of the WrNexus framework.", "description": "@wrnexus/core — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/csr — part of the WrNexus framework.", "description": "@wrnexus/csr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7" "@wrnexus/core": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Typed database drivers, migrations, instrumentation, repositories, pagination, and transaction helpers.", "description": "Typed database drivers, migrations, instrumentation, repositories, pagination, and transaction helpers.",
"license": "MIT", "license": "MIT",
+22 -22
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/dev-server — part of the WrNexus framework.", "description": "@wrnexus/dev-server — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -41,27 +41,27 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/authz": "^0.8.7", "@wrnexus/authz": "^0.8.8",
"@wrnexus/rpc": "^0.8.7", "@wrnexus/rpc": "^0.8.8",
"@wrnexus/core": "^0.8.7", "@wrnexus/core": "^0.8.8",
"@wrnexus/dev-toolbar": "^0.8.7", "@wrnexus/dev-toolbar": "^0.8.8",
"@wrnexus/router": "^0.8.7", "@wrnexus/router": "^0.8.8",
"@wrnexus/ssr": "^0.8.7", "@wrnexus/ssr": "^0.8.8",
"@wrnexus/csr": "^0.8.7", "@wrnexus/csr": "^0.8.8",
"@wrnexus/compiler": "^0.8.7", "@wrnexus/compiler": "^0.8.8",
"@wrnexus/styles": "^0.8.7", "@wrnexus/styles": "^0.8.8",
"@wrnexus/ui": "^0.8.7", "@wrnexus/ui": "^0.8.8",
"@wrnexus/validation": "^0.8.7", "@wrnexus/validation": "^0.8.8",
"@wrnexus/i18n": "^0.8.7", "@wrnexus/i18n": "^0.8.8",
"@wrnexus/db": "^0.8.7", "@wrnexus/db": "^0.8.8",
"@wrnexus/pubsub": "^0.8.7", "@wrnexus/pubsub": "^0.8.8",
"@wrnexus/uploader": "^0.8.7", "@wrnexus/uploader": "^0.8.8",
"@wrnexus/plugin": "^0.8.7", "@wrnexus/plugin": "^0.8.8",
"@wrnexus/store": "^0.8.7", "@wrnexus/store": "^0.8.8",
"@wrnexus/security": "^0.8.7", "@wrnexus/security": "^0.8.8",
"@wrnexus/observability": "^0.8.7", "@wrnexus/observability": "^0.8.8",
"@wrnexus/cache": "^0.8.7", "@wrnexus/cache": "^0.8.8",
"@wrnexus/pwa": "^0.8.7" "@wrnexus/pwa": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/dev-toolbar — part of the WrNexus framework.", "description": "@wrnexus/dev-toolbar — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Authenticated encryption, key rotation, hashing, and optional application-layer encrypted HTTP envelopes.", "description": "Authenticated encryption, key rotation, hashing, and optional application-layer encrypted HTTP envelopes.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7" "@wrnexus/core": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7" "@wrnexus/core": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Locale loading, fallback resolution, SSR/browser translations, formatters, and WRNexusJS language components.", "description": "Locale loading, fallback resolution, SSR/browser translations, formatters, and WRNexusJS language components.",
"license": "MIT", "license": "MIT",
@@ -42,9 +42,9 @@
"./components/*": "./components/*" "./components/*": "./components/*"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7", "@wrnexus/core": "^0.8.8",
"@wrnexus/plugin": "^0.8.7", "@wrnexus/plugin": "^0.8.8",
"@wrnexus/ui": "^0.8.7" "@wrnexus/ui": "^0.8.8"
}, },
"wrnexus": { "wrnexus": {
"plugin": { "plugin": {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "HS256 JSON Web Tokens, key rotation, access/refresh helpers, scopes, cookies, and auth middleware.", "description": "HS256 JSON Web Tokens, key rotation, access/refresh helpers, scopes, cookies, and auth middleware.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7" "@wrnexus/core": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/mobile — part of the WrNexus framework.", "description": "@wrnexus/mobile — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/native": "^0.8.7" "@wrnexus/native": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/native — part of the WrNexus framework.", "description": "@wrnexus/native — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/oauth — part of the WrNexus framework.", "description": "@wrnexus/oauth — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,7 +37,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/jwt": "^0.8.7" "@wrnexus/jwt": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/plugin", "name": "@wrnexus/plugin",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/plugin — part of the WrNexus framework.", "description": "@wrnexus/plugin — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -49,7 +49,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/syntax": "^0.8.7" "@wrnexus/syntax": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/pubsub — part of the WrNexus framework.", "description": "@wrnexus/pubsub — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -45,8 +45,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7", "@wrnexus/core": "^0.8.8",
"@wrnexus/rpc": "^0.8.7" "@wrnexus/rpc": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/queue — part of the WrNexus framework.", "description": "@wrnexus/queue — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,8 +37,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7", "@wrnexus/core": "^0.8.8",
"@wrnexus/rpc": "^0.8.7" "@wrnexus/rpc": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/reactive — part of the WrNexus framework.", "description": "@wrnexus/reactive — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/router — part of the WrNexus framework.", "description": "@wrnexus/router — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,8 +37,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/compiler": "^0.8.7", "@wrnexus/compiler": "^0.8.8",
"@wrnexus/core": "^0.8.7" "@wrnexus/core": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/ssr — part of the WrNexus framework.", "description": "@wrnexus/ssr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -45,9 +45,9 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7", "@wrnexus/core": "^0.8.8",
"@wrnexus/store": "^0.8.7", "@wrnexus/store": "^0.8.8",
"@wrnexus/security": "^0.8.7" "@wrnexus/security": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/styles — part of the WrNexus framework.", "description": "@wrnexus/styles — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -37,9 +37,9 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/uploader": "^0.8.7", "@wrnexus/uploader": "^0.8.8",
"@wrnexus/core": "^0.8.7", "@wrnexus/core": "^0.8.8",
"@wrnexus/plugin": "^0.8.7" "@wrnexus/plugin": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/syntax", "name": "@wrnexus/syntax",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/syntax — part of the WrNexus framework.", "description": "@wrnexus/syntax — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/test — part of the WrNexus framework.", "description": "@wrnexus/test — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/tracking — part of the WrNexus framework.", "description": "@wrnexus/tracking — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ui", "name": "@wrnexus/ui",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "@wrnexus/ui — part of the WrNexus framework.", "description": "@wrnexus/ui — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -46,7 +46,7 @@
"./ui.css": "./ui.css" "./ui.css": "./ui.css"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7" "@wrnexus/core": "^0.8.8"
}, },
"files": [ "files": [
"dist", "dist",
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Secure upload drivers, policies, client runtime, helper functions, and reusable upload components.", "description": "Secure upload drivers, policies, client runtime, helper functions, and reusable upload components.",
"license": "MIT", "license": "MIT",
@@ -42,9 +42,9 @@
"./components/*": "./components/*" "./components/*": "./components/*"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7", "@wrnexus/core": "^0.8.8",
"@wrnexus/plugin": "^0.8.7", "@wrnexus/plugin": "^0.8.8",
"@wrnexus/ui": "^0.8.7" "@wrnexus/ui": "^0.8.8"
}, },
"wrnexus": { "wrnexus": {
"plugin": { "plugin": {
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/validation", "name": "@wrnexus/validation",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Shared server/browser schemas, validation helpers, form runtime, and reusable error components.", "description": "Shared server/browser schemas, validation helpers, form runtime, and reusable error components.",
"license": "MIT", "license": "MIT",
@@ -42,9 +42,9 @@
"./components/*": "./components/*" "./components/*": "./components/*"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.8.7", "@wrnexus/core": "^0.8.8",
"@wrnexus/plugin": "^0.8.7", "@wrnexus/plugin": "^0.8.8",
"@wrnexus/ui": "^0.8.7" "@wrnexus/ui": "^0.8.8"
}, },
"wrnexus": { "wrnexus": {
"plugin": { "plugin": {
+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## 0.8.8
- Added the framework request context to `.wrn` language-server type environments.
- Kept workspace type declarations inside runnable apps during updates.
- Regenerated lint-safe application declarations before update verification.
All notable framework changes are recorded here. Every release must also include an idempotent All notable framework changes are recorded here. Every release must also include an idempotent
entry in the CLI migration registry. entry in the CLI migration registry.
+47 -47
View File
@@ -208,11 +208,11 @@
}, },
"packages/ai": { "packages/ai": {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.8.7", "version": "0.8.8",
}, },
"packages/auth": { "packages/auth": {
"name": "@wrnexus/auth", "name": "@wrnexus/auth",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/authz": "workspace:*", "@wrnexus/authz": "workspace:*",
"@wrnexus/captcha": "workspace:*", "@wrnexus/captcha": "workspace:*",
@@ -234,7 +234,7 @@
}, },
"packages/authz": { "packages/authz": {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/db": "workspace:*", "@wrnexus/db": "workspace:*",
@@ -242,18 +242,18 @@
}, },
"packages/benchmark": { "packages/benchmark": {
"name": "@wrnexus/benchmark", "name": "@wrnexus/benchmark",
"version": "0.8.7", "version": "0.8.8",
}, },
"packages/cache": { "packages/cache": {
"name": "@wrnexus/cache", "name": "@wrnexus/cache",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/captcha": { "packages/captcha": {
"name": "@wrnexus/captcha", "name": "@wrnexus/captcha",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -268,7 +268,7 @@
}, },
"packages/cli": { "packages/cli": {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.8.7", "version": "0.8.8",
"bin": { "bin": {
"wrnexus": "src/index.ts", "wrnexus": "src/index.ts",
}, },
@@ -295,7 +295,7 @@
}, },
"packages/compiler": { "packages/compiler": {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/csr": "workspace:*", "@wrnexus/csr": "workspace:*",
"@wrnexus/store": "workspace:*", "@wrnexus/store": "workspace:*",
@@ -305,7 +305,7 @@
}, },
"packages/content": { "packages/content": {
"name": "@wrnexus/content", "name": "@wrnexus/content",
"version": "0.8.7", "version": "0.8.8",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^5.9.2", "typescript": "^5.9.2",
@@ -313,18 +313,18 @@
}, },
"packages/core": { "packages/core": {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.8.7", "version": "0.8.8",
}, },
"packages/csr": { "packages/csr": {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/db": { "packages/db": {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.8.7", "version": "0.8.8",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^5.9.2", "typescript": "^5.9.2",
@@ -332,7 +332,7 @@
}, },
"packages/dev-server": { "packages/dev-server": {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/authz": "workspace:*", "@wrnexus/authz": "workspace:*",
"@wrnexus/cache": "workspace:*", "@wrnexus/cache": "workspace:*",
@@ -359,7 +359,7 @@
}, },
"packages/dev-toolbar": { "packages/dev-toolbar": {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.8.7", "version": "0.8.8",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^5.9.2", "typescript": "^5.9.2",
@@ -367,7 +367,7 @@
}, },
"packages/encryption": { "packages/encryption": {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
@@ -378,21 +378,21 @@
}, },
"packages/graphql": { "packages/graphql": {
"name": "@wrnexus/graphql", "name": "@wrnexus/graphql",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
}, },
}, },
"packages/helpers": { "packages/helpers": {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/i18n": { "packages/i18n": {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -406,7 +406,7 @@
}, },
"packages/identity": { "packages/identity": {
"name": "@wrnexus/identity", "name": "@wrnexus/identity",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/auth": "workspace:*", "@wrnexus/auth": "workspace:*",
"@wrnexus/authz": "workspace:*", "@wrnexus/authz": "workspace:*",
@@ -415,7 +415,7 @@
}, },
"packages/image": { "packages/image": {
"name": "@wrnexus/image", "name": "@wrnexus/image",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
"@wrnexus/security": "workspace:*", "@wrnexus/security": "workspace:*",
@@ -435,7 +435,7 @@
}, },
"packages/jwt": { "packages/jwt": {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
@@ -446,7 +446,7 @@
}, },
"packages/language-server": { "packages/language-server": {
"name": "@wrnexus/language-server", "name": "@wrnexus/language-server",
"version": "0.8.7", "version": "0.8.8",
"bin": { "bin": {
"wrnexus-language-server": "src/server.ts", "wrnexus-language-server": "src/server.ts",
}, },
@@ -457,39 +457,39 @@
}, },
"packages/mcp": { "packages/mcp": {
"name": "@wrnexus/mcp", "name": "@wrnexus/mcp",
"version": "0.8.7", "version": "0.8.8",
"bin": { "bin": {
"wrnexus-mcp": "src/stdio.ts", "wrnexus-mcp": "src/stdio.ts",
}, },
}, },
"packages/mobile": { "packages/mobile": {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/native": "workspace:*", "@wrnexus/native": "workspace:*",
}, },
}, },
"packages/native": { "packages/native": {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.8.7", "version": "0.8.8",
}, },
"packages/oauth": { "packages/oauth": {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/jwt": "workspace:*", "@wrnexus/jwt": "workspace:*",
}, },
}, },
"packages/observability": { "packages/observability": {
"name": "@wrnexus/observability", "name": "@wrnexus/observability",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/playground": { "packages/playground": {
"name": "@wrnexus/playground", "name": "@wrnexus/playground",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/compiler": "workspace:*", "@wrnexus/compiler": "workspace:*",
"@wrnexus/syntax": "workspace:*", "@wrnexus/syntax": "workspace:*",
@@ -497,14 +497,14 @@
}, },
"packages/plugin": { "packages/plugin": {
"name": "@wrnexus/plugin", "name": "@wrnexus/plugin",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/syntax": "workspace:*", "@wrnexus/syntax": "workspace:*",
}, },
}, },
"packages/pubsub": { "packages/pubsub": {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/rpc": "workspace:*", "@wrnexus/rpc": "workspace:*",
@@ -512,7 +512,7 @@
}, },
"packages/pwa": { "packages/pwa": {
"name": "@wrnexus/pwa", "name": "@wrnexus/pwa",
"version": "0.8.7", "version": "0.8.8",
"devDependencies": { "devDependencies": {
"@types/bun": "^1.3.14", "@types/bun": "^1.3.14",
"typescript": "^5.9.2", "typescript": "^5.9.2",
@@ -520,7 +520,7 @@
}, },
"packages/queue": { "packages/queue": {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/rpc": "workspace:*", "@wrnexus/rpc": "workspace:*",
@@ -528,11 +528,11 @@
}, },
"packages/reactive": { "packages/reactive": {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.8.7", "version": "0.8.8",
}, },
"packages/realtime": { "packages/realtime": {
"name": "@wrnexus/realtime", "name": "@wrnexus/realtime",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -546,7 +546,7 @@
}, },
"packages/router": { "packages/router": {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/compiler": "workspace:*", "@wrnexus/compiler": "workspace:*",
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
@@ -554,7 +554,7 @@
}, },
"packages/rpc": { "packages/rpc": {
"name": "@wrnexus/rpc", "name": "@wrnexus/rpc",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/helpers": "workspace:*", "@wrnexus/helpers": "workspace:*",
@@ -568,14 +568,14 @@
}, },
"packages/security": { "packages/security": {
"name": "@wrnexus/security", "name": "@wrnexus/security",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/ssr": { "packages/ssr": {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/security": "workspace:*", "@wrnexus/security": "workspace:*",
@@ -584,11 +584,11 @@
}, },
"packages/store": { "packages/store": {
"name": "@wrnexus/store", "name": "@wrnexus/store",
"version": "0.8.7", "version": "0.8.8",
}, },
"packages/styles": { "packages/styles": {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -597,19 +597,19 @@
}, },
"packages/syntax": { "packages/syntax": {
"name": "@wrnexus/syntax", "name": "@wrnexus/syntax",
"version": "0.8.7", "version": "0.8.8",
}, },
"packages/test": { "packages/test": {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.8.7", "version": "0.8.8",
}, },
"packages/tracking": { "packages/tracking": {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
"version": "0.8.7", "version": "0.8.8",
}, },
"packages/typecheck": { "packages/typecheck": {
"name": "@wrnexus/typecheck", "name": "@wrnexus/typecheck",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/syntax": "workspace:*", "@wrnexus/syntax": "workspace:*",
"typescript": "^5.5.0", "typescript": "^5.5.0",
@@ -617,14 +617,14 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@wrnexus/ui", "name": "@wrnexus/ui",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
}, },
}, },
"packages/uploader": { "packages/uploader": {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
@@ -638,7 +638,7 @@
}, },
"packages/validation": { "packages/validation": {
"name": "@wrnexus/validation", "name": "@wrnexus/validation",
"version": "0.8.7", "version": "0.8.8",
"dependencies": { "dependencies": {
"@wrnexus/core": "workspace:*", "@wrnexus/core": "workspace:*",
"@wrnexus/plugin": "workspace:*", "@wrnexus/plugin": "workspace:*",
+4 -1
View File
@@ -1830,10 +1830,13 @@
"htmlToWrn", "htmlToWrn",
"offsetAt", "offsetAt",
"positionAt", "positionAt",
"semanticTokens",
"semanticTokensLegend",
"symbolLocations", "symbolLocations",
"virtualTypeScriptDocument", "virtualTypeScriptDocument",
"wordAt", "wordAt",
"workspaceCompletionItems" "workspaceCompletionItems",
"workspaceSymbolLocations"
], ],
"./server": [] "./server": []
}, },
+2
View File
@@ -10,3 +10,5 @@ package-lock.json
bun.lock bun.lock
../../** ../../**
node_modules/** node_modules/**
src/*.js
src/compiler.cjs
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "wrnexus", "name": "wrnexus",
"version": "0.8.7", "version": "0.8.8",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wrnexus", "name": "wrnexus",
"version": "0.8.7", "version": "0.8.8",
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
"dependencies": { "dependencies": {
"vscode-languageclient": "^10.1.0" "vscode-languageclient": "^10.1.0"
+4 -4
View File
@@ -2,7 +2,7 @@
"name": "wrnexus", "name": "wrnexus",
"displayName": "WRNexus Language Support", "displayName": "WRNexus Language Support",
"description": "Complete WRNexus v0.8.3 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.", "description": "Complete WRNexus v0.8.3 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.",
"version": "0.8.7", "version": "0.8.8",
"publisher": "wrnexus", "publisher": "wrnexus",
"private": true, "private": true,
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
@@ -275,9 +275,9 @@
"validate": "node test/validate.mjs && node ../../scripts/build-editor-compiler.mjs --check && node ../../scripts/build-editor-language-server.mjs --check && node ../../scripts/build-editor-extension.mjs --check", "validate": "node test/validate.mjs && node ../../scripts/build-editor-compiler.mjs --check && node ../../scripts/build-editor-language-server.mjs --check && node ../../scripts/build-editor-extension.mjs --check",
"check": "bun run build && bun run test && bun run validate", "check": "bun run build && bun run test && bun run validate",
"vscode:prepublish": "bun run check", "vscode:prepublish": "bun run check",
"package": "vsce package --no-dependencies", "package": "vsce package --no-dependencies --no-rewrite-relative-links",
"publish": "vsce publish --no-dependencies", "publish": "vsce publish --no-dependencies --no-rewrite-relative-links",
"publish:azure": "vsce publish --no-dependencies --azure-credential" "publish:azure": "vsce publish --no-dependencies --no-rewrite-relative-links --azure-credential"
}, },
"devDependencies": { "devDependencies": {
"@vscode/vsce": "^3.9.2" "@vscode/vsce": "^3.9.2"
File diff suppressed because it is too large Load Diff
+23 -575
View File
@@ -1,321 +1,18 @@
// @ts-check // @ts-check
"use strict"; "use strict";
const vscode = require("vscode");
const path = require("node:path"); const path = require("node:path");
const vscode = require("vscode");
const { LanguageClient, TransportKind } = require("vscode-languageclient/node"); const { LanguageClient, TransportKind } = require("vscode-languageclient/node");
const { formatWrn } = require("./formatter");
const { registerCompletionProvider } = require("./completion");
const { registerDefinitionProvider } = require("./definition");
const { registerComponentIntelligence } = require("./component-intelligence");
const { registerDiagnostics } = require("./diagnostics");
const { registerV060LanguageFeatures } = require("./v060-language");
/**
* The WRN compiler is bundled to CommonJS using:
*
* bun run build:compiler
*
* Loading is optional so highlighting, formatting, snippets,
* autocomplete and navigation continue working when the compiler
* bundle is temporarily unavailable.
*/
let compiler = null;
try {
compiler = require("./compiler.cjs");
} catch (error) {
console.warn(
"[wrnexus] compiler bundle not found; compiler diagnostics disabled.",
error instanceof Error ? error.message : String(error),
);
}
const WRN_LANGUAGE_ID = "wrn"; const WRN_LANGUAGE_ID = "wrn";
const COMPILER_DIAGNOSTIC_COLLECTION = "wrnexus-compiler"; /** @type {LanguageClient | undefined} */
let client;
/** /** @param {vscode.TextDocument} document */
* Register the WRN document formatter.
*
* @param {vscode.ExtensionContext} context
*/
function registerFormatter(context) {
const selector = {
language: WRN_LANGUAGE_ID,
};
const provider = vscode.languages.registerDocumentFormattingEditProvider(selector, {
provideDocumentFormattingEdits(document, options, token) {
if (token.isCancellationRequested) {
return [];
}
const configuration = vscode.workspace.getConfiguration("wrnexus", document.uri);
const enabled = configuration.get("format.enable", true);
if (!enabled) {
return [];
}
const source = document.getText();
if (!source.trim()) {
return [];
}
const printWidth = configuration.get("formatting.printWidth", 100);
const multilineAttributes = configuration.get("formatting.multilineAttributes", true);
try {
const formatted = formatWrn(source, {
tabSize: options.tabSize || 4,
insertSpaces: options.insertSpaces !== false,
printWidth,
multilineAttributes,
});
if (typeof formatted !== "string" || formatted === source) {
return [];
}
const fullRange = new vscode.Range(
document.positionAt(0),
document.positionAt(source.length),
);
return [vscode.TextEdit.replace(fullRange, formatted)];
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error("[wrnexus] formatting failed:", message);
vscode.window.showErrorMessage(`WRNexus formatting failed: ${message}`);
return [];
}
},
});
context.subscriptions.push(provider);
}
/**
* Convert a compiler ParseError into a VS Code diagnostic.
*
* Compiler errors normally contain:
*
* at offset 123
*
* When no offset exists, the diagnostic is attached to the last
* line of the document.
*
* @param {vscode.TextDocument} document
* @param {unknown} error
* @returns {vscode.Diagnostic}
*/
function toCompilerDiagnostic(document, error) {
const message =
error && typeof error === "object" && "message" in error
? String(error.message)
: "Failed to compile .wrn file.";
const offsetMatch = /offset\s+(\d+)/i.exec(message);
let range;
if (offsetMatch) {
const requestedOffset = Number(offsetMatch[1]);
const safeOffset = Math.max(0, Math.min(requestedOffset, document.getText().length));
const start = document.positionAt(safeOffset);
const wordRange = document.getWordRangeAtPosition(start);
range =
wordRange ||
new vscode.Range(
start,
document.positionAt(Math.min(safeOffset + 1, document.getText().length)),
);
} else {
const lastLine = document.lineAt(Math.max(0, document.lineCount - 1));
range = lastLine.range;
}
const diagnostic = new vscode.Diagnostic(range, message, vscode.DiagnosticSeverity.Error);
diagnostic.source = "WRNexus Compiler";
diagnostic.code = "wrn-compiler-error";
return diagnostic;
}
/**
* Register diagnostics produced by the actual WRN compiler.
*
* The lightweight diagnostics in diagnostics.js provide immediate
* editor feedback. Compiler diagnostics verify that the document
* can also be parsed and generated by the real framework compiler.
*
* @param {vscode.ExtensionContext} context
*/
function registerCompilerDiagnostics(context) {
const collection = vscode.languages.createDiagnosticCollection(COMPILER_DIAGNOSTIC_COLLECTION);
const timers = new Map();
/**
* @param {vscode.TextDocument} document
*/
const run = (document) => {
if (document.languageId !== WRN_LANGUAGE_ID) {
return;
}
const configuration = vscode.workspace.getConfiguration("wrnexus", document.uri);
const enabled = configuration.get("diagnostics.enable", true);
if (!enabled) {
collection.delete(document.uri);
return;
}
if (!compiler || typeof compiler.compileWireFile !== "function") {
collection.delete(document.uri);
return;
}
const source = document.getText();
if (!source.trim()) {
collection.delete(document.uri);
return;
}
try {
compiler.compileWireFile(source);
collection.set(document.uri, []);
} catch (error) {
collection.set(document.uri, [toCompilerDiagnostic(document, error)]);
}
};
/**
* @param {vscode.TextDocument} document
*/
const schedule = (document) => {
if (document.languageId !== WRN_LANGUAGE_ID) {
return;
}
const key = document.uri.toString();
const existing = timers.get(key);
if (existing) {
clearTimeout(existing);
}
const timer = setTimeout(() => {
timers.delete(key);
run(document);
}, 250);
timers.set(key, timer);
};
for (const document of vscode.workspace.textDocuments) {
run(document);
}
context.subscriptions.push(
collection,
vscode.workspace.onDidOpenTextDocument(run),
vscode.workspace.onDidChangeTextDocument((event) => {
schedule(event.document);
}),
vscode.workspace.onDidSaveTextDocument(run),
vscode.workspace.onDidCloseTextDocument((document) => {
const key = document.uri.toString();
const timer = timers.get(key);
if (timer) {
clearTimeout(timer);
timers.delete(key);
}
collection.delete(document.uri);
}),
vscode.workspace.onDidChangeConfiguration((event) => {
if (!event.affectsConfiguration("wrnexus.diagnostics.enable")) {
return;
}
for (const document of vscode.workspace.textDocuments) {
run(document);
}
}),
{
dispose() {
for (const timer of timers.values()) {
clearTimeout(timer);
}
timers.clear();
},
},
);
}
/**
* Register semantic highlighting for WRN state declarations and references.
*
* @param {vscode.ExtensionContext} context
*/
function registerSemanticTokens(context) {
context.subscriptions.push(
vscode.languages.registerDocumentSemanticTokensProvider(
{
language: WRN_LANGUAGE_ID,
},
wrnSemanticTokensProvider,
semanticTokenLegend,
),
);
}
/**
* Recover files affected by older workspace settings that associated `*.wrn`
* with the obsolete `wire` language id. Normal language contribution matching
* happens before activation; this fallback is intentionally limited to Plain
* Text and the legacy id so explicit third-party associations are respected.
*
* @param {vscode.TextDocument} document
*/
async function recoverWrnLanguage(document) { async function recoverWrnLanguage(document) {
if (!document.fileName.toLowerCase().endsWith(".wrn")) return; if (!document.fileName.toLowerCase().endsWith(".wrn")) return;
if (!["plaintext", "wire"].includes(document.languageId)) return; if (!["plaintext", "wire"].includes(document.languageId)) return;
try { try {
await vscode.languages.setTextDocumentLanguage(document, WRN_LANGUAGE_ID); await vscode.languages.setTextDocumentLanguage(document, WRN_LANGUAGE_ID);
} catch (error) { } catch (error) {
@@ -326,281 +23,32 @@ async function recoverWrnLanguage(document) {
} }
} }
/** /** @param {vscode.ExtensionContext} context */
* Activate the WRNexus VS Code extension.
*
* @param {vscode.ExtensionContext} context
*/
async function activate(context) { async function activate(context) {
for (const document of vscode.workspace.textDocuments) { for (const document of vscode.workspace.textDocuments) void recoverWrnLanguage(document);
void recoverWrnLanguage(document);
}
context.subscriptions.push( context.subscriptions.push(
vscode.workspace.onDidOpenTextDocument((document) => { vscode.workspace.onDidOpenTextDocument((document) => void recoverWrnLanguage(document)),
void recoverWrnLanguage(document);
}),
); );
const useLanguageServer = vscode.workspace if (!vscode.workspace.getConfiguration("wrnexus").get("languageServer.enable", true)) return;
.getConfiguration("wrnexus")
.get("languageServer.enable", true); const module = path.join(context.extensionPath, "src", "language-server.cjs");
let languageServerStarted = false; client = new LanguageClient(
if (useLanguageServer) { "wrnexusLanguageServer",
const module = path.join(context.extensionPath, "src", "language-server.cjs"); "WRNexus Language Server",
const serverOptions = { {
run: { module, transport: TransportKind.stdio }, run: { module, transport: TransportKind.stdio },
debug: { module, transport: TransportKind.stdio, options: { execArgv: ["--nolazy"] } }, debug: { module, transport: TransportKind.stdio, options: { execArgv: ["--nolazy"] } },
}; },
const client = new LanguageClient( { documentSelector: [{ scheme: "file", language: WRN_LANGUAGE_ID }] },
"wrnexusLanguageServer", );
"WRNexus Language Server", await client.start();
serverOptions,
{
documentSelector: [{ scheme: "file", language: WRN_LANGUAGE_ID }],
},
);
try {
await client.start();
languageServerStarted = true;
context.subscriptions.push({ dispose: () => void client.stop() });
} catch (error) {
console.warn(
"[wrnexus] language server failed to start; using built-in providers.",
error instanceof Error ? error.message : String(error),
);
}
}
if (!languageServerStarted) {
registerDiagnostics(context);
registerCompilerDiagnostics(context);
registerCompletionProvider(context);
registerDefinitionProvider(context);
registerFormatter(context);
}
// Package/local component metadata is not yet part of the LSP workspace
// catalog, so keep prop/event completion, hover, and validation available in
// both LSP and built-in-provider modes.
registerComponentIntelligence(context);
registerSemanticTokens(context);
registerV060LanguageFeatures(context);
} }
function deactivate() {} async function deactivate() {
const running = client;
const semanticTokenLegend = new vscode.SemanticTokensLegend( client = undefined;
["variable"], if (running) await running.stop();
["declaration", "modification"],
);
/**
* @param {string} value
* @returns {string}
*/
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
} }
/** module.exports = { activate, deactivate, recoverWrnLanguage };
* @param {string} text
* @returns {Map<string, Set<number>>}
*/
function collectStateVariables(text) {
const states = new Map();
const statePattern = /\bstate\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?==|;|\r?$)/gm;
let match;
while ((match = statePattern.exec(text)) !== null) {
const name = match[1];
if (!name) {
continue;
}
const offset = match.index + match[0].lastIndexOf(name);
const declarations = states.get(name) || new Set();
declarations.add(offset);
states.set(name, declarations);
}
return states;
}
/**
* Comments are excluded, but quoted attribute expressions are deliberately
* included because WRN state references commonly appear in values such as
* class:flex="centered" and @click="enabled = !enabled".
*
* @param {string} text
* @returns {Array<{start: number, end: number}>}
*/
function collectCommentRanges(text) {
const ranges = [];
let index = 0;
let quote = null;
let escaped = false;
while (index < text.length) {
const current = text[index];
const next = text[index + 1];
if (quote) {
if (escaped) {
escaped = false;
index++;
continue;
}
if (current === "\\") {
escaped = true;
index++;
continue;
}
if (current === quote) {
quote = null;
}
index++;
continue;
}
if (current === '"' || current === "'" || current === "`") {
quote = current;
index++;
continue;
}
if (current === "/" && next === "/") {
const start = index;
index += 2;
while (index < text.length && text[index] !== "\n") {
index++;
}
ranges.push({ start, end: index });
continue;
}
if (current === "/" && next === "*") {
const start = index;
index += 2;
while (index < text.length && !(text[index] === "*" && text[index + 1] === "/")) {
index++;
}
index = Math.min(text.length, index + 2);
ranges.push({ start, end: index });
continue;
}
index++;
}
return ranges;
}
/**
* @param {number} offset
* @param {Array<{start: number, end: number}>} ranges
* @returns {boolean}
*/
function isInsideComment(offset, ranges) {
let low = 0;
let high = ranges.length - 1;
while (low <= high) {
const middle = Math.floor((low + high) / 2);
const range = ranges[middle];
if (!range) {
return false;
}
if (offset < range.start) {
high = middle - 1;
} else if (offset >= range.end) {
low = middle + 1;
} else {
return true;
}
}
return false;
}
/**
* @param {string} text
* @param {number} offset
* @param {number} length
* @returns {boolean}
*/
function isStateModification(text, offset, length) {
const after = text.slice(offset + length).match(/^\s*(=|\+=|-=|\*=|\/=|%=|\+\+|--)/);
if (after) {
return true;
}
const before = text.slice(Math.max(0, offset - 8), offset).match(/(\+\+|--)\s*$/);
return Boolean(before);
}
const wrnSemanticTokensProvider = {
/**
* @param {vscode.TextDocument} document
* @param {vscode.CancellationToken} token
*/
provideDocumentSemanticTokens(document, token) {
const builder = new vscode.SemanticTokensBuilder(semanticTokenLegend);
const text = document.getText();
const states = collectStateVariables(text);
const commentRanges = collectCommentRanges(text);
for (const [name, declarationOffsets] of states) {
if (token.isCancellationRequested) {
return builder.build();
}
const pattern = new RegExp(`(?<![A-Za-z0-9_$])${escapeRegExp(name)}(?![A-Za-z0-9_$])`, "g");
let match;
while ((match = pattern.exec(text)) !== null) {
const offset = match.index;
if (isInsideComment(offset, commentRanges)) {
continue;
}
const position = document.positionAt(offset);
let modifiers = [];
if (declarationOffsets.has(offset)) {
modifiers = ["declaration"];
} else if (isStateModification(text, offset, name.length)) {
modifiers = ["modification"];
}
builder.push(position.line, position.character, name.length, "variable", modifiers);
}
}
return builder.build();
},
};
module.exports = {
activate,
deactivate,
registerCompilerDiagnostics,
registerFormatter,
registerSemanticTokens,
recoverWrnLanguage,
toCompilerDiagnostic,
};
+191 -30
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
// WRN editor language server source hash: eb2126d09754a516bf6846aea69409578879532f74759f66ae37cf60c961b3bf // WRN editor language server source hash: da83a5e1dbf90523ee0fe9a0d232182b0a78d025f3be8d07c324728d1cdcc17c
// WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72 // WRN editor language server generator hash: f593a44aaf05495b789ce7a3086bee1eebb951b884d41c0e017bbcfe5f547e72
// @bun @bun-cjs // @bun @bun-cjs
(function(exports, require, module, __filename, __dirname) {var __create = Object.create; (function(exports, require, module, __filename, __dirname) {var __create = Object.create;
@@ -171925,6 +171925,7 @@ function virtualTypeScriptModule(source, filePath = "component.wrn", appRoot = f
append(ast.types.join(` append(ast.types.join(`
`), ast.types[0]?.trim()); `), ast.types[0]?.trim());
append('declare const ctx: import("@wrnexus/core").Context;');
for (const prop of ast.props) { for (const prop of ast.props) {
if (!safeBindingName(prop.name)) if (!safeBindingName(prop.name))
continue; continue;
@@ -172370,7 +172371,7 @@ var SKIPPED_DIRECTORIES = new Set([
]); ]);
var MAX_INDEX_FILES = 5000; var MAX_INDEX_FILES = 5000;
var MAX_SOURCE_BYTES = 1048576; var MAX_SOURCE_BYTES = 1048576;
var INDEX_TTL_MS = 5000; var INDEX_TTL_MS = 5 * 60000;
var MAX_CACHED_ROOTS = 8; var MAX_CACHED_ROOTS = 8;
var workspaceIndexCache = new Map; var workspaceIndexCache = new Map;
function walk3(root, test) { function walk3(root, test) {
@@ -172494,6 +172495,36 @@ function workspaceCompletionItems(root) {
} }
return items; return items;
} }
function clearWorkspaceIndexCache(root) {
if (root)
workspaceIndexCache.delete(import_node_path3.resolve(root));
else
workspaceIndexCache.clear();
}
function workspaceSymbolLocations(root, name, limit = 1000) {
if (!/^[A-Za-z_$][\w$]*$/.test(name))
return [];
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const pattern = new RegExp(`(?<![A-Za-z0-9_$])${escaped}(?![A-Za-z0-9_$])`, "g");
const locations = [];
for (const file of walk3(import_node_path3.resolve(root), (path) => import_node_path3.extname(path) === ".wrn").slice(0, limit)) {
let source;
try {
source = import_node_fs3.readFileSync(file, "utf8");
} catch {
continue;
}
for (const match of source.matchAll(pattern)) {
const before = source.slice(0, match.index).split(/\r?\n/);
const start = { line: before.length - 1, character: before.at(-1)?.length ?? 0 };
locations.push({
uri: `file:///${file.replace(/\\/g, "/")}`,
range: { start, end: { line: start.line, character: start.character + name.length } }
});
}
}
return locations;
}
function extractComponentRefactor(document, range, name) { function extractComponentRefactor(document, range, name) {
if (!/^[A-Z][A-Za-z0-9_$]*$/.test(name)) if (!/^[A-Z][A-Za-z0-9_$]*$/.test(name))
throw new Error("Component name must use PascalCase"); throw new Error("Component name must use PascalCase");
@@ -172599,7 +172630,7 @@ function diagnosticRange(diagnostic) {
} }
}; };
} }
function documentDiagnostics(document) { function documentDiagnostics(document, options = {}) {
if (Buffer.byteLength(document.text, "utf8") > 1048576) { if (Buffer.byteLength(document.text, "utf8") > 1048576) {
return [ return [
{ {
@@ -172618,7 +172649,7 @@ function documentDiagnostics(document) {
source: "wrnexus", source: "wrnexus",
message: diagnostic.message message: diagnostic.message
})); }));
if (syntax.some((diagnostic) => diagnostic.severity === 1)) if (syntax.some((diagnostic) => diagnostic.severity === 1) || options.includeTypes === false)
return syntax; return syntax;
const types = checkWrnSource(document.text, { filePath: documentPath(document.uri) }).map((diagnostic) => ({ const types = checkWrnSource(document.text, { filePath: documentPath(document.uri) }).map((diagnostic) => ({
range: { range: {
@@ -172688,19 +172719,6 @@ function documentSymbols(document) {
}; };
}); });
} }
function symbolLocations(document, position) {
const selected = wordAt(document.text, position);
if (!selected)
return [];
const pattern = new RegExp(`(?<![A-Za-z0-9_$])${selected.word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![A-Za-z0-9_$])`, "g");
return [...document.text.matchAll(pattern)].map((match) => ({
uri: document.uri,
range: {
start: positionAt2(document.text, match.index),
end: positionAt2(document.text, match.index + selected.word.length)
}
}));
}
function definitionLocation(document, position) { function definitionLocation(document, position) {
const selected = wordAt(document.text, position); const selected = wordAt(document.text, position);
if (!selected) if (!selected)
@@ -172734,6 +172752,55 @@ ${declaration[0]}
function completionItems() { function completionItems() {
return WRN_COMPLETIONS.map((label) => ({ label, kind: 14, detail: "WRNexus language keyword" })); return WRN_COMPLETIONS.map((label) => ({ label, kind: 14, detail: "WRNexus language keyword" }));
} }
var semanticTokenTypes = ["variable"];
var semanticTokenModifiers = ["declaration", "modification"];
var semanticTokensLegend = {
tokenTypes: [...semanticTokenTypes],
tokenModifiers: [...semanticTokenModifiers]
};
function semanticTokens(document) {
const declarations = new Map;
for (const match of document.text.matchAll(/\bstate\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?==|;|\r?$)/gm)) {
const name = match[1];
const offset = match.index + match[0].lastIndexOf(name);
const offsets = declarations.get(name) ?? new Set;
offsets.add(offset);
declarations.set(name, offsets);
}
const comments = [...document.text.matchAll(/\/\/[^\n]*|\/\*[\s\S]*?\*\//g)].map((match) => ({
start: match.index,
end: match.index + match[0].length
}));
const tokens = [];
for (const [name, offsets] of declarations) {
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
for (const match of document.text.matchAll(new RegExp(`(?<![A-Za-z0-9_$])${escaped}(?![A-Za-z0-9_$])`, "g"))) {
const offset = match.index;
if (comments.some((range) => offset >= range.start && offset < range.end))
continue;
const position = positionAt2(document.text, offset);
const after = document.text.slice(offset + name.length).match(/^\s*(?:=|\+=|-=|\*=|\/=|%=|\+\+|--)/);
const before = document.text.slice(Math.max(0, offset - 8), offset).match(/(?:\+\+|--)\s*$/);
tokens.push({
...position,
length: name.length,
modifiers: offsets.has(offset) ? 1 : after || before ? 2 : 0
});
}
}
tokens.sort((a, b) => a.line - b.line || a.character - b.character);
const data = [];
let previousLine = 0;
let previousCharacter = 0;
for (const token of tokens) {
const deltaLine = token.line - previousLine;
const deltaCharacter = deltaLine === 0 ? token.character - previousCharacter : token.character;
data.push(deltaLine, deltaCharacter, token.length, 0, token.modifiers);
previousLine = token.line;
previousCharacter = token.character;
}
return { data };
}
// packages/language-server/src/server.ts // packages/language-server/src/server.ts
var documents = new Map; var documents = new Map;
@@ -172767,20 +172834,20 @@ function internalDiagnostic(document, error) {
message: `WRNexus language analysis failed safely: ${message}` message: `WRNexus language analysis failed safely: ${message}`
}; };
} }
function safeDocumentDiagnostics(document) { function safeDocumentDiagnostics(document, includeTypes = true) {
try { try {
return documentDiagnostics(document); return documentDiagnostics(document, { includeTypes });
} catch (error) { } catch (error) {
process.stderr.write(`[wrnexus-lsp] diagnostics failed for ${document.uri}: ${error instanceof Error ? error.stack ?? error.message : String(error)} process.stderr.write(`[wrnexus-lsp] diagnostics failed for ${document.uri}: ${error instanceof Error ? error.stack ?? error.message : String(error)}
`); `);
return [internalDiagnostic(document, error)]; return [internalDiagnostic(document, error)];
} }
} }
function publish(document) { function publish(document, includeTypes = true) {
send({ send({
jsonrpc: "2.0", jsonrpc: "2.0",
method: "textDocument/publishDiagnostics", method: "textDocument/publishDiagnostics",
params: { uri: document.uri, diagnostics: safeDocumentDiagnostics(document) } params: { uri: document.uri, diagnostics: safeDocumentDiagnostics(document, includeTypes) }
}); });
} }
function clearDiagnosticTimer(uri) { function clearDiagnosticTimer(uri) {
@@ -172796,7 +172863,7 @@ function schedulePublish(document) {
diagnosticTimers.delete(document.uri); diagnosticTimers.delete(document.uri);
const current = documents.get(document.uri); const current = documents.get(document.uri);
if (current && current.version === expectedVersion) if (current && current.version === expectedVersion)
publish(current); publish(current, false);
}, DIAGNOSTIC_DEBOUNCE_MS)); }, DIAGNOSTIC_DEBOUNCE_MS));
} }
function rememberDocument(document) { function rememberDocument(document) {
@@ -172818,7 +172885,7 @@ async function handle(message) {
result(message.id, { result(message.id, {
serverInfo: { name: "WRNexus Language Server", version: "0.8.3" }, serverInfo: { name: "WRNexus Language Server", version: "0.8.3" },
capabilities: { capabilities: {
textDocumentSync: 1, textDocumentSync: { openClose: true, change: 1, save: true },
documentFormattingProvider: true, documentFormattingProvider: true,
completionProvider: { triggerCharacters: ["<", "@", ":", "."] }, completionProvider: { triggerCharacters: ["<", "@", ":", "."] },
hoverProvider: true, hoverProvider: true,
@@ -172826,8 +172893,14 @@ async function handle(message) {
referencesProvider: true, referencesProvider: true,
renameProvider: { prepareProvider: true }, renameProvider: { prepareProvider: true },
documentSymbolProvider: true, documentSymbolProvider: true,
semanticTokensProvider: { legend: semanticTokensLegend, full: true },
codeActionProvider: { codeActionProvider: {
codeActionKinds: ["quickfix", "refactor.extract", "refactor.rewrite"] codeActionKinds: [
"quickfix",
"refactor.extract",
"refactor.rewrite",
"source.organizeImports"
]
}, },
experimental: { wrnexusVirtualTypeScript: true } experimental: { wrnexusVirtualTypeScript: true }
} }
@@ -172842,6 +172915,10 @@ async function handle(message) {
result(message.id, null); result(message.id, null);
break; break;
case "exit": case "exit":
for (const timer of diagnosticTimers.values())
clearTimeout(timer);
diagnosticTimers.clear();
documents.clear();
process.exit(0); process.exit(0);
break; break;
case "textDocument/didOpen": { case "textDocument/didOpen": {
@@ -172861,6 +172938,15 @@ async function handle(message) {
} }
break; break;
} }
case "textDocument/didSave": {
const document = documents.get(params.textDocument.uri);
if (document) {
clearDiagnosticTimer(document.uri);
clearWorkspaceIndexCache(workspaceRoot);
publish(document, true);
}
break;
}
case "textDocument/didClose": case "textDocument/didClose":
clearDiagnosticTimer(params.textDocument.uri); clearDiagnosticTimer(params.textDocument.uri);
documents.delete(params.textDocument.uri); documents.delete(params.textDocument.uri);
@@ -172890,6 +172976,11 @@ async function handle(message) {
result(message.id, document ? documentSymbols(document) : []); result(message.id, document ? documentSymbols(document) : []);
break; break;
} }
case "textDocument/semanticTokens/full": {
const document = documents.get(params.textDocument.uri);
result(message.id, document ? semanticTokens(document) : { data: [] });
break;
}
case "textDocument/hover": { case "textDocument/hover": {
const document = documents.get(params.textDocument.uri); const document = documents.get(params.textDocument.uri);
result(message.id, document ? hover(document, params.position) : null); result(message.id, document ? hover(document, params.position) : null);
@@ -172902,7 +172993,8 @@ async function handle(message) {
} }
case "textDocument/references": { case "textDocument/references": {
const document = documents.get(params.textDocument.uri); const document = documents.get(params.textDocument.uri);
result(message.id, document ? symbolLocations(document, params.position) : []); const selected = document ? wordAt(document.text, params.position) : null;
result(message.id, selected ? workspaceSymbolLocations(workspaceRoot, selected.word) : []);
break; break;
} }
case "textDocument/prepareRename": { case "textDocument/prepareRename": {
@@ -172912,11 +173004,16 @@ async function handle(message) {
} }
case "textDocument/rename": { case "textDocument/rename": {
const document = documents.get(params.textDocument.uri); const document = documents.get(params.textDocument.uri);
const edits = document ? symbolLocations(document, params.position).map(({ range }) => ({ const selected = document ? wordAt(document.text, params.position) : null;
range, if (!selected || !/^[A-Za-z_$][\w$]*$/.test(params.newName)) {
newText: params.newName result(message.id, null);
})) : []; break;
result(message.id, document ? { changes: { [document.uri]: edits } } : null); }
const changes = {};
for (const location of workspaceSymbolLocations(workspaceRoot, selected.word)) {
(changes[location.uri] ??= []).push({ range: location.range, newText: params.newName });
}
result(message.id, { changes });
break; break;
} }
case "textDocument/codeAction": { case "textDocument/codeAction": {
@@ -172937,6 +173034,70 @@ async function handle(message) {
} }
} }
})); }));
const imports = [
...document.text.matchAll(/^import(?:\s+type)?[\s\S]*?(?:;\s*|\n(?=import|\s*(?:page|component|layout|global\s+store|page\s+store)\b))/gm)
].map((match) => ({
start: match.index,
end: match.index + match[0].length,
text: match[0].trim()
}));
if (imports.length > 1) {
const ordered = [...imports].sort((a, b) => {
const aType = /^import\s+type\b/.test(a.text) ? 0 : 1;
const bType = /^import\s+type\b/.test(b.text) ? 0 : 1;
return aType - bType || a.text.localeCompare(b.text);
});
const replacement = ordered.map((entry) => entry.text.replace(/;$/, "")).join(`
`) + `
`;
const start = imports[0].start;
const end = imports.at(-1).end;
if (document.text.slice(start, end) !== replacement) {
actions.push({
title: "Organize WRN imports",
kind: "source.organizeImports",
edit: {
changes: {
[document.uri]: [
{
range: {
start: positionAt2(document.text, start),
end: positionAt2(document.text, end)
},
newText: replacement
}
]
}
}
});
}
}
for (const diagnostic of params.context?.diagnostics ?? []) {
if (diagnostic.code !== "WRN-OUTPUT-LEGACY-EMIT")
continue;
const start = offsetAt(document.text, diagnostic.range.start);
const end = offsetAt(document.text, diagnostic.range.end);
const source = document.text.slice(start, end);
const match = /\$emit\(\s*["']([A-Za-z_$][\w$]*)["']\s*,?/.exec(source);
if (!match)
continue;
actions.push({
title: `Convert $emit to output.${match[1]}`,
kind: "quickfix",
diagnostics: [diagnostic],
isPreferred: true,
edit: {
changes: {
[document.uri]: [
{
range: diagnostic.range,
newText: source.replace(/\$emit\(\s*["'][A-Za-z_$][\w$]*["']\s*,?\s*/, `output.${match[1]}(`)
}
]
}
}
});
}
const selected = document.text.slice(document.text.split(/\r?\n/).slice(0, params.range.start.line).reduce((n, line) => n + line.length + 1, 0) + params.range.start.character, document.text.split(/\r?\n/).slice(0, params.range.end.line).reduce((n, line) => n + line.length + 1, 0) + params.range.end.character); const selected = document.text.slice(document.text.split(/\r?\n/).slice(0, params.range.start.line).reduce((n, line) => n + line.length + 1, 0) + params.range.start.character, document.text.split(/\r?\n/).slice(0, params.range.end.line).reduce((n, line) => n + line.length + 1, 0) + params.range.end.character);
if (selected.trim().startsWith("<")) { if (selected.trim().startsWith("<")) {
try { try {
+37 -1
View File
@@ -68,7 +68,13 @@ test("language server starts under Node and keeps official typed components clea
messages.push({ jsonrpc: "2.0", method: "exit", params: {} }); messages.push({ jsonrpc: "2.0", method: "exit", params: {} });
const responses = run(messages); const responses = run(messages);
assert.equal(responses.find((item) => item.id === 1)?.result?.serverInfo?.version, "0.8.3"); const initialization = responses.find((item) => item.id === 1)?.result;
assert.equal(initialization?.serverInfo?.version, "0.8.3");
assert.deepEqual(initialization?.capabilities?.textDocumentSync, {
openClose: true,
change: 1,
save: true,
});
const diagnostics = responses.filter((item) => item.method === "textDocument/publishDiagnostics"); const diagnostics = responses.filter((item) => item.method === "textDocument/publishDiagnostics");
assert.equal(diagnostics.length, 2); assert.equal(diagnostics.length, 2);
assert.deepEqual( assert.deepEqual(
@@ -77,6 +83,36 @@ test("language server starts under Node and keeps official typed components clea
); );
}); });
test("language server defers expensive type analysis while typing and restores it on save", () => {
const uri = "file:///tmp/wrnexus-save-analysis.wrn";
const valid = `component SaveAnalysis {
outputs { save(payload: { id: string }) }
functions { client function submit(): void { output.save({ id: "ok" }) } }
view { <button @click="submit()"></button> }
}`;
const invalid = valid.replace('id: "ok"', "id: 123");
const responses = run([
{ jsonrpc: "2.0", id: 1, method: "initialize", params: { rootPath: root } },
{ jsonrpc: "2.0", method: "initialized", params: {} },
{
jsonrpc: "2.0",
method: "textDocument/didOpen",
params: { textDocument: { uri, languageId: "wrn", version: 1, text: valid } },
},
{
jsonrpc: "2.0",
method: "textDocument/didChange",
params: { textDocument: { uri, version: 2 }, contentChanges: [{ text: invalid }] },
},
{ jsonrpc: "2.0", method: "textDocument/didSave", params: { textDocument: { uri } } },
{ jsonrpc: "2.0", id: 2, method: "shutdown", params: {} },
{ jsonrpc: "2.0", method: "exit", params: {} },
]);
const diagnostics = responses.filter((item) => item.method === "textDocument/publishDiagnostics");
assert.equal(diagnostics.length, 2);
assert.ok(diagnostics.at(-1).params.diagnostics.some((item) => item.code === "WRN-TYPE-2322"));
});
test("language server formatter preserves code samples and compact sibling markup", () => { test("language server formatter preserves code samples and compact sibling markup", () => {
const source = `page Docs { const source = `page Docs {
view { view {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "wrnexus", "name": "wrnexus",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "An SSR-first full-stack web framework with server-rendered reactive components. Bun-first, Node-friendly.", "description": "An SSR-first full-stack web framework with server-rendered reactive components. Bun-first, Node-friendly.",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.", "description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/auth", "name": "@wrnexus/auth",
"version": "0.8.7", "version": "0.8.8",
"description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.", "description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/benchmark", "name": "@wrnexus/benchmark",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Deterministic benchmark runner and performance regression budgets for WRNexusJS.", "description": "Deterministic benchmark runner and performance regression budgets for WRNexusJS.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cache", "name": "@wrnexus/cache",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Tag-aware memory and response caching with stale-while-revalidate for WRNexusJS.", "description": "Tag-aware memory and response caching with stale-while-revalidate for WRNexusJS.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/captcha", "name": "@wrnexus/captcha",
"version": "0.8.7", "version": "0.8.8",
"description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.", "description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+12 -18
View File
@@ -24,6 +24,12 @@ function literalUnion(values: Iterable<string>): string {
return unique.length ? unique.map((value) => JSON.stringify(value)).join(" | ") : "never"; return unique.length ? unique.map((value) => JSON.stringify(value)).join(" | ") : "never";
} }
function generatedContractMap(name: string, members: string): string {
return members.trim()
? `interface ${name} {\n${members}\n }`
: `type ${name} = Record<string, never>;`;
}
function typeImport(fromDirectory: string, file: string): string { function typeImport(fromDirectory: string, file: string): string {
const path = relative(fromDirectory, file).replace(/\\/g, "/"); const path = relative(fromDirectory, file).replace(/\\/g, "/");
return JSON.stringify(path.startsWith(".") ? path : `./${path}`); return JSON.stringify(path.startsWith(".") ? path : `./${path}`);
@@ -203,24 +209,12 @@ declare namespace WRNexusGenerated {
type TranslationKey = ${literalUnion(localeKeys)}; type TranslationKey = ${literalUnion(localeKeys)};
type QueueName = ${literalUnion(queueNames)}; type QueueName = ${literalUnion(queueNames)};
type CacheKey = ${literalUnion(cacheKeys)}; type CacheKey = ${literalUnion(cacheKeys)};
interface Components { ${generatedContractMap("Components", componentMap)}
${componentMap} ${generatedContractMap("ApiContracts", apiContracts)}
} ${generatedContractMap("MiddlewareContexts", middlewareContracts)}
interface ApiContracts { ${generatedContractMap("DatabaseQueries", databaseContracts)}
${apiContracts} ${generatedContractMap("RealtimeMessages", realtimeContracts)}
} ${generatedContractMap("QueuePayloads", queueContracts)}
interface MiddlewareContexts {
${middlewareContracts}
}
interface DatabaseQueries {
${databaseContracts}
}
interface RealtimeMessages {
${realtimeContracts}
}
interface QueuePayloads {
${queueContracts}
}
type ApplicationConfig = ${configFile ? `(typeof import(${typeImport(typeDir, configFile)}))["default"]` : "Record<string, never>"}; type ApplicationConfig = ${configFile ? `(typeof import(${typeImport(typeDir, configFile)}))["default"]` : "Record<string, never>"};
} }
`; `;
+20
View File
@@ -33,6 +33,7 @@ import { pathToFileURL } from "node:url";
import { formatWrn, parse } from "@wrnexus/syntax"; import { formatWrn, parse } from "@wrnexus/syntax";
import { AI_GUIDE, CLAUDE_MD } from "./ai-guide.ts"; import { AI_GUIDE, CLAUDE_MD } from "./ai-guide.ts";
import { inspectProject, type DoctorCheck } from "./doctor.ts"; import { inspectProject, type DoctorCheck } from "./doctor.ts";
import { generateApplicationTypes } from "./types.ts";
/** The version of the CLI currently running (its own package.json). */ /** The version of the CLI currently running (its own package.json). */
function cliVersion(): string { function cliVersion(): string {
@@ -1783,6 +1784,10 @@ const MIGRATIONS: Migration[] = [
description: description:
"Adds compatible imports, type checking, stores, and legacy behavior configuration.", "Adds compatible imports, type checking, stores, and legacy behavior configuration.",
apply(ctx) { apply(ctx) {
// Workspace manifests are updated alongside each configured application,
// but they are not themselves runnable apps. Keep app-owned declarations
// under the individual app roots instead of creating `<workspace>/app`.
if (!existsSync(join(ctx.appRoot, "app", "pages"))) return;
updateV060Config(ctx); updateV060Config(ctx);
const types = join(ctx.appRoot, "app", "types"); const types = join(ctx.appRoot, "app", "types");
if (!existsSync(types)) { if (!existsSync(types)) {
@@ -2115,6 +2120,17 @@ const MIGRATIONS: Migration[] = [
// select for non-link items as well as navigation items. // select for non-link items as well as navigation items.
}, },
}, },
{
version: "0.8.8",
id: "0.8.8-editor-types-and-update-verification",
description:
"Adds typed page context support and regenerates lint-safe application declarations before update verification.",
apply() {
// No source rewrite is required. The 0.8.8 CLI regenerates application
// declarations immediately before verification, repairing stale empty
// interface contracts produced by earlier releases.
},
},
]; ];
/** Release tooling uses this to require an explicit migration entry per version. */ /** Release tooling uses this to require an explicit migration entry per version. */
@@ -2297,6 +2313,10 @@ export function blockingVerificationChecks(checks: DoctorCheck[]): DoctorCheck[]
} }
function verifyApp(app: UpdatedApp): boolean { function verifyApp(app: UpdatedApp): boolean {
// Generated declarations must reflect the newly installed CLI before the
// application's lint/typecheck scripts inspect them. This also repairs stale
// generated output left by an older framework version.
if (existsSync(join(app.root, "app", "pages"))) generateApplicationTypes(app.root);
const health = inspectProject(app.root); const health = inspectProject(app.root);
const failed = blockingVerificationChecks(health); const failed = blockingVerificationChecks(health);
+3
View File
@@ -48,6 +48,9 @@ test("generate types emits application-wide deterministic contracts", () => {
expect(output).toContain('"/realtime/chat": RealtimeMessage<'); expect(output).toContain('"/realtime/chat": RealtimeMessage<');
expect(output).toContain("interface QueuePayloads"); expect(output).toContain("interface QueuePayloads");
expect(output).toContain('"email": QueuePayload<'); expect(output).toContain('"email": QueuePayload<');
expect(output).toContain("type MiddlewareContexts = Record<string, never>");
expect(output).toContain("type DatabaseQueries = Record<string, never>");
expect(output).not.toMatch(/interface\s+\w+\s*\{\s*\}/);
}); });
test("application checker validates every wrn source", () => { test("application checker validates every wrn source", () => {
+24
View File
@@ -169,6 +169,30 @@ test("update verification skips unavailable scripts", () => {
expect(verificationCommands({ check: "bun run lint" })).toEqual([["run", "check"]]); expect(verificationCommands({ check: "bun run lint" })).toEqual([["run", "check"]]);
}); });
test("0.6 type declarations are created only inside runnable apps", () => {
const workspaceRoot = mkdtempSync(join(tmpdir(), "wrnexus-update-workspace-"));
const appRoot = join(workspaceRoot, "apps", "web");
mkdirSync(join(appRoot, "app", "pages"), { recursive: true });
writeFileSync(
join(workspaceRoot, "package.json"),
JSON.stringify({ name: "workspace", wrnexus: { version: "0.5.9" } }),
);
writeFileSync(
join(appRoot, "package.json"),
JSON.stringify({ name: "web", wrnexus: { version: "0.5.9" } }),
);
try {
updateApp(workspaceRoot, "0.6.0", false);
updateApp(appRoot, "0.6.0", false);
expect(existsSync(join(workspaceRoot, "app", "types", "global.d.ts"))).toBe(false);
expect(existsSync(join(appRoot, "app", "types", "global.d.ts"))).toBe(true);
} finally {
rmSync(workspaceRoot, { recursive: true, force: true });
}
});
test("0.3 WRN source normalization is conservative and idempotent", async () => { test("0.3 WRN source normalization is conservative and idempotent", async () => {
const { migrateWrnSource } = await import("../src/update.ts"); const { migrateWrnSource } = await import("../src/update.ts");
const source = `component Card { const source = `component Card {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/content", "name": "@wrnexus/content",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Typed Markdown content collections, loaders, indexes, feeds, and preview workflows for WRNexusJS.", "description": "Typed Markdown content collections, loaders, indexes, feeds, and preview workflows for WRNexusJS.",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/csr", "name": "@wrnexus/csr",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-toolbar", "name": "@wrnexus/dev-toolbar",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/graphql", "name": "@wrnexus/graphql",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.", "description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/identity", "name": "@wrnexus/identity",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Enterprise federation, provisioning, machine identity, and privacy governance for WRNexusJS.", "description": "Enterprise federation, provisioning, machine identity, and privacy governance for WRNexusJS.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/image", "name": "@wrnexus/image",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Responsive image planning, secure remote image policies, and performance auditing for WRNexusJS.", "description": "Responsive image planning, secure remote image policies, and performance auditing for WRNexusJS.",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/language-server", "name": "@wrnexus/language-server",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Editor-neutral Language Server Protocol implementation for WRNexus .wrn files.", "description": "Editor-neutral Language Server Protocol implementation for WRNexus .wrn files.",
"main": "src/index.ts", "main": "src/index.ts",
+63 -2
View File
@@ -75,7 +75,10 @@ function diagnosticRange(diagnostic: WrnDiagnostic): Range {
}; };
} }
export function documentDiagnostics(document: TextDocument) { export function documentDiagnostics(
document: TextDocument,
options: { includeTypes?: boolean } = {},
) {
if (Buffer.byteLength(document.text, "utf8") > 1_048_576) { if (Buffer.byteLength(document.text, "utf8") > 1_048_576) {
return [ return [
{ {
@@ -96,7 +99,8 @@ export function documentDiagnostics(document: TextDocument) {
message: diagnostic.message, message: diagnostic.message,
}), }),
); );
if (syntax.some((diagnostic) => diagnostic.severity === 1)) return syntax; if (syntax.some((diagnostic) => diagnostic.severity === 1) || options.includeTypes === false)
return syntax;
const types = checkWrnSource(document.text, { filePath: documentPath(document.uri) }).map( const types = checkWrnSource(document.text, { filePath: documentPath(document.uri) }).map(
(diagnostic) => ({ (diagnostic) => ({
range: { range: {
@@ -235,4 +239,61 @@ export function hover(document: TextDocument, position: Position) {
export function completionItems() { export function completionItems() {
return WRN_COMPLETIONS.map((label) => ({ label, kind: 14, detail: "WRNexus language keyword" })); return WRN_COMPLETIONS.map((label) => ({ label, kind: 14, detail: "WRNexus language keyword" }));
} }
const semanticTokenTypes = ["variable"] as const;
const semanticTokenModifiers = ["declaration", "modification"] as const;
export const semanticTokensLegend = {
tokenTypes: [...semanticTokenTypes],
tokenModifiers: [...semanticTokenModifiers],
};
/** Encode state declarations, reads, and writes using the LSP relative token format. */
export function semanticTokens(document: TextDocument): { data: number[] } {
const declarations = new Map<string, Set<number>>();
for (const match of document.text.matchAll(
/\bstate\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?==|;|\r?$)/gm,
)) {
const name = match[1]!;
const offset = match.index! + match[0].lastIndexOf(name);
const offsets = declarations.get(name) ?? new Set<number>();
offsets.add(offset);
declarations.set(name, offsets);
}
const comments = [...document.text.matchAll(/\/\/[^\n]*|\/\*[\s\S]*?\*\//g)].map((match) => ({
start: match.index!,
end: match.index! + match[0].length,
}));
const tokens: Array<{ line: number; character: number; length: number; modifiers: number }> = [];
for (const [name, offsets] of declarations) {
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
for (const match of document.text.matchAll(
new RegExp(`(?<![A-Za-z0-9_$])${escaped}(?![A-Za-z0-9_$])`, "g"),
)) {
const offset = match.index!;
if (comments.some((range) => offset >= range.start && offset < range.end)) continue;
const position = positionAt(document.text, offset);
const after = document.text
.slice(offset + name.length)
.match(/^\s*(?:=|\+=|-=|\*=|\/=|%=|\+\+|--)/);
const before = document.text.slice(Math.max(0, offset - 8), offset).match(/(?:\+\+|--)\s*$/);
tokens.push({
...position,
length: name.length,
modifiers: offsets.has(offset) ? 1 : after || before ? 2 : 0,
});
}
}
tokens.sort((a, b) => a.line - b.line || a.character - b.character);
const data: number[] = [];
let previousLine = 0;
let previousCharacter = 0;
for (const token of tokens) {
const deltaLine = token.line - previousLine;
const deltaCharacter = deltaLine === 0 ? token.character - previousCharacter : token.character;
data.push(deltaLine, deltaCharacter, token.length, 0, token.modifiers);
previousLine = token.line;
previousCharacter = token.character;
}
return { data };
}
export * from "./workspace.ts"; export * from "./workspace.ts";
+113 -16
View File
@@ -6,10 +6,15 @@ import {
documentSymbols, documentSymbols,
formatDocument, formatDocument,
hover, hover,
symbolLocations, offsetAt,
positionAt,
semanticTokens,
semanticTokensLegend,
wordAt, wordAt,
virtualTypeScriptDocument, virtualTypeScriptDocument,
workspaceCompletionItems, workspaceCompletionItems,
workspaceSymbolLocations,
clearWorkspaceIndexCache,
extractComponentRefactor, extractComponentRefactor,
htmlToWrn, htmlToWrn,
type TextDocument, type TextDocument,
@@ -47,9 +52,9 @@ function internalDiagnostic(document: TextDocument, error: unknown) {
}; };
} }
function safeDocumentDiagnostics(document: TextDocument) { function safeDocumentDiagnostics(document: TextDocument, includeTypes = true) {
try { try {
return documentDiagnostics(document); return documentDiagnostics(document, { includeTypes });
} catch (error) { } catch (error) {
process.stderr.write( process.stderr.write(
`[wrnexus-lsp] diagnostics failed for ${document.uri}: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`, `[wrnexus-lsp] diagnostics failed for ${document.uri}: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`,
@@ -58,11 +63,11 @@ function safeDocumentDiagnostics(document: TextDocument) {
} }
} }
function publish(document: TextDocument): void { function publish(document: TextDocument, includeTypes = true): void {
send({ send({
jsonrpc: "2.0", jsonrpc: "2.0",
method: "textDocument/publishDiagnostics", method: "textDocument/publishDiagnostics",
params: { uri: document.uri, diagnostics: safeDocumentDiagnostics(document) }, params: { uri: document.uri, diagnostics: safeDocumentDiagnostics(document, includeTypes) },
}); });
} }
@@ -80,7 +85,7 @@ function schedulePublish(document: TextDocument): void {
setTimeout(() => { setTimeout(() => {
diagnosticTimers.delete(document.uri); diagnosticTimers.delete(document.uri);
const current = documents.get(document.uri); const current = documents.get(document.uri);
if (current && current.version === expectedVersion) publish(current); if (current && current.version === expectedVersion) publish(current, false);
}, DIAGNOSTIC_DEBOUNCE_MS), }, DIAGNOSTIC_DEBOUNCE_MS),
); );
} }
@@ -104,7 +109,7 @@ async function handle(message: JsonRpc): Promise<void> {
result(message.id, { result(message.id, {
serverInfo: { name: "WRNexus Language Server", version: "0.8.3" }, serverInfo: { name: "WRNexus Language Server", version: "0.8.3" },
capabilities: { capabilities: {
textDocumentSync: 1, textDocumentSync: { openClose: true, change: 1, save: true },
documentFormattingProvider: true, documentFormattingProvider: true,
completionProvider: { triggerCharacters: ["<", "@", ":", "."] }, completionProvider: { triggerCharacters: ["<", "@", ":", "."] },
hoverProvider: true, hoverProvider: true,
@@ -112,8 +117,14 @@ async function handle(message: JsonRpc): Promise<void> {
referencesProvider: true, referencesProvider: true,
renameProvider: { prepareProvider: true }, renameProvider: { prepareProvider: true },
documentSymbolProvider: true, documentSymbolProvider: true,
semanticTokensProvider: { legend: semanticTokensLegend, full: true },
codeActionProvider: { codeActionProvider: {
codeActionKinds: ["quickfix", "refactor.extract", "refactor.rewrite"], codeActionKinds: [
"quickfix",
"refactor.extract",
"refactor.rewrite",
"source.organizeImports",
],
}, },
experimental: { wrnexusVirtualTypeScript: true }, experimental: { wrnexusVirtualTypeScript: true },
}, },
@@ -127,6 +138,9 @@ async function handle(message: JsonRpc): Promise<void> {
result(message.id, null); result(message.id, null);
break; break;
case "exit": case "exit":
for (const timer of diagnosticTimers.values()) clearTimeout(timer);
diagnosticTimers.clear();
documents.clear();
process.exit(0); process.exit(0);
break; break;
case "textDocument/didOpen": { case "textDocument/didOpen": {
@@ -146,6 +160,15 @@ async function handle(message: JsonRpc): Promise<void> {
} }
break; break;
} }
case "textDocument/didSave": {
const document = documents.get(params.textDocument.uri);
if (document) {
clearDiagnosticTimer(document.uri);
clearWorkspaceIndexCache(workspaceRoot);
publish(document, true);
}
break;
}
case "textDocument/didClose": case "textDocument/didClose":
clearDiagnosticTimer(params.textDocument.uri); clearDiagnosticTimer(params.textDocument.uri);
documents.delete(params.textDocument.uri); documents.delete(params.textDocument.uri);
@@ -180,6 +203,11 @@ async function handle(message: JsonRpc): Promise<void> {
result(message.id, document ? documentSymbols(document) : []); result(message.id, document ? documentSymbols(document) : []);
break; break;
} }
case "textDocument/semanticTokens/full": {
const document = documents.get(params.textDocument.uri);
result(message.id, document ? semanticTokens(document) : { data: [] });
break;
}
case "textDocument/hover": { case "textDocument/hover": {
const document = documents.get(params.textDocument.uri); const document = documents.get(params.textDocument.uri);
result(message.id, document ? hover(document, params.position) : null); result(message.id, document ? hover(document, params.position) : null);
@@ -192,7 +220,8 @@ async function handle(message: JsonRpc): Promise<void> {
} }
case "textDocument/references": { case "textDocument/references": {
const document = documents.get(params.textDocument.uri); const document = documents.get(params.textDocument.uri);
result(message.id, document ? symbolLocations(document, params.position) : []); const selected = document ? wordAt(document.text, params.position) : null;
result(message.id, selected ? workspaceSymbolLocations(workspaceRoot, selected.word) : []);
break; break;
} }
case "textDocument/prepareRename": { case "textDocument/prepareRename": {
@@ -202,13 +231,16 @@ async function handle(message: JsonRpc): Promise<void> {
} }
case "textDocument/rename": { case "textDocument/rename": {
const document = documents.get(params.textDocument.uri); const document = documents.get(params.textDocument.uri);
const edits = document const selected = document ? wordAt(document.text, params.position) : null;
? symbolLocations(document, params.position).map(({ range }) => ({ if (!selected || !/^[A-Za-z_$][\w$]*$/.test(params.newName)) {
range, result(message.id, null);
newText: params.newName, break;
})) }
: []; const changes: Record<string, Array<{ range: any; newText: string }>> = {};
result(message.id, document ? { changes: { [document.uri]: edits } } : null); for (const location of workspaceSymbolLocations(workspaceRoot, selected.word)) {
(changes[location.uri] ??= []).push({ range: location.range, newText: params.newName });
}
result(message.id, { changes });
break; break;
} }
case "textDocument/codeAction": { case "textDocument/codeAction": {
@@ -231,6 +263,71 @@ async function handle(message: JsonRpc): Promise<void> {
}, },
}, },
})); }));
const imports = [
...document.text.matchAll(
/^import(?:\s+type)?[\s\S]*?(?:;\s*|\n(?=import|\s*(?:page|component|layout|global\s+store|page\s+store)\b))/gm,
),
].map((match) => ({
start: match.index!,
end: match.index! + match[0].length,
text: match[0].trim(),
}));
if (imports.length > 1) {
const ordered = [...imports].sort((a, b) => {
const aType = /^import\s+type\b/.test(a.text) ? 0 : 1;
const bType = /^import\s+type\b/.test(b.text) ? 0 : 1;
return aType - bType || a.text.localeCompare(b.text);
});
const replacement = ordered.map((entry) => entry.text.replace(/;$/, "")).join("\n") + "\n";
const start = imports[0]!.start;
const end = imports.at(-1)!.end;
if (document.text.slice(start, end) !== replacement) {
actions.push({
title: "Organize WRN imports",
kind: "source.organizeImports",
edit: {
changes: {
[document.uri]: [
{
range: {
start: positionAt(document.text, start),
end: positionAt(document.text, end),
},
newText: replacement,
},
],
},
},
});
}
}
for (const diagnostic of params.context?.diagnostics ?? []) {
if (diagnostic.code !== "WRN-OUTPUT-LEGACY-EMIT") continue;
const start = offsetAt(document.text, diagnostic.range.start);
const end = offsetAt(document.text, diagnostic.range.end);
const source = document.text.slice(start, end);
const match = /\$emit\(\s*["']([A-Za-z_$][\w$]*)["']\s*,?/.exec(source);
if (!match) continue;
actions.push({
title: `Convert $emit to output.${match[1]}`,
kind: "quickfix",
diagnostics: [diagnostic],
isPreferred: true,
edit: {
changes: {
[document.uri]: [
{
range: diagnostic.range,
newText: source.replace(
/\$emit\(\s*["'][A-Za-z_$][\w$]*["']\s*,?\s*/,
`output.${match[1]}(`,
),
},
],
},
},
});
}
const selected = document.text.slice( const selected = document.text.slice(
document.text document.text
.split(/\r?\n/) .split(/\r?\n/)
+27 -1
View File
@@ -15,7 +15,9 @@ const SKIPPED_DIRECTORIES = new Set([
]); ]);
const MAX_INDEX_FILES = 5_000; const MAX_INDEX_FILES = 5_000;
const MAX_SOURCE_BYTES = 1_048_576; const MAX_SOURCE_BYTES = 1_048_576;
const INDEX_TTL_MS = 5_000; // Completion can be requested repeatedly while the list is visible. Avoid
// rescanning thousands of files on that hot path; document saves invalidate it.
const INDEX_TTL_MS = 5 * 60_000;
const MAX_CACHED_ROOTS = 8; const MAX_CACHED_ROOTS = 8;
const workspaceIndexCache = new Map< const workspaceIndexCache = new Map<
string, string,
@@ -168,6 +170,30 @@ export function clearWorkspaceIndexCache(root?: string): void {
else workspaceIndexCache.clear(); else workspaceIndexCache.clear();
} }
export function workspaceSymbolLocations(root: string, name: string, limit = 1_000) {
if (!/^[A-Za-z_$][\w$]*$/.test(name)) return [];
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const pattern = new RegExp(`(?<![A-Za-z0-9_$])${escaped}(?![A-Za-z0-9_$])`, "g");
const locations: Array<{ uri: string; range: Range }> = [];
for (const file of walk(resolve(root), (path) => extname(path) === ".wrn").slice(0, limit)) {
let source: string;
try {
source = readFileSync(file, "utf8");
} catch {
continue;
}
for (const match of source.matchAll(pattern)) {
const before = source.slice(0, match.index!).split(/\r?\n/);
const start = { line: before.length - 1, character: before.at(-1)?.length ?? 0 };
locations.push({
uri: `file:///${file.replace(/\\/g, "/")}`,
range: { start, end: { line: start.line, character: start.character + name.length } },
});
}
}
return locations;
}
export function extractComponentRefactor(document: TextDocument, range: Range, name: string) { export function extractComponentRefactor(document: TextDocument, range: Range, name: string) {
if (!/^[A-Z][A-Za-z0-9_$]*$/.test(name)) throw new Error("Component name must use PascalCase"); if (!/^[A-Z][A-Za-z0-9_$]*$/.test(name)) throw new Error("Component name must use PascalCase");
const lines = document.text.split(/\r?\n/); const lines = document.text.split(/\r?\n/);
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mcp", "name": "@wrnexus/mcp",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Model Context Protocol server exposing WRNexus application and framework context.", "description": "Model Context Protocol server exposing WRNexus application and framework context.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/observability", "name": "@wrnexus/observability",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Metrics, Web Vitals collection, request instrumentation, and exporter adapters for WRNexusJS.", "description": "Metrics, Web Vitals collection, request instrumentation, and exporter adapters for WRNexusJS.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/playground", "name": "@wrnexus/playground",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Secure, shareable WRNexus compiler and UI playground.", "description": "Secure, shareable WRNexus compiler and UI playground.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/plugin", "name": "@wrnexus/plugin",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pwa", "name": "@wrnexus/pwa",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Service workers, manifests, offline queues, background sync, push, and conflict resolution for WRNexusJS.", "description": "Service workers, manifests, offline queues, background sync, push, and conflict resolution for WRNexusJS.",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/realtime", "name": "@wrnexus/realtime",
"version": "0.8.7", "version": "0.8.8",
"description": "Typed realtime rooms, message helpers, presence utilities, package UI blocks, and WRNexusJS integration.", "description": "Typed realtime rooms, message helpers, presence utilities, package UI blocks, and WRNexusJS integration.",
"type": "module", "type": "module",
"sideEffects": false, "sideEffects": false,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/rpc", "name": "@wrnexus/rpc",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/security", "name": "@wrnexus/security",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"description": "Security policies, safe serialization, SSRF protection, request limits, and secure cookie helpers for WRNexusJS.", "description": "Security policies, safe serialization, SSRF protection, request limits, and secure cookie helpers for WRNexusJS.",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/store", "name": "@wrnexus/store",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/syntax", "name": "@wrnexus/syntax",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/tracking", "name": "@wrnexus/tracking",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/typecheck", "name": "@wrnexus/typecheck",
"version": "0.8.7", "version": "0.8.8",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+4
View File
@@ -239,6 +239,10 @@ export function virtualTypeScriptModule(
append(retainedImports(ast), ast.imports[0]); append(retainedImports(ast), ast.imports[0]);
append(ast.types.join("\n\n"), ast.types[0]?.trim()); append(ast.types.join("\n\n"), ast.types[0]?.trim());
// Pages, components, and stores are rendered with the framework request
// context in scope. Mirror the compiler-generated function parameter here so
// expressions such as `state user = ctx.user` type-check in the editor.
append('declare const ctx: import("@wrnexus/core").Context;');
for (const prop of ast.props) { for (const prop of ast.props) {
// JavaScript keywords such as the conventional `class` component prop are // JavaScript keywords such as the conventional `class` component prop are
+12
View File
@@ -73,3 +73,15 @@ test("treats omitted runtime parameter types as dynamic JavaScript values", () =
); );
expect(diagnostics.filter((diagnostic) => diagnostic.code.startsWith("WRN-TYPE-"))).toEqual([]); expect(diagnostics.filter((diagnostic) => diagnostic.code.startsWith("WRN-TYPE-"))).toEqual([]);
}); });
test("provides the framework context to page state expressions", () => {
const root = app();
const diagnostics = checkWrnSource(
`page AccountPage {
state user = ctx.user
view { <main></main> }
}`,
{ appRoot: root, filePath: join(root, "app", "pages", "account.wrn") },
);
expect(diagnostics.some((diagnostic) => diagnostic.code === "WRN-TYPE-2304")).toBe(false);
});
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ui", "name": "@wrnexus/ui",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/validation", "name": "@wrnexus/validation",
"version": "0.8.7", "version": "0.8.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
+6 -1
View File
@@ -1,7 +1,7 @@
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path"; import { join } from "node:path";
const version = "0.8.7"; const version = "0.8.8";
const dependencyGroups = [ const dependencyGroups = [
"dependencies", "dependencies",
@@ -10,6 +10,11 @@ const dependencyGroups = [
"optionalDependencies", "optionalDependencies",
]; ];
const rootPackageFile = "package.json";
const rootPackage = JSON.parse(readFileSync(rootPackageFile, "utf8"));
rootPackage.version = version;
writeFileSync(rootPackageFile, `${JSON.stringify(rootPackage, null, 2)}\n`);
for (const entry of readdirSync("packages", { for (const entry of readdirSync("packages", {
withFileTypes: true, withFileTypes: true,
})) { })) {