@useavalon/avalon 0.1.11 → 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.
- package/README.md +54 -54
- package/dist/mod.js +1 -0
- package/dist/src/build/integration-bundler-plugin.js +1 -0
- package/dist/src/build/integration-config.js +1 -0
- package/dist/src/build/integration-detection-plugin.js +1 -0
- package/dist/src/build/integration-resolver-plugin.js +1 -0
- package/dist/src/build/island-manifest.js +1 -0
- package/dist/src/build/island-types-generator.js +5 -0
- package/dist/src/build/mdx-island-transform.js +2 -0
- package/dist/src/build/mdx-plugin.js +1 -0
- package/dist/src/build/page-island-transform.js +3 -0
- package/dist/src/build/prop-extractors/index.js +1 -0
- package/dist/src/build/prop-extractors/lit.js +1 -0
- package/dist/src/build/prop-extractors/qwik.js +1 -0
- package/dist/src/build/prop-extractors/solid.js +1 -0
- package/dist/src/build/prop-extractors/svelte.js +1 -0
- package/dist/src/build/prop-extractors/vue.js +1 -0
- package/dist/src/build/sidecar-file-manager.js +1 -0
- package/dist/src/build/sidecar-renderer.js +6 -0
- package/dist/src/client/adapters/index.js +1 -0
- package/dist/src/client/components.js +1 -0
- package/dist/src/client/css-hmr-handler.js +1 -0
- package/dist/src/client/framework-adapter.js +13 -0
- package/dist/src/client/hmr-coordinator.js +1 -0
- package/dist/src/client/hmr-error-overlay.js +214 -0
- package/dist/src/client/main.js +39 -0
- package/{src → dist/src}/client/types/framework-runtime.d.ts +68 -68
- package/{src → dist/src}/client/types/vite-hmr.d.ts +46 -46
- package/dist/src/client/types/vite-virtual-modules.d.ts +70 -0
- package/dist/src/components/Image.js +1 -0
- package/dist/src/components/IslandErrorBoundary.js +1 -0
- package/dist/src/components/LayoutDataErrorBoundary.js +1 -0
- package/dist/src/components/LayoutErrorBoundary.js +1 -0
- package/dist/src/components/PersistentIsland.js +1 -0
- package/dist/src/components/StreamingErrorBoundary.js +1 -0
- package/dist/src/components/StreamingLayout.js +29 -0
- package/dist/src/core/components/component-analyzer.js +1 -0
- package/dist/src/core/components/component-detection.js +5 -0
- package/dist/src/core/components/enhanced-framework-detector.js +1 -0
- package/dist/src/core/components/framework-registry.js +1 -0
- package/dist/src/core/content/mdx-processor.js +1 -0
- package/dist/src/core/integrations/index.js +1 -0
- package/dist/src/core/integrations/loader.js +1 -0
- package/dist/src/core/integrations/registry.js +1 -0
- package/dist/src/core/islands/island-persistence.js +1 -0
- package/dist/src/core/islands/island-state-serializer.js +1 -0
- package/dist/src/core/islands/persistent-island-context.js +1 -0
- package/dist/src/core/islands/use-persistent-state.js +1 -0
- package/dist/src/core/layout/enhanced-layout-resolver.js +1 -0
- package/dist/src/core/layout/layout-cache-manager.js +1 -0
- package/dist/src/core/layout/layout-composer.js +1 -0
- package/dist/src/core/layout/layout-data-loader.js +1 -0
- package/dist/src/core/layout/layout-discovery.js +1 -0
- package/dist/src/core/layout/layout-matcher.js +1 -0
- package/dist/src/core/layout/layout-types.js +1 -0
- package/dist/src/core/modules/framework-module-resolver.js +1 -0
- package/dist/src/islands/component-analysis.js +1 -0
- package/dist/src/islands/css-utils.js +17 -0
- package/dist/src/islands/discovery/index.js +1 -0
- package/dist/src/islands/discovery/registry.js +1 -0
- package/dist/src/islands/discovery/resolver.js +2 -0
- package/dist/src/islands/discovery/scanner.js +1 -0
- package/dist/src/islands/discovery/types.js +1 -0
- package/dist/src/islands/discovery/validator.js +18 -0
- package/dist/src/islands/discovery/watcher.js +1 -0
- package/dist/src/islands/framework-detection.js +1 -0
- package/dist/src/islands/integration-loader.js +1 -0
- package/dist/src/islands/island.js +1 -0
- package/dist/src/islands/render-cache.js +1 -0
- package/dist/src/islands/types.js +1 -0
- package/dist/src/islands/universal-css-collector.js +5 -0
- package/dist/src/islands/universal-head-collector.js +2 -0
- package/{src → dist/src}/layout-system.d.ts +592 -592
- package/dist/src/layout-system.js +1 -0
- package/dist/src/middleware/discovery.js +1 -0
- package/dist/src/middleware/executor.js +1 -0
- package/dist/src/middleware/index.js +1 -0
- package/dist/src/middleware/types.js +1 -0
- package/dist/src/nitro/build-config.js +1 -0
- package/dist/src/nitro/config.js +1 -0
- package/dist/src/nitro/error-handler.js +198 -0
- package/dist/src/nitro/index.js +1 -0
- package/dist/src/nitro/island-manifest.js +2 -0
- package/dist/src/nitro/middleware-adapter.js +1 -0
- package/dist/src/nitro/renderer.js +183 -0
- package/dist/src/nitro/route-discovery.js +1 -0
- package/dist/src/nitro/types.js +1 -0
- package/dist/src/render/collect-css.js +3 -0
- package/{src/render/error-pages.ts → dist/src/render/error-pages.js} +7 -38
- package/dist/src/render/isolated-ssr-renderer.js +1 -0
- package/dist/src/render/ssr.js +90 -0
- package/dist/src/schemas/api.js +1 -0
- package/dist/src/schemas/core.js +1 -0
- package/dist/src/schemas/index.js +1 -0
- package/dist/src/schemas/layout.js +1 -0
- package/dist/src/schemas/routing/index.js +1 -0
- package/dist/src/schemas/routing.js +1 -0
- package/dist/src/types/as-island.js +1 -0
- package/{src → dist/src}/types/image.d.ts +106 -106
- package/{src → dist/src}/types/index.d.ts +22 -22
- package/{src → dist/src}/types/island-jsx.d.ts +33 -33
- package/{src → dist/src}/types/island-prop.d.ts +20 -20
- package/dist/src/types/layout.js +1 -0
- package/{src → dist/src}/types/mdx.d.ts +6 -6
- package/dist/src/types/routing.js +1 -0
- package/dist/src/types/types.js +1 -0
- package/{src → dist/src}/types/urlpattern.d.ts +49 -49
- package/{src → dist/src}/types/vite-env.d.ts +11 -11
- package/dist/src/utils/dev-logger.js +12 -0
- package/dist/src/utils/fs.js +1 -0
- package/dist/src/vite-plugin/auto-discover.js +1 -0
- package/dist/src/vite-plugin/config.js +1 -0
- package/dist/src/vite-plugin/errors.js +1 -0
- package/dist/src/vite-plugin/image-optimization.js +45 -0
- package/dist/src/vite-plugin/integration-activator.js +1 -0
- package/dist/src/vite-plugin/island-sidecar-plugin.js +1 -0
- package/dist/src/vite-plugin/module-discovery.js +1 -0
- package/dist/src/vite-plugin/nitro-integration.js +42 -0
- package/dist/src/vite-plugin/plugin.js +1 -0
- package/dist/src/vite-plugin/types.js +1 -0
- package/dist/src/vite-plugin/validation.js +2 -0
- package/package.json +57 -26
- package/mod.ts +0 -302
- package/src/build/integration-bundler-plugin.ts +0 -116
- package/src/build/integration-config.ts +0 -168
- package/src/build/integration-detection-plugin.ts +0 -117
- package/src/build/integration-resolver-plugin.ts +0 -90
- package/src/build/island-manifest.ts +0 -269
- package/src/build/island-types-generator.ts +0 -476
- package/src/build/mdx-island-transform.ts +0 -464
- package/src/build/mdx-plugin.ts +0 -98
- package/src/build/page-island-transform.ts +0 -598
- package/src/build/prop-extractors/index.ts +0 -21
- package/src/build/prop-extractors/lit.ts +0 -140
- package/src/build/prop-extractors/qwik.ts +0 -16
- package/src/build/prop-extractors/solid.ts +0 -125
- package/src/build/prop-extractors/svelte.ts +0 -194
- package/src/build/prop-extractors/vue.ts +0 -111
- package/src/build/sidecar-file-manager.ts +0 -104
- package/src/build/sidecar-renderer.ts +0 -30
- package/src/client/adapters/index.js +0 -12
- package/src/client/adapters/index.ts +0 -13
- package/src/client/adapters/lit-adapter.js +0 -467
- package/src/client/adapters/lit-adapter.ts +0 -654
- package/src/client/adapters/preact-adapter.js +0 -223
- package/src/client/adapters/preact-adapter.ts +0 -331
- package/src/client/adapters/qwik-adapter.js +0 -259
- package/src/client/adapters/qwik-adapter.ts +0 -345
- package/src/client/adapters/react-adapter.js +0 -220
- package/src/client/adapters/react-adapter.ts +0 -353
- package/src/client/adapters/solid-adapter.js +0 -295
- package/src/client/adapters/solid-adapter.ts +0 -451
- package/src/client/adapters/svelte-adapter.js +0 -368
- package/src/client/adapters/svelte-adapter.ts +0 -524
- package/src/client/adapters/vue-adapter.js +0 -278
- package/src/client/adapters/vue-adapter.ts +0 -467
- package/src/client/components.js +0 -23
- package/src/client/components.ts +0 -35
- package/src/client/css-hmr-handler.js +0 -263
- package/src/client/css-hmr-handler.ts +0 -344
- package/src/client/framework-adapter.js +0 -283
- package/src/client/framework-adapter.ts +0 -462
- package/src/client/hmr-coordinator.js +0 -274
- package/src/client/hmr-coordinator.ts +0 -396
- package/src/client/hmr-error-overlay.js +0 -533
- package/src/client/main.js +0 -816
- package/src/client/types/vite-virtual-modules.d.ts +0 -60
- package/src/components/Image.tsx +0 -123
- package/src/components/IslandErrorBoundary.tsx +0 -145
- package/src/components/LayoutDataErrorBoundary.tsx +0 -141
- package/src/components/LayoutErrorBoundary.tsx +0 -127
- package/src/components/PersistentIsland.tsx +0 -52
- package/src/components/StreamingErrorBoundary.tsx +0 -233
- package/src/components/StreamingLayout.tsx +0 -538
- package/src/core/components/component-analyzer.ts +0 -192
- package/src/core/components/component-detection.ts +0 -508
- package/src/core/components/enhanced-framework-detector.ts +0 -500
- package/src/core/components/framework-registry.ts +0 -563
- package/src/core/content/mdx-processor.ts +0 -46
- package/src/core/integrations/index.ts +0 -19
- package/src/core/integrations/loader.ts +0 -125
- package/src/core/integrations/registry.ts +0 -175
- package/src/core/islands/island-persistence.ts +0 -325
- package/src/core/islands/island-state-serializer.ts +0 -258
- package/src/core/islands/persistent-island-context.tsx +0 -80
- package/src/core/islands/use-persistent-state.ts +0 -68
- package/src/core/layout/enhanced-layout-resolver.ts +0 -322
- package/src/core/layout/layout-cache-manager.ts +0 -485
- package/src/core/layout/layout-composer.ts +0 -357
- package/src/core/layout/layout-data-loader.ts +0 -516
- package/src/core/layout/layout-discovery.ts +0 -243
- package/src/core/layout/layout-matcher.ts +0 -299
- package/src/core/layout/layout-types.ts +0 -110
- package/src/core/modules/framework-module-resolver.ts +0 -273
- package/src/islands/component-analysis.ts +0 -213
- package/src/islands/css-utils.ts +0 -565
- package/src/islands/discovery/index.ts +0 -80
- package/src/islands/discovery/registry.ts +0 -340
- package/src/islands/discovery/resolver.ts +0 -477
- package/src/islands/discovery/scanner.ts +0 -386
- package/src/islands/discovery/types.ts +0 -117
- package/src/islands/discovery/validator.ts +0 -544
- package/src/islands/discovery/watcher.ts +0 -368
- package/src/islands/framework-detection.ts +0 -428
- package/src/islands/integration-loader.ts +0 -490
- package/src/islands/island.tsx +0 -565
- package/src/islands/render-cache.ts +0 -550
- package/src/islands/types.ts +0 -80
- package/src/islands/universal-css-collector.ts +0 -157
- package/src/islands/universal-head-collector.ts +0 -137
- package/src/layout-system.ts +0 -218
- package/src/middleware/discovery.ts +0 -268
- package/src/middleware/executor.ts +0 -315
- package/src/middleware/index.ts +0 -76
- package/src/middleware/types.ts +0 -99
- package/src/nitro/build-config.ts +0 -576
- package/src/nitro/config.ts +0 -483
- package/src/nitro/error-handler.ts +0 -636
- package/src/nitro/index.ts +0 -173
- package/src/nitro/island-manifest.ts +0 -584
- package/src/nitro/middleware-adapter.ts +0 -260
- package/src/nitro/renderer.ts +0 -1471
- package/src/nitro/route-discovery.ts +0 -439
- package/src/nitro/types.ts +0 -321
- package/src/render/collect-css.ts +0 -198
- package/src/render/isolated-ssr-renderer.ts +0 -654
- package/src/render/ssr.ts +0 -1030
- package/src/schemas/api.ts +0 -30
- package/src/schemas/core.ts +0 -64
- package/src/schemas/index.ts +0 -212
- package/src/schemas/layout.ts +0 -279
- package/src/schemas/routing/index.ts +0 -38
- package/src/schemas/routing.ts +0 -376
- package/src/types/as-island.ts +0 -20
- package/src/types/layout.ts +0 -285
- package/src/types/routing.ts +0 -555
- package/src/types/types.ts +0 -5
- package/src/utils/dev-logger.ts +0 -299
- package/src/utils/fs.ts +0 -151
- package/src/vite-plugin/auto-discover.ts +0 -551
- package/src/vite-plugin/config.ts +0 -266
- package/src/vite-plugin/errors.ts +0 -127
- package/src/vite-plugin/image-optimization.ts +0 -156
- package/src/vite-plugin/integration-activator.ts +0 -126
- package/src/vite-plugin/island-sidecar-plugin.ts +0 -176
- package/src/vite-plugin/module-discovery.ts +0 -189
- package/src/vite-plugin/nitro-integration.ts +0 -1354
- package/src/vite-plugin/plugin.ts +0 -409
- package/src/vite-plugin/types.ts +0 -327
- 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
|
-
}
|