@useavalon/avalon 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/mod.ts +301 -0
- package/package.json +85 -0
- package/src/build/README.md +310 -0
- package/src/build/integration-bundler-plugin.ts +116 -0
- package/src/build/integration-config.ts +168 -0
- package/src/build/integration-detection-plugin.ts +117 -0
- package/src/build/integration-resolver-plugin.ts +90 -0
- package/src/build/island-manifest.ts +269 -0
- package/src/build/island-types-generator.ts +476 -0
- package/src/build/mdx-island-transform.ts +464 -0
- package/src/build/mdx-plugin.ts +98 -0
- package/src/build/page-island-transform.ts +598 -0
- package/src/build/prop-extractors/index.ts +21 -0
- package/src/build/prop-extractors/lit.ts +140 -0
- package/src/build/prop-extractors/qwik.ts +16 -0
- package/src/build/prop-extractors/solid.ts +125 -0
- package/src/build/prop-extractors/svelte.ts +194 -0
- package/src/build/prop-extractors/vue.ts +111 -0
- package/src/build/sidecar-file-manager.ts +104 -0
- package/src/build/sidecar-renderer.ts +30 -0
- package/src/client/adapters/index.ts +13 -0
- package/src/client/adapters/lit-adapter.ts +654 -0
- package/src/client/adapters/preact-adapter.ts +331 -0
- package/src/client/adapters/qwik-adapter.ts +345 -0
- package/src/client/adapters/react-adapter.ts +353 -0
- package/src/client/adapters/solid-adapter.ts +451 -0
- package/src/client/adapters/svelte-adapter.ts +524 -0
- package/src/client/adapters/vue-adapter.ts +467 -0
- package/src/client/components.ts +35 -0
- package/src/client/css-hmr-handler.ts +344 -0
- package/src/client/framework-adapter.ts +462 -0
- package/src/client/hmr-coordinator.ts +396 -0
- package/src/client/hmr-error-overlay.js +533 -0
- package/src/client/main.js +816 -0
- package/src/client/tests/css-hmr-handler.test.ts +360 -0
- package/src/client/tests/framework-adapter.test.ts +519 -0
- package/src/client/tests/hmr-coordinator.test.ts +176 -0
- package/src/client/tests/hydration-option-parsing.test.ts +107 -0
- package/src/client/tests/lit-adapter.test.ts +427 -0
- package/src/client/tests/preact-adapter.test.ts +353 -0
- package/src/client/tests/qwik-adapter.test.ts +343 -0
- package/src/client/tests/react-adapter.test.ts +317 -0
- package/src/client/tests/solid-adapter.test.ts +396 -0
- package/src/client/tests/svelte-adapter.test.ts +387 -0
- package/src/client/tests/vue-adapter.test.ts +407 -0
- package/src/client/types/framework-runtime.d.ts +68 -0
- package/src/client/types/vite-hmr.d.ts +46 -0
- package/src/client/types/vite-virtual-modules.d.ts +60 -0
- package/src/components/Image.tsx +123 -0
- package/src/components/IslandErrorBoundary.tsx +145 -0
- package/src/components/LayoutDataErrorBoundary.tsx +141 -0
- package/src/components/LayoutErrorBoundary.tsx +127 -0
- package/src/components/PersistentIsland.tsx +52 -0
- package/src/components/StreamingErrorBoundary.tsx +233 -0
- package/src/components/StreamingLayout.tsx +538 -0
- package/src/components/tests/component-analyzer.test.ts +96 -0
- package/src/components/tests/component-detection.test.ts +347 -0
- package/src/components/tests/persistent-islands.test.ts +398 -0
- package/src/core/components/component-analyzer.ts +192 -0
- package/src/core/components/component-detection.ts +508 -0
- package/src/core/components/enhanced-framework-detector.ts +500 -0
- package/src/core/components/framework-registry.ts +563 -0
- package/src/core/components/tests/enhanced-framework-detector.test.ts +577 -0
- package/src/core/components/tests/framework-registry.test.ts +465 -0
- package/src/core/content/mdx-processor.ts +46 -0
- package/src/core/integrations/README.md +282 -0
- package/src/core/integrations/index.ts +19 -0
- package/src/core/integrations/loader.ts +125 -0
- package/src/core/integrations/registry.ts +195 -0
- package/src/core/islands/island-persistence.ts +325 -0
- package/src/core/islands/island-state-serializer.ts +258 -0
- package/src/core/islands/persistent-island-context.tsx +80 -0
- package/src/core/islands/use-persistent-state.ts +68 -0
- package/src/core/layout/enhanced-layout-resolver.ts +322 -0
- package/src/core/layout/layout-cache-manager.ts +485 -0
- package/src/core/layout/layout-composer.ts +357 -0
- package/src/core/layout/layout-data-loader.ts +516 -0
- package/src/core/layout/layout-discovery.ts +243 -0
- package/src/core/layout/layout-matcher.ts +299 -0
- package/src/core/layout/layout-types.ts +110 -0
- package/src/core/layout/tests/enhanced-layout-resolver.test.ts +477 -0
- package/src/core/layout/tests/layout-cache-optimization.test.ts +149 -0
- package/src/core/layout/tests/layout-composer.test.ts +486 -0
- package/src/core/layout/tests/layout-data-loader.test.ts +443 -0
- package/src/core/layout/tests/layout-discovery.test.ts +253 -0
- package/src/core/layout/tests/layout-matcher.test.ts +480 -0
- package/src/core/modules/framework-module-resolver.ts +273 -0
- package/src/core/modules/tests/framework-module-resolver.test.ts +263 -0
- package/src/core/modules/tests/module-resolution-integration.test.ts +117 -0
- package/src/islands/component-analysis.ts +213 -0
- package/src/islands/css-utils.ts +565 -0
- package/src/islands/discovery/index.ts +80 -0
- package/src/islands/discovery/registry.ts +340 -0
- package/src/islands/discovery/resolver.ts +477 -0
- package/src/islands/discovery/scanner.ts +386 -0
- package/src/islands/discovery/tests/island-discovery.test.ts +881 -0
- package/src/islands/discovery/types.ts +117 -0
- package/src/islands/discovery/validator.ts +544 -0
- package/src/islands/discovery/watcher.ts +368 -0
- package/src/islands/framework-detection.ts +428 -0
- package/src/islands/integration-loader.ts +490 -0
- package/src/islands/island.tsx +565 -0
- package/src/islands/render-cache.ts +550 -0
- package/src/islands/types.ts +80 -0
- package/src/islands/universal-css-collector.ts +157 -0
- package/src/islands/universal-head-collector.ts +137 -0
- package/src/layout-system.d.ts +592 -0
- package/src/layout-system.ts +218 -0
- package/src/middleware/__tests__/discovery.test.ts +107 -0
- package/src/middleware/discovery.ts +268 -0
- package/src/middleware/executor.ts +315 -0
- package/src/middleware/index.ts +76 -0
- package/src/middleware/types.ts +99 -0
- package/src/nitro/build-config.ts +576 -0
- package/src/nitro/config.ts +483 -0
- package/src/nitro/error-handler.ts +636 -0
- package/src/nitro/index.ts +173 -0
- package/src/nitro/island-manifest.ts +584 -0
- package/src/nitro/middleware-adapter.ts +260 -0
- package/src/nitro/renderer.ts +1458 -0
- package/src/nitro/route-discovery.ts +439 -0
- package/src/nitro/types.ts +321 -0
- package/src/render/collect-css.ts +198 -0
- package/src/render/error-pages.ts +79 -0
- package/src/render/isolated-ssr-renderer.ts +654 -0
- package/src/render/ssr.ts +1030 -0
- package/src/schemas/api.ts +30 -0
- package/src/schemas/core.ts +64 -0
- package/src/schemas/index.ts +212 -0
- package/src/schemas/layout.ts +279 -0
- package/src/schemas/routing/index.ts +38 -0
- package/src/schemas/routing.ts +376 -0
- package/src/types/as-island.ts +20 -0
- package/src/types/image.d.ts +106 -0
- package/src/types/index.d.ts +22 -0
- package/src/types/island-jsx.d.ts +33 -0
- package/src/types/island-prop.d.ts +20 -0
- package/src/types/layout.ts +285 -0
- package/src/types/mdx.d.ts +6 -0
- package/src/types/routing.ts +555 -0
- package/src/types/tests/layout-types.test.ts +197 -0
- package/src/types/types.ts +5 -0
- package/src/types/urlpattern.d.ts +49 -0
- package/src/types/vite-env.d.ts +11 -0
- package/src/utils/dev-logger.ts +299 -0
- package/src/utils/fs.ts +151 -0
- package/src/vite-plugin/auto-discover.ts +551 -0
- package/src/vite-plugin/config.ts +266 -0
- package/src/vite-plugin/errors.ts +127 -0
- package/src/vite-plugin/image-optimization.ts +151 -0
- package/src/vite-plugin/integration-activator.ts +126 -0
- package/src/vite-plugin/island-sidecar-plugin.ts +176 -0
- package/src/vite-plugin/module-discovery.ts +189 -0
- package/src/vite-plugin/nitro-integration.ts +1334 -0
- package/src/vite-plugin/plugin.ts +329 -0
- package/src/vite-plugin/tests/image-optimization.test.ts +54 -0
- package/src/vite-plugin/types.ts +327 -0
- package/src/vite-plugin/validation.ts +228 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { Plugin } from "vite";
|
|
2
|
+
import { readFile, access } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { detectFrameworkFromPath } from "../islands/integration-loader.ts";
|
|
5
|
+
import { EXTRACTOR_MAP } from "../build/prop-extractors/index.ts";
|
|
6
|
+
import { renderSidecarContent } from "../build/sidecar-renderer.ts";
|
|
7
|
+
import {
|
|
8
|
+
getSidecarPath,
|
|
9
|
+
writeSidecarIfChanged,
|
|
10
|
+
deleteSidecar,
|
|
11
|
+
isSidecarFresh,
|
|
12
|
+
} from "../build/sidecar-file-manager.ts";
|
|
13
|
+
|
|
14
|
+
export interface SidecarPluginOptions {
|
|
15
|
+
/** Whether to log verbose output */
|
|
16
|
+
verbose?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Frameworks that should be skipped — they already work natively with Preact/React JSX */
|
|
20
|
+
const SKIP_FRAMEWORKS = new Set(["react", "preact"]);
|
|
21
|
+
|
|
22
|
+
/** File extensions that qualify as island source files for HMR */
|
|
23
|
+
const ISLAND_EXTENSIONS = [".vue", ".svelte", ".lit.ts", ".solid.tsx", ".qwik.tsx"];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check tsconfig.json for `allowArbitraryExtensions` and warn if missing.
|
|
27
|
+
*/
|
|
28
|
+
export async function checkTsConfigForArbitraryExtensions(
|
|
29
|
+
projectRoot: string,
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
const tsconfigPath = path.join(projectRoot, "tsconfig.json");
|
|
32
|
+
try {
|
|
33
|
+
const raw = await readFile(tsconfigPath, "utf-8");
|
|
34
|
+
const tsconfig = JSON.parse(raw);
|
|
35
|
+
if (tsconfig?.compilerOptions?.allowArbitraryExtensions !== true) {
|
|
36
|
+
console.warn(
|
|
37
|
+
'[avalon] tsconfig.json is missing "allowArbitraryExtensions: true" — sidecar .d.[ext].ts files require this setting',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// tsconfig doesn't exist or can't be parsed - skip warning
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if a file path is already a sidecar declaration file.
|
|
47
|
+
* Sidecar files contain `.d.` before the framework extension.
|
|
48
|
+
*/
|
|
49
|
+
function isSidecarFile(filePath: string): boolean {
|
|
50
|
+
const basename = path.basename(filePath);
|
|
51
|
+
return /\.d\.(vue|svelte|lit|solid\.tsx)/.test(basename);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if a file path looks like a supported component file that needs a sidecar.
|
|
56
|
+
* Excludes files that are already sidecar declaration files.
|
|
57
|
+
*/
|
|
58
|
+
function needsSidecar(filePath: string): boolean {
|
|
59
|
+
if (isSidecarFile(filePath)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return ISLAND_EXTENSIONS.some((ext) => filePath.endsWith(ext));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate a sidecar for a single component file.
|
|
67
|
+
* Returns true if a sidecar was written/updated, false otherwise.
|
|
68
|
+
*/
|
|
69
|
+
async function generateSidecarForFile(filePath: string, verbose?: boolean): Promise<boolean> {
|
|
70
|
+
try {
|
|
71
|
+
const framework = detectFrameworkFromPath(filePath);
|
|
72
|
+
if (SKIP_FRAMEWORKS.has(framework)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const extractor = EXTRACTOR_MAP[framework];
|
|
77
|
+
if (!extractor) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const source = await readFile(filePath, "utf-8");
|
|
82
|
+
const result = extractor(source);
|
|
83
|
+
const content = renderSidecarContent(result.propsType);
|
|
84
|
+
const sidecarPath = getSidecarPath(filePath);
|
|
85
|
+
return await writeSidecarIfChanged(sidecarPath, content);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
if (verbose) {
|
|
88
|
+
console.warn(
|
|
89
|
+
`[avalon] Failed to generate sidecar for ${filePath}:`,
|
|
90
|
+
err instanceof Error ? err.message : err,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Vite plugin that auto-generates `.d.[ext].ts` sidecar declaration files
|
|
99
|
+
* for non-React/Preact components when they are used as islands.
|
|
100
|
+
*
|
|
101
|
+
* Sidecars are generated on-demand when component files are loaded,
|
|
102
|
+
* rather than scanning a fixed directory at startup.
|
|
103
|
+
*/
|
|
104
|
+
export function islandSidecarPlugin(options: SidecarPluginOptions = {}): Plugin {
|
|
105
|
+
let projectRoot: string;
|
|
106
|
+
const processedFiles = new Set<string>();
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
name: "avalon:island-sidecar",
|
|
110
|
+
|
|
111
|
+
configResolved(config) {
|
|
112
|
+
projectRoot = config.root;
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async buildStart() {
|
|
116
|
+
await checkTsConfigForArbitraryExtensions(projectRoot);
|
|
117
|
+
processedFiles.clear();
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
// Generate sidecar when a component file is loaded
|
|
121
|
+
async load(id) {
|
|
122
|
+
if (!needsSidecar(id) || processedFiles.has(id)) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
processedFiles.add(id);
|
|
127
|
+
|
|
128
|
+
// Check if sidecar needs regeneration
|
|
129
|
+
const sidecarPath = getSidecarPath(id);
|
|
130
|
+
if (await isSidecarFresh(id, sidecarPath)) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const wrote = await generateSidecarForFile(id, options.verbose);
|
|
135
|
+
if (wrote && options.verbose) {
|
|
136
|
+
console.log(`[avalon] Generated sidecar for: ${id}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return null; // Let Vite handle the actual file loading
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
async handleHotUpdate(ctx) {
|
|
143
|
+
const filePath = ctx.file;
|
|
144
|
+
|
|
145
|
+
if (!needsSidecar(filePath)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check if the file was deleted
|
|
150
|
+
let fileExists = true;
|
|
151
|
+
try {
|
|
152
|
+
await access(filePath);
|
|
153
|
+
} catch {
|
|
154
|
+
fileExists = false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!fileExists) {
|
|
158
|
+
// File was deleted — remove the sidecar
|
|
159
|
+
const sidecarPath = getSidecarPath(filePath);
|
|
160
|
+
const deleted = await deleteSidecar(sidecarPath);
|
|
161
|
+
if (deleted && options.verbose) {
|
|
162
|
+
console.log(`[avalon] Deleted sidecar for removed file: ${filePath}`);
|
|
163
|
+
}
|
|
164
|
+
processedFiles.delete(filePath);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// File was changed — regenerate sidecar
|
|
169
|
+
processedFiles.delete(filePath); // Allow re-processing
|
|
170
|
+
const wrote = await generateSidecarForFile(filePath, options.verbose);
|
|
171
|
+
if (wrote && options.verbose) {
|
|
172
|
+
console.log(`[avalon] Updated sidecar for: ${filePath}`);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module Discovery for Modular Architecture
|
|
3
|
+
*
|
|
4
|
+
* Discovers pages and layouts within feature modules for file-based routing.
|
|
5
|
+
* Supports co-located architecture where each module contains its own pages/layouts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readdir, stat } from "node:fs/promises";
|
|
9
|
+
import { resolve, join } from "node:path";
|
|
10
|
+
import type { ResolvedModulesConfig } from "./types.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Discovered module with its pages and layouts directories
|
|
14
|
+
*/
|
|
15
|
+
export interface DiscoveredModule {
|
|
16
|
+
/** Module name (folder name) */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Absolute path to the module directory */
|
|
19
|
+
path: string;
|
|
20
|
+
/** Absolute path to pages directory (if exists) */
|
|
21
|
+
pagesDir: string | null;
|
|
22
|
+
/** Absolute path to layouts directory (if exists) */
|
|
23
|
+
layoutsDir: string | null;
|
|
24
|
+
/** Route prefix derived from module name */
|
|
25
|
+
routePrefix: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Result of module discovery
|
|
30
|
+
*/
|
|
31
|
+
export interface ModuleDiscoveryResult {
|
|
32
|
+
/** All discovered modules */
|
|
33
|
+
modules: DiscoveredModule[];
|
|
34
|
+
/** All page directories (for route discovery) */
|
|
35
|
+
pageDirs: Array<{ dir: string; prefix: string }>;
|
|
36
|
+
/** All layout directories */
|
|
37
|
+
layoutDirs: Array<{ dir: string; prefix: string }>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Discover all modules within the modules directory
|
|
42
|
+
*
|
|
43
|
+
* @param modulesConfig - Resolved modules configuration
|
|
44
|
+
* @param projectRoot - Project root directory
|
|
45
|
+
* @returns Discovery result with modules, page dirs, and layout dirs
|
|
46
|
+
*/
|
|
47
|
+
export async function discoverModules(
|
|
48
|
+
modulesConfig: ResolvedModulesConfig,
|
|
49
|
+
projectRoot: string
|
|
50
|
+
): Promise<ModuleDiscoveryResult> {
|
|
51
|
+
const modulesDir = resolve(projectRoot, modulesConfig.dir);
|
|
52
|
+
const modules: DiscoveredModule[] = [];
|
|
53
|
+
const pageDirs: Array<{ dir: string; prefix: string }> = [];
|
|
54
|
+
const layoutDirs: Array<{ dir: string; prefix: string }> = [];
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const entries = await readdir(modulesDir, { withFileTypes: true });
|
|
58
|
+
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
if (!entry.isDirectory()) continue;
|
|
61
|
+
|
|
62
|
+
// Skip hidden directories and common non-module folders
|
|
63
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
64
|
+
|
|
65
|
+
const modulePath = join(modulesDir, entry.name);
|
|
66
|
+
const pagesPath = join(modulePath, modulesConfig.pagesDirName);
|
|
67
|
+
const layoutsPath = join(modulePath, modulesConfig.layoutsDirName);
|
|
68
|
+
|
|
69
|
+
// Check if pages/layouts directories exist
|
|
70
|
+
const [pagesExists, layoutsExists] = await Promise.all([
|
|
71
|
+
directoryExists(pagesPath),
|
|
72
|
+
directoryExists(layoutsPath),
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
// Determine route prefix
|
|
76
|
+
// 'home' or 'root' module maps to '/', others map to '/moduleName'
|
|
77
|
+
const routePrefix = getRoutePrefix(entry.name);
|
|
78
|
+
|
|
79
|
+
const module: DiscoveredModule = {
|
|
80
|
+
name: entry.name,
|
|
81
|
+
path: modulePath,
|
|
82
|
+
pagesDir: pagesExists ? pagesPath : null,
|
|
83
|
+
layoutsDir: layoutsExists ? layoutsPath : null,
|
|
84
|
+
routePrefix,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
modules.push(module);
|
|
88
|
+
|
|
89
|
+
if (pagesExists) {
|
|
90
|
+
pageDirs.push({ dir: pagesPath, prefix: routePrefix });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (layoutsExists) {
|
|
94
|
+
layoutDirs.push({ dir: layoutsPath, prefix: routePrefix });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
// Modules directory doesn't exist or can't be read
|
|
99
|
+
// This is fine - modules are optional
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Sort modules so 'home'/'root' comes first (for route priority)
|
|
103
|
+
modules.sort((a, b) => {
|
|
104
|
+
if (a.routePrefix === '/') return -1;
|
|
105
|
+
if (b.routePrefix === '/') return 1;
|
|
106
|
+
return a.name.localeCompare(b.name);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return { modules, pageDirs, layoutDirs };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get the route prefix for a module
|
|
114
|
+
* Special modules like 'home', 'root', 'main' map to '/'
|
|
115
|
+
*/
|
|
116
|
+
function getRoutePrefix(moduleName: string): string {
|
|
117
|
+
const rootModules = ['home', 'root', 'main', 'index'];
|
|
118
|
+
if (rootModules.includes(moduleName.toLowerCase())) {
|
|
119
|
+
return '/';
|
|
120
|
+
}
|
|
121
|
+
return '/' + moduleName;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check if a directory exists
|
|
126
|
+
*/
|
|
127
|
+
async function directoryExists(path: string): Promise<boolean> {
|
|
128
|
+
try {
|
|
129
|
+
const stats = await stat(path);
|
|
130
|
+
return stats.isDirectory();
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get all page directories including both traditional and modular
|
|
138
|
+
*
|
|
139
|
+
* @param pagesDir - Traditional pages directory
|
|
140
|
+
* @param modulesConfig - Modules configuration (if any)
|
|
141
|
+
* @param projectRoot - Project root
|
|
142
|
+
* @returns Array of page directories with their route prefixes
|
|
143
|
+
*/
|
|
144
|
+
export async function getAllPageDirs(
|
|
145
|
+
pagesDir: string,
|
|
146
|
+
modulesConfig: ResolvedModulesConfig | null,
|
|
147
|
+
projectRoot: string
|
|
148
|
+
): Promise<Array<{ dir: string; prefix: string }>> {
|
|
149
|
+
const dirs: Array<{ dir: string; prefix: string }> = [];
|
|
150
|
+
|
|
151
|
+
// Add traditional pages directory
|
|
152
|
+
const traditionalPagesPath = resolve(projectRoot, pagesDir);
|
|
153
|
+
if (await directoryExists(traditionalPagesPath)) {
|
|
154
|
+
dirs.push({ dir: traditionalPagesPath, prefix: '/' });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Add modular page directories
|
|
158
|
+
if (modulesConfig) {
|
|
159
|
+
const { pageDirs } = await discoverModules(modulesConfig, projectRoot);
|
|
160
|
+
dirs.push(...pageDirs);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return dirs;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get all layout directories including both traditional and modular
|
|
168
|
+
*/
|
|
169
|
+
export async function getAllLayoutDirs(
|
|
170
|
+
layoutsDir: string,
|
|
171
|
+
modulesConfig: ResolvedModulesConfig | null,
|
|
172
|
+
projectRoot: string
|
|
173
|
+
): Promise<Array<{ dir: string; prefix: string }>> {
|
|
174
|
+
const dirs: Array<{ dir: string; prefix: string }> = [];
|
|
175
|
+
|
|
176
|
+
// Add traditional layouts directory
|
|
177
|
+
const traditionalLayoutsPath = resolve(projectRoot, layoutsDir);
|
|
178
|
+
if (await directoryExists(traditionalLayoutsPath)) {
|
|
179
|
+
dirs.push({ dir: traditionalLayoutsPath, prefix: '/' });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Add modular layout directories
|
|
183
|
+
if (modulesConfig) {
|
|
184
|
+
const { layoutDirs } = await discoverModules(modulesConfig, projectRoot);
|
|
185
|
+
dirs.push(...layoutDirs);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return dirs;
|
|
189
|
+
}
|