@zenithbuild/language-server 0.2.7 → 0.5.0-beta.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.8] - 2026-01-26
9
+
10
+ ### 📝 Other Changes
11
+
12
+ - **** ()
13
+
8
14
  ## [0.2.1] - 2026-01-16
9
15
 
10
16
  ### 🐛 Bug Fixes
package/README.md CHANGED
@@ -12,6 +12,20 @@ This package provides the "brains" for Zenith editor support. It implements the
12
12
  - **Completion**: Context-aware suggestions for Zenith-specific syntax and standard HTML.
13
13
  - **Hover Information**: Detailed documentation on hover for core components and hooks.
14
14
  - **Document Symbols**: Outline and navigation support for complex components.
15
+ - **Contract Enforcement**:
16
+ - `on:click={handler}` event syntax diagnostics + quick fixes for `onclick` / `@click`.
17
+ - Component script policy (`zenith.componentScripts`: `forbid` | `allow`).
18
+ - CSS import contract diagnostics for local precompiled CSS only.
19
+ - **Project Root Resolution**:
20
+ - nearest `zenith.config.*`
21
+ - nearest `package.json` with `@zenithbuild/cli`
22
+ - workspace-aware fallback heuristics
23
+
24
+ ## Settings
25
+
26
+ - `zenith.componentScripts`
27
+ - `forbid` (default): components may not contain `<script>`.
28
+ - `allow`: disables the component-script contract diagnostic.
15
29
 
16
30
  ## Architecture
17
31
 
@@ -0,0 +1,33 @@
1
+ # 🚀 @zenithbuild/language-server v0.2.9
2
+
3
+ ## [0.2.9] - 2026-02-15
4
+
5
+ ### ✨ Contract Updates
6
+
7
+ - Added component script policy diagnostics with configurable mode:
8
+ - `zenith.componentScripts = "forbid" | "allow"`.
9
+ - Added event binding syntax diagnostics and quick fixes:
10
+ - invalid: `onclick="..."`, `@click={...}`
11
+ - valid: `on:click={handler}`.
12
+ - Added Tailwind v4-compatible CSS import contract diagnostics:
13
+ - allow local CSS imports (including `?query`/`#hash` suffixes)
14
+ - fail bare CSS package imports and project-root traversal escapes.
15
+ - Added workspace-aware project root resolution heuristics.
16
+ - Added unit tests for root resolution, CSS suffix/path contracts, component script diagnostics, and event-binding code actions.
17
+
18
+
19
+
20
+ ## 📦 Installation
21
+
22
+ ```bash
23
+ bun add @zenithbuild/language-server@0.2.9
24
+ ```
25
+
26
+ *or with npm:*
27
+
28
+ ```bash
29
+ npm install @zenithbuild/language-server@0.2.9
30
+ ```
31
+
32
+ ---
33
+ *Generated by Zenith Automated Release*
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@zenithbuild/language-server",
3
- "version": "0.2.7",
3
+ "version": "0.5.0-beta.2.19",
4
4
  "description": "Language Server for Zenith Framework",
5
5
  "main": "./dist/server.js",
6
6
  "types": "./dist/server.d.ts",
7
7
  "scripts": {
8
- "build": "npx esbuild src/server.ts --bundle --outdir=dist --platform=node --format=cjs",
8
+ "build": "npx esbuild src/server.ts --bundle --outdir=dist --platform=node --format=cjs --external:@zenithbuild/compiler --external:vscode-languageserver/node --external:vscode-languageserver-textdocument",
9
+ "test": "npm run test:unit",
10
+ "test:unit": "bun test test/*.spec.ts",
9
11
  "dev": "npm run build -- --watch",
10
12
  "release": "bun run scripts/release.ts",
11
13
  "release:dry": "bun run scripts/release.ts --dry-run",
@@ -14,6 +16,7 @@
14
16
  "release:major": "bun run scripts/release.ts --bump=major"
15
17
  },
16
18
  "dependencies": {
19
+ "@zenithbuild/compiler": "^1.3.0",
17
20
  "vscode-languageserver": "^9.0.1",
18
21
  "vscode-languageserver-textdocument": "^1.0.11"
19
22
  },
