@useavalon/avalon 0.1.13 → 0.1.15

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 (230) hide show
  1. package/dist/mod.js +1 -0
  2. package/dist/src/build/integration-bundler-plugin.js +1 -0
  3. package/dist/src/build/integration-config.js +1 -0
  4. package/dist/src/build/integration-detection-plugin.js +1 -0
  5. package/dist/src/build/integration-resolver-plugin.js +1 -0
  6. package/dist/src/build/island-manifest.js +1 -0
  7. package/dist/src/build/island-types-generator.js +5 -0
  8. package/dist/src/build/mdx-island-transform.js +2 -0
  9. package/dist/src/build/mdx-plugin.js +1 -0
  10. package/dist/src/build/page-island-transform.js +3 -0
  11. package/dist/src/build/prop-extractors/index.js +1 -0
  12. package/dist/src/build/prop-extractors/lit.js +1 -0
  13. package/dist/src/build/prop-extractors/qwik.js +1 -0
  14. package/dist/src/build/prop-extractors/solid.js +1 -0
  15. package/dist/src/build/prop-extractors/svelte.js +1 -0
  16. package/dist/src/build/prop-extractors/vue.js +1 -0
  17. package/dist/src/build/sidecar-file-manager.js +1 -0
  18. package/dist/src/build/sidecar-renderer.js +6 -0
  19. package/dist/src/client/adapters/index.js +1 -0
  20. package/dist/src/client/components.js +1 -0
  21. package/dist/src/client/css-hmr-handler.js +1 -0
  22. package/dist/src/client/framework-adapter.js +13 -0
  23. package/dist/src/client/hmr-coordinator.js +1 -0
  24. package/dist/src/client/hmr-error-overlay.js +214 -0
  25. package/dist/src/client/main.js +39 -0
  26. package/dist/src/components/Image.js +1 -0
  27. package/dist/src/components/IslandErrorBoundary.js +1 -0
  28. package/dist/src/components/LayoutDataErrorBoundary.js +1 -0
  29. package/dist/src/components/LayoutErrorBoundary.js +1 -0
  30. package/dist/src/components/PersistentIsland.js +1 -0
  31. package/dist/src/components/StreamingErrorBoundary.js +1 -0
  32. package/dist/src/components/StreamingLayout.js +29 -0
  33. package/dist/src/core/components/component-analyzer.js +1 -0
  34. package/dist/src/core/components/component-detection.js +5 -0
  35. package/dist/src/core/components/enhanced-framework-detector.js +1 -0
  36. package/dist/src/core/components/framework-registry.js +1 -0
  37. package/dist/src/core/content/mdx-processor.js +1 -0
  38. package/dist/src/core/integrations/index.js +1 -0
  39. package/dist/src/core/integrations/loader.js +1 -0
  40. package/dist/src/core/integrations/registry.js +1 -0
  41. package/dist/src/core/islands/island-persistence.js +1 -0
  42. package/dist/src/core/islands/island-state-serializer.js +1 -0
  43. package/dist/src/core/islands/persistent-island-context.js +1 -0
  44. package/dist/src/core/islands/use-persistent-state.js +1 -0
  45. package/dist/src/core/layout/enhanced-layout-resolver.js +1 -0
  46. package/dist/src/core/layout/layout-cache-manager.js +1 -0
  47. package/dist/src/core/layout/layout-composer.js +1 -0
  48. package/dist/src/core/layout/layout-data-loader.js +1 -0
  49. package/dist/src/core/layout/layout-discovery.js +1 -0
  50. package/dist/src/core/layout/layout-matcher.js +1 -0
  51. package/dist/src/core/layout/layout-types.js +1 -0
  52. package/dist/src/core/modules/framework-module-resolver.js +1 -0
  53. package/dist/src/islands/component-analysis.js +1 -0
  54. package/dist/src/islands/css-utils.js +17 -0
  55. package/dist/src/islands/discovery/index.js +1 -0
  56. package/dist/src/islands/discovery/registry.js +1 -0
  57. package/dist/src/islands/discovery/resolver.js +2 -0
  58. package/dist/src/islands/discovery/scanner.js +1 -0
  59. package/dist/src/islands/discovery/types.js +1 -0
  60. package/dist/src/islands/discovery/validator.js +18 -0
  61. package/dist/src/islands/discovery/watcher.js +1 -0
  62. package/dist/src/islands/framework-detection.js +1 -0
  63. package/dist/src/islands/integration-loader.js +1 -0
  64. package/dist/src/islands/island.js +1 -0
  65. package/dist/src/islands/render-cache.js +1 -0
  66. package/dist/src/islands/types.js +1 -0
  67. package/dist/src/islands/universal-css-collector.js +5 -0
  68. package/dist/src/islands/universal-head-collector.js +2 -0
  69. package/dist/src/layout-system.js +1 -0
  70. package/dist/src/middleware/discovery.js +1 -0
  71. package/dist/src/middleware/executor.js +1 -0
  72. package/dist/src/middleware/index.js +1 -0
  73. package/dist/src/middleware/types.js +1 -0
  74. package/dist/src/nitro/build-config.js +1 -0
  75. package/dist/src/nitro/config.js +1 -0
  76. package/dist/src/nitro/error-handler.js +198 -0
  77. package/dist/src/nitro/index.js +1 -0
  78. package/dist/src/nitro/island-manifest.js +2 -0
  79. package/dist/src/nitro/middleware-adapter.js +1 -0
  80. package/dist/src/nitro/renderer.js +183 -0
  81. package/dist/src/nitro/route-discovery.js +1 -0
  82. package/dist/src/nitro/types.js +1 -0
  83. package/dist/src/render/collect-css.js +3 -0
  84. package/dist/src/render/error-pages.js +48 -0
  85. package/dist/src/render/isolated-ssr-renderer.js +1 -0
  86. package/dist/src/render/ssr.js +90 -0
  87. package/dist/src/schemas/api.js +1 -0
  88. package/dist/src/schemas/core.js +1 -0
  89. package/dist/src/schemas/index.js +1 -0
  90. package/dist/src/schemas/layout.js +1 -0
  91. package/dist/src/schemas/routing/index.js +1 -0
  92. package/dist/src/schemas/routing.js +1 -0
  93. package/dist/src/types/as-island.js +1 -0
  94. package/dist/src/types/layout.js +1 -0
  95. package/dist/src/types/routing.js +1 -0
  96. package/dist/src/types/types.js +1 -0
  97. package/dist/src/utils/dev-logger.js +12 -0
  98. package/dist/src/utils/fs.js +1 -0
  99. package/dist/src/vite-plugin/auto-discover.js +1 -0
  100. package/dist/src/vite-plugin/config.js +1 -0
  101. package/dist/src/vite-plugin/errors.js +1 -0
  102. package/dist/src/vite-plugin/image-optimization.js +45 -0
  103. package/dist/src/vite-plugin/integration-activator.js +1 -0
  104. package/dist/src/vite-plugin/island-sidecar-plugin.js +1 -0
  105. package/dist/src/vite-plugin/module-discovery.js +1 -0
  106. package/dist/src/vite-plugin/nitro-integration.js +42 -0
  107. package/dist/src/vite-plugin/plugin.js +1 -0
  108. package/dist/src/vite-plugin/types.js +1 -0
  109. package/dist/src/vite-plugin/validation.js +2 -0
  110. package/package.json +14 -20
  111. package/mod.ts +0 -302
  112. package/src/build/integration-bundler-plugin.ts +0 -116
  113. package/src/build/integration-config.ts +0 -168
  114. package/src/build/integration-detection-plugin.ts +0 -117
  115. package/src/build/integration-resolver-plugin.ts +0 -90
  116. package/src/build/island-manifest.ts +0 -269
  117. package/src/build/island-types-generator.ts +0 -476
  118. package/src/build/mdx-island-transform.ts +0 -464
  119. package/src/build/mdx-plugin.ts +0 -98
  120. package/src/build/page-island-transform.ts +0 -598
  121. package/src/build/prop-extractors/index.ts +0 -21
  122. package/src/build/prop-extractors/lit.ts +0 -140
  123. package/src/build/prop-extractors/qwik.ts +0 -16
  124. package/src/build/prop-extractors/solid.ts +0 -125
  125. package/src/build/prop-extractors/svelte.ts +0 -194
  126. package/src/build/prop-extractors/vue.ts +0 -111
  127. package/src/build/sidecar-file-manager.ts +0 -104
  128. package/src/build/sidecar-renderer.ts +0 -30
  129. package/src/client/adapters/index.ts +0 -21
  130. package/src/client/components.ts +0 -35
  131. package/src/client/css-hmr-handler.ts +0 -344
  132. package/src/client/framework-adapter.ts +0 -462
  133. package/src/client/hmr-coordinator.ts +0 -396
  134. package/src/client/hmr-error-overlay.js +0 -533
  135. package/src/client/main.js +0 -824
  136. package/src/components/Image.tsx +0 -123
  137. package/src/components/IslandErrorBoundary.tsx +0 -145
  138. package/src/components/LayoutDataErrorBoundary.tsx +0 -141
  139. package/src/components/LayoutErrorBoundary.tsx +0 -127
  140. package/src/components/PersistentIsland.tsx +0 -52
  141. package/src/components/StreamingErrorBoundary.tsx +0 -233
  142. package/src/components/StreamingLayout.tsx +0 -538
  143. package/src/core/components/component-analyzer.ts +0 -192
  144. package/src/core/components/component-detection.ts +0 -508
  145. package/src/core/components/enhanced-framework-detector.ts +0 -500
  146. package/src/core/components/framework-registry.ts +0 -563
  147. package/src/core/content/mdx-processor.ts +0 -46
  148. package/src/core/integrations/index.ts +0 -19
  149. package/src/core/integrations/loader.ts +0 -125
  150. package/src/core/integrations/registry.ts +0 -175
  151. package/src/core/islands/island-persistence.ts +0 -325
  152. package/src/core/islands/island-state-serializer.ts +0 -258
  153. package/src/core/islands/persistent-island-context.tsx +0 -80
  154. package/src/core/islands/use-persistent-state.ts +0 -68
  155. package/src/core/layout/enhanced-layout-resolver.ts +0 -322
  156. package/src/core/layout/layout-cache-manager.ts +0 -485
  157. package/src/core/layout/layout-composer.ts +0 -357
  158. package/src/core/layout/layout-data-loader.ts +0 -516
  159. package/src/core/layout/layout-discovery.ts +0 -243
  160. package/src/core/layout/layout-matcher.ts +0 -299
  161. package/src/core/layout/layout-types.ts +0 -110
  162. package/src/core/modules/framework-module-resolver.ts +0 -273
  163. package/src/islands/component-analysis.ts +0 -213
  164. package/src/islands/css-utils.ts +0 -565
  165. package/src/islands/discovery/index.ts +0 -80
  166. package/src/islands/discovery/registry.ts +0 -340
  167. package/src/islands/discovery/resolver.ts +0 -477
  168. package/src/islands/discovery/scanner.ts +0 -386
  169. package/src/islands/discovery/types.ts +0 -117
  170. package/src/islands/discovery/validator.ts +0 -544
  171. package/src/islands/discovery/watcher.ts +0 -368
  172. package/src/islands/framework-detection.ts +0 -428
  173. package/src/islands/integration-loader.ts +0 -490
  174. package/src/islands/island.tsx +0 -565
  175. package/src/islands/render-cache.ts +0 -550
  176. package/src/islands/types.ts +0 -80
  177. package/src/islands/universal-css-collector.ts +0 -157
  178. package/src/islands/universal-head-collector.ts +0 -137
  179. package/src/layout-system.ts +0 -218
  180. package/src/middleware/discovery.ts +0 -268
  181. package/src/middleware/executor.ts +0 -315
  182. package/src/middleware/index.ts +0 -76
  183. package/src/middleware/types.ts +0 -99
  184. package/src/nitro/build-config.ts +0 -576
  185. package/src/nitro/config.ts +0 -483
  186. package/src/nitro/error-handler.ts +0 -636
  187. package/src/nitro/index.ts +0 -173
  188. package/src/nitro/island-manifest.ts +0 -584
  189. package/src/nitro/middleware-adapter.ts +0 -260
  190. package/src/nitro/renderer.ts +0 -1471
  191. package/src/nitro/route-discovery.ts +0 -439
  192. package/src/nitro/types.ts +0 -321
  193. package/src/render/collect-css.ts +0 -198
  194. package/src/render/error-pages.ts +0 -79
  195. package/src/render/isolated-ssr-renderer.ts +0 -654
  196. package/src/render/ssr.ts +0 -1030
  197. package/src/schemas/api.ts +0 -30
  198. package/src/schemas/core.ts +0 -64
  199. package/src/schemas/index.ts +0 -212
  200. package/src/schemas/layout.ts +0 -279
  201. package/src/schemas/routing/index.ts +0 -38
  202. package/src/schemas/routing.ts +0 -376
  203. package/src/types/as-island.ts +0 -20
  204. package/src/types/layout.ts +0 -285
  205. package/src/types/routing.ts +0 -555
  206. package/src/types/types.ts +0 -5
  207. package/src/utils/dev-logger.ts +0 -299
  208. package/src/utils/fs.ts +0 -151
  209. package/src/vite-plugin/auto-discover.ts +0 -551
  210. package/src/vite-plugin/config.ts +0 -266
  211. package/src/vite-plugin/errors.ts +0 -127
  212. package/src/vite-plugin/image-optimization.ts +0 -156
  213. package/src/vite-plugin/integration-activator.ts +0 -126
  214. package/src/vite-plugin/island-sidecar-plugin.ts +0 -176
  215. package/src/vite-plugin/module-discovery.ts +0 -189
  216. package/src/vite-plugin/nitro-integration.ts +0 -1354
  217. package/src/vite-plugin/plugin.ts +0 -403
  218. package/src/vite-plugin/types.ts +0 -327
  219. package/src/vite-plugin/validation.ts +0 -228
  220. /package/{src → dist/src}/client/types/framework-runtime.d.ts +0 -0
  221. /package/{src → dist/src}/client/types/vite-hmr.d.ts +0 -0
  222. /package/{src → dist/src}/client/types/vite-virtual-modules.d.ts +0 -0
  223. /package/{src → dist/src}/layout-system.d.ts +0 -0
  224. /package/{src → dist/src}/types/image.d.ts +0 -0
  225. /package/{src → dist/src}/types/index.d.ts +0 -0
  226. /package/{src → dist/src}/types/island-jsx.d.ts +0 -0
  227. /package/{src → dist/src}/types/island-prop.d.ts +0 -0
  228. /package/{src → dist/src}/types/mdx.d.ts +0 -0
  229. /package/{src → dist/src}/types/urlpattern.d.ts +0 -0
  230. /package/{src → dist/src}/types/vite-env.d.ts +0 -0
@@ -1,551 +0,0 @@
1
- /**
2
- * Auto-Discovery for Avalon Vite Plugin
3
- *
4
- * This module handles automatic discovery of framework integrations
5
- * based on island prop usage in pages and layouts. It scans for components
6
- * used with the `island` prop and detects their framework from file extensions
7
- * and content.
8
- */
9
-
10
- import type { IntegrationName } from "./types.ts";
11
- import { resolve } from "node:path";
12
- import { stat as fsStat, readdir, readFile } from "node:fs/promises";
13
- import { openSync, readSync, closeSync } from "node:fs";
14
-
15
- /**
16
- * File extension to integration name mapping
17
- * Maps file extensions and naming conventions to their corresponding integration
18
- */
19
- const EXTENSION_TO_INTEGRATION: Record<string, IntegrationName> = {
20
- // Vue
21
- ".vue": "vue",
22
- // Svelte
23
- ".svelte": "svelte",
24
- };
25
-
26
- /**
27
- * Framework-specific naming patterns (e.g., .solid.tsx, .lit.ts)
28
- * These take priority over generic extensions
29
- */
30
- const FRAMEWORK_NAMING_PATTERNS: Array<{
31
- pattern: RegExp;
32
- integration: IntegrationName;
33
- }> = [
34
- { pattern: /\.solid\.(tsx|jsx)$/, integration: "solid" },
35
- { pattern: /\.react\.(tsx|jsx)$/, integration: "react" },
36
- { pattern: /\.lit\.(ts|js)$/, integration: "lit" },
37
- { pattern: /\.preact\.(tsx|jsx)$/, integration: "preact" },
38
- { pattern: /\.qwik\.(tsx|jsx)$/, integration: "qwik" },
39
- ];
40
-
41
- /**
42
- * Content-based detection patterns for JSX/TSX files
43
- * These patterns detect framework usage from file content
44
- */
45
- const CONTENT_DETECTION_PATTERNS: Array<{
46
- pattern: RegExp;
47
- integration: IntegrationName;
48
- }> = [
49
- // React detection: imports from 'react' or @jsxImportSource react
50
- { pattern: /from\s+['"]react['"]/, integration: "react" },
51
- { pattern: /@jsxImportSource\s+react/, integration: "react" },
52
- // Solid detection: imports from 'solid-js' or @jsxImportSource solid-js
53
- { pattern: /from\s+['"]solid-js['"]/, integration: "solid" },
54
- { pattern: /@jsxImportSource\s+solid-js/, integration: "solid" },
55
- // Preact detection: imports from 'preact' or @jsxImportSource preact
56
- { pattern: /from\s+['"]preact['"]/, integration: "preact" },
57
- { pattern: /@jsxImportSource\s+preact/, integration: "preact" },
58
- // Qwik detection: imports from '@builder.io/qwik' or @jsxImportSource @builder.io/qwik
59
- { pattern: /from\s+['"]@builder\.io\/qwik['"]/, integration: "qwik" },
60
- { pattern: /@jsxImportSource\s+@builder\.io\/qwik/, integration: "qwik" },
61
- ];
62
-
63
- /**
64
- * Default integration for generic JSX/TSX files
65
- * When a .tsx or .jsx file doesn't have a framework-specific naming convention
66
- * or detectable imports, we default to preact as per Avalon's conventions
67
- */
68
- const DEFAULT_JSX_INTEGRATION: IntegrationName = "preact";
69
-
70
- /**
71
- * Supported file extensions for island components
72
- */
73
- const SUPPORTED_EXTENSIONS = [
74
- ".tsx",
75
- ".jsx",
76
- ".ts",
77
- ".js",
78
- ".vue",
79
- ".svelte",
80
- ];
81
-
82
- /**
83
- * Discover integrations from files in the islands directory
84
- *
85
- * Scans the specified directory for component files and determines
86
- * which framework integrations are needed based on file extensions
87
- * and naming conventions.
88
- *
89
- * @param islandsDir - Path to the islands directory (relative or absolute)
90
- * @param projectRoot - Optional project root for resolving relative paths
91
- * @returns Set of discovered integration names
92
- *
93
- * @example
94
- * ```ts
95
- * const integrations = await discoverIntegrationsFromFiles("src/islands");
96
- * // Returns Set { "vue", "svelte", "preact" } based on files found
97
- * ```
98
- */
99
- export async function discoverIntegrationsFromFiles(
100
- islandsDir: string,
101
- projectRoot?: string
102
- ): Promise<Set<IntegrationName>> {
103
- // Resolve the islands directory path
104
- const resolvedDir = projectRoot
105
- ? resolve(projectRoot, islandsDir)
106
- : resolve(islandsDir);
107
-
108
- // Check if directory exists
109
- try {
110
- const statResult = await fsStat(resolvedDir);
111
- if (!statResult.isDirectory()) {
112
- return new Set();
113
- }
114
- } catch {
115
- return new Set();
116
- }
117
-
118
- const discovered = new Set<IntegrationName>();
119
- await scanDirectoryForIntegrations(resolvedDir, discovered);
120
- return discovered;
121
- }
122
-
123
- /**
124
- * Recursively scan a directory for component files
125
- *
126
- * @param dirPath - Directory path to scan
127
- * @param discovered - Set to add discovered integrations to
128
- */
129
- async function scanDirectoryForIntegrations(
130
- dirPath: string,
131
- discovered: Set<IntegrationName>
132
- ): Promise<void> {
133
- try {
134
- const entries = await readdir(dirPath, { withFileTypes: true });
135
- for (const entry of entries) {
136
- const fullPath = resolve(dirPath, entry.name);
137
-
138
- if (entry.isDirectory()) {
139
- // Recursively scan subdirectories
140
- await scanDirectoryForIntegrations(fullPath, discovered);
141
- } else if (entry.isFile()) {
142
- // Check if this is a supported component file
143
- const integration = await detectIntegrationFromFile(fullPath, entry.name);
144
- if (integration) {
145
- discovered.add(integration);
146
- }
147
- }
148
- }
149
- } catch (error) {
150
- // Log but don't fail on permission errors or other issues
151
- if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== 'EACCES') {
152
- console.warn(`Warning: Could not scan directory ${dirPath}:`, error);
153
- }
154
- }
155
- }
156
-
157
- function detectIntegrationFromContent(filePath: string): IntegrationName {
158
- try {
159
- const fd = openSync(filePath, 'r');
160
- const buffer = Buffer.alloc(500);
161
- readSync(fd, buffer, 0, 500, 0);
162
- closeSync(fd);
163
- const content = buffer.toString('utf-8');
164
- for (const { pattern, integration } of CONTENT_DETECTION_PATTERNS) {
165
- if (pattern.test(content)) return integration;
166
- }
167
- } catch {
168
- // fall through to default
169
- }
170
- return DEFAULT_JSX_INTEGRATION;
171
- }
172
-
173
- /**
174
- * Detect the integration from a file by checking name patterns and content
175
- *
176
- * @param filePath - Full path to the file
177
- * @param fileName - The file name
178
- * @returns The integration name or null if not a supported component file
179
- */
180
- async function detectIntegrationFromFile(
181
- filePath: string,
182
- fileName: string
183
- ): Promise<IntegrationName | null> {
184
- const normalizedName = fileName.toLowerCase();
185
-
186
- // First, check for framework-specific naming patterns (highest priority)
187
- for (const { pattern, integration } of FRAMEWORK_NAMING_PATTERNS) {
188
- if (pattern.test(normalizedName)) {
189
- return integration;
190
- }
191
- }
192
-
193
- // Second, check for unique file extensions (.vue, .svelte)
194
- for (const [ext, integration] of Object.entries(EXTENSION_TO_INTEGRATION)) {
195
- if (normalizedName.endsWith(ext)) {
196
- return integration;
197
- }
198
- }
199
-
200
- // Third, for JSX/TSX files, read content to detect framework
201
- if (normalizedName.endsWith(".tsx") || normalizedName.endsWith(".jsx")) {
202
- return detectIntegrationFromContent(filePath);
203
- }
204
-
205
- // Fourth, check for Lit components (.ts/.js files with PascalCase names)
206
- if (
207
- (normalizedName.endsWith(".ts") || normalizedName.endsWith(".js")) &&
208
- !normalizedName.endsWith(".d.ts")
209
- ) {
210
- // Check if the original filename (not lowercased) starts with uppercase
211
- // This indicates a component file (PascalCase convention)
212
- if (/^[A-Z]/.test(fileName)) {
213
- return "lit";
214
- }
215
- }
216
-
217
- // Not a supported component file
218
- return null;
219
- }
220
-
221
- /**
222
- * Detect the integration name from a file name
223
- *
224
- * Uses file extensions and naming conventions to determine
225
- * which framework integration a component file belongs to.
226
- *
227
- * @param fileName - The file name to analyze
228
- * @returns The integration name or null if not a supported component file
229
- *
230
- * @example
231
- * ```ts
232
- * detectIntegrationFromFileName("Counter.vue") // "vue"
233
- * detectIntegrationFromFileName("Button.svelte") // "svelte"
234
- * detectIntegrationFromFileName("Card.solid.tsx") // "solid"
235
- * detectIntegrationFromFileName("Form.tsx") // "preact" (default)
236
- * detectIntegrationFromFileName("styles.css") // null
237
- * ```
238
- */
239
- export function detectIntegrationFromFileName(
240
- fileName: string
241
- ): IntegrationName | null {
242
- const normalizedName = fileName.toLowerCase();
243
-
244
- // First, check for framework-specific naming patterns (highest priority)
245
- for (const { pattern, integration } of FRAMEWORK_NAMING_PATTERNS) {
246
- if (pattern.test(normalizedName)) {
247
- return integration;
248
- }
249
- }
250
-
251
- // Second, check for unique file extensions (.vue, .svelte)
252
- for (const [ext, integration] of Object.entries(EXTENSION_TO_INTEGRATION)) {
253
- if (normalizedName.endsWith(ext)) {
254
- return integration;
255
- }
256
- }
257
-
258
- // Third, check for generic JSX/TSX files (default to preact)
259
- if (normalizedName.endsWith(".tsx") || normalizedName.endsWith(".jsx")) {
260
- return DEFAULT_JSX_INTEGRATION;
261
- }
262
-
263
- // Fourth, check for Lit components (.ts/.js files with PascalCase names)
264
- if (
265
- (normalizedName.endsWith(".ts") || normalizedName.endsWith(".js")) &&
266
- !normalizedName.endsWith(".d.ts")
267
- ) {
268
- // Check if the original filename (not lowercased) starts with uppercase
269
- // This indicates a component file (PascalCase convention)
270
- if (/^[A-Z]/.test(fileName)) {
271
- return "lit";
272
- }
273
- }
274
-
275
- // Not a supported component file
276
- return null;
277
- }
278
-
279
- /**
280
- * Check if a file extension is supported for island components
281
- *
282
- * @param extension - The file extension (including the dot)
283
- * @returns True if the extension is supported
284
- */
285
- export function isSupportedExtension(extension: string): boolean {
286
- return SUPPORTED_EXTENSIONS.includes(extension.toLowerCase());
287
- }
288
-
289
- /**
290
- * Get all supported file extensions
291
- *
292
- * @returns Array of supported file extensions
293
- */
294
- export function getSupportedExtensions(): readonly string[] {
295
- return SUPPORTED_EXTENSIONS;
296
- }
297
-
298
- /**
299
- * Discover integrations by scanning pages and layouts for island prop usage.
300
- *
301
- * This function scans page and layout files for components used with the `island` prop,
302
- * then resolves the import paths to detect which frameworks are needed.
303
- *
304
- * @param pagesDir - Path to the pages directory
305
- * @param layoutsDir - Path to the layouts directory
306
- * @param projectRoot - Optional project root for resolving relative paths
307
- * @param modulesDir - Optional modules directory for modular architecture
308
- * @returns Set of discovered integration names
309
- */
310
- export async function discoverIntegrationsFromIslandUsage(
311
- pagesDir: string,
312
- layoutsDir: string,
313
- projectRoot?: string,
314
- modulesDir?: string
315
- ): Promise<Set<IntegrationName>> {
316
- const discovered = new Set<IntegrationName>();
317
- const root = projectRoot ?? process.cwd();
318
-
319
- // Scan both pages and layouts directories
320
- const dirsToScan = [
321
- resolve(root, pagesDir),
322
- resolve(root, layoutsDir),
323
- ];
324
-
325
- // Also scan modules directory if provided (modular architecture)
326
- if (modulesDir) {
327
- dirsToScan.push(resolve(root, modulesDir));
328
- }
329
-
330
- for (const dir of dirsToScan) {
331
- try {
332
- const statResult = await fsStat(dir);
333
- if (statResult.isDirectory()) {
334
- await scanForIslandUsage(dir, root, discovered);
335
- }
336
- } catch {
337
- // Directory doesn't exist, skip
338
- }
339
- }
340
-
341
- return discovered;
342
- }
343
-
344
- /**
345
- * Recursively scan a directory for files that use the island prop
346
- */
347
- async function scanForIslandUsage(
348
- dirPath: string,
349
- projectRoot: string,
350
- discovered: Set<IntegrationName>
351
- ): Promise<void> {
352
- try {
353
- const entries = await readdir(dirPath, { withFileTypes: true });
354
-
355
- for (const entry of entries) {
356
- const fullPath = resolve(dirPath, entry.name);
357
-
358
- if (entry.isDirectory()) {
359
- await scanForIslandUsage(fullPath, projectRoot, discovered);
360
- } else if (entry.isFile() && isPageOrLayoutFile(entry.name)) {
361
- await extractIslandIntegrations(fullPath, projectRoot, discovered);
362
- }
363
- }
364
- } catch (error) {
365
- // Log but don't fail
366
- if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== 'EACCES') {
367
- // Silently skip inaccessible directories
368
- }
369
- }
370
- }
371
-
372
- /**
373
- * Check if a file is a page or layout file that might contain island usage
374
- */
375
- function isPageOrLayoutFile(fileName: string): boolean {
376
- const lower = fileName.toLowerCase();
377
- return (
378
- lower.endsWith('.tsx') ||
379
- lower.endsWith('.jsx') ||
380
- lower.endsWith('.mdx')
381
- );
382
- }
383
-
384
- /**
385
- * Extract integrations from a file by finding island prop usage and resolving imports
386
- */
387
- /** Import paths that indicate an auto-island framework (no `island` prop needed). */
388
- const AUTO_ISLAND_IMPORT_PATTERNS: Array<{ pattern: RegExp; integration: IntegrationName }> = [
389
- { pattern: /\.qwik\./, integration: 'qwik' },
390
- ];
391
-
392
- async function extractIslandIntegrations(
393
- filePath: string,
394
- projectRoot: string,
395
- discovered: Set<IntegrationName>
396
- ): Promise<void> {
397
- try {
398
- const content = await readFile(filePath, 'utf-8');
399
-
400
- // Find all imports
401
- const importMap = parseImports(content);
402
-
403
- // Find components used with island prop
404
- const islandComponents = findIslandPropUsage(content);
405
-
406
- // For each island component, resolve its import and detect framework
407
- for (const componentName of islandComponents) {
408
- const importPath = importMap.get(componentName);
409
- if (!importPath) continue;
410
-
411
- // Resolve the import path to an actual file
412
- const resolvedPath = resolveImportPath(importPath, filePath, projectRoot);
413
- if (!resolvedPath) continue;
414
-
415
- // Detect framework from the resolved file
416
- const integration = await detectIntegrationFromResolvedPath(resolvedPath);
417
- if (integration) {
418
- discovered.add(integration);
419
- }
420
- }
421
-
422
- // Also detect auto-island frameworks by import path alone (e.g. .qwik.tsx).
423
- // These don't require the `island` prop so findIslandPropUsage won't find them.
424
- for (const [, importPath] of importMap) {
425
- for (const { pattern, integration } of AUTO_ISLAND_IMPORT_PATTERNS) {
426
- if (pattern.test(importPath)) {
427
- discovered.add(integration);
428
- }
429
- }
430
- }
431
- } catch {
432
- // Skip files that can't be read
433
- }
434
- }
435
-
436
- /**
437
- * Parse import statements from file content
438
- * Returns a map of component name -> import path
439
- */
440
- function parseImports(content: string): Map<string, string> {
441
- const imports = new Map<string, string>();
442
-
443
- // Match: import ComponentName from 'path'
444
- const defaultImportRe = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g;
445
- let match;
446
-
447
- while ((match = defaultImportRe.exec(content)) !== null) {
448
- imports.set(match[1], match[2]);
449
- }
450
-
451
- // Match: import { ComponentName } from 'path' or import { ComponentName as Alias } from 'path'
452
- const namedImportRe = /import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g;
453
-
454
- while ((match = namedImportRe.exec(content)) !== null) {
455
- const names = match[1].split(',').map(n => n.trim());
456
- const importPath = match[2];
457
-
458
- for (const name of names) {
459
- // Handle "Name as Alias" syntax
460
- const asMatch = name.match(/(\w+)\s+as\s+(\w+)/);
461
- if (asMatch) {
462
- imports.set(asMatch[2], importPath); // Use alias as key
463
- } else if (name) {
464
- imports.set(name, importPath);
465
- }
466
- }
467
- }
468
-
469
- return imports;
470
- }
471
-
472
- /**
473
- * Find component names that are used with the island prop
474
- */
475
- function findIslandPropUsage(content: string): Set<string> {
476
- const components = new Set<string>();
477
-
478
- // Match: <ComponentName ... island={...} or <ComponentName ... island ...
479
- // This regex finds JSX elements with an island prop
480
- const islandUsageRe = /<([A-Z]\w*)\s+[^>]*\bisland\b/g;
481
- let match;
482
-
483
- while ((match = islandUsageRe.exec(content)) !== null) {
484
- components.add(match[1]);
485
- }
486
-
487
- return components;
488
- }
489
-
490
- /**
491
- * Resolve an import path to an actual file path
492
- */
493
- function resolveImportPath(
494
- importPath: string,
495
- fromFile: string,
496
- projectRoot: string
497
- ): string | null {
498
- // Handle relative imports
499
- if (importPath.startsWith('.')) {
500
- const dir = resolve(fromFile, '..');
501
- return resolve(dir, importPath);
502
- }
503
-
504
- // Handle alias imports (common patterns)
505
- const aliasPatterns: Array<{ prefix: string; replacement: string }> = [
506
- { prefix: '@/', replacement: 'src/' },
507
- { prefix: '$components/', replacement: 'src/components/' },
508
- { prefix: '$islands/', replacement: 'src/islands/' },
509
- { prefix: '~/', replacement: 'src/' },
510
- ];
511
-
512
- for (const { prefix, replacement } of aliasPatterns) {
513
- if (importPath.startsWith(prefix)) {
514
- const relativePath = importPath.slice(prefix.length);
515
- return resolve(projectRoot, replacement, relativePath);
516
- }
517
- }
518
-
519
- // Handle absolute imports from src
520
- if (importPath.startsWith('/src/')) {
521
- return resolve(projectRoot, importPath.slice(1));
522
- }
523
-
524
- // Can't resolve - might be a node_modules import
525
- return null;
526
- }
527
-
528
- /**
529
- * Detect integration from a resolved file path
530
- */
531
- async function detectIntegrationFromResolvedPath(
532
- filePath: string
533
- ): Promise<IntegrationName | null> {
534
- // Try with common extensions if no extension provided
535
- const extensions = ['', '.tsx', '.jsx', '.ts', '.js', '.vue', '.svelte'];
536
-
537
- for (const ext of extensions) {
538
- const fullPath = filePath + ext;
539
- try {
540
- const statResult = await fsStat(fullPath);
541
- if (statResult.isFile()) {
542
- const fileName = fullPath.split('/').pop() || '';
543
- return detectIntegrationFromFile(fullPath, fileName);
544
- }
545
- } catch {
546
- // Try next extension
547
- }
548
- }
549
-
550
- return null;
551
- }