@useavalon/avalon 0.1.0

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.
Files changed (159) hide show
  1. package/README.md +54 -0
  2. package/mod.ts +301 -0
  3. package/package.json +85 -0
  4. package/src/build/README.md +310 -0
  5. package/src/build/integration-bundler-plugin.ts +116 -0
  6. package/src/build/integration-config.ts +168 -0
  7. package/src/build/integration-detection-plugin.ts +117 -0
  8. package/src/build/integration-resolver-plugin.ts +90 -0
  9. package/src/build/island-manifest.ts +269 -0
  10. package/src/build/island-types-generator.ts +476 -0
  11. package/src/build/mdx-island-transform.ts +464 -0
  12. package/src/build/mdx-plugin.ts +98 -0
  13. package/src/build/page-island-transform.ts +598 -0
  14. package/src/build/prop-extractors/index.ts +21 -0
  15. package/src/build/prop-extractors/lit.ts +140 -0
  16. package/src/build/prop-extractors/qwik.ts +16 -0
  17. package/src/build/prop-extractors/solid.ts +125 -0
  18. package/src/build/prop-extractors/svelte.ts +194 -0
  19. package/src/build/prop-extractors/vue.ts +111 -0
  20. package/src/build/sidecar-file-manager.ts +104 -0
  21. package/src/build/sidecar-renderer.ts +30 -0
  22. package/src/client/adapters/index.ts +13 -0
  23. package/src/client/adapters/lit-adapter.ts +654 -0
  24. package/src/client/adapters/preact-adapter.ts +331 -0
  25. package/src/client/adapters/qwik-adapter.ts +345 -0
  26. package/src/client/adapters/react-adapter.ts +353 -0
  27. package/src/client/adapters/solid-adapter.ts +451 -0
  28. package/src/client/adapters/svelte-adapter.ts +524 -0
  29. package/src/client/adapters/vue-adapter.ts +467 -0
  30. package/src/client/components.ts +35 -0
  31. package/src/client/css-hmr-handler.ts +344 -0
  32. package/src/client/framework-adapter.ts +462 -0
  33. package/src/client/hmr-coordinator.ts +396 -0
  34. package/src/client/hmr-error-overlay.js +533 -0
  35. package/src/client/main.js +816 -0
  36. package/src/client/tests/css-hmr-handler.test.ts +360 -0
  37. package/src/client/tests/framework-adapter.test.ts +519 -0
  38. package/src/client/tests/hmr-coordinator.test.ts +176 -0
  39. package/src/client/tests/hydration-option-parsing.test.ts +107 -0
  40. package/src/client/tests/lit-adapter.test.ts +427 -0
  41. package/src/client/tests/preact-adapter.test.ts +353 -0
  42. package/src/client/tests/qwik-adapter.test.ts +343 -0
  43. package/src/client/tests/react-adapter.test.ts +317 -0
  44. package/src/client/tests/solid-adapter.test.ts +396 -0
  45. package/src/client/tests/svelte-adapter.test.ts +387 -0
  46. package/src/client/tests/vue-adapter.test.ts +407 -0
  47. package/src/client/types/framework-runtime.d.ts +68 -0
  48. package/src/client/types/vite-hmr.d.ts +46 -0
  49. package/src/client/types/vite-virtual-modules.d.ts +60 -0
  50. package/src/components/Image.tsx +123 -0
  51. package/src/components/IslandErrorBoundary.tsx +145 -0
  52. package/src/components/LayoutDataErrorBoundary.tsx +141 -0
  53. package/src/components/LayoutErrorBoundary.tsx +127 -0
  54. package/src/components/PersistentIsland.tsx +52 -0
  55. package/src/components/StreamingErrorBoundary.tsx +233 -0
  56. package/src/components/StreamingLayout.tsx +538 -0
  57. package/src/components/tests/component-analyzer.test.ts +96 -0
  58. package/src/components/tests/component-detection.test.ts +347 -0
  59. package/src/components/tests/persistent-islands.test.ts +398 -0
  60. package/src/core/components/component-analyzer.ts +192 -0
  61. package/src/core/components/component-detection.ts +508 -0
  62. package/src/core/components/enhanced-framework-detector.ts +500 -0
  63. package/src/core/components/framework-registry.ts +563 -0
  64. package/src/core/components/tests/enhanced-framework-detector.test.ts +577 -0
  65. package/src/core/components/tests/framework-registry.test.ts +465 -0
  66. package/src/core/content/mdx-processor.ts +46 -0
  67. package/src/core/integrations/README.md +282 -0
  68. package/src/core/integrations/index.ts +19 -0
  69. package/src/core/integrations/loader.ts +125 -0
  70. package/src/core/integrations/registry.ts +195 -0
  71. package/src/core/islands/island-persistence.ts +325 -0
  72. package/src/core/islands/island-state-serializer.ts +258 -0
  73. package/src/core/islands/persistent-island-context.tsx +80 -0
  74. package/src/core/islands/use-persistent-state.ts +68 -0
  75. package/src/core/layout/enhanced-layout-resolver.ts +322 -0
  76. package/src/core/layout/layout-cache-manager.ts +485 -0
  77. package/src/core/layout/layout-composer.ts +357 -0
  78. package/src/core/layout/layout-data-loader.ts +516 -0
  79. package/src/core/layout/layout-discovery.ts +243 -0
  80. package/src/core/layout/layout-matcher.ts +299 -0
  81. package/src/core/layout/layout-types.ts +110 -0
  82. package/src/core/layout/tests/enhanced-layout-resolver.test.ts +477 -0
  83. package/src/core/layout/tests/layout-cache-optimization.test.ts +149 -0
  84. package/src/core/layout/tests/layout-composer.test.ts +486 -0
  85. package/src/core/layout/tests/layout-data-loader.test.ts +443 -0
  86. package/src/core/layout/tests/layout-discovery.test.ts +253 -0
  87. package/src/core/layout/tests/layout-matcher.test.ts +480 -0
  88. package/src/core/modules/framework-module-resolver.ts +273 -0
  89. package/src/core/modules/tests/framework-module-resolver.test.ts +263 -0
  90. package/src/core/modules/tests/module-resolution-integration.test.ts +117 -0
  91. package/src/islands/component-analysis.ts +213 -0
  92. package/src/islands/css-utils.ts +565 -0
  93. package/src/islands/discovery/index.ts +80 -0
  94. package/src/islands/discovery/registry.ts +340 -0
  95. package/src/islands/discovery/resolver.ts +477 -0
  96. package/src/islands/discovery/scanner.ts +386 -0
  97. package/src/islands/discovery/tests/island-discovery.test.ts +881 -0
  98. package/src/islands/discovery/types.ts +117 -0
  99. package/src/islands/discovery/validator.ts +544 -0
  100. package/src/islands/discovery/watcher.ts +368 -0
  101. package/src/islands/framework-detection.ts +428 -0
  102. package/src/islands/integration-loader.ts +490 -0
  103. package/src/islands/island.tsx +565 -0
  104. package/src/islands/render-cache.ts +550 -0
  105. package/src/islands/types.ts +80 -0
  106. package/src/islands/universal-css-collector.ts +157 -0
  107. package/src/islands/universal-head-collector.ts +137 -0
  108. package/src/layout-system.d.ts +592 -0
  109. package/src/layout-system.ts +218 -0
  110. package/src/middleware/__tests__/discovery.test.ts +107 -0
  111. package/src/middleware/discovery.ts +268 -0
  112. package/src/middleware/executor.ts +315 -0
  113. package/src/middleware/index.ts +76 -0
  114. package/src/middleware/types.ts +99 -0
  115. package/src/nitro/build-config.ts +576 -0
  116. package/src/nitro/config.ts +483 -0
  117. package/src/nitro/error-handler.ts +636 -0
  118. package/src/nitro/index.ts +173 -0
  119. package/src/nitro/island-manifest.ts +584 -0
  120. package/src/nitro/middleware-adapter.ts +260 -0
  121. package/src/nitro/renderer.ts +1458 -0
  122. package/src/nitro/route-discovery.ts +439 -0
  123. package/src/nitro/types.ts +321 -0
  124. package/src/render/collect-css.ts +198 -0
  125. package/src/render/error-pages.ts +79 -0
  126. package/src/render/isolated-ssr-renderer.ts +654 -0
  127. package/src/render/ssr.ts +1030 -0
  128. package/src/schemas/api.ts +30 -0
  129. package/src/schemas/core.ts +64 -0
  130. package/src/schemas/index.ts +212 -0
  131. package/src/schemas/layout.ts +279 -0
  132. package/src/schemas/routing/index.ts +38 -0
  133. package/src/schemas/routing.ts +376 -0
  134. package/src/types/as-island.ts +20 -0
  135. package/src/types/image.d.ts +106 -0
  136. package/src/types/index.d.ts +22 -0
  137. package/src/types/island-jsx.d.ts +33 -0
  138. package/src/types/island-prop.d.ts +20 -0
  139. package/src/types/layout.ts +285 -0
  140. package/src/types/mdx.d.ts +6 -0
  141. package/src/types/routing.ts +555 -0
  142. package/src/types/tests/layout-types.test.ts +197 -0
  143. package/src/types/types.ts +5 -0
  144. package/src/types/urlpattern.d.ts +49 -0
  145. package/src/types/vite-env.d.ts +11 -0
  146. package/src/utils/dev-logger.ts +299 -0
  147. package/src/utils/fs.ts +151 -0
  148. package/src/vite-plugin/auto-discover.ts +551 -0
  149. package/src/vite-plugin/config.ts +266 -0
  150. package/src/vite-plugin/errors.ts +127 -0
  151. package/src/vite-plugin/image-optimization.ts +151 -0
  152. package/src/vite-plugin/integration-activator.ts +126 -0
  153. package/src/vite-plugin/island-sidecar-plugin.ts +176 -0
  154. package/src/vite-plugin/module-discovery.ts +189 -0
  155. package/src/vite-plugin/nitro-integration.ts +1334 -0
  156. package/src/vite-plugin/plugin.ts +329 -0
  157. package/src/vite-plugin/tests/image-optimization.test.ts +54 -0
  158. package/src/vite-plugin/types.ts +327 -0
  159. package/src/vite-plugin/validation.ts +228 -0