@@ -279,9 +279,13 @@ function generateChangelog(
279
279
  changelog += `### ⚠️ BREAKING CHANGES\n\n`;
280
280
  for (const commit of breakingChanges) {
281
281
  const scope = commit.scope ? `**${commit.scope}**: ` : "";
282
- changelog += `- ${scope}${commit.subject} (${commit.hash.slice(0, 7)})\n`;
282
+ changelog += `#### ${scope}${commit.subject} (${commit.hash.slice(0, 7)})\n`;
283
+ if (commit.body) {
284
+ changelog += `\n${commit.body.split('\n').map(line => `> ${line}`).join('\n')}\n`;
285
+ }
286
+ changelog += "\n";
283
287
  }
284
- changelog += "\n";
288
+ changelog += "---\n\n";
285
289
  }
286
290
 
287
291
  // Regular changes by type
@@ -294,7 +298,14 @@ function generateChangelog(
294
298
  changelog += `### ${typeConfig.title}\n\n`;
295
299
  for (const commit of typeCommits) {
296
300
  const scope = commit.scope ? `**${commit.scope}**: ` : "";
297
- changelog += `- ${scope}${commit.subject} (${commit.hash.slice(0, 7)})\n`;
301
+ changelog += `- **${scope}${commit.subject}** (${commit.hash.slice(0, 7)})\n`;
302
+
303
+ // Include body if present
304
+ if (commit.body && commit.body.trim()) {
305
+ const cleanedBody = commit.body.trim();
306
+ // Indent body for better hierarchy
307
+ changelog += `${cleanedBody.split('\n').map(line => ` > ${line}`).join('\n')}\n`;
308
+ }
298
309
  }
299
310
  changelog += "\n";
300
311
  }
@@ -303,7 +314,10 @@ function generateChangelog(
303
314
  if (groupedCommits.other && groupedCommits.other.length > 0) {
304
315
  changelog += `### 📝 Other Changes\n\n`;
305
316
  for (const commit of groupedCommits.other) {
306
- changelog += `- ${commit.subject} (${commit.hash.slice(0, 7)})\n`;
317
+ changelog += `- **${commit.subject}** (${commit.hash.slice(0, 7)})\n`;
318
+ if (commit.body && commit.body.trim()) {
319
+ changelog += `${commit.body.trim().split('\n').map(line => ` > ${line}`).join('\n')}\n`;
320
+ }
307
321
  }
308
322
  changelog += "\n";
309
323
  }
@@ -319,21 +333,24 @@ function generateReleaseNotes(
319
333
  ): string {
320
334
  const changelog = generateChangelog(commits, newVersion, config);
321
335
 
322
- return `# ${packageName} v${newVersion}
336
+ return `# 🚀 ${packageName} v${newVersion}
323
337
 
324
338
  ${changelog}
325
339
 
326
- ## Installation
340
+ ## 📦 Installation
327
341
 
328
342
  \`\`\`bash
329
343
  bun add ${packageName}@${newVersion}
330
344
  \`\`\`
331
345
 
332
- or with npm:
346
+ *or with npm:*
333
347
 
334
348
  \`\`\`bash
335
349
  npm install ${packageName}@${newVersion}
336
350
  \`\`\`
351
+
352
+ ---
353
+ *Generated by Zenith Automated Release*
337
354
  `;
338
355
  }
339
356
 
@@ -0,0 +1,60 @@
1
+ import {
2
+ ZenithDiagnostic,
3
+ ZenithRange
4
+ } from './diagnostics';
5
+
6
+ export const EVENT_BINDING_DIAGNOSTIC_CODE = 'zenith.event.binding.syntax';
7
+
8
+ export interface EventBindingCodeActionData {
9
+ replacement: string;
10
+ title: string;
11
+ }
12
+
13
+ export interface ZenithCodeAction {
14
+ title: string;
15
+ kind: string;
16
+ diagnostics: ZenithDiagnostic[];
17
+ edit: {
18
+ changes: Record<string, Array<{ range: ZenithRange; newText: string }>>;
19
+ };
20
+ isPreferred?: boolean;
21
+ }
22
+
23
+ interface ZenithTextDocumentLike {
24
+ uri: string;
25
+ }
26
+
27
+ export function buildEventBindingCodeActions(
28
+ document: ZenithTextDocumentLike,
29
+ diagnostics: ZenithDiagnostic[]
30
+ ): ZenithCodeAction[] {
31
+ const actions: ZenithCodeAction[] = [];
32
+
33
+ for (const diagnostic of diagnostics) {
34
+ if (diagnostic.code !== EVENT_BINDING_DIAGNOSTIC_CODE) {
35
+ continue;
36
+ }
37
+
38
+ const data = diagnostic.data as EventBindingCodeActionData | undefined;
39
+ if (!data || typeof data.replacement !== 'string' || typeof data.title !== 'string') {
40
+ continue;
41
+ }
42
+
43
+ actions.push({
44
+ title: data.title,
45
+ kind: 'quickfix',
46
+ diagnostics: [diagnostic],
47
+ edit: {
48
+ changes: {
49
+ [document.uri]: [{
50
+ range: diagnostic.range,
51
+ newText: data.replacement
52
+ }]
53
+ }
54
+ },
55
+ isPreferred: true
56
+ });
57
+ }
58
+
59
+ return actions;
60
+ }
@@ -0,0 +1,100 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ export type ZenithFileKind = 'page' | 'layout' | 'component' | 'unknown';
5
+
6
+ export function stripImportSuffix(specifier: string): string {
7
+ const hashIndex = specifier.indexOf('#');
8
+ const queryIndex = specifier.indexOf('?');
9
+
10
+ let cutAt = -1;
11
+ if (hashIndex >= 0 && queryIndex >= 0) {
12
+ cutAt = Math.min(hashIndex, queryIndex);
13
+ } else if (hashIndex >= 0) {
14
+ cutAt = hashIndex;
15
+ } else if (queryIndex >= 0) {
16
+ cutAt = queryIndex;
17
+ }
18
+
19
+ return cutAt >= 0 ? specifier.slice(0, cutAt) : specifier;
20
+ }
21
+
22
+ export function isLocalCssSpecifier(specifier: string): boolean {
23
+ return (
24
+ specifier.startsWith('./') ||
25
+ specifier.startsWith('../') ||
26
+ specifier.startsWith('/')
27
+ );
28
+ }
29
+
30
+ export function isCssContractImportSpecifier(specifier: string): boolean {
31
+ const normalized = stripImportSuffix(specifier).trim();
32
+ if (!normalized) {
33
+ return false;
34
+ }
35
+
36
+ if (normalized.endsWith('.css')) {
37
+ return true;
38
+ }
39
+
40
+ if (normalized === 'tailwindcss') {
41
+ return true;
42
+ }
43
+
44
+ if (/^@[^/]+\/css(?:$|\/)/.test(normalized)) {
45
+ return true;
46
+ }
47
+
48
+ return false;
49
+ }
50
+
51
+ function canonicalizePath(candidate: string): string {
52
+ try {
53
+ return fs.realpathSync.native(candidate);
54
+ } catch {
55
+ return path.resolve(candidate);
56
+ }
57
+ }
58
+
59
+ export function resolveCssImportPath(
60
+ importingFilePath: string,
61
+ specifier: string,
62
+ projectRoot: string
63
+ ): { resolvedPath: string; escapesProjectRoot: boolean } {
64
+ const normalizedSpecifier = stripImportSuffix(specifier);
65
+ const importingDir = path.dirname(importingFilePath);
66
+ const rootCanonical = canonicalizePath(projectRoot);
67
+
68
+ const unresolvedTarget = normalizedSpecifier.startsWith('/')
69
+ ? path.join(rootCanonical, normalizedSpecifier.slice(1))
70
+ : path.resolve(importingDir, normalizedSpecifier);
71
+
72
+ const targetCanonical = canonicalizePath(unresolvedTarget);
73
+ const relativeToRoot = path.relative(rootCanonical, targetCanonical);
74
+ const escapesProjectRoot =
75
+ relativeToRoot.startsWith('..') ||
76
+ path.isAbsolute(relativeToRoot);
77
+
78
+ return {
79
+ resolvedPath: targetCanonical,
80
+ escapesProjectRoot
81
+ };
82
+ }
83
+
84
+ export function classifyZenithFile(filePath: string): ZenithFileKind {
85
+ const normalized = filePath.replace(/\\/g, '/');
86
+
87
+ if (!normalized.endsWith('.zen')) {
88
+ return 'unknown';
89
+ }
90
+
91
+ if (normalized.includes('/src/pages/') || normalized.includes('/app/pages/')) {
92
+ return 'page';
93
+ }
94
+
95
+ if (normalized.includes('/src/layouts/') || normalized.includes('/app/layouts/')) {
96
+ return 'layout';
97
+ }
98
+
99
+ return 'component';
100
+ }