@hypen-space/core 0.4.37 → 0.4.39
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/README.md +13 -14
- package/dist/app.js +304 -227
- package/dist/app.js.map +5 -5
- package/dist/components/builtin.d.ts +17 -0
- package/dist/components/builtin.js +336 -256
- package/dist/components/builtin.js.map +6 -6
- package/dist/context.d.ts +11 -20
- package/dist/context.js +69 -101
- package/dist/context.js.map +3 -3
- package/dist/datasource.js +80 -0
- package/dist/datasource.js.map +10 -0
- package/dist/disposable.js +60 -63
- package/dist/disposable.js.map +2 -2
- package/dist/events.js +60 -63
- package/dist/events.js.map +2 -2
- package/dist/hypen.js +78 -0
- package/dist/hypen.js.map +10 -0
- package/dist/index.browser.d.ts +2 -1
- package/dist/index.browser.js +327 -281
- package/dist/index.browser.js.map +7 -8
- package/dist/index.d.ts +0 -3
- package/dist/index.js +547 -246
- package/dist/index.js.map +9 -9
- package/dist/logger.js +60 -64
- package/dist/logger.js.map +2 -2
- package/dist/remote/client.js +258 -158
- package/dist/remote/client.js.map +6 -6
- package/dist/remote/index.d.ts +6 -5
- package/dist/remote/index.js +255 -1985
- package/dist/remote/index.js.map +6 -13
- package/dist/remote/session.js +151 -0
- package/dist/remote/session.js.map +10 -0
- package/dist/renderer.js +60 -63
- package/dist/renderer.js.map +2 -2
- package/dist/result.js +235 -0
- package/dist/result.js.map +10 -0
- package/dist/retry.js +344 -0
- package/dist/retry.js.map +11 -0
- package/dist/router.js +62 -70
- package/dist/router.js.map +2 -2
- package/dist/state.js +3 -8
- package/dist/state.js.map +2 -2
- package/package.json +11 -56
- package/src/components/builtin.ts +78 -56
- package/src/context.ts +22 -65
- package/src/index.browser.ts +5 -4
- package/src/index.ts +10 -23
- package/src/remote/index.ts +9 -5
- package/src/result.ts +11 -0
- package/dist/discovery.d.ts +0 -90
- package/dist/discovery.js +0 -1334
- package/dist/discovery.js.map +0 -15
- package/dist/engine.browser.d.ts +0 -116
- package/dist/engine.browser.js +0 -479
- package/dist/engine.browser.js.map +0 -12
- package/dist/engine.d.ts +0 -107
- package/dist/engine.js +0 -543
- package/dist/engine.js.map +0 -12
- package/dist/loader.d.ts +0 -51
- package/dist/loader.js +0 -292
- package/dist/loader.js.map +0 -11
- package/dist/plugin.d.ts +0 -39
- package/dist/plugin.js +0 -685
- package/dist/plugin.js.map +0 -12
- package/dist/remote/server.d.ts +0 -188
- package/dist/remote/server.js +0 -2270
- package/dist/remote/server.js.map +0 -19
- package/src/discovery.ts +0 -527
- package/src/engine.browser.ts +0 -302
- package/src/engine.ts +0 -282
- package/src/loader.ts +0 -136
- package/src/plugin.ts +0 -220
- package/src/remote/server.ts +0 -879
- package/wasm-browser/README.md +0 -594
- package/wasm-browser/hypen_engine.d.ts +0 -389
- package/wasm-browser/hypen_engine.js +0 -1070
- package/wasm-browser/hypen_engine_bg.wasm +0 -0
- package/wasm-browser/hypen_engine_bg.wasm.d.ts +0 -37
- package/wasm-browser/package.json +0 -24
- package/wasm-node/README.md +0 -594
- package/wasm-node/hypen_engine.d.ts +0 -327
- package/wasm-node/hypen_engine.js +0 -979
- package/wasm-node/hypen_engine_bg.wasm +0 -0
- package/wasm-node/hypen_engine_bg.wasm.d.ts +0 -37
- package/wasm-node/package.json +0 -22
package/src/discovery.ts
DELETED
|
@@ -1,527 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Component Discovery
|
|
3
|
-
*
|
|
4
|
-
* Automatically discovers Hypen components from the filesystem.
|
|
5
|
-
*
|
|
6
|
-
* Supports multiple conventions:
|
|
7
|
-
* 1. Single-file: ComponentName.ts with .ui(hypen`...`) inline template
|
|
8
|
-
* 2. Folder-based: ComponentName/component.ts + ComponentName/component.hypen
|
|
9
|
-
* 3. Sibling files: ComponentName.ts + ComponentName.hypen
|
|
10
|
-
* 4. Index-based: ComponentName/index.ts + ComponentName/index.hypen
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { existsSync, readdirSync, readFileSync, watch } from "fs";
|
|
14
|
-
import { join, basename, resolve, relative } from "path";
|
|
15
|
-
import type { HypenModuleDefinition } from "./app.js";
|
|
16
|
-
import { frameworkLoggers } from "./logger.js";
|
|
17
|
-
|
|
18
|
-
export interface DiscoveredComponent {
|
|
19
|
-
name: string;
|
|
20
|
-
/** Path to .hypen file (null for single-file components with inline templates) */
|
|
21
|
-
hypenPath: string | null;
|
|
22
|
-
/** Path to .ts module file */
|
|
23
|
-
modulePath: string | null;
|
|
24
|
-
/** The UI template (from .hypen file or inline) */
|
|
25
|
-
template: string;
|
|
26
|
-
/** Whether this component has a module (state/actions) */
|
|
27
|
-
hasModule: boolean;
|
|
28
|
-
/** Whether this is a single-file component with inline template */
|
|
29
|
-
isSingleFile: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface DiscoveryOptions {
|
|
33
|
-
/**
|
|
34
|
-
* Which naming patterns to look for
|
|
35
|
-
* Default: ["single-file", "folder", "sibling", "index"]
|
|
36
|
-
*
|
|
37
|
-
* - single-file: ComponentName.ts with .ui(hypen`...`) inline template
|
|
38
|
-
* - folder: ComponentName/component.ts + ComponentName/component.hypen
|
|
39
|
-
* - sibling: ComponentName.ts + ComponentName.hypen
|
|
40
|
-
* - index: ComponentName/index.ts + ComponentName/index.hypen
|
|
41
|
-
*/
|
|
42
|
-
patterns?: ("single-file" | "folder" | "sibling" | "index")[];
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Recursively scan subdirectories
|
|
46
|
-
* Default: true
|
|
47
|
-
*/
|
|
48
|
-
recursive?: boolean;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Enable debug logging
|
|
52
|
-
* Default: false
|
|
53
|
-
*/
|
|
54
|
-
debug?: boolean;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface WatchOptions extends DiscoveryOptions {
|
|
58
|
-
/**
|
|
59
|
-
* Callback when components change
|
|
60
|
-
*/
|
|
61
|
-
onChange?: (components: DiscoveredComponent[]) => void;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Callback when a component is added
|
|
65
|
-
*/
|
|
66
|
-
onAdd?: (component: DiscoveredComponent) => void;
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Callback when a component is removed
|
|
70
|
-
*/
|
|
71
|
-
onRemove?: (name: string) => void;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Callback when a component is updated
|
|
75
|
-
*/
|
|
76
|
-
onUpdate?: (component: DiscoveredComponent) => void;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Remove import statements from Hypen template
|
|
81
|
-
*/
|
|
82
|
-
function removeImports(text: string): string {
|
|
83
|
-
return text.replace(
|
|
84
|
-
/import\s+(?:\{[^}]+\}|\w+)\s+from\s+["'][^"']+["']\s*/g,
|
|
85
|
-
""
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Discover all Hypen components in a directory
|
|
91
|
-
*/
|
|
92
|
-
export async function discoverComponents(
|
|
93
|
-
baseDir: string,
|
|
94
|
-
options: DiscoveryOptions = {}
|
|
95
|
-
): Promise<DiscoveredComponent[]> {
|
|
96
|
-
const {
|
|
97
|
-
patterns = ["single-file", "folder", "sibling", "index"],
|
|
98
|
-
recursive = true,
|
|
99
|
-
debug = false,
|
|
100
|
-
} = options;
|
|
101
|
-
|
|
102
|
-
const log = debug
|
|
103
|
-
? (...args: unknown[]) => frameworkLoggers.discovery.debug(...args)
|
|
104
|
-
: () => {};
|
|
105
|
-
|
|
106
|
-
const resolvedDir = resolve(baseDir);
|
|
107
|
-
const components: DiscoveredComponent[] = [];
|
|
108
|
-
const seen = new Set<string>();
|
|
109
|
-
|
|
110
|
-
log("Scanning directory:", resolvedDir);
|
|
111
|
-
log("Patterns:", patterns);
|
|
112
|
-
|
|
113
|
-
// Helper to add a two-file component (.hypen + .ts)
|
|
114
|
-
const addTwoFileComponent = (
|
|
115
|
-
name: string,
|
|
116
|
-
hypenPath: string,
|
|
117
|
-
modulePath: string | null
|
|
118
|
-
) => {
|
|
119
|
-
if (seen.has(name)) {
|
|
120
|
-
log(`Skipping duplicate: ${name}`);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
seen.add(name);
|
|
125
|
-
const templateRaw = readFileSync(hypenPath, "utf-8");
|
|
126
|
-
// Preserve import statements in templates — the engine now processes them
|
|
127
|
-
// via parse_document() and resolves imports through the component resolver
|
|
128
|
-
const template = templateRaw.trim();
|
|
129
|
-
|
|
130
|
-
components.push({
|
|
131
|
-
name,
|
|
132
|
-
hypenPath,
|
|
133
|
-
modulePath,
|
|
134
|
-
template,
|
|
135
|
-
hasModule: modulePath !== null,
|
|
136
|
-
isSingleFile: false,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
log(`Found: ${name} (two-file, ${modulePath ? "with module" : "stateless"})`);
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
// Helper to add a single-file component (.ts with inline template)
|
|
143
|
-
const addSingleFileComponent = (
|
|
144
|
-
name: string,
|
|
145
|
-
modulePath: string,
|
|
146
|
-
template: string
|
|
147
|
-
) => {
|
|
148
|
-
if (seen.has(name)) {
|
|
149
|
-
log(`Skipping duplicate: ${name}`);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
seen.add(name);
|
|
154
|
-
components.push({
|
|
155
|
-
name,
|
|
156
|
-
hypenPath: null,
|
|
157
|
-
modulePath,
|
|
158
|
-
template,
|
|
159
|
-
hasModule: true,
|
|
160
|
-
isSingleFile: true,
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
log(`Found: ${name} (single-file with inline template)`);
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
// Scan directory for folder-based components
|
|
167
|
-
const scanForFolderComponents = (dir: string) => {
|
|
168
|
-
if (!existsSync(dir)) return;
|
|
169
|
-
|
|
170
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
171
|
-
|
|
172
|
-
for (const entry of entries) {
|
|
173
|
-
if (!entry.isDirectory()) continue;
|
|
174
|
-
|
|
175
|
-
const folderPath = join(dir, entry.name);
|
|
176
|
-
const componentName = entry.name;
|
|
177
|
-
|
|
178
|
-
// Check folder-based pattern: Name/component.hypen
|
|
179
|
-
if (patterns.includes("folder")) {
|
|
180
|
-
const hypenPath = join(folderPath, "component.hypen");
|
|
181
|
-
if (existsSync(hypenPath)) {
|
|
182
|
-
const modulePath = join(folderPath, "component.ts");
|
|
183
|
-
addTwoFileComponent(
|
|
184
|
-
componentName,
|
|
185
|
-
hypenPath,
|
|
186
|
-
existsSync(modulePath) ? modulePath : null
|
|
187
|
-
);
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Check index-based pattern: Name/index.hypen
|
|
193
|
-
if (patterns.includes("index")) {
|
|
194
|
-
const hypenPath = join(folderPath, "index.hypen");
|
|
195
|
-
if (existsSync(hypenPath)) {
|
|
196
|
-
const modulePath = join(folderPath, "index.ts");
|
|
197
|
-
addTwoFileComponent(
|
|
198
|
-
componentName,
|
|
199
|
-
hypenPath,
|
|
200
|
-
existsSync(modulePath) ? modulePath : null
|
|
201
|
-
);
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Recursive scan
|
|
207
|
-
if (recursive) {
|
|
208
|
-
scanForFolderComponents(folderPath);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
// Scan for sibling file components (.ts + .hypen pairs)
|
|
214
|
-
const scanForSiblingComponents = (dir: string) => {
|
|
215
|
-
if (!existsSync(dir)) return;
|
|
216
|
-
|
|
217
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
218
|
-
|
|
219
|
-
for (const entry of entries) {
|
|
220
|
-
if (entry.isDirectory()) {
|
|
221
|
-
if (recursive) {
|
|
222
|
-
scanForSiblingComponents(join(dir, entry.name));
|
|
223
|
-
}
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (!entry.name.endsWith(".hypen")) continue;
|
|
228
|
-
|
|
229
|
-
const hypenPath = join(dir, entry.name);
|
|
230
|
-
const baseName = basename(entry.name, ".hypen");
|
|
231
|
-
|
|
232
|
-
// Skip component.hypen and index.hypen (handled by folder patterns)
|
|
233
|
-
if (baseName === "component" || baseName === "index") continue;
|
|
234
|
-
|
|
235
|
-
const modulePath = join(dir, `${baseName}.ts`);
|
|
236
|
-
addTwoFileComponent(
|
|
237
|
-
baseName,
|
|
238
|
-
hypenPath,
|
|
239
|
-
existsSync(modulePath) ? modulePath : null
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
// Scan for single-file components (.ts files with inline templates)
|
|
245
|
-
const scanForSingleFileComponents = async (dir: string) => {
|
|
246
|
-
if (!existsSync(dir)) return;
|
|
247
|
-
|
|
248
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
249
|
-
|
|
250
|
-
for (const entry of entries) {
|
|
251
|
-
if (entry.isDirectory()) {
|
|
252
|
-
if (recursive) {
|
|
253
|
-
await scanForSingleFileComponents(join(dir, entry.name));
|
|
254
|
-
}
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Only look at .ts files
|
|
259
|
-
if (!entry.name.endsWith(".ts")) continue;
|
|
260
|
-
|
|
261
|
-
// Skip files that are clearly not components
|
|
262
|
-
if (entry.name.startsWith('.') || entry.name.includes('.test.') || entry.name.includes('.spec.')) continue;
|
|
263
|
-
|
|
264
|
-
// Skip component.ts and index.ts (handled by folder patterns)
|
|
265
|
-
const baseName = basename(entry.name, ".ts");
|
|
266
|
-
if (baseName === "component" || baseName === "index") continue;
|
|
267
|
-
|
|
268
|
-
// Skip if there's a matching .hypen file (handled by sibling pattern)
|
|
269
|
-
const hypenPath = join(dir, `${baseName}.hypen`);
|
|
270
|
-
if (existsSync(hypenPath)) continue;
|
|
271
|
-
|
|
272
|
-
// Check if this looks like a single-file component by looking for .ui( pattern
|
|
273
|
-
const modulePath = join(dir, entry.name);
|
|
274
|
-
const content = readFileSync(modulePath, "utf-8");
|
|
275
|
-
|
|
276
|
-
// Quick heuristic: look for .ui( or .ui(hypen pattern
|
|
277
|
-
if (content.includes(".ui(") || content.includes(".ui(hypen")) {
|
|
278
|
-
// Try to import and check for template
|
|
279
|
-
try {
|
|
280
|
-
const moduleExport = await import(modulePath);
|
|
281
|
-
const module = moduleExport.default as HypenModuleDefinition<any>;
|
|
282
|
-
|
|
283
|
-
if (module && typeof module === "object" && module.template) {
|
|
284
|
-
addSingleFileComponent(baseName, modulePath, module.template);
|
|
285
|
-
} else if (module && typeof module === "object") {
|
|
286
|
-
frameworkLoggers.discovery.warn(`Skipping "${entry.name}": has .ui() pattern but module has no .template property. Did you forget to call .ui(hypen\`...\`)?`);
|
|
287
|
-
}
|
|
288
|
-
} catch (e) {
|
|
289
|
-
// Failed to import, skip this file
|
|
290
|
-
log(`Failed to import potential single-file component: ${entry.name}`, e);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
// Run scans based on patterns
|
|
297
|
-
if (patterns.includes("folder") || patterns.includes("index")) {
|
|
298
|
-
scanForFolderComponents(resolvedDir);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (patterns.includes("sibling")) {
|
|
302
|
-
scanForSiblingComponents(resolvedDir);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (patterns.includes("single-file")) {
|
|
306
|
-
await scanForSingleFileComponents(resolvedDir);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
log(`Discovered ${components.length} components`);
|
|
310
|
-
|
|
311
|
-
return components;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Load discovered components into a map for use with Hypen
|
|
316
|
-
*/
|
|
317
|
-
export async function loadDiscoveredComponents(
|
|
318
|
-
components: DiscoveredComponent[]
|
|
319
|
-
): Promise<
|
|
320
|
-
Map<
|
|
321
|
-
string,
|
|
322
|
-
{
|
|
323
|
-
name: string;
|
|
324
|
-
module: HypenModuleDefinition<any>;
|
|
325
|
-
template: string;
|
|
326
|
-
}
|
|
327
|
-
>
|
|
328
|
-
> {
|
|
329
|
-
// Dynamically import app to avoid circular dependencies
|
|
330
|
-
const { app } = await import("./app.js");
|
|
331
|
-
|
|
332
|
-
const loaded = new Map<
|
|
333
|
-
string,
|
|
334
|
-
{
|
|
335
|
-
name: string;
|
|
336
|
-
module: HypenModuleDefinition;
|
|
337
|
-
template: string;
|
|
338
|
-
}
|
|
339
|
-
>();
|
|
340
|
-
|
|
341
|
-
for (const component of components) {
|
|
342
|
-
let module: HypenModuleDefinition<any>;
|
|
343
|
-
let template = component.template;
|
|
344
|
-
|
|
345
|
-
if (component.modulePath) {
|
|
346
|
-
// Import the TypeScript module
|
|
347
|
-
const moduleExport = await import(component.modulePath);
|
|
348
|
-
module = moduleExport.default as HypenModuleDefinition<any>;
|
|
349
|
-
|
|
350
|
-
// For single-file components, template is already in the module
|
|
351
|
-
if (component.isSingleFile && module.template) {
|
|
352
|
-
template = module.template;
|
|
353
|
-
}
|
|
354
|
-
} else {
|
|
355
|
-
// Create a stateless module
|
|
356
|
-
module = app.defineState({}).build();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
loaded.set(component.name, {
|
|
360
|
-
name: component.name,
|
|
361
|
-
module,
|
|
362
|
-
template,
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return loaded;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Watch a directory for component changes
|
|
371
|
-
*/
|
|
372
|
-
export function watchComponents(
|
|
373
|
-
baseDir: string,
|
|
374
|
-
options: WatchOptions = {}
|
|
375
|
-
): { stop: () => void } {
|
|
376
|
-
const resolvedDir = resolve(baseDir);
|
|
377
|
-
const {
|
|
378
|
-
onChange,
|
|
379
|
-
onAdd,
|
|
380
|
-
onRemove,
|
|
381
|
-
onUpdate,
|
|
382
|
-
debug = false,
|
|
383
|
-
...discoveryOptions
|
|
384
|
-
} = options;
|
|
385
|
-
|
|
386
|
-
const log = debug
|
|
387
|
-
? (...args: unknown[]) => frameworkLoggers.discovery.debug("[watch]", ...args)
|
|
388
|
-
: () => {};
|
|
389
|
-
|
|
390
|
-
let currentComponents = new Map<string, DiscoveredComponent>();
|
|
391
|
-
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
392
|
-
|
|
393
|
-
// Initial scan
|
|
394
|
-
const initialScan = async () => {
|
|
395
|
-
const components = await discoverComponents(resolvedDir, discoveryOptions);
|
|
396
|
-
currentComponents = new Map(components.map((c) => [c.name, c]));
|
|
397
|
-
onChange?.(components);
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
// Debounced rescan
|
|
401
|
-
const rescan = async () => {
|
|
402
|
-
if (debounceTimer) {
|
|
403
|
-
clearTimeout(debounceTimer);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
debounceTimer = setTimeout(async () => {
|
|
407
|
-
log("Rescanning...");
|
|
408
|
-
const newComponents = await discoverComponents(
|
|
409
|
-
resolvedDir,
|
|
410
|
-
discoveryOptions
|
|
411
|
-
);
|
|
412
|
-
const newMap = new Map(newComponents.map((c) => [c.name, c]));
|
|
413
|
-
|
|
414
|
-
// Find added/removed/updated
|
|
415
|
-
for (const [name, component] of newMap) {
|
|
416
|
-
const existing = currentComponents.get(name);
|
|
417
|
-
if (!existing) {
|
|
418
|
-
log("Added:", name);
|
|
419
|
-
onAdd?.(component);
|
|
420
|
-
} else if (
|
|
421
|
-
existing.template !== component.template ||
|
|
422
|
-
existing.modulePath !== component.modulePath
|
|
423
|
-
) {
|
|
424
|
-
log("Updated:", name);
|
|
425
|
-
onUpdate?.(component);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
for (const name of currentComponents.keys()) {
|
|
430
|
-
if (!newMap.has(name)) {
|
|
431
|
-
log("Removed:", name);
|
|
432
|
-
onRemove?.(name);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
currentComponents = newMap;
|
|
437
|
-
onChange?.(newComponents);
|
|
438
|
-
}, 100);
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
// Start watching
|
|
442
|
-
const watcher = watch(
|
|
443
|
-
resolvedDir,
|
|
444
|
-
{ recursive: true },
|
|
445
|
-
(event, filename) => {
|
|
446
|
-
if (!filename) return;
|
|
447
|
-
if (filename.endsWith(".hypen") || filename.endsWith(".ts")) {
|
|
448
|
-
log("File changed:", filename);
|
|
449
|
-
rescan();
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
// Initial scan
|
|
455
|
-
initialScan();
|
|
456
|
-
|
|
457
|
-
return {
|
|
458
|
-
stop: () => {
|
|
459
|
-
watcher.close();
|
|
460
|
-
if (debounceTimer) {
|
|
461
|
-
clearTimeout(debounceTimer);
|
|
462
|
-
}
|
|
463
|
-
},
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Generate a components object from discovered components
|
|
469
|
-
* Useful for static imports / code generation
|
|
470
|
-
*/
|
|
471
|
-
export async function generateComponentsCode(
|
|
472
|
-
baseDir: string,
|
|
473
|
-
options: DiscoveryOptions & { outputDir?: string } = {}
|
|
474
|
-
): Promise<string> {
|
|
475
|
-
const components = await discoverComponents(baseDir, options);
|
|
476
|
-
const resolvedDir = resolve(baseDir);
|
|
477
|
-
const outputBase = options.outputDir ? resolve(options.outputDir) : resolvedDir;
|
|
478
|
-
|
|
479
|
-
let code = `/**
|
|
480
|
-
* Auto-generated component imports
|
|
481
|
-
* Generated by @hypen-space/core discovery
|
|
482
|
-
*/
|
|
483
|
-
|
|
484
|
-
`;
|
|
485
|
-
|
|
486
|
-
for (const component of components) {
|
|
487
|
-
let importPath: string | null = null;
|
|
488
|
-
if (component.modulePath) {
|
|
489
|
-
const rel = relative(outputBase, component.modulePath)
|
|
490
|
-
.replace(/\.ts$/, ".js");
|
|
491
|
-
importPath = rel.startsWith(".") ? rel : "./" + rel;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (importPath) {
|
|
495
|
-
code += `import ${component.name}Module from "${importPath}";\n`;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
code += `\nimport { app } from "@hypen-space/core";\n\n`;
|
|
500
|
-
|
|
501
|
-
for (const component of components) {
|
|
502
|
-
if (component.isSingleFile) {
|
|
503
|
-
// Single-file component: template is in the module itself
|
|
504
|
-
code += `export const ${component.name} = {
|
|
505
|
-
module: ${component.name}Module,
|
|
506
|
-
template: ${component.name}Module.template,
|
|
507
|
-
};\n\n`;
|
|
508
|
-
} else if (component.hasModule) {
|
|
509
|
-
// Two-file component with module
|
|
510
|
-
const templateJson = JSON.stringify(component.template);
|
|
511
|
-
code += `export const ${component.name} = {
|
|
512
|
-
module: ${component.name}Module,
|
|
513
|
-
template: ${templateJson},
|
|
514
|
-
};\n\n`;
|
|
515
|
-
} else {
|
|
516
|
-
// Stateless two-file component
|
|
517
|
-
const templateJson = JSON.stringify(component.template);
|
|
518
|
-
code += `const ${component.name}Module = app.defineState({}).build();
|
|
519
|
-
export const ${component.name} = {
|
|
520
|
-
module: ${component.name}Module,
|
|
521
|
-
template: ${templateJson},
|
|
522
|
-
};\n\n`;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
return code;
|
|
527
|
-
}
|