release: WRNexusJS 0.7.0
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wrnexus",
|
||||
"version": "0.5.0",
|
||||
"version": "0.7.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wrnexus",
|
||||
"version": "0.5.0",
|
||||
"version": "0.7.0",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"devDependencies": {
|
||||
"@vscode/vsce": "^3.9.2"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "wrnexus",
|
||||
"displayName": "WRNexus Language Support",
|
||||
"description": "Complete WRNexus v0.6 language support for typed imports, props, state, outputs, runtime functions, stores, diagnostics, formatting, navigation, and migration assistance.",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"publisher": "wrnexus",
|
||||
"private": true,
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
|
||||
+191
-17
@@ -3,6 +3,79 @@
|
||||
const __nodeRequire = require;
|
||||
const __path = __nodeRequire("node:path");
|
||||
const __modules = {
|
||||
"packages/compiler/src/analysis.ts": function (module, exports, require, __filename, __dirname) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.analyzeRuntimeRequirements = analyzeRuntimeRequirements;
|
||||
function hasEvent(nodes) {
|
||||
for (const node of nodes) {
|
||||
if (node.type === "element") {
|
||||
if (node.attrs.some((attribute) => attribute.event))
|
||||
return true;
|
||||
if (hasEvent(node.children))
|
||||
return true;
|
||||
}
|
||||
else if (node.type === "each") {
|
||||
if (hasEvent(node.body) || hasEvent(node.empty))
|
||||
return true;
|
||||
}
|
||||
else if (node.type === "if") {
|
||||
if (node.branches.some((branch) => hasEvent(branch.body)))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function analyzeRuntimeRequirements(ast) {
|
||||
const reasons = [];
|
||||
const clientFunctions = ast.runtimeFunctions.some((fn) => fn.runtime !== "server");
|
||||
const clientState = ast.states.some((state) => state.runtime !== "server");
|
||||
const interactive = clientFunctions ||
|
||||
clientState ||
|
||||
ast.effects.length > 0 ||
|
||||
ast.watches.length > 0 ||
|
||||
hasEvent(ast.view);
|
||||
if (interactive)
|
||||
reasons.push("client interactivity");
|
||||
const requestData = ast.loads.length > 0 ||
|
||||
ast.actions.length > 0 ||
|
||||
ast.dataApis.length > 0 ||
|
||||
ast.apis.length > 0 ||
|
||||
ast.realtimes.length > 0 ||
|
||||
ast.runtimeFunctions.some((fn) => fn.runtime === "server") ||
|
||||
ast.states.some((state) => state.runtime === "server");
|
||||
if (requestData)
|
||||
reasons.push("server/request data");
|
||||
const authenticated = /^(?:required|true)$/i.test(ast.security.auth ?? "");
|
||||
if (authenticated)
|
||||
reasons.push("authentication required");
|
||||
const streaming = /^(?:true|required)$/i.test(ast.security.streaming ?? "");
|
||||
if (streaming)
|
||||
reasons.push("streaming enabled");
|
||||
let kind;
|
||||
if (streaming)
|
||||
kind = "streaming-ssr";
|
||||
else if (authenticated)
|
||||
kind = "authenticated-ssr";
|
||||
else if (requestData && interactive)
|
||||
kind = "dynamic";
|
||||
else if (requestData)
|
||||
kind = "request-ssr";
|
||||
else if (interactive)
|
||||
kind = "static-interactive";
|
||||
else
|
||||
kind = "static";
|
||||
return {
|
||||
kind,
|
||||
canPrerender: kind === "static" || kind === "static-interactive",
|
||||
needsClientRuntime: interactive && ast.hydrate !== "none" && ast.runtime !== "server",
|
||||
needsServerRuntime: requestData || authenticated || streaming || ast.runtime === "server",
|
||||
hydrationStrategy: interactive ? (ast.hydrate ?? "load") : null,
|
||||
reasons,
|
||||
};
|
||||
}
|
||||
|
||||
},
|
||||
"packages/compiler/src/cache.ts": function (module, exports, require, __filename, __dirname) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
@@ -339,6 +412,30 @@ const HTML_BOOLEAN_ATTRIBUTES = new Set([
|
||||
function isHtmlBooleanAttribute(name) {
|
||||
return HTML_BOOLEAN_ATTRIBUTES.has(name.toLowerCase());
|
||||
}
|
||||
const URL_ATTRIBUTES = new Set([
|
||||
"href",
|
||||
"src",
|
||||
"action",
|
||||
"formaction",
|
||||
"poster",
|
||||
"cite",
|
||||
"background",
|
||||
"xlink:href",
|
||||
]);
|
||||
function sanitizeUrlAttribute(value) {
|
||||
const compact = value.trim().replace(/[\u0000-\u0020]+/g, "");
|
||||
const lower = compact.toLowerCase();
|
||||
if (/^(?:javascript|vbscript|file):/.test(lower))
|
||||
return "about:blank";
|
||||
if (/^data:(?!image\/(?:png|gif|jpeg|webp|avif);)/.test(lower))
|
||||
return "about:blank";
|
||||
return value;
|
||||
}
|
||||
function safeAttributeValue(name, value) {
|
||||
if (!URL_ATTRIBUTES.has(name.toLowerCase()) || value.includes("{"))
|
||||
return value;
|
||||
return sanitizeUrlAttribute(value);
|
||||
}
|
||||
/** Escape a value placed inside a double-quoted HTML attribute. */
|
||||
function attrEscape(value) {
|
||||
return value
|
||||
@@ -368,7 +465,9 @@ function renderAttr(attr) {
|
||||
case "csrText":
|
||||
return "";
|
||||
default:
|
||||
return attr.boolean ? ` ${attr.name}` : ` ${attr.name}="${attrEscape(attr.value)}"`;
|
||||
return attr.boolean
|
||||
? ` ${attr.name}`
|
||||
: ` ${attr.name}="${attrEscape(safeAttributeValue(attr.name, attr.value))}"`;
|
||||
}
|
||||
}
|
||||
function eventAttribute(name) {
|
||||
@@ -423,7 +522,7 @@ function renderAttrs(attrs, csrId, reactive = null, dynamicExpressions) {
|
||||
if (initial === null)
|
||||
return base;
|
||||
const marker = JSON.stringify([attr.name, attr.value]);
|
||||
return ` ${attr.name}="${attrEscape(initial)}" data-wrn-bind-${bindIndex++}="${attrEscape(marker)}"`;
|
||||
return ` ${attr.name}="${attrEscape(URL_ATTRIBUTES.has(attr.name.toLowerCase()) ? sanitizeUrlAttribute(initial) : initial)}" data-wrn-bind-${bindIndex++}="${attrEscape(marker)}"`;
|
||||
})
|
||||
.join("");
|
||||
return csrId ? `${rendered} data-wrnexus-csr="${attrEscape(csrId)}"` : rendered;
|
||||
@@ -2009,7 +2108,7 @@ function renderPageComponentAttr(attr, dynamicExpressions) {
|
||||
}
|
||||
const expression = wholeAttributeExpression(attr.value);
|
||||
if (!expression) {
|
||||
return ` ${attr.name}="${attrEscape(attr.value)}"`;
|
||||
return ` ${attr.name}="${attrEscape(safeAttributeValue(attr.name, attr.value))}"`;
|
||||
}
|
||||
dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`);
|
||||
const marker = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
|
||||
@@ -2097,7 +2196,7 @@ function resolveWrnImports(declarations, importer, options) {
|
||||
* `@wrnexus/syntax` package. This package owns platform-specific codegen.
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DependencyGraph = exports.createCompilationCache = exports.compilationKey = exports.runtimeTypeOf = exports.inferredRuntimeType = exports.eraseFunctionTypes = exports.LexError = exports.Lexer = exports.NativeCompileError = exports.generateNative = exports.createWrnSourceMap = exports.resolveWrnImports = exports.resolveWrnImport = exports.createComponentContract = exports.generateStoreModule = exports.generateStoreBrowserModule = exports.generateDeclarations = exports.rpcManifest = exports.generateServerFunctionsModule = exports.generateBrowserModule = exports.generateTargets = exports.generate = exports.ParseError = exports.parse = exports.formatDiagnostic = exports.diagnosticFromError = exports.diagnose = exports.assertValidAst = void 0;
|
||||
exports.DependencyGraph = exports.createCompilationCache = exports.compilationKey = exports.runtimeTypeOf = exports.inferredRuntimeType = exports.eraseFunctionTypes = exports.LexError = exports.Lexer = exports.NativeCompileError = exports.generateNative = exports.analyzeRuntimeRequirements = exports.createWrnSourceMap = exports.resolveWrnImports = exports.resolveWrnImport = exports.createComponentContract = exports.generateStoreModule = exports.generateStoreBrowserModule = exports.generateDeclarations = exports.rpcManifest = exports.generateServerFunctionsModule = exports.generateBrowserModule = exports.generateTargets = exports.generate = exports.ParseError = exports.parse = exports.formatDiagnostic = exports.diagnosticFromError = exports.diagnose = exports.assertValidAst = void 0;
|
||||
exports.compileNativeWireFile = compileNativeWireFile;
|
||||
exports.compileWireFile = compileWireFile;
|
||||
exports.compile = compile;
|
||||
@@ -2132,6 +2231,8 @@ Object.defineProperty(exports, "resolveWrnImport", { enumerable: true, get: func
|
||||
Object.defineProperty(exports, "resolveWrnImports", { enumerable: true, get: function () { return import_resolver_ts_1.resolveWrnImports; } });
|
||||
var source_map_ts_1 = require("./source-map.js");
|
||||
Object.defineProperty(exports, "createWrnSourceMap", { enumerable: true, get: function () { return source_map_ts_1.createWrnSourceMap; } });
|
||||
var analysis_ts_1 = require("./analysis.js");
|
||||
Object.defineProperty(exports, "analyzeRuntimeRequirements", { enumerable: true, get: function () { return analysis_ts_1.analyzeRuntimeRequirements; } });
|
||||
var native_codegen_ts_2 = require("./native-codegen.js");
|
||||
Object.defineProperty(exports, "generateNative", { enumerable: true, get: function () { return native_codegen_ts_2.generateNative; } });
|
||||
Object.defineProperty(exports, "NativeCompileError", { enumerable: true, get: function () { return native_codegen_ts_2.NativeCompileError; } });
|
||||
@@ -2649,7 +2750,7 @@ function __diagnostic(code, message, details) {
|
||||
}
|
||||
function __csrfToken() {
|
||||
if (typeof document === "undefined") return undefined;
|
||||
const match = /(?:^|;\\s*)wrnexus_csrf=([^;]+)/.exec(document.cookie || "");
|
||||
const match = /(?:^|;\\s*)wire-csrf=([^;]+)/.exec(document.cookie || "");
|
||||
return match ? decodeURIComponent(match[1]) : undefined;
|
||||
}
|
||||
async function __callServerFunction(storeName, functionName, args, options) {
|
||||
@@ -2660,7 +2761,7 @@ async function __callServerFunction(storeName, functionName, args, options) {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
signal: options.signal,
|
||||
headers: Object.assign({ "content-type": "application/json", "x-request-id": traceId }, csrf ? { "x-wrnexus-csrf": csrf } : {}, options.headers || {}),
|
||||
headers: Object.assign({ "content-type": "application/json", "x-request-id": traceId }, csrf ? { "x-csrf-token": csrf } : {}, options.headers || {}),
|
||||
body: JSON.stringify({ component: storeName, function: functionName, args: args }),
|
||||
});
|
||||
const payload = await response.json().catch(function () { return null; });
|
||||
@@ -3129,20 +3230,65 @@ function astDiagnostics(ast, options) {
|
||||
});
|
||||
}
|
||||
let interactive = ast.states.length > 0 || ast.effects.length > 0 || ast.watches.length > 0;
|
||||
const urlAttributes = new Set(["href", "src", "action", "formaction", "poster", "cite", "background", "xlink:href"]);
|
||||
walk(ast.view, (node) => {
|
||||
if (node.type === "element" && node.attrs.some((attribute) => attribute.event))
|
||||
interactive = true;
|
||||
if (!options.accessibility || node.type !== "element")
|
||||
if (node.type !== "element")
|
||||
return;
|
||||
if (node.attrs.some((attribute) => attribute.event))
|
||||
interactive = true;
|
||||
const tag = node.tag.toLowerCase();
|
||||
if (tag === "img" && !node.attrs.some((attribute) => attribute.name === "alt")) {
|
||||
diagnostics.push({
|
||||
code: spec_ts_1.WRN_DIAGNOSTIC_CODES.accessibility,
|
||||
severity: "warning",
|
||||
message: "Image is missing an alt attribute.",
|
||||
hint: 'Add alt text, or alt="" for a decorative image.',
|
||||
file: options.file,
|
||||
});
|
||||
for (const attribute of node.attrs) {
|
||||
if (attribute.event || attribute.boolean || !urlAttributes.has(attribute.name.toLowerCase()))
|
||||
continue;
|
||||
if (attribute.value.includes("{"))
|
||||
continue;
|
||||
const value = attribute.value.trim().replace(/[\u0000-\u0020]+/g, "").toLowerCase();
|
||||
if (/^(?:javascript|vbscript|file):/.test(value) || /^data:(?!image\/(?:png|gif|jpeg|webp|avif);)/.test(value)) {
|
||||
diagnostics.push({
|
||||
code: "WRN-SEC-UNSAFE-URL",
|
||||
severity: "error",
|
||||
message: `Unsafe URL protocol in ${attribute.name} on <${node.tag}>.`,
|
||||
hint: "Use a relative URL, https:, mailto:, tel:, or a framework-validated URL helper.",
|
||||
file: options.file,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (tag === "a") {
|
||||
const target = node.attrs.find((attribute) => attribute.name === "target")?.value;
|
||||
const rel = node.attrs.find((attribute) => attribute.name === "rel")?.value ?? "";
|
||||
if (target === "_blank" && !/\bnoopener\b/i.test(rel)) {
|
||||
diagnostics.push({
|
||||
code: "WRN-SEC-BLANK-REL",
|
||||
severity: "warning",
|
||||
message: "A target=_blank link should include rel=noopener.",
|
||||
hint: 'Add rel="noopener noreferrer".',
|
||||
file: options.file,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!options.accessibility)
|
||||
return;
|
||||
if (tag === "img") {
|
||||
if (!node.attrs.some((attribute) => attribute.name === "alt")) {
|
||||
diagnostics.push({
|
||||
code: spec_ts_1.WRN_DIAGNOSTIC_CODES.accessibility,
|
||||
severity: "warning",
|
||||
message: "Image is missing an alt attribute.",
|
||||
hint: 'Add alt text, or alt="" for a decorative image.',
|
||||
file: options.file,
|
||||
});
|
||||
}
|
||||
const hasWidth = node.attrs.some((attribute) => attribute.name === "width");
|
||||
const hasHeight = node.attrs.some((attribute) => attribute.name === "height");
|
||||
if (!hasWidth || !hasHeight) {
|
||||
diagnostics.push({
|
||||
code: "WRN-PERF-IMAGE-DIMENSIONS",
|
||||
severity: "warning",
|
||||
message: "Image width and height are required to prevent layout shifts.",
|
||||
hint: "Declare intrinsic width and height, or use @wrnexus/image.",
|
||||
file: options.file,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (ast.runtime === "server" && interactive) {
|
||||
@@ -3171,6 +3317,24 @@ function astDiagnostics(ast, options) {
|
||||
if (fn.runtime === "server" && /\b(?:window|document|localStorage|sessionStorage|navigator)\b/.test(fn.body)) {
|
||||
diagnostics.push({ code: "WRN-SERVER-BROWSER-API", severity: "error", message: `Server function '${fn.name}' references a browser-only API.`, hint: "Move that code into a client function.", file: options.file });
|
||||
}
|
||||
if (fn.runtime !== "server" && /\b(?:eval\s*\(|new\s+Function\s*\(|document\.write\s*\(|\.innerHTML\s*=|\.outerHTML\s*=|insertAdjacentHTML\s*\()/.test(fn.body)) {
|
||||
diagnostics.push({
|
||||
code: "WRN-SEC-DOM-SINK",
|
||||
severity: "error",
|
||||
message: `Client function '${fn.name}' uses an unsafe dynamic-code or HTML sink.`,
|
||||
hint: "Use compiled templates, textContent, typed outputs, or a reviewed TrustedHTML sanitizer.",
|
||||
file: options.file,
|
||||
});
|
||||
}
|
||||
if (fn.runtime !== "server" && /\b(?:setTimeout|setInterval)\s*\(\s*["'`]/.test(fn.body)) {
|
||||
diagnostics.push({
|
||||
code: "WRN-SEC-STRING-TIMER",
|
||||
severity: "error",
|
||||
message: `Client function '${fn.name}' passes a string to a timer.`,
|
||||
hint: "Pass a function instead of executable text.",
|
||||
file: options.file,
|
||||
});
|
||||
}
|
||||
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
|
||||
for (const prop of ast.props) {
|
||||
if (containsReadonlyPropMutation(fn.body, prop.name, parameterNames)) {
|
||||
@@ -3182,6 +3346,16 @@ function astDiagnostics(ast, options) {
|
||||
if (state.runtime === "shared" && /^(?:new\s+(?:Map|Set|WeakMap|WeakSet)|(?:async\s+)?function\b|.*=>)/.test(state.expr.trim())) {
|
||||
diagnostics.push({ code: "WRN-STATE-NON-SERIALIZABLE", severity: "error", message: `Shared state '${state.name}' is not safely serializable.`, hint: "Use JSON-compatible data or move the value into client/server state.", file: options.file });
|
||||
}
|
||||
if (state.runtime !== "server" &&
|
||||
/\b(?:process\.env|Bun\.env|Deno\.env|ctx\.env|import\.meta\.env)\b/.test(state.expr)) {
|
||||
diagnostics.push({
|
||||
code: "WRN-SEC-SERVER-SECRET-SOURCE",
|
||||
severity: "error",
|
||||
message: `Browser-visible state '${state.name}' reads from a server environment source.`,
|
||||
hint: "Move environment-backed values into server state and return only an explicitly safe result.",
|
||||
file: options.file,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (ast.persist) {
|
||||
const stateNames = new Set(ast.states.filter((state) => state.runtime !== "server").map((state) => state.name));
|
||||
|
||||
Reference in New Issue
Block a user