@zenithbuild/language-server 0.2.2
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/.github/workflows/release.yml +254 -0
- package/.releaserc.json +73 -0
- package/CHANGELOG.md +37 -0
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/bun.lock +83 -0
- package/package.json +34 -0
- package/scripts/release.ts +554 -0
- package/src/diagnostics.ts +260 -0
- package/src/imports.ts +207 -0
- package/src/metadata/core-imports.ts +163 -0
- package/src/metadata/directive-metadata.ts +109 -0
- package/src/metadata/plugin-imports.ts +116 -0
- package/src/project.ts +167 -0
- package/src/router.ts +180 -0
- package/src/server.ts +841 -0
- package/test/fixtures/content-plugin.zen +77 -0
- package/test/fixtures/core-only.zen +59 -0
- package/test/fixtures/no-plugins.zen +115 -0
- package/test/fixtures/router-enabled.zen +76 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostics
|
|
3
|
+
*
|
|
4
|
+
* Compile-time validation mirroring the Zenith compiler.
|
|
5
|
+
* The LSP must surface compiler-level errors early.
|
|
6
|
+
*
|
|
7
|
+
* Important: No runtime execution. Pure static analysis only.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver/node';
|
|
11
|
+
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
12
|
+
import { isDirective, parseForExpression } from './metadata/directive-metadata';
|
|
13
|
+
import { parseZenithImports, resolveModule, isPluginModule } from './imports';
|
|
14
|
+
import type { ProjectGraph } from './project';
|
|
15
|
+
|
|
16
|
+
export interface ValidationContext {
|
|
17
|
+
document: TextDocument;
|
|
18
|
+
text: string;
|
|
19
|
+
graph: ProjectGraph | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Collect all diagnostics for a document
|
|
24
|
+
*/
|
|
25
|
+
export function collectDiagnostics(document: TextDocument, graph: ProjectGraph | null): Diagnostic[] {
|
|
26
|
+
const diagnostics: Diagnostic[] = [];
|
|
27
|
+
const text = document.getText();
|
|
28
|
+
|
|
29
|
+
// Validate component references
|
|
30
|
+
collectComponentDiagnostics(document, text, graph, diagnostics);
|
|
31
|
+
|
|
32
|
+
// Validate directive usage
|
|
33
|
+
collectDirectiveDiagnostics(document, text, diagnostics);
|
|
34
|
+
|
|
35
|
+
// Validate imports
|
|
36
|
+
collectImportDiagnostics(document, text, diagnostics);
|
|
37
|
+
|
|
38
|
+
// Validate expressions
|
|
39
|
+
collectExpressionDiagnostics(document, text, diagnostics);
|
|
40
|
+
|
|
41
|
+
return diagnostics;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validate component references
|
|
46
|
+
*/
|
|
47
|
+
function collectComponentDiagnostics(
|
|
48
|
+
document: TextDocument,
|
|
49
|
+
text: string,
|
|
50
|
+
graph: ProjectGraph | null,
|
|
51
|
+
diagnostics: Diagnostic[]
|
|
52
|
+
): void {
|
|
53
|
+
if (!graph) return;
|
|
54
|
+
|
|
55
|
+
// Strip script and style blocks to avoid matching PascalCase names in TS generics
|
|
56
|
+
// We use a simplified version that preserves indices by replacing content with spaces
|
|
57
|
+
const strippedText = text
|
|
58
|
+
.replace(/<(script|style)[^>]*>([\s\S]*?)<\/\1>/gi, (match, tag, content) => {
|
|
59
|
+
return match.replace(content, ' '.repeat(content.length));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Match component tags (PascalCase)
|
|
63
|
+
const componentPattern = /<([A-Z][a-zA-Z0-9]*)(?=[\s/>])/g;
|
|
64
|
+
let match;
|
|
65
|
+
|
|
66
|
+
while ((match = componentPattern.exec(strippedText)) !== null) {
|
|
67
|
+
const componentName = match[1];
|
|
68
|
+
|
|
69
|
+
// Skip known router components
|
|
70
|
+
if (componentName === 'ZenLink') continue;
|
|
71
|
+
|
|
72
|
+
// Check if component exists in project graph
|
|
73
|
+
const inLayouts = graph.layouts.has(componentName);
|
|
74
|
+
const inComponents = graph.components.has(componentName);
|
|
75
|
+
|
|
76
|
+
if (!inLayouts && !inComponents) {
|
|
77
|
+
const startPos = document.positionAt(match.index + 1);
|
|
78
|
+
const endPos = document.positionAt(match.index + 1 + componentName.length);
|
|
79
|
+
|
|
80
|
+
diagnostics.push({
|
|
81
|
+
severity: DiagnosticSeverity.Warning,
|
|
82
|
+
range: { start: startPos, end: endPos },
|
|
83
|
+
message: `Unknown component: '<${componentName}>'. Ensure it exists in src/layouts/ or src/components/`,
|
|
84
|
+
source: 'zenith'
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Validate directive usage
|
|
92
|
+
*/
|
|
93
|
+
function collectDirectiveDiagnostics(
|
|
94
|
+
document: TextDocument,
|
|
95
|
+
text: string,
|
|
96
|
+
diagnostics: Diagnostic[]
|
|
97
|
+
): void {
|
|
98
|
+
// Match zen:* directives
|
|
99
|
+
const directivePattern = /(zen:(?:if|for|effect|show))\s*=\s*["']([^"']*)["']/g;
|
|
100
|
+
let match;
|
|
101
|
+
|
|
102
|
+
while ((match = directivePattern.exec(text)) !== null) {
|
|
103
|
+
const directiveName = match[1];
|
|
104
|
+
const directiveValue = match[2];
|
|
105
|
+
|
|
106
|
+
// Validate zen:for syntax
|
|
107
|
+
if (directiveName === 'zen:for') {
|
|
108
|
+
const parsed = parseForExpression(directiveValue);
|
|
109
|
+
if (!parsed) {
|
|
110
|
+
const startPos = document.positionAt(match.index);
|
|
111
|
+
const endPos = document.positionAt(match.index + match[0].length);
|
|
112
|
+
|
|
113
|
+
diagnostics.push({
|
|
114
|
+
severity: DiagnosticSeverity.Error,
|
|
115
|
+
range: { start: startPos, end: endPos },
|
|
116
|
+
message: `Invalid zen:for syntax. Expected: "item in items" or "item, index in items"`,
|
|
117
|
+
source: 'zenith'
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check for empty directive values
|
|
123
|
+
if (!directiveValue.trim()) {
|
|
124
|
+
const startPos = document.positionAt(match.index);
|
|
125
|
+
const endPos = document.positionAt(match.index + match[0].length);
|
|
126
|
+
|
|
127
|
+
diagnostics.push({
|
|
128
|
+
severity: DiagnosticSeverity.Error,
|
|
129
|
+
range: { start: startPos, end: endPos },
|
|
130
|
+
message: `${directiveName} requires a value`,
|
|
131
|
+
source: 'zenith'
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check for zen:for on slot elements (forbidden)
|
|
137
|
+
const slotForPattern = /<slot[^>]*zen:for/g;
|
|
138
|
+
while ((match = slotForPattern.exec(text)) !== null) {
|
|
139
|
+
const startPos = document.positionAt(match.index);
|
|
140
|
+
const endPos = document.positionAt(match.index + match[0].length);
|
|
141
|
+
|
|
142
|
+
diagnostics.push({
|
|
143
|
+
severity: DiagnosticSeverity.Error,
|
|
144
|
+
range: { start: startPos, end: endPos },
|
|
145
|
+
message: `zen:for cannot be used on <slot> elements`,
|
|
146
|
+
source: 'zenith'
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Validate imports
|
|
153
|
+
*/
|
|
154
|
+
function collectImportDiagnostics(
|
|
155
|
+
document: TextDocument,
|
|
156
|
+
text: string,
|
|
157
|
+
diagnostics: Diagnostic[]
|
|
158
|
+
): void {
|
|
159
|
+
// Extract script content
|
|
160
|
+
const scriptMatch = text.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
|
|
161
|
+
if (!scriptMatch) return;
|
|
162
|
+
|
|
163
|
+
const scriptContent = scriptMatch[1];
|
|
164
|
+
const scriptStart = scriptMatch.index! + scriptMatch[0].indexOf(scriptContent);
|
|
165
|
+
|
|
166
|
+
const imports = parseZenithImports(scriptContent);
|
|
167
|
+
|
|
168
|
+
for (const imp of imports) {
|
|
169
|
+
const resolved = resolveModule(imp.module);
|
|
170
|
+
|
|
171
|
+
// Warn about unknown plugin modules (soft diagnostic)
|
|
172
|
+
if (isPluginModule(imp.module) && !resolved.isKnown) {
|
|
173
|
+
// Find the import line in the document
|
|
174
|
+
const importPattern = new RegExp(`import[^'"]*['"]${imp.module.replace(':', '\\:')}['"]`);
|
|
175
|
+
const importMatch = scriptContent.match(importPattern);
|
|
176
|
+
|
|
177
|
+
if (importMatch) {
|
|
178
|
+
const importOffset = scriptStart + (importMatch.index || 0);
|
|
179
|
+
const startPos = document.positionAt(importOffset);
|
|
180
|
+
const endPos = document.positionAt(importOffset + importMatch[0].length);
|
|
181
|
+
|
|
182
|
+
diagnostics.push({
|
|
183
|
+
severity: DiagnosticSeverity.Information,
|
|
184
|
+
range: { start: startPos, end: endPos },
|
|
185
|
+
message: `Unknown plugin module: '${imp.module}'. Make sure the plugin is installed.`,
|
|
186
|
+
source: 'zenith'
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check for invalid specifiers in known modules
|
|
192
|
+
if (resolved.isKnown && resolved.metadata) {
|
|
193
|
+
const validExports = resolved.metadata.exports.map(e => e.name);
|
|
194
|
+
|
|
195
|
+
for (const specifier of imp.specifiers) {
|
|
196
|
+
if (!validExports.includes(specifier)) {
|
|
197
|
+
const specPattern = new RegExp(`\\b${specifier}\\b`);
|
|
198
|
+
const specMatch = scriptContent.match(specPattern);
|
|
199
|
+
|
|
200
|
+
if (specMatch) {
|
|
201
|
+
const specOffset = scriptStart + (specMatch.index || 0);
|
|
202
|
+
const startPos = document.positionAt(specOffset);
|
|
203
|
+
const endPos = document.positionAt(specOffset + specifier.length);
|
|
204
|
+
|
|
205
|
+
diagnostics.push({
|
|
206
|
+
severity: DiagnosticSeverity.Warning,
|
|
207
|
+
range: { start: startPos, end: endPos },
|
|
208
|
+
message: `'${specifier}' is not exported from '${imp.module}'`,
|
|
209
|
+
source: 'zenith'
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Validate expressions for dangerous patterns
|
|
220
|
+
*/
|
|
221
|
+
function collectExpressionDiagnostics(
|
|
222
|
+
document: TextDocument,
|
|
223
|
+
text: string,
|
|
224
|
+
diagnostics: Diagnostic[]
|
|
225
|
+
): void {
|
|
226
|
+
// Match expressions in templates
|
|
227
|
+
const expressionPattern = /\{([^}]+)\}/g;
|
|
228
|
+
let match;
|
|
229
|
+
|
|
230
|
+
while ((match = expressionPattern.exec(text)) !== null) {
|
|
231
|
+
const expression = match[1];
|
|
232
|
+
const offset = match.index;
|
|
233
|
+
|
|
234
|
+
// Check for dangerous patterns
|
|
235
|
+
if (expression.includes('eval(') || expression.includes('Function(')) {
|
|
236
|
+
const startPos = document.positionAt(offset);
|
|
237
|
+
const endPos = document.positionAt(offset + match[0].length);
|
|
238
|
+
|
|
239
|
+
diagnostics.push({
|
|
240
|
+
severity: DiagnosticSeverity.Error,
|
|
241
|
+
range: { start: startPos, end: endPos },
|
|
242
|
+
message: `Dangerous pattern detected: eval() and Function() are not allowed in expressions`,
|
|
243
|
+
source: 'zenith'
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Check for with statement
|
|
248
|
+
if (/\bwith\s*\(/.test(expression)) {
|
|
249
|
+
const startPos = document.positionAt(offset);
|
|
250
|
+
const endPos = document.positionAt(offset + match[0].length);
|
|
251
|
+
|
|
252
|
+
diagnostics.push({
|
|
253
|
+
severity: DiagnosticSeverity.Error,
|
|
254
|
+
range: { start: startPos, end: endPos },
|
|
255
|
+
message: `'with' statement is not allowed in expressions`,
|
|
256
|
+
source: 'zenith'
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
package/src/imports.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import Resolution & Awareness
|
|
3
|
+
*
|
|
4
|
+
* Handles recognition and resolution of Zenith imports.
|
|
5
|
+
* - zenith/* imports are core modules (virtual, symbolic resolution)
|
|
6
|
+
* - zenith:* imports are plugin modules (soft diagnostics if missing)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
CORE_MODULES,
|
|
11
|
+
getCoreModule,
|
|
12
|
+
getCoreExport,
|
|
13
|
+
isCoreModule,
|
|
14
|
+
type CoreModuleMetadata,
|
|
15
|
+
type ModuleExport
|
|
16
|
+
} from './metadata/core-imports';
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
PLUGIN_MODULES,
|
|
20
|
+
getPluginModule,
|
|
21
|
+
getPluginExport,
|
|
22
|
+
isPluginModule,
|
|
23
|
+
isKnownPluginModule,
|
|
24
|
+
type PluginModuleMetadata,
|
|
25
|
+
type PluginExport
|
|
26
|
+
} from './metadata/plugin-imports';
|
|
27
|
+
|
|
28
|
+
export interface ParsedImport {
|
|
29
|
+
module: string;
|
|
30
|
+
specifiers: string[];
|
|
31
|
+
isType: boolean;
|
|
32
|
+
line: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ResolvedImport {
|
|
36
|
+
module: string;
|
|
37
|
+
kind: 'core' | 'plugin' | 'external';
|
|
38
|
+
metadata?: CoreModuleMetadata | PluginModuleMetadata;
|
|
39
|
+
isKnown: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parse Zenith imports from script content
|
|
44
|
+
*/
|
|
45
|
+
export function parseZenithImports(script: string): ParsedImport[] {
|
|
46
|
+
const imports: ParsedImport[] = [];
|
|
47
|
+
const lines = script.split('\n');
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < lines.length; i++) {
|
|
50
|
+
const line = lines[i];
|
|
51
|
+
|
|
52
|
+
// Match: import { x, y } from 'module' or import type { x } from 'module'
|
|
53
|
+
const importMatch = line.match(/import\s+(type\s+)?(?:\{([^}]+)\}|(\*\s+as\s+\w+)|(\w+))\s+from\s+['"]([^'"]+)['"]/);
|
|
54
|
+
|
|
55
|
+
if (importMatch) {
|
|
56
|
+
const isType = !!importMatch[1];
|
|
57
|
+
const namedImports = importMatch[2];
|
|
58
|
+
const namespaceImport = importMatch[3];
|
|
59
|
+
const defaultImport = importMatch[4];
|
|
60
|
+
const moduleName = importMatch[5];
|
|
61
|
+
|
|
62
|
+
// Only track zenith imports
|
|
63
|
+
if (moduleName.startsWith('zenith') || moduleName.startsWith('zenith:')) {
|
|
64
|
+
const specifiers: string[] = [];
|
|
65
|
+
|
|
66
|
+
if (namedImports) {
|
|
67
|
+
// Parse named imports: { a, b as c, d }
|
|
68
|
+
const parts = namedImports.split(',');
|
|
69
|
+
for (const part of parts) {
|
|
70
|
+
const cleaned = part.trim().split(/\s+as\s+/)[0].trim();
|
|
71
|
+
if (cleaned) specifiers.push(cleaned);
|
|
72
|
+
}
|
|
73
|
+
} else if (namespaceImport) {
|
|
74
|
+
specifiers.push(namespaceImport.trim());
|
|
75
|
+
} else if (defaultImport) {
|
|
76
|
+
specifiers.push(defaultImport);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
imports.push({
|
|
80
|
+
module: moduleName,
|
|
81
|
+
specifiers,
|
|
82
|
+
isType,
|
|
83
|
+
line: i + 1
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Match: import 'module' (side-effect import)
|
|
89
|
+
const sideEffectMatch = line.match(/import\s+['"]([^'"]+)['"]/);
|
|
90
|
+
if (sideEffectMatch && !importMatch) {
|
|
91
|
+
const moduleName = sideEffectMatch[1];
|
|
92
|
+
if (moduleName.startsWith('zenith') || moduleName.startsWith('zenith:')) {
|
|
93
|
+
imports.push({
|
|
94
|
+
module: moduleName,
|
|
95
|
+
specifiers: [],
|
|
96
|
+
isType: false,
|
|
97
|
+
line: i + 1
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return imports;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resolve a module name to its metadata
|
|
108
|
+
*/
|
|
109
|
+
export function resolveModule(moduleName: string): ResolvedImport {
|
|
110
|
+
if (isCoreModule(moduleName)) {
|
|
111
|
+
return {
|
|
112
|
+
module: moduleName,
|
|
113
|
+
kind: 'core',
|
|
114
|
+
metadata: getCoreModule(moduleName),
|
|
115
|
+
isKnown: true
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (isPluginModule(moduleName)) {
|
|
120
|
+
return {
|
|
121
|
+
module: moduleName,
|
|
122
|
+
kind: 'plugin',
|
|
123
|
+
metadata: getPluginModule(moduleName),
|
|
124
|
+
isKnown: isKnownPluginModule(moduleName)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
module: moduleName,
|
|
130
|
+
kind: 'external',
|
|
131
|
+
isKnown: false
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get export metadata for a specific import
|
|
137
|
+
*/
|
|
138
|
+
export function resolveExport(moduleName: string, exportName: string): ModuleExport | PluginExport | undefined {
|
|
139
|
+
if (isCoreModule(moduleName)) {
|
|
140
|
+
return getCoreExport(moduleName, exportName);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (isKnownPluginModule(moduleName)) {
|
|
144
|
+
return getPluginExport(moduleName, exportName);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Check if router is imported in the given imports
|
|
152
|
+
*/
|
|
153
|
+
export function hasRouterImport(imports: ParsedImport[]): boolean {
|
|
154
|
+
return imports.some(i => i.module === 'zenith/router');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Check if a specific export is imported
|
|
159
|
+
*/
|
|
160
|
+
export function hasImport(imports: ParsedImport[], exportName: string): boolean {
|
|
161
|
+
return imports.some(i => i.specifiers.includes(exportName));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get all available modules for completion
|
|
166
|
+
*/
|
|
167
|
+
export function getAllModules(): Array<{ module: string; kind: 'core' | 'plugin'; description: string }> {
|
|
168
|
+
const modules: Array<{ module: string; kind: 'core' | 'plugin'; description: string }> = [];
|
|
169
|
+
|
|
170
|
+
for (const [name, meta] of Object.entries(CORE_MODULES)) {
|
|
171
|
+
modules.push({
|
|
172
|
+
module: name,
|
|
173
|
+
kind: 'core',
|
|
174
|
+
description: meta.description
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (const [name, meta] of Object.entries(PLUGIN_MODULES)) {
|
|
179
|
+
modules.push({
|
|
180
|
+
module: name,
|
|
181
|
+
kind: 'plugin',
|
|
182
|
+
description: meta.description
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return modules;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get exports for completion from a module
|
|
191
|
+
*/
|
|
192
|
+
export function getModuleExports(moduleName: string): Array<ModuleExport | PluginExport> {
|
|
193
|
+
const coreModule = getCoreModule(moduleName);
|
|
194
|
+
if (coreModule) return coreModule.exports;
|
|
195
|
+
|
|
196
|
+
const pluginModule = getPluginModule(moduleName);
|
|
197
|
+
if (pluginModule) return pluginModule.exports;
|
|
198
|
+
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Re-export utilities
|
|
203
|
+
export { isPluginModule } from './metadata/plugin-imports';
|
|
204
|
+
|
|
205
|
+
// Re-export types
|
|
206
|
+
export type { CoreModuleMetadata, ModuleExport } from './metadata/core-imports';
|
|
207
|
+
export type { PluginModuleMetadata, PluginExport } from './metadata/plugin-imports';
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Import Metadata
|
|
3
|
+
*
|
|
4
|
+
* Static metadata for Zenith core modules.
|
|
5
|
+
* These are virtual modules resolved symbolically (no FS probing).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ModuleExport {
|
|
9
|
+
name: string;
|
|
10
|
+
kind: 'function' | 'component' | 'type' | 'variable';
|
|
11
|
+
description: string;
|
|
12
|
+
signature?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CoreModuleMetadata {
|
|
16
|
+
module: string;
|
|
17
|
+
description: string;
|
|
18
|
+
exports: ModuleExport[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Core Zenith module exports
|
|
23
|
+
*/
|
|
24
|
+
export const CORE_MODULES: Record<string, CoreModuleMetadata> = {
|
|
25
|
+
'zenith': {
|
|
26
|
+
module: 'zenith',
|
|
27
|
+
description: 'Core Zenith runtime primitives and lifecycle hooks.',
|
|
28
|
+
exports: [
|
|
29
|
+
{
|
|
30
|
+
name: 'zenEffect',
|
|
31
|
+
kind: 'function',
|
|
32
|
+
description: 'Reactive effect that re-runs when dependencies change.',
|
|
33
|
+
signature: 'zenEffect(callback: () => void | (() => void)): void'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'zenOnMount',
|
|
37
|
+
kind: 'function',
|
|
38
|
+
description: 'Called when component is mounted to the DOM.',
|
|
39
|
+
signature: 'zenOnMount(callback: () => void | (() => void)): void'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'zenOnDestroy',
|
|
43
|
+
kind: 'function',
|
|
44
|
+
description: 'Called when component is removed from the DOM.',
|
|
45
|
+
signature: 'zenOnDestroy(callback: () => void): void'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'zenOnUpdate',
|
|
49
|
+
kind: 'function',
|
|
50
|
+
description: 'Called after any state update causes a re-render.',
|
|
51
|
+
signature: 'zenOnUpdate(callback: () => void): void'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'zenRef',
|
|
55
|
+
kind: 'function',
|
|
56
|
+
description: 'Create a reactive reference.',
|
|
57
|
+
signature: 'zenRef<T>(initial: T): { value: T }'
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'zenState',
|
|
61
|
+
kind: 'function',
|
|
62
|
+
description: 'Create reactive state.',
|
|
63
|
+
signature: 'zenState<T>(initial: T): [T, (value: T) => void]'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'zenMemo',
|
|
67
|
+
kind: 'function',
|
|
68
|
+
description: 'Memoize a computed value.',
|
|
69
|
+
signature: 'zenMemo<T>(compute: () => T): T'
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'zenBatch',
|
|
73
|
+
kind: 'function',
|
|
74
|
+
description: 'Batch multiple state updates.',
|
|
75
|
+
signature: 'zenBatch(callback: () => void): void'
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'zenUntrack',
|
|
79
|
+
kind: 'function',
|
|
80
|
+
description: 'Run code without tracking dependencies.',
|
|
81
|
+
signature: 'zenUntrack<T>(callback: () => T): T'
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
'zenith/router': {
|
|
86
|
+
module: 'zenith/router',
|
|
87
|
+
description: 'File-based SPA router for Zenith framework.',
|
|
88
|
+
exports: [
|
|
89
|
+
{
|
|
90
|
+
name: 'ZenLink',
|
|
91
|
+
kind: 'component',
|
|
92
|
+
description: 'Declarative navigation component for routes.',
|
|
93
|
+
signature: '<ZenLink to="/path" preload?>{children}</ZenLink>'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'useRoute',
|
|
97
|
+
kind: 'function',
|
|
98
|
+
description: 'Provides reactive access to the current route. Must be called at top-level script scope.',
|
|
99
|
+
signature: 'useRoute(): { path: string; params: Record<string, string>; query: Record<string, string> }'
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'useRouter',
|
|
103
|
+
kind: 'function',
|
|
104
|
+
description: 'Provides programmatic navigation methods.',
|
|
105
|
+
signature: 'useRouter(): { navigate: (to: string, options?: { replace?: boolean }) => void; back: () => void; forward: () => void }'
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'navigate',
|
|
109
|
+
kind: 'function',
|
|
110
|
+
description: 'Navigate to a route programmatically.',
|
|
111
|
+
signature: 'navigate(to: string, options?: { replace?: boolean }): void'
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'prefetch',
|
|
115
|
+
kind: 'function',
|
|
116
|
+
description: 'Prefetch a route for faster navigation.',
|
|
117
|
+
signature: 'prefetch(path: string): Promise<void>'
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'isActive',
|
|
121
|
+
kind: 'function',
|
|
122
|
+
description: 'Check if a route is currently active.',
|
|
123
|
+
signature: 'isActive(path: string, exact?: boolean): boolean'
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'getRoute',
|
|
127
|
+
kind: 'function',
|
|
128
|
+
description: 'Get the current route state.',
|
|
129
|
+
signature: 'getRoute(): { path: string; params: Record<string, string>; query: Record<string, string> }'
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get a core module by name
|
|
137
|
+
*/
|
|
138
|
+
export function getCoreModule(moduleName: string): CoreModuleMetadata | undefined {
|
|
139
|
+
return CORE_MODULES[moduleName];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get all core module names
|
|
144
|
+
*/
|
|
145
|
+
export function getCoreModuleNames(): string[] {
|
|
146
|
+
return Object.keys(CORE_MODULES);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get an export from a core module
|
|
151
|
+
*/
|
|
152
|
+
export function getCoreExport(moduleName: string, exportName: string): ModuleExport | undefined {
|
|
153
|
+
const module = CORE_MODULES[moduleName];
|
|
154
|
+
if (!module) return undefined;
|
|
155
|
+
return module.exports.find(e => e.name === exportName);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Check if a module is a core Zenith module
|
|
160
|
+
*/
|
|
161
|
+
export function isCoreModule(moduleName: string): boolean {
|
|
162
|
+
return moduleName in CORE_MODULES;
|
|
163
|
+
}
|