release: WRNexusJS 0.2.29

This commit is contained in:
2026-07-14 17:34:51 +05:30
parent e27c92e3b6
commit e6dbb5b0fc
62 changed files with 513 additions and 292 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.2.28", "version": "0.2.29",
"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",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/authz", "name": "@wrnexus/authz",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/authz — part of the WrNexus framework.", "description": "@wrnexus/authz — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+11 -11
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/cli — part of the WrNexus framework.", "description": "@wrnexus/cli — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -28,16 +28,16 @@
"wrnexus": "./dist/index.js" "wrnexus": "./dist/index.js"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.28", "@wrnexus/core": "^0.2.29",
"@wrnexus/router": "^0.2.28", "@wrnexus/router": "^0.2.29",
"@wrnexus/csr": "^0.2.28", "@wrnexus/csr": "^0.2.29",
"@wrnexus/compiler": "^0.2.28", "@wrnexus/compiler": "^0.2.29",
"@wrnexus/styles": "^0.2.28", "@wrnexus/styles": "^0.2.29",
"@wrnexus/dev-server": "^0.2.28", "@wrnexus/dev-server": "^0.2.29",
"@wrnexus/ui": "^0.2.28", "@wrnexus/ui": "^0.2.29",
"@wrnexus/validation": "^0.2.28", "@wrnexus/validation": "^0.2.29",
"@wrnexus/i18n": "^0.2.28", "@wrnexus/i18n": "^0.2.29",
"@wrnexus/db": "^0.2.28" "@wrnexus/db": "^0.2.29"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/compiler — part of the WrNexus framework.", "description": "@wrnexus/compiler — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/csr — part of the WrNexus framework.", "description": "@wrnexus/csr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.28" "@wrnexus/core": "^0.2.29"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/db", "name": "@wrnexus/db",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/db — part of the WrNexus framework.", "description": "@wrnexus/db — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+13 -13
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.2.28", "version": "0.2.29",
"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",
@@ -25,18 +25,18 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.28", "@wrnexus/core": "^0.2.29",
"@wrnexus/router": "^0.2.28", "@wrnexus/router": "^0.2.29",
"@wrnexus/ssr": "^0.2.28", "@wrnexus/ssr": "^0.2.29",
"@wrnexus/csr": "^0.2.28", "@wrnexus/csr": "^0.2.29",
"@wrnexus/compiler": "^0.2.28", "@wrnexus/compiler": "^0.2.29",
"@wrnexus/styles": "^0.2.28", "@wrnexus/styles": "^0.2.29",
"@wrnexus/ui": "^0.2.28", "@wrnexus/ui": "^0.2.29",
"@wrnexus/validation": "^0.2.28", "@wrnexus/validation": "^0.2.29",
"@wrnexus/i18n": "^0.2.28", "@wrnexus/i18n": "^0.2.29",
"@wrnexus/db": "^0.2.28", "@wrnexus/db": "^0.2.29",
"@wrnexus/pubsub": "^0.2.28", "@wrnexus/pubsub": "^0.2.29",
"@wrnexus/uploader": "^0.2.28" "@wrnexus/uploader": "^0.2.29"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/encryption — part of the WrNexus framework.", "description": "@wrnexus/encryption — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/helpers", "name": "@wrnexus/helpers",
"version": "0.2.28", "version": "0.2.29",
"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",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.28" "@wrnexus/core": "^0.2.29"
}, },
"files": [ "files": [
"dist" "dist"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/i18n — part of the WrNexus framework.", "description": "@wrnexus/i18n — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.28" "@wrnexus/core": "^0.2.29"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/jwt", "name": "@wrnexus/jwt",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/jwt — part of the WrNexus framework.", "description": "@wrnexus/jwt — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/mobile", "name": "@wrnexus/mobile",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/mobile — part of the WrNexus framework.", "description": "@wrnexus/mobile — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/native": "^0.2.28" "@wrnexus/native": "^0.2.29"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/native", "name": "@wrnexus/native",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/native — part of the WrNexus framework.", "description": "@wrnexus/native — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/oauth", "name": "@wrnexus/oauth",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/oauth — part of the WrNexus framework.", "description": "@wrnexus/oauth — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/pubsub", "name": "@wrnexus/pubsub",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/pubsub — part of the WrNexus framework.", "description": "@wrnexus/pubsub — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/queue", "name": "@wrnexus/queue",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/queue — part of the WrNexus framework.", "description": "@wrnexus/queue — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/reactive", "name": "@wrnexus/reactive",
"version": "0.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/router — part of the WrNexus framework.", "description": "@wrnexus/router — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,8 +21,8 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/compiler": "^0.2.28", "@wrnexus/compiler": "^0.2.29",
"@wrnexus/core": "^0.2.28" "@wrnexus/core": "^0.2.29"
}, },
"files": [ "files": [
"dist" "dist"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/ssr — part of the WrNexus framework.", "description": "@wrnexus/ssr — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.28" "@wrnexus/core": "^0.2.29"
}, },
"files": [ "files": [
"dist" "dist"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/styles — part of the WrNexus framework.", "description": "@wrnexus/styles — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/uploader": "^0.2.28" "@wrnexus/uploader": "^0.2.29"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/test", "name": "@wrnexus/test",
"version": "0.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/ui — part of the WrNexus framework.", "description": "@wrnexus/ui — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -22,7 +22,7 @@
"./ui.css": "./ui.css" "./ui.css": "./ui.css"
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.28" "@wrnexus/core": "^0.2.29"
}, },
"files": [ "files": [
"dist", "dist",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/uploader", "name": "@wrnexus/uploader",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/uploader — part of the WrNexus framework.", "description": "@wrnexus/uploader — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
@@ -21,7 +21,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@wrnexus/core": "^0.2.28" "@wrnexus/core": "^0.2.29"
}, },
"files": [ "files": [
"dist" "dist"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/validation", "name": "@wrnexus/validation",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"description": "@wrnexus/validation — part of the WrNexus framework.", "description": "@wrnexus/validation — part of the WrNexus framework.",
"license": "MIT", "license": "MIT",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "wrnexus", "name": "wrnexus",
"displayName": "WRNexus Language Support", "displayName": "WRNexus Language Support",
"description": "Syntax highlighting, formatting, diagnostics, snippets and completions for WrNexus .wrn files.", "description": "Syntax highlighting, formatting, diagnostics, snippets and completions for WrNexus .wrn files.",
"version": "0.2.5", "version": "0.2.6",
"publisher": "wrnexus", "publisher": "wrnexus",
"private": true, "private": true,
"license": "SEE LICENSE IN LICENSE", "license": "SEE LICENSE IN LICENSE",
+8 -25
View File
@@ -2,14 +2,10 @@
const vscode = require("vscode"); const vscode = require("vscode");
const COMPONENT_DECLARATION = const COMPONENT_DECLARATION = /^\s*(component|layout)\s+([A-Za-z_$][\w$]*)\s*\{/gm;
/^\s*(component|layout)\s+([A-Za-z_$][\w$]*)\s*\{/gm;
function getTagAtPosition(document, position) { function getTagAtPosition(document, position) {
const range = document.getWordRangeAtPosition( const range = document.getWordRangeAtPosition(position, /[A-Za-z_$][\w$]*/);
position,
/[A-Za-z_$][\w$]*/,
);
if (!range) { if (!range) {
return null; return null;
@@ -23,17 +19,13 @@ function getTagAtPosition(document, position) {
const line = document.lineAt(position.line).text; const line = document.lineAt(position.line).text;
const offset = document.offsetAt(position); const offset = document.offsetAt(position);
const lineStart = document.offsetAt( const lineStart = document.offsetAt(new vscode.Position(position.line, 0));
new vscode.Position(position.line, 0),
);
const characterOffset = offset - lineStart; const characterOffset = offset - lineStart;
const before = line.slice(0, characterOffset); const before = line.slice(0, characterOffset);
const after = line.slice(characterOffset); const after = line.slice(characterOffset);
const insideTag = const insideTag = before.lastIndexOf("<") > before.lastIndexOf(">") && after.includes(">");
before.lastIndexOf("<") > before.lastIndexOf(">") &&
after.includes(">");
if (!insideTag) { if (!insideTag) {
return null; return null;
@@ -75,21 +67,12 @@ async function findDeclaration(name) {
continue; continue;
} }
const nameOffset = const nameOffset = match.index + match[0].indexOf(declarationName);
match.index +
match[0].indexOf(declarationName);
const start = document.positionAt(nameOffset); const start = document.positionAt(nameOffset);
const end = document.positionAt( const end = document.positionAt(nameOffset + declarationName.length);
nameOffset + declarationName.length,
);
matches.push( matches.push(new vscode.Location(uri, new vscode.Range(start, end)));
new vscode.Location(
uri,
new vscode.Range(start, end),
),
);
} }
} }
@@ -131,4 +114,4 @@ module.exports = {
getTagAtPosition, getTagAtPosition,
provideDefinition, provideDefinition,
registerDefinitionProvider, registerDefinitionProvider,
}; };
+55 -160
View File
@@ -5,14 +5,9 @@ const vscode = require("vscode");
const COLLECTION_NAME = "wrnexus"; const COLLECTION_NAME = "wrnexus";
const WRN_LANGUAGE_ID = "wrn"; const WRN_LANGUAGE_ID = "wrn";
const TOP_LEVEL_PATTERN = const TOP_LEVEL_PATTERN = /^\s*(page|component|layout)\s+([A-Za-z_$][\w$]*)\s*\{/;
/^\s*(page|component|layout)\s+([A-Za-z_$][\w$]*)\s*\{/;
const VALID_TOP_LEVEL_KINDS = new Set([ const VALID_TOP_LEVEL_KINDS = new Set(["page", "component", "layout"]);
"page",
"component",
"layout",
]);
const VALID_MEMBERS = { const VALID_MEMBERS = {
page: new Set([ page: new Set([
@@ -28,21 +23,9 @@ const VALID_MEMBERS = {
"realtime", "realtime",
]), ]),
component: new Set([ component: new Set(["props", "state", "view", "style", "functions"]),
"props",
"state",
"view",
"style",
"functions",
]),
layout: new Set([ layout: new Set(["props", "state", "view", "style", "functions"]),
"props",
"state",
"view",
"style",
"functions",
]),
}; };
function createDiagnostic( function createDiagnostic(
@@ -54,10 +37,7 @@ function createDiagnostic(
code, code,
) { ) {
const diagnostic = new vscode.Diagnostic( const diagnostic = new vscode.Diagnostic(
new vscode.Range( new vscode.Range(document.positionAt(startOffset), document.positionAt(endOffset)),
document.positionAt(startOffset),
document.positionAt(endOffset),
),
message, message,
severity, severity,
); );
@@ -80,11 +60,7 @@ function lineDiagnostic(
) { ) {
const line = document.lineAt(lineNumber); const line = document.lineAt(lineNumber);
const diagnostic = new vscode.Diagnostic( const diagnostic = new vscode.Diagnostic(line.range, message, severity);
line.range,
message,
severity,
);
diagnostic.source = "WRNexus"; diagnostic.source = "WRNexus";
@@ -96,18 +72,14 @@ function lineDiagnostic(
} }
function stripComments(source) { function stripComments(source) {
return source.replace(/<!--[\s\S]*?-->/g, (comment) => return source.replace(/<!--[\s\S]*?-->/g, (comment) => comment.replace(/[^\n]/g, " "));
comment.replace(/[^\n]/g, " "),
);
} }
function findTopLevelDeclaration(document, source) { function findTopLevelDeclaration(document, source) {
const match = TOP_LEVEL_PATTERN.exec(source); const match = TOP_LEVEL_PATTERN.exec(source);
if (!match) { if (!match) {
const firstMeaningfulLine = source const firstMeaningfulLine = source.split(/\r?\n/).findIndex((line) => line.trim().length > 0);
.split(/\r?\n/)
.findIndex((line) => line.trim().length > 0);
return { return {
diagnostic: lineDiagnostic( diagnostic: lineDiagnostic(
@@ -166,9 +138,7 @@ function validateBalancedCharacters(document, source) {
continue; continue;
} }
if ( if (source.startsWith("<!--", index)) {
source.startsWith("<!--", index)
) {
const commentEnd = source.indexOf("-->", index + 4); const commentEnd = source.indexOf("-->", index + 4);
if (commentEnd === -1) { if (commentEnd === -1) {
@@ -190,11 +160,7 @@ function validateBalancedCharacters(document, source) {
continue; continue;
} }
if ( if (character === "{" || character === "[" || character === "(") {
character === "{" ||
character === "[" ||
character === "("
) {
stack.push({ stack.push({
character, character,
offset: index, offset: index,
@@ -203,11 +169,7 @@ function validateBalancedCharacters(document, source) {
continue; continue;
} }
if ( if (character === "}" || character === "]" || character === ")") {
character === "}" ||
character === "]" ||
character === ")"
) {
const expectedOpening = pairs[character]; const expectedOpening = pairs[character];
const opening = stack.pop(); const opening = stack.pop();
@@ -227,12 +189,7 @@ function validateBalancedCharacters(document, source) {
} }
for (const opening of stack) { for (const opening of stack) {
const expectedClosing = const expectedClosing = opening.character === "{" ? "}" : opening.character === "[" ? "]" : ")";
opening.character === "{"
? "}"
: opening.character === "["
? "]"
: ")";
diagnostics.push( diagnostics.push(
createDiagnostic( createDiagnostic(
@@ -284,8 +241,7 @@ function validateHtmlTags(document, source) {
]); ]);
const cleaned = stripComments(source); const cleaned = stripComments(source);
const tagPattern = const tagPattern = /<\/?([A-Za-z][A-Za-z0-9_$:.-]*)(?:\s[\s\S]*?)?\/?>/g;
/<\/?([A-Za-z][A-Za-z0-9_$:.-]*)(?:\s[\s\S]*?)?\/?>/g;
let match; let match;
@@ -358,18 +314,13 @@ function validateHtmlTags(document, source) {
} }
function getRootBodyRange(source, rootMatch) { function getRootBodyRange(source, rootMatch) {
const openingBrace = const openingBrace = rootMatch.index + rootMatch[0].lastIndexOf("{");
rootMatch.index + rootMatch[0].lastIndexOf("{");
let depth = 0; let depth = 0;
let quote = null; let quote = null;
let escaped = false; let escaped = false;
for ( for (let index = openingBrace; index < source.length; index += 1) {
let index = openingBrace;
index < source.length;
index += 1
) {
const character = source[index]; const character = source[index];
if (quote !== null) { if (quote !== null) {
@@ -425,6 +376,12 @@ function findRootMembers(source, bodyStart, bodyEnd) {
let quote = null; let quote = null;
let escaped = false; let escaped = false;
const skipLine = () => {
while (index < bodyEnd && source[index] !== "\n" && source[index] !== "\r") {
index += 1;
}
};
while (index < bodyEnd) { while (index < bodyEnd) {
const character = source[index]; const character = source[index];
@@ -473,17 +430,11 @@ function findRootMembers(source, bodyStart, bodyEnd) {
continue; continue;
} }
if ( if (depth === 0 && /[A-Za-z_]/.test(character)) {
depth === 0 &&
/[A-Za-z_]/.test(character)
) {
const start = index; const start = index;
index += 1; index += 1;
while ( while (index < bodyEnd && /[A-Za-z0-9_-]/.test(source[index])) {
index < bodyEnd &&
/[A-Za-z0-9_-]/.test(source[index])
) {
index += 1; index += 1;
} }
@@ -495,6 +446,11 @@ function findRootMembers(source, bodyStart, bodyEnd) {
end: index, end: index,
}); });
// Single-line declarations must skip the rest of the line.
if (name === "state" || name === "layout") {
skipLine();
}
continue; continue;
} }
@@ -504,12 +460,7 @@ function findRootMembers(source, bodyStart, bodyEnd) {
return members; return members;
} }
function validateRootMembers( function validateRootMembers(document, source, rootKind, rootMatch) {
document,
source,
rootKind,
rootMatch,
) {
const diagnostics = []; const diagnostics = [];
const allowed = VALID_MEMBERS[rootKind]; const allowed = VALID_MEMBERS[rootKind];
@@ -518,11 +469,7 @@ function validateRootMembers(
} }
const bodyRange = getRootBodyRange(source, rootMatch); const bodyRange = getRootBodyRange(source, rootMatch);
const members = findRootMembers( const members = findRootMembers(source, bodyRange.start, bodyRange.end);
source,
bodyRange.start,
bodyRange.end,
);
for (const member of members) { for (const member of members) {
if (allowed.has(member.name)) { if (allowed.has(member.name)) {
@@ -544,12 +491,7 @@ function validateRootMembers(
return diagnostics; return diagnostics;
} }
function validateRequiredView( function validateRequiredView(document, source, rootKind, rootMatch) {
document,
source,
rootKind,
rootMatch,
) {
const bodyRange = getRootBodyRange(source, rootMatch); const bodyRange = getRootBodyRange(source, rootMatch);
const body = source.slice(bodyRange.start, bodyRange.end); const body = source.slice(bodyRange.start, bodyRange.end);
@@ -569,17 +511,10 @@ function validateRequiredView(
]; ];
} }
function validateLayoutUsage( function validateLayoutUsage(document, source, rootKind) {
document,
source,
rootKind,
) {
const diagnostics = []; const diagnostics = [];
if ( if (rootKind !== "page" && /^\s*layout\s*=/m.test(source)) {
rootKind !== "page" &&
/^\s*layout\s*=/m.test(source)
) {
const match = /^\s*layout\s*=/m.exec(source); const match = /^\s*layout\s*=/m.exec(source);
if (match) { if (match) {
@@ -588,7 +523,7 @@ function validateLayoutUsage(
document, document,
match.index, match.index,
match.index + match[0].length, match.index + match[0].length,
"`layout = \"...\"` is only valid inside a page.", '`layout = "..."` is only valid inside a page.',
vscode.DiagnosticSeverity.Error, vscode.DiagnosticSeverity.Error,
"wrn-invalid-layout-member", "wrn-invalid-layout-member",
), ),
@@ -611,16 +546,11 @@ function validateDocument(document) {
} }
const diagnostics = []; const diagnostics = [];
const declaration = findTopLevelDeclaration( const declaration = findTopLevelDeclaration(document, source);
document,
source,
);
if (declaration.diagnostic) { if (declaration.diagnostic) {
diagnostics.push(declaration.diagnostic); diagnostics.push(declaration.diagnostic);
diagnostics.push( diagnostics.push(...validateBalancedCharacters(document, source));
...validateBalancedCharacters(document, source),
);
return diagnostics; return diagnostics;
} }
@@ -630,8 +560,7 @@ function validateDocument(document) {
createDiagnostic( createDiagnostic(
document, document,
declaration.match.index, declaration.match.index,
declaration.match.index + declaration.match.index + declaration.match[0].length,
declaration.match[0].length,
`Unsupported WRN declaration \`${declaration.kind}\`.`, `Unsupported WRN declaration \`${declaration.kind}\`.`,
vscode.DiagnosticSeverity.Error, vscode.DiagnosticSeverity.Error,
"wrn-invalid-kind", "wrn-invalid-kind",
@@ -641,48 +570,21 @@ function validateDocument(document) {
return diagnostics; return diagnostics;
} }
diagnostics.push( diagnostics.push(...validateBalancedCharacters(document, source));
...validateBalancedCharacters(document, source),
);
diagnostics.push( diagnostics.push(...validateHtmlTags(document, source));
...validateHtmlTags(document, source),
);
diagnostics.push( diagnostics.push(...validateRootMembers(document, source, declaration.kind, declaration.match));
...validateRootMembers(
document,
source,
declaration.kind,
declaration.match,
),
);
diagnostics.push( diagnostics.push(...validateRequiredView(document, source, declaration.kind, declaration.match));
...validateRequiredView(
document,
source,
declaration.kind,
declaration.match,
),
);
diagnostics.push( diagnostics.push(...validateLayoutUsage(document, source, declaration.kind));
...validateLayoutUsage(
document,
source,
declaration.kind,
),
);
return diagnostics; return diagnostics;
} }
function registerDiagnostics(context) { function registerDiagnostics(context) {
const collection = const collection = vscode.languages.createDiagnosticCollection(COLLECTION_NAME);
vscode.languages.createDiagnosticCollection(
COLLECTION_NAME,
);
const timers = new Map(); const timers = new Map();
@@ -691,9 +593,7 @@ function registerDiagnostics(context) {
return; return;
} }
const previousTimer = timers.get( const previousTimer = timers.get(document.uri.toString());
document.uri.toString(),
);
if (previousTimer) { if (previousTimer) {
clearTimeout(previousTimer); clearTimeout(previousTimer);
@@ -702,10 +602,7 @@ function registerDiagnostics(context) {
const timer = setTimeout(() => { const timer = setTimeout(() => {
timers.delete(document.uri.toString()); timers.delete(document.uri.toString());
collection.set( collection.set(document.uri, validateDocument(document));
document.uri,
validateDocument(document),
);
}, 150); }, 150);
timers.set(document.uri.toString(), timer); timers.set(document.uri.toString(), timer);
@@ -726,19 +623,17 @@ function registerDiagnostics(context) {
vscode.workspace.onDidSaveTextDocument(update), vscode.workspace.onDidSaveTextDocument(update),
vscode.workspace.onDidCloseTextDocument( vscode.workspace.onDidCloseTextDocument((document) => {
(document) => { const key = document.uri.toString();
const key = document.uri.toString(); const timer = timers.get(key);
const timer = timers.get(key);
if (timer) { if (timer) {
clearTimeout(timer); clearTimeout(timer);
timers.delete(key); timers.delete(key);
} }
collection.delete(document.uri); collection.delete(document.uri);
}, }),
),
{ {
dispose() { dispose() {
@@ -758,4 +653,4 @@ module.exports = {
validateDocument, validateDocument,
validateHtmlTags, validateHtmlTags,
validateRootMembers, validateRootMembers,
}; };
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ai", "name": "@wrnexus/ai",
"version": "0.2.28", "version": "0.2.29",
"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/authz", "name": "@wrnexus/authz",
"version": "0.2.28", "version": "0.2.29",
"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/cli", "name": "@wrnexus/cli",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+10
View File
@@ -329,6 +329,16 @@ const MIGRATIONS: Migration[] = [
// No project file migration is required for this framework release. // No project file migration is required for this framework release.
}, },
}, },
{
version: "0.2.29",
id: "workspace-config-improvements",
description:
"Adds environment-aware workspace domains, app URL helpers, component tag syntax, layouts, and editor improvements.",
apply() {
// No generated project files require automatic changes.
// Existing applications remain compatible with this release.
},
},
]; ];
/** Release tooling uses this to require an explicit migration entry per version. */ /** Release tooling uses this to require an explicit migration entry per version. */
+214 -16
View File
@@ -19,17 +19,44 @@ import type { GatewayAuth, GatewaySecurity } from "@wrnexus/dev-server";
const frameworkVersion = currentCliVersion(); const frameworkVersion = currentCliVersion();
export type WorkspaceEnvironment = "development" | "staging" | "production" | (string & {});
export interface WorkspaceEnvironmentConfig {
protocol?: "http" | "https";
rootDomain?: string;
port?: number;
}
export interface WorkspaceApp { export interface WorkspaceApp {
name: string; name: string;
dir: string; dir: string;
domains: string[];
/**
* Generates <subdomain>.<environment.rootDomain>.
* Example: subdomain "sso" + rootDomain "local.wrnexus.test".
*/
subdomain?: string;
/**
* Explicit hostname overrides by environment.
*/
domains?: string[] | Partial<Record<WorkspaceEnvironment, string | string[]>>;
/**
* Complete public URL override by environment.
*/
urls?: Partial<Record<WorkspaceEnvironment, string>>;
port?: number; port?: number;
/** Per-app access control enforced at the gateway (basic auth, IP allowlist, forward-auth). */
auth?: GatewayAuth; auth?: GatewayAuth;
} }
export interface WorkspaceConfig { export interface WorkspaceConfig {
defaultEnvironment?: WorkspaceEnvironment;
environments?: Record<WorkspaceEnvironment, WorkspaceEnvironmentConfig>;
apps: WorkspaceApp[]; apps: WorkspaceApp[];
/** Gateway-wide security (trusted hosts, rate limit, headers, access log). */
security?: GatewaySecurity; security?: GatewaySecurity;
} }
@@ -263,7 +290,13 @@ export async function addWorkspaceApp(root: string, name: string, args: string[]
if (config.apps.some((app) => app.name === name || app.dir.replace(/\\/g, "/") === dir)) { if (config.apps.some((app) => app.name === name || app.dir.replace(/\\/g, "/") === dir)) {
throw new Error(`Workspace app '${name}' already exists.`); throw new Error(`Workspace app '${name}' already exists.`);
} }
if (config.apps.some((app) => app.domains.includes(domain))) { const normalizedDomain = domain.toLowerCase();
if (
config.apps.some((app) =>
configuredDomains(app).some((configured) => configured.toLowerCase() === normalizedDomain),
)
) {
throw new Error(`Workspace domain '${domain}' is already assigned.`); throw new Error(`Workspace domain '${domain}' is already assigned.`);
} }
const appRoot = join(resolvedRoot, "apps", name); const appRoot = join(resolvedRoot, "apps", name);
@@ -285,6 +318,24 @@ export async function addWorkspaceApp(root: string, name: string, args: string[]
console.log("\nRun `bun install`, then `bun run dev`."); console.log("\nRun `bun install`, then `bun run dev`.");
} }
function configuredDomains(app: WorkspaceApp): string[] {
if (!app.domains) {
return [];
}
if (Array.isArray(app.domains)) {
return app.domains;
}
return Object.values(app.domains).flatMap((value) => {
if (!value) {
return [];
}
return Array.isArray(value) ? value : [value];
});
}
/** Scaffold a monorepo workspace with two starter apps + a shared package. */ /** Scaffold a monorepo workspace with two starter apps + a shared package. */
export function createWorkspace(name: string): void { export function createWorkspace(name: string): void {
if (!name) { if (!name) {
@@ -329,24 +380,171 @@ export async function loadWorkspaceConfig(root: string): Promise<WorkspaceConfig
/** Run the multi-app gateway from `wrnexus.workspace.ts`. */ /** Run the multi-app gateway from `wrnexus.workspace.ts`. */
export async function runGateway(root: string, args: string[]): Promise<void> { export async function runGateway(root: string, args: string[]): Promise<void> {
const config = await loadWorkspaceConfig(resolve(root)); const config = await loadWorkspaceConfig(resolve(root));
const portArg = args.find((a) => a.startsWith("--port="));
const hostArg = args.find((a) => a.startsWith("--host=")); const portArg = args.find((arg) => arg.startsWith("--port="));
const port = portArg ? Number(portArg.split("=")[1]) : 3000;
const hostArg = args.find((arg) => arg.startsWith("--host="));
const environmentArg = args.find((arg) => arg.startsWith("--environment="));
const profileArg = args.find((arg) => arg.startsWith("--profile="));
const environment =
environmentArg?.split("=")[1] ||
profileArg?.split("=")[1] ||
process.env.WRNEXUS_ENV ||
process.env.WRNEXUS_PROFILE ||
config.defaultEnvironment ||
(args.includes("--prod") ? "production" : "development");
const resolved = resolveWorkspaceConfig(config, environment);
const port = portArg
? Number(portArg.split("=")[1])
: resolved.environment === "development"
? 3000
: 80;
const hostname = hostArg?.split("=")[1]; const hostname = hostArg?.split("=")[1];
const mode = args.includes("--prod") ? "production" : "development";
const { startGateway } = await import("@wrnexus/dev-server"); const { startGateway } = await import("@wrnexus/dev-server");
await startGateway({ await startGateway({
port, port,
hostname, hostname,
mode, mode: environment === "production" ? "production" : "development",
security: config.security, environment,
apps: config.apps.map((a) => ({ security: resolved.security,
name: a.name, apps: resolved.apps.map((app) => ({
dir: resolve(root, a.dir), name: app.name,
domains: a.domains, dir: resolve(root, app.dir),
port: a.port, domains: app.domains,
auth: a.auth, publicOrigin: app.publicOrigin,
port: app.port,
auth: app.auth,
})), })),
}); });
} }
function environmentVariableName(appName: string): string {
return `WRNEXUS_APP_${appName.replace(/[^A-Za-z0-9]/g, "_").toUpperCase()}_URL`;
}
function normalizeOrigin(value: string): string {
const url = new URL(value);
if (url.protocol !== "http:" && url.protocol !== "https:") {
throw new Error(`Application URL must use http or https: ${value}`);
}
return url.origin;
}
function valuesForEnvironment(
value: WorkspaceApp["domains"],
environment: WorkspaceEnvironment,
): string[] {
if (!value) return [];
if (Array.isArray(value)) {
return value;
}
const selected = value[environment];
if (!selected) return [];
return Array.isArray(selected) ? selected : [selected];
}
export interface ResolvedWorkspaceApp extends WorkspaceApp {
domains: string[];
publicOrigin: string;
}
export interface ResolvedWorkspaceConfig {
environment: WorkspaceEnvironment;
apps: ResolvedWorkspaceApp[];
security?: GatewaySecurity;
}
export function resolveWorkspaceConfig(
config: WorkspaceConfig,
environment: WorkspaceEnvironment,
): ResolvedWorkspaceConfig {
const environmentConfig = config.environments?.[environment] ?? {};
const protocol = environmentConfig.protocol ?? (environment === "production" ? "https" : "http");
const configuredPort = environmentConfig.port;
const apps = config.apps.map((app) => {
const envOverride = process.env[environmentVariableName(app.name)];
const configuredUrl = app.urls?.[environment];
const explicitDomains = valuesForEnvironment(app.domains, environment);
const generatedDomain =
app.subdomain && environmentConfig.rootDomain
? `${app.subdomain}.${environmentConfig.rootDomain}`
: undefined;
const domains =
explicitDomains.length > 0 ? explicitDomains : generatedDomain ? [generatedDomain] : [];
let publicOrigin: string;
if (envOverride) {
publicOrigin = normalizeOrigin(envOverride);
} else if (configuredUrl) {
publicOrigin = normalizeOrigin(configuredUrl);
} else if (domains[0]) {
const port =
configuredPort &&
!(
(protocol === "http" && configuredPort === 80) ||
(protocol === "https" && configuredPort === 443)
)
? `:${configuredPort}`
: "";
publicOrigin = `${protocol}://${domains[0]}${port}`;
} else {
throw new Error(
`No domain or URL configured for workspace app "${app.name}" in environment "${environment}".`,
);
}
const publicUrl = new URL(publicOrigin);
return {
...app,
domains: domains.length > 0 ? domains : [publicUrl.hostname],
publicOrigin,
};
});
const usedDomains = new Map<string, string>();
for (const app of apps) {
for (const domain of app.domains) {
const normalized = domain.toLowerCase();
const owner = usedDomains.get(normalized);
if (owner) {
throw new Error(
`Workspace domain "${domain}" is assigned to both "${owner}" and "${app.name}".`,
);
}
usedDomains.set(normalized, app.name);
}
}
return {
environment,
apps,
security: config.security,
};
}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/compiler", "name": "@wrnexus/compiler",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/core", "name": "@wrnexus/core",
"version": "0.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+75 -6
View File
@@ -35,6 +35,7 @@ export interface GatewayApp {
dir: string; dir: string;
/** Host names routed to this app (e.g. ["localhost", "web.localhost"]). */ /** Host names routed to this app (e.g. ["localhost", "web.localhost"]). */
domains: string[]; domains: string[];
publicOrigin?: string;
/** Optional fixed internal port; otherwise assigned from the gateway port. */ /** Optional fixed internal port; otherwise assigned from the gateway port. */
port?: number; port?: number;
/** Access control enforced at the edge for this app. */ /** Access control enforced at the edge for this app. */
@@ -59,6 +60,7 @@ export interface GatewayOptions {
port?: number; port?: number;
hostname?: string; hostname?: string;
mode?: "development" | "production"; mode?: "development" | "production";
environment?: string;
apps: GatewayApp[]; apps: GatewayApp[];
security?: GatewaySecurity; security?: GatewaySecurity;
} }
@@ -79,7 +81,7 @@ interface WsBridge {
origin: string; origin: string;
path: string; path: string;
backend?: WebSocket; backend?: WebSocket;
queue: (string | ArrayBufferLike | ArrayBufferView)[]; queue: Array<string | ArrayBuffer>;
} }
/** Decide whether a gateway child should be relaunched after it exits. */ /** Decide whether a gateway child should be relaunched after it exits. */
@@ -262,6 +264,11 @@ export async function startGateway(opts: GatewayOptions): Promise<RunningGateway
const port = opts.port ?? 3000; const port = opts.port ?? 3000;
const mode = opts.mode ?? "development"; const mode = opts.mode ?? "development";
const hostname = opts.hostname ?? defaultGatewayHostname(mode); const hostname = opts.hostname ?? defaultGatewayHostname(mode);
const environment = opts.environment ?? (mode === "production" ? "production" : "development");
const workspaceOrigins = Object.fromEntries(
opts.apps.map((app) => [app.name, app.publicOrigin ?? `http://${app.domains[0]}:${port}`]),
);
const displayHost = hostname === "0.0.0.0" || hostname === "::" ? "localhost" : hostname; const displayHost = hostname === "0.0.0.0" || hostname === "::" ? "localhost" : hostname;
const serveEntry = fileURLToPath(import.meta.resolve("@wrnexus/dev-server/serve-entry")); const serveEntry = fileURLToPath(import.meta.resolve("@wrnexus/dev-server/serve-entry"));
@@ -281,12 +288,28 @@ export async function startGateway(opts: GatewayOptions): Promise<RunningGateway
mode === "production" mode === "production"
? spawn(process.execPath, [join(dir, "dist", "server.js")], { ? spawn(process.execPath, [join(dir, "dist", "server.js")], {
stdio: "inherit", stdio: "inherit",
env: { ...process.env, PORT: String(appPort) }, env: {
...process.env,
PORT: String(appPort),
WRNEXUS_ENV: environment,
WRNEXUS_APP_NAME: app.name,
WRNEXUS_APP_ORIGIN: app.publicOrigin ?? `http://${app.domains[0]}:${port}`,
WRNEXUS_WORKSPACE_ORIGINS: JSON.stringify(workspaceOrigins),
},
}) })
: spawn( : spawn(
process.execPath, process.execPath,
[serveEntry, join(dir, "app"), String(appPort), mode, "127.0.0.1"], [serveEntry, join(dir, "app"), String(appPort), mode, "127.0.0.1"],
{ stdio: "inherit" }, {
stdio: "inherit",
env: {
...process.env,
WRNEXUS_ENV: environment,
WRNEXUS_APP_NAME: app.name,
WRNEXUS_APP_ORIGIN: app.publicOrigin ?? `http://${app.domains[0]}:${port}`,
WRNEXUS_WORKSPACE_ORIGINS: JSON.stringify(workspaceOrigins),
},
},
); );
target.child = child; target.child = child;
@@ -415,14 +438,60 @@ export async function startGateway(opts: GatewayOptions): Promise<RunningGateway
for (const m of ws.data.queue) backend.send(m); for (const m of ws.data.queue) backend.send(m);
ws.data.queue = []; ws.data.queue = [];
}); });
backend.addEventListener("message", (e) => ws.send(e.data as string | ArrayBufferLike)); backend.addEventListener("message", async (event) => {
const data = event.data;
if (typeof data === "string") {
ws.send(data);
return;
}
if (data instanceof Blob) {
const buffer = await data.arrayBuffer();
ws.send(buffer);
return;
}
if (data instanceof ArrayBuffer) {
ws.send(data);
return;
}
if (ArrayBuffer.isView(data)) {
const buffer = data.buffer.slice(
data.byteOffset,
data.byteOffset + data.byteLength,
) as ArrayBuffer;
ws.send(buffer);
}
});
backend.addEventListener("close", () => ws.close()); backend.addEventListener("close", () => ws.close());
backend.addEventListener("error", () => ws.close()); backend.addEventListener("error", () => ws.close());
}, },
message(ws, message) { message(ws, message) {
let data: string | ArrayBuffer;
if (typeof message === "string") {
data = message;
} else if (message instanceof ArrayBuffer) {
data = message;
} else {
const view = message as ArrayBufferView;
data = view.buffer.slice(
view.byteOffset,
view.byteOffset + view.byteLength,
) as ArrayBuffer;
}
const backend = ws.data.backend; const backend = ws.data.backend;
if (backend && backend.readyState === WebSocket.OPEN) backend.send(message);
else ws.data.queue.push(message); if (backend && backend.readyState === WebSocket.OPEN) {
backend.send(data);
} else {
ws.data.queue.push(data);
}
}, },
close(ws) { close(ws) {
ws.data.backend?.close(); ws.data.backend?.close();
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/encryption", "name": "@wrnexus/encryption",
"version": "0.2.28", "version": "0.2.29",
"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/helpers", "name": "@wrnexus/helpers",
"version": "0.2.28", "version": "0.2.29",
"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.",
+8
View File
@@ -136,3 +136,11 @@ export function redirectToLogin(
target.searchParams.set(options.returnToParam ?? "returnTo", original.href); target.searchParams.set(options.returnToParam ?? "returnTo", original.href);
return Response.redirect(target, options.status ?? 302); return Response.redirect(target, options.status ?? 302);
} }
export {
appOrigin,
appUrl,
currentAppName,
currentAppOrigin,
workspaceAppOrigins,
} from "./workspace.ts";
+58
View File
@@ -0,0 +1,58 @@
function workspaceOrigins(): Record<string, string> {
const value = process.env.WRNEXUS_WORKSPACE_ORIGINS;
if (!value) {
return {};
}
try {
const parsed = JSON.parse(value);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
return {};
}
return parsed as Record<string, string>;
} catch {
return {};
}
}
function normalizePath(path: string): string {
if (!path) return "/";
return path.startsWith("/") ? path : `/${path}`;
}
export function appOrigin(appName: string): string {
const origins = workspaceOrigins();
const origin = origins[appName];
if (!origin) {
throw new Error(
`Unknown workspace app "${appName}". Available apps: ${
Object.keys(origins).join(", ") || "none"
}.`,
);
}
return new URL(origin).origin;
}
export function appUrl(appName: string, path = "/"): string {
return new URL(normalizePath(path), `${appOrigin(appName)}/`).href;
}
export function currentAppName(): string | undefined {
return process.env.WRNEXUS_APP_NAME;
}
export function currentAppOrigin(): string | undefined {
const value = process.env.WRNEXUS_APP_ORIGIN;
return value ? new URL(value).origin : undefined;
}
export function workspaceAppOrigins(): Readonly<Record<string, string>> {
return Object.freeze({ ...workspaceOrigins() });
}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/i18n", "name": "@wrnexus/i18n",
"version": "0.2.28", "version": "0.2.29",
"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/jwt", "name": "@wrnexus/jwt",
"version": "0.2.28", "version": "0.2.29",
"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/mobile", "name": "@wrnexus/mobile",
"version": "0.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"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/pubsub", "name": "@wrnexus/pubsub",
"version": "0.2.28", "version": "0.2.29",
"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/queue", "name": "@wrnexus/queue",
"version": "0.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/router", "name": "@wrnexus/router",
"version": "0.2.28", "version": "0.2.29",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/ssr", "name": "@wrnexus/ssr",
"version": "0.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"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/ui", "name": "@wrnexus/ui",
"version": "0.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"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.2.28", "version": "0.2.29",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",