@@ -0,0 +1,477 @@
1
+ /**
2
+ * Island Resolver
3
+ *
4
+ * Handles runtime resolution of island references to actual file paths.
5
+ * Supports priority-based resolution with default /src/islands/ having highest priority.
6
+ *
7
+ * ## Resolution Order
8
+ *
9
+ * When resolving an island reference, the resolver follows this priority order:
10
+ *
11
+ * 1. **Explicit path-based references** (e.g., "src/modules/auth/islands/Counter")
12
+ * - Full path to the island file
13
+ * - Always unambiguous
14
+ *
15
+ * 2. **Qualified name matches** (e.g., "modules/auth/Counter")
16
+ * - Namespace/name format
17
+ * - Namespace is derived from directory path between src/ and islands/
18
+ *
19
+ * 3. **Default /src/islands/ directory**
20
+ * - Highest priority for simple names (backward compatibility)
21
+ * - If an island exists in both default and nested directories,
22
+ * the default directory wins
23
+ *
24
+ * 4. **Nested directories in alphabetical order**
25
+ * - When not found in default directory
26
+ * - Sorted alphabetically by namespace
27
+ *
28
+ * ## Namespace Conventions
29
+ *
30
+ * - src/islands/Counter.tsx -> namespace: "", qualified: "Counter"
31
+ * - src/modules/auth/islands/LoginForm.tsx -> namespace: "modules/auth", qualified: "modules/auth/LoginForm"
32
+ * - src/features/checkout/islands/PaymentForm.tsx -> namespace: "features/checkout", qualified: "features/checkout/PaymentForm"
33
+ *
34
+ * ## Handling Collisions
35
+ *
36
+ * When multiple islands share the same name:
37
+ * - Use qualified names to disambiguate
38
+ * - The resolver returns `ambiguous: true` with alternatives
39
+ * - Default directory always has priority for simple name resolution
40
+ */
41
+
42
+ import { relative, resolve, dirname } from "node:path";
43
+ import type { DiscoveredIsland, IslandDirectory } from "./types.ts";
44
+ import type { IslandRegistry } from "./registry.ts";
45
+ import { parseQualifiedIslandName, getQualifiedIslandName } from "./scanner.ts";
46
+
47
+ /**
48
+ * Result of resolving an island reference
49
+ */
50
+ export interface ResolutionResult {
51
+ /** Resolved island */
52
+ island: DiscoveredIsland;
53
+ /** Import path for the island */
54
+ importPath: string;
55
+ /** Whether resolution was ambiguous (multiple matches found) */
56
+ ambiguous: boolean;
57
+ /** Alternative matches if ambiguous */
58
+ alternatives?: DiscoveredIsland[];
59
+ }
60
+
61
+ /**
62
+ * Options for import path generation
63
+ */
64
+ export interface ImportPathOptions {
65
+ /** Whether generating for development (true) or production (false) */
66
+ isDevelopment?: boolean;
67
+ /** Base path for imports (e.g., "/@fs/" for dev, "/" for prod) */
68
+ basePath?: string;
69
+ /** Project root directory */
70
+ projectRoot?: string;
71
+ /** Whether to use absolute paths */
72
+ absolute?: boolean;
73
+ }
74
+
75
+ /**
76
+ * Default options for import path generation
77
+ */
78
+ const DEFAULT_IMPORT_OPTIONS: Required<ImportPathOptions> = {
79
+ isDevelopment: true,
80
+ basePath: "/",
81
+ projectRoot: "",
82
+ absolute: false,
83
+ };
84
+
85
+ /**
86
+ * Island Resolver class
87
+ *
88
+ * Resolves island references to actual file paths with priority-based resolution.
89
+ * Default /src/islands/ directory has highest priority for backward compatibility.
90
+ */
91
+ export class IslandResolver {
92
+ private _registry: IslandRegistry;
93
+ private _projectRoot: string;
94
+
95
+ constructor(registry: IslandRegistry, projectRoot: string) {
96
+ this._registry = registry;
97
+ this._projectRoot = projectRoot;
98
+ }
99
+
100
+ /**
101
+ * Get the underlying registry
102
+ */
103
+ get registry(): IslandRegistry {
104
+ return this._registry;
105
+ }
106
+
107
+ /**
108
+ * Get the project root
109
+ */
110
+ get projectRoot(): string {
111
+ return this._projectRoot;
112
+ }
113
+
114
+ /**
115
+ * Resolve an island reference to a file path.
116
+ *
117
+ * Resolution priority:
118
+ * 1. Explicit path-based reference (e.g., "modules/auth/Counter")
119
+ * 2. Qualified name match (namespace/name)
120
+ * 3. Default islands directory (highest priority for unqualified names)
121
+ * 4. First match in alphabetical order by namespace
122
+ *
123
+ * @param reference - Island reference (name, qualified name, or path)
124
+ * @returns Resolution result or null if not found
125
+ */
126
+ resolve(reference: string): ResolutionResult | null {
127
+ // Normalize the reference
128
+ const normalizedRef = this.normalizeReference(reference);
129
+
130
+ // Try to resolve as explicit path first
131
+ const explicitResult = this.resolveExplicitPath(normalizedRef);
132
+ if (explicitResult) {
133
+ return explicitResult;
134
+ }
135
+
136
+ // Try to resolve as qualified name (namespace/name)
137
+ if (normalizedRef.includes("/")) {
138
+ const qualifiedResult = this.resolveQualifiedName(normalizedRef);
139
+ if (qualifiedResult) {
140
+ return qualifiedResult;
141
+ }
142
+ }
143
+
144
+ // Resolve by name with priority-based resolution
145
+ return this.resolveByName(normalizedRef);
146
+ }
147
+
148
+ /**
149
+ * Normalize an island reference for consistent resolution.
150
+ */
151
+ private normalizeReference(reference: string): string {
152
+ // Remove leading/trailing slashes and whitespace
153
+ let normalized = reference.trim().replace(/^\/+|\/+$/g, "");
154
+
155
+ // Normalize path separators
156
+ normalized = normalized.replace(/\\/g, "/");
157
+
158
+ // Remove file extension if present
159
+ const extensionPatterns = [
160
+ /\.solid\.(tsx|jsx)$/,
161
+ /\.react\.(tsx|jsx)$/,
162
+ /\.lit\.(ts|js)$/,
163
+ /\.preact\.(tsx|jsx)$/,
164
+ /\.(tsx|ts|jsx|js|vue|svelte)$/,
165
+ ];
166
+
167
+ for (const pattern of extensionPatterns) {
168
+ if (pattern.test(normalized)) {
169
+ normalized = normalized.replace(pattern, "");
170
+ break;
171
+ }
172
+ }
173
+
174
+ return normalized;
175
+ }
176
+
177
+ /**
178
+ * Resolve an explicit path-based reference.
179
+ * Handles references like "src/modules/auth/islands/Counter"
180
+ */
181
+ private resolveExplicitPath(reference: string): ResolutionResult | null {
182
+ // Check if reference looks like a path (contains "islands/")
183
+ if (!reference.includes("islands/")) {
184
+ return null;
185
+ }
186
+
187
+ // Try to find an island whose relative path matches
188
+ const allIslands = this._registry.getAllIslands();
189
+
190
+ for (const island of allIslands) {
191
+ // Check if the reference matches the relative path (without extension)
192
+ const islandPathWithoutExt = island.relativePath.replace(/\.[^.]+$/, "");
193
+
194
+ // Handle framework-specific extensions
195
+ const frameworkPatterns = [
196
+ /\.solid$/, /\.react$/, /\.lit$/, /\.preact$/
197
+ ];
198
+ let cleanIslandPath = islandPathWithoutExt;
199
+ for (const pattern of frameworkPatterns) {
200
+ cleanIslandPath = cleanIslandPath.replace(pattern, "");
201
+ }
202
+
203
+ if (cleanIslandPath === reference ||
204
+ cleanIslandPath.endsWith("/" + reference) ||
205
+ islandPathWithoutExt === reference ||
206
+ islandPathWithoutExt.endsWith("/" + reference)) {
207
+ return {
208
+ island,
209
+ importPath: this.generateImportPath(island),
210
+ ambiguous: false,
211
+ };
212
+ }
213
+ }
214
+
215
+ return null;
216
+ }
217
+
218
+ /**
219
+ * Resolve a qualified name (namespace/name).
220
+ */
221
+ private resolveQualifiedName(qualifiedName: string): ResolutionResult | null {
222
+ const { namespace, name } = parseQualifiedIslandName(qualifiedName);
223
+ const island = this._registry.resolve(name, namespace);
224
+
225
+ if (!island) {
226
+ return null;
227
+ }
228
+
229
+ // Check if there are other islands with the same name
230
+ const allMatches = this._registry.findByName(name);
231
+ const ambiguous = allMatches.length > 1;
232
+
233
+ return {
234
+ island,
235
+ importPath: this.generateImportPath(island),
236
+ ambiguous,
237
+ alternatives: ambiguous ? allMatches.filter(i => i !== island) : undefined,
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Resolve by name with priority-based resolution.
243
+ * Default /src/islands/ has highest priority.
244
+ */
245
+ private resolveByName(name: string): ResolutionResult | null {
246
+ const matches = this._registry.findByName(name);
247
+
248
+ if (matches.length === 0) {
249
+ return null;
250
+ }
251
+
252
+ // Sort matches by priority:
253
+ // 1. Default directory first
254
+ // 2. Then alphabetically by namespace
255
+ const sortedMatches = [...matches].sort((a, b) => {
256
+ if (a.directory.isDefault && !b.directory.isDefault) return -1;
257
+ if (!a.directory.isDefault && b.directory.isDefault) return 1;
258
+ return a.namespace.localeCompare(b.namespace);
259
+ });
260
+
261
+ const island = sortedMatches[0];
262
+ const ambiguous = matches.length > 1;
263
+
264
+ return {
265
+ island,
266
+ importPath: this.generateImportPath(island),
267
+ ambiguous,
268
+ alternatives: ambiguous ? sortedMatches.slice(1) : undefined,
269
+ };
270
+ }
271
+
272
+ /**
273
+ * Generate an import path for an island.
274
+ * Handles both development and production paths.
275
+ *
276
+ * @param island - The island to generate an import path for
277
+ * @param options - Options for path generation
278
+ * @returns The import path string
279
+ */
280
+ generateImportPath(
281
+ island: DiscoveredIsland,
282
+ options: ImportPathOptions = {}
283
+ ): string {
284
+ const opts = { ...DEFAULT_IMPORT_OPTIONS, ...options };
285
+ const projectRoot = opts.projectRoot || this._projectRoot;
286
+
287
+ if (opts.absolute) {
288
+ // Return absolute file path
289
+ return island.filePath;
290
+ }
291
+
292
+ if (opts.isDevelopment) {
293
+ // Development: use relative path from project root with leading slash
294
+ const relativePath = relative(projectRoot, island.filePath);
295
+ const normalizedPath = relativePath.replace(/\\/g, "/");
296
+ return `${opts.basePath}${normalizedPath}`;
297
+ }
298
+
299
+ // Production: use the relative path for bundled imports
300
+ // The build system will handle the actual resolution
301
+ return `${opts.basePath}${island.relativePath}`;
302
+ }
303
+
304
+ /**
305
+ * Generate a qualified import path for disambiguation.
306
+ * Always includes the namespace for clarity.
307
+ *
308
+ * @param island - The island to generate a path for
309
+ * @returns Qualified import path
310
+ */
311
+ generateQualifiedImportPath(island: DiscoveredIsland): string {
312
+ const qualifiedName = getQualifiedIslandName(island);
313
+ return qualifiedName;
314
+ }
315
+
316
+ /**
317
+ * Get the resolution order documentation.
318
+ * Describes how islands are resolved when multiple matches exist.
319
+ *
320
+ * @returns Array of resolution order descriptions
321
+ */
322
+ getResolutionOrder(): string[] {
323
+ const directories = this._registry.directories;
324
+
325
+ return [
326
+ "Island Resolution Order:",
327
+ "1. Explicit path-based references (e.g., 'src/modules/auth/islands/Counter')",
328
+ "2. Qualified name matches (e.g., 'modules/auth/Counter')",
329
+ "3. Default /src/islands/ directory (highest priority for unqualified names)",
330
+ "4. Nested directories in alphabetical order by namespace",
331
+ "",
332
+ "Discovered directories (in priority order):",
333
+ ...directories.map((dir, index) =>
334
+ ` ${index + 1}. ${dir.relativePath}${dir.isDefault ? " (default)" : ""}`
335
+ ),
336
+ ];
337
+ }
338
+
339
+ /**
340
+ * Check if a reference would resolve ambiguously.
341
+ *
342
+ * @param reference - Island reference to check
343
+ * @returns True if the reference matches multiple islands
344
+ */
345
+ isAmbiguous(reference: string): boolean {
346
+ const result = this.resolve(reference);
347
+ return result?.ambiguous ?? false;
348
+ }
349
+
350
+ /**
351
+ * Get all possible resolutions for a reference.
352
+ * Useful for providing suggestions when resolution is ambiguous.
353
+ *
354
+ * @param reference - Island reference
355
+ * @returns Array of all matching islands
356
+ */
357
+ getAllMatches(reference: string): DiscoveredIsland[] {
358
+ const normalized = this.normalizeReference(reference);
359
+
360
+ // If it's a qualified name, return exact match only
361
+ if (normalized.includes("/") && !normalized.includes("islands/")) {
362
+ const { namespace, name } = parseQualifiedIslandName(normalized);
363
+ const island = this._registry.resolve(name, namespace);
364
+ return island ? [island] : [];
365
+ }
366
+
367
+ // For simple names, return all matches
368
+ return this._registry.findByName(normalized);
369
+ }
370
+
371
+ /**
372
+ * Suggest qualified names for disambiguation.
373
+ *
374
+ * @param name - Component name with multiple matches
375
+ * @returns Array of qualified name suggestions
376
+ */
377
+ suggestQualifiedNames(name: string): string[] {
378
+ const matches = this._registry.findByName(name);
379
+ return matches.map(island => getQualifiedIslandName(island));
380
+ }
381
+
382
+ /**
383
+ * Resolve an island and throw if not found.
384
+ *
385
+ * @param reference - Island reference
386
+ * @throws Error if island is not found
387
+ * @returns Resolution result
388
+ */
389
+ resolveOrThrow(reference: string): ResolutionResult {
390
+ const result = this.resolve(reference);
391
+
392
+ if (!result) {
393
+ const suggestions = this.findSimilarNames(reference);
394
+ let message = `Island not found: "${reference}"`;
395
+
396
+ if (suggestions.length > 0) {
397
+ message += `\n\nDid you mean one of these?\n${suggestions.map(s => ` - ${s}`).join("\n")}`;
398
+ }
399
+
400
+ throw new Error(message);
401
+ }
402
+
403
+ return result;
404
+ }
405
+
406
+ /**
407
+ * Find similar island names for suggestions.
408
+ * Uses simple string similarity for fuzzy matching.
409
+ */
410
+ private findSimilarNames(reference: string): string[] {
411
+ const allIslands = this._registry.getAllIslands();
412
+ const normalized = reference.toLowerCase();
413
+
414
+ // Find islands with similar names
415
+ const similar = allIslands
416
+ .filter(island => {
417
+ const name = island.name.toLowerCase();
418
+ const qualified = getQualifiedIslandName(island).toLowerCase();
419
+
420
+ // Check for substring match
421
+ return name.includes(normalized) ||
422
+ normalized.includes(name) ||
423
+ qualified.includes(normalized) ||
424
+ this.levenshteinDistance(name, normalized) <= 3;
425
+ })
426
+ .map(island => getQualifiedIslandName(island))
427
+ .slice(0, 5); // Limit suggestions
428
+
429
+ return similar;
430
+ }
431
+
432
+ /**
433
+ * Calculate Levenshtein distance between two strings.
434
+ * Used for fuzzy name matching.
435
+ */
436
+ private levenshteinDistance(a: string, b: string): number {
437
+ const matrix: number[][] = [];
438
+
439
+ for (let i = 0; i <= b.length; i++) {
440
+ matrix[i] = [i];
441
+ }
442
+
443
+ for (let j = 0; j <= a.length; j++) {
444
+ matrix[0][j] = j;
445
+ }
446
+
447
+ for (let i = 1; i <= b.length; i++) {
448
+ for (let j = 1; j <= a.length; j++) {
449
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
450
+ matrix[i][j] = matrix[i - 1][j - 1];
451
+ } else {
452
+ matrix[i][j] = Math.min(
453
+ matrix[i - 1][j - 1] + 1,
454
+ matrix[i][j - 1] + 1,
455
+ matrix[i - 1][j] + 1
456
+ );
457
+ }
458
+ }
459
+ }
460
+
461
+ return matrix[b.length][a.length];
462
+ }
463
+ }
464
+
465
+ /**
466
+ * Create an island resolver from a registry.
467
+ *
468
+ * @param registry - The island registry to use
469
+ * @param projectRoot - The project root directory
470
+ * @returns A new IslandResolver instance
471
+ */
472
+ export function createIslandResolver(
473
+ registry: IslandRegistry,
474
+ projectRoot: string
475
+ ): IslandResolver {
476
+ return new IslandResolver(registry, projectRoot);
477
+ }