@useavalon/avalon 0.1.10 → 0.1.12

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 (250) hide show
  1. package/README.md +54 -54
  2. package/dist/mod.js +1 -0
  3. package/dist/src/build/integration-bundler-plugin.js +1 -0
  4. package/dist/src/build/integration-config.js +1 -0
  5. package/dist/src/build/integration-detection-plugin.js +1 -0
  6. package/dist/src/build/integration-resolver-plugin.js +1 -0
  7. package/dist/src/build/island-manifest.js +1 -0
  8. package/dist/src/build/island-types-generator.js +5 -0
  9. package/dist/src/build/mdx-island-transform.js +2 -0
  10. package/dist/src/build/mdx-plugin.js +1 -0
  11. package/dist/src/build/page-island-transform.js +3 -0
  12. package/dist/src/build/prop-extractors/index.js +1 -0
  13. package/dist/src/build/prop-extractors/lit.js +1 -0
  14. package/dist/src/build/prop-extractors/qwik.js +1 -0
  15. package/dist/src/build/prop-extractors/solid.js +1 -0
  16. package/dist/src/build/prop-extractors/svelte.js +1 -0
  17. package/dist/src/build/prop-extractors/vue.js +1 -0
  18. package/dist/src/build/sidecar-file-manager.js +1 -0
  19. package/dist/src/build/sidecar-renderer.js +6 -0
  20. package/dist/src/client/adapters/index.js +1 -0
  21. package/dist/src/client/components.js +1 -0
  22. package/dist/src/client/css-hmr-handler.js +1 -0
  23. package/dist/src/client/framework-adapter.js +13 -0
  24. package/dist/src/client/hmr-coordinator.js +1 -0
  25. package/dist/src/client/hmr-error-overlay.js +214 -0
  26. package/dist/src/client/main.js +39 -0
  27. package/{src → dist/src}/client/types/framework-runtime.d.ts +68 -68
  28. package/{src → dist/src}/client/types/vite-hmr.d.ts +46 -46
  29. package/dist/src/client/types/vite-virtual-modules.d.ts +70 -0
  30. package/dist/src/components/Image.js +1 -0
  31. package/dist/src/components/IslandErrorBoundary.js +1 -0
  32. package/dist/src/components/LayoutDataErrorBoundary.js +1 -0
  33. package/dist/src/components/LayoutErrorBoundary.js +1 -0
  34. package/dist/src/components/PersistentIsland.js +1 -0
  35. package/dist/src/components/StreamingErrorBoundary.js +1 -0
  36. package/dist/src/components/StreamingLayout.js +29 -0
  37. package/dist/src/core/components/component-analyzer.js +1 -0
  38. package/dist/src/core/components/component-detection.js +5 -0
  39. package/dist/src/core/components/enhanced-framework-detector.js +1 -0
  40. package/dist/src/core/components/framework-registry.js +1 -0
  41. package/dist/src/core/content/mdx-processor.js +1 -0
  42. package/dist/src/core/integrations/index.js +1 -0
  43. package/dist/src/core/integrations/loader.js +1 -0
  44. package/dist/src/core/integrations/registry.js +1 -0
  45. package/dist/src/core/islands/island-persistence.js +1 -0
  46. package/dist/src/core/islands/island-state-serializer.js +1 -0
  47. package/dist/src/core/islands/persistent-island-context.js +1 -0
  48. package/dist/src/core/islands/use-persistent-state.js +1 -0
  49. package/dist/src/core/layout/enhanced-layout-resolver.js +1 -0
  50. package/dist/src/core/layout/layout-cache-manager.js +1 -0
  51. package/dist/src/core/layout/layout-composer.js +1 -0
  52. package/dist/src/core/layout/layout-data-loader.js +1 -0
  53. package/dist/src/core/layout/layout-discovery.js +1 -0
  54. package/dist/src/core/layout/layout-matcher.js +1 -0
  55. package/dist/src/core/layout/layout-types.js +1 -0
  56. package/dist/src/core/modules/framework-module-resolver.js +1 -0
  57. package/dist/src/islands/component-analysis.js +1 -0
  58. package/dist/src/islands/css-utils.js +17 -0
  59. package/dist/src/islands/discovery/index.js +1 -0
  60. package/dist/src/islands/discovery/registry.js +1 -0
  61. package/dist/src/islands/discovery/resolver.js +2 -0
  62. package/dist/src/islands/discovery/scanner.js +1 -0
  63. package/dist/src/islands/discovery/types.js +1 -0
  64. package/dist/src/islands/discovery/validator.js +18 -0
  65. package/dist/src/islands/discovery/watcher.js +1 -0
  66. package/dist/src/islands/framework-detection.js +1 -0
  67. package/dist/src/islands/integration-loader.js +1 -0
  68. package/dist/src/islands/island.js +1 -0
  69. package/dist/src/islands/render-cache.js +1 -0
  70. package/dist/src/islands/types.js +1 -0
  71. package/dist/src/islands/universal-css-collector.js +5 -0
  72. package/dist/src/islands/universal-head-collector.js +2 -0
  73. package/{src → dist/src}/layout-system.d.ts +592 -592
  74. package/dist/src/layout-system.js +1 -0
  75. package/dist/src/middleware/discovery.js +1 -0
  76. package/dist/src/middleware/executor.js +1 -0
  77. package/dist/src/middleware/index.js +1 -0
  78. package/dist/src/middleware/types.js +1 -0
  79. package/dist/src/nitro/build-config.js +1 -0
  80. package/dist/src/nitro/config.js +1 -0
  81. package/dist/src/nitro/error-handler.js +198 -0
  82. package/dist/src/nitro/index.js +1 -0
  83. package/dist/src/nitro/island-manifest.js +2 -0
  84. package/dist/src/nitro/middleware-adapter.js +1 -0
  85. package/dist/src/nitro/renderer.js +183 -0
  86. package/dist/src/nitro/route-discovery.js +1 -0
  87. package/dist/src/nitro/types.js +1 -0
  88. package/dist/src/render/collect-css.js +3 -0
  89. package/{src/render/error-pages.ts → dist/src/render/error-pages.js} +7 -38
  90. package/dist/src/render/isolated-ssr-renderer.js +1 -0
  91. package/dist/src/render/ssr.js +90 -0
  92. package/dist/src/schemas/api.js +1 -0
  93. package/dist/src/schemas/core.js +1 -0
  94. package/dist/src/schemas/index.js +1 -0
  95. package/dist/src/schemas/layout.js +1 -0
  96. package/dist/src/schemas/routing/index.js +1 -0
  97. package/dist/src/schemas/routing.js +1 -0
  98. package/dist/src/types/as-island.js +1 -0
  99. package/{src → dist/src}/types/image.d.ts +106 -106
  100. package/{src → dist/src}/types/index.d.ts +22 -22
  101. package/{src → dist/src}/types/island-jsx.d.ts +33 -33
  102. package/{src → dist/src}/types/island-prop.d.ts +20 -20
  103. package/dist/src/types/layout.js +1 -0
  104. package/{src → dist/src}/types/mdx.d.ts +6 -6
  105. package/dist/src/types/routing.js +1 -0
  106. package/dist/src/types/types.js +1 -0
  107. package/{src → dist/src}/types/urlpattern.d.ts +49 -49
  108. package/{src → dist/src}/types/vite-env.d.ts +11 -11
  109. package/dist/src/utils/dev-logger.js +12 -0
  110. package/dist/src/utils/fs.js +1 -0
  111. package/dist/src/vite-plugin/auto-discover.js +1 -0
  112. package/dist/src/vite-plugin/config.js +1 -0
  113. package/dist/src/vite-plugin/errors.js +1 -0
  114. package/dist/src/vite-plugin/image-optimization.js +45 -0
  115. package/dist/src/vite-plugin/integration-activator.js +1 -0
  116. package/dist/src/vite-plugin/island-sidecar-plugin.js +1 -0
  117. package/dist/src/vite-plugin/module-discovery.js +1 -0
  118. package/dist/src/vite-plugin/nitro-integration.js +42 -0
  119. package/dist/src/vite-plugin/plugin.js +1 -0
  120. package/dist/src/vite-plugin/types.js +1 -0
  121. package/dist/src/vite-plugin/validation.js +2 -0
  122. package/package.json +57 -26
  123. package/mod.ts +0 -302
  124. package/src/build/integration-bundler-plugin.ts +0 -116
  125. package/src/build/integration-config.ts +0 -168
  126. package/src/build/integration-detection-plugin.ts +0 -117
  127. package/src/build/integration-resolver-plugin.ts +0 -90
  128. package/src/build/island-manifest.ts +0 -269
  129. package/src/build/island-types-generator.ts +0 -476
  130. package/src/build/mdx-island-transform.ts +0 -464
  131. package/src/build/mdx-plugin.ts +0 -98
  132. package/src/build/page-island-transform.ts +0 -598
  133. package/src/build/prop-extractors/index.ts +0 -21
  134. package/src/build/prop-extractors/lit.ts +0 -140
  135. package/src/build/prop-extractors/qwik.ts +0 -16
  136. package/src/build/prop-extractors/solid.ts +0 -125
  137. package/src/build/prop-extractors/svelte.ts +0 -194
  138. package/src/build/prop-extractors/vue.ts +0 -111
  139. package/src/build/sidecar-file-manager.ts +0 -104
  140. package/src/build/sidecar-renderer.ts +0 -30
  141. package/src/client/adapters/index.js +0 -12
  142. package/src/client/adapters/index.ts +0 -13
  143. package/src/client/adapters/lit-adapter.js +0 -467
  144. package/src/client/adapters/lit-adapter.ts +0 -654
  145. package/src/client/adapters/preact-adapter.js +0 -223
  146. package/src/client/adapters/preact-adapter.ts +0 -331
  147. package/src/client/adapters/qwik-adapter.js +0 -259
  148. package/src/client/adapters/qwik-adapter.ts +0 -345
  149. package/src/client/adapters/react-adapter.js +0 -220
  150. package/src/client/adapters/react-adapter.ts +0 -353
  151. package/src/client/adapters/solid-adapter.js +0 -295
  152. package/src/client/adapters/solid-adapter.ts +0 -451
  153. package/src/client/adapters/svelte-adapter.js +0 -368
  154. package/src/client/adapters/svelte-adapter.ts +0 -524
  155. package/src/client/adapters/vue-adapter.js +0 -278
  156. package/src/client/adapters/vue-adapter.ts +0 -467
  157. package/src/client/components.js +0 -23
  158. package/src/client/components.ts +0 -35
  159. package/src/client/css-hmr-handler.js +0 -263
  160. package/src/client/css-hmr-handler.ts +0 -344
  161. package/src/client/framework-adapter.js +0 -283
  162. package/src/client/framework-adapter.ts +0 -462
  163. package/src/client/hmr-coordinator.js +0 -274
  164. package/src/client/hmr-coordinator.ts +0 -396
  165. package/src/client/hmr-error-overlay.js +0 -533
  166. package/src/client/main.js +0 -816
  167. package/src/client/types/vite-virtual-modules.d.ts +0 -60
  168. package/src/components/Image.tsx +0 -123
  169. package/src/components/IslandErrorBoundary.tsx +0 -145
  170. package/src/components/LayoutDataErrorBoundary.tsx +0 -141
  171. package/src/components/LayoutErrorBoundary.tsx +0 -127
  172. package/src/components/PersistentIsland.tsx +0 -52
  173. package/src/components/StreamingErrorBoundary.tsx +0 -233
  174. package/src/components/StreamingLayout.tsx +0 -538
  175. package/src/core/components/component-analyzer.ts +0 -192
  176. package/src/core/components/component-detection.ts +0 -508
  177. package/src/core/components/enhanced-framework-detector.ts +0 -500
  178. package/src/core/components/framework-registry.ts +0 -563
  179. package/src/core/content/mdx-processor.ts +0 -46
  180. package/src/core/integrations/index.ts +0 -19
  181. package/src/core/integrations/loader.ts +0 -125
  182. package/src/core/integrations/registry.ts +0 -175
  183. package/src/core/islands/island-persistence.ts +0 -325
  184. package/src/core/islands/island-state-serializer.ts +0 -258
  185. package/src/core/islands/persistent-island-context.tsx +0 -80
  186. package/src/core/islands/use-persistent-state.ts +0 -68
  187. package/src/core/layout/enhanced-layout-resolver.ts +0 -322
  188. package/src/core/layout/layout-cache-manager.ts +0 -485
  189. package/src/core/layout/layout-composer.ts +0 -357
  190. package/src/core/layout/layout-data-loader.ts +0 -516
  191. package/src/core/layout/layout-discovery.ts +0 -243
  192. package/src/core/layout/layout-matcher.ts +0 -299
  193. package/src/core/layout/layout-types.ts +0 -110
  194. package/src/core/modules/framework-module-resolver.ts +0 -273
  195. package/src/islands/component-analysis.ts +0 -213
  196. package/src/islands/css-utils.ts +0 -565
  197. package/src/islands/discovery/index.ts +0 -80
  198. package/src/islands/discovery/registry.ts +0 -340
  199. package/src/islands/discovery/resolver.ts +0 -477
  200. package/src/islands/discovery/scanner.ts +0 -386
  201. package/src/islands/discovery/types.ts +0 -117
  202. package/src/islands/discovery/validator.ts +0 -544
  203. package/src/islands/discovery/watcher.ts +0 -368
  204. package/src/islands/framework-detection.ts +0 -428
  205. package/src/islands/integration-loader.ts +0 -490
  206. package/src/islands/island.tsx +0 -565
  207. package/src/islands/render-cache.ts +0 -550
  208. package/src/islands/types.ts +0 -80
  209. package/src/islands/universal-css-collector.ts +0 -157
  210. package/src/islands/universal-head-collector.ts +0 -137
  211. package/src/layout-system.ts +0 -218
  212. package/src/middleware/discovery.ts +0 -268
  213. package/src/middleware/executor.ts +0 -315
  214. package/src/middleware/index.ts +0 -76
  215. package/src/middleware/types.ts +0 -99
  216. package/src/nitro/build-config.ts +0 -576
  217. package/src/nitro/config.ts +0 -483
  218. package/src/nitro/error-handler.ts +0 -636
  219. package/src/nitro/index.ts +0 -173
  220. package/src/nitro/island-manifest.ts +0 -584
  221. package/src/nitro/middleware-adapter.ts +0 -260
  222. package/src/nitro/renderer.ts +0 -1471
  223. package/src/nitro/route-discovery.ts +0 -439
  224. package/src/nitro/types.ts +0 -321
  225. package/src/render/collect-css.ts +0 -198
  226. package/src/render/isolated-ssr-renderer.ts +0 -654
  227. package/src/render/ssr.ts +0 -1030
  228. package/src/schemas/api.ts +0 -30
  229. package/src/schemas/core.ts +0 -64
  230. package/src/schemas/index.ts +0 -212
  231. package/src/schemas/layout.ts +0 -279
  232. package/src/schemas/routing/index.ts +0 -38
  233. package/src/schemas/routing.ts +0 -376
  234. package/src/types/as-island.ts +0 -20
  235. package/src/types/layout.ts +0 -285
  236. package/src/types/routing.ts +0 -555
  237. package/src/types/types.ts +0 -5
  238. package/src/utils/dev-logger.ts +0 -299
  239. package/src/utils/fs.ts +0 -151
  240. package/src/vite-plugin/auto-discover.ts +0 -551
  241. package/src/vite-plugin/config.ts +0 -266
  242. package/src/vite-plugin/errors.ts +0 -127
  243. package/src/vite-plugin/image-optimization.ts +0 -156
  244. package/src/vite-plugin/integration-activator.ts +0 -126
  245. package/src/vite-plugin/island-sidecar-plugin.ts +0 -176
  246. package/src/vite-plugin/module-discovery.ts +0 -189
  247. package/src/vite-plugin/nitro-integration.ts +0 -1354
  248. package/src/vite-plugin/plugin.ts +0 -401
  249. package/src/vite-plugin/types.ts +0 -327
  250. package/src/vite-plugin/validation.ts +0 -228
@@ -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
- }