@kimesh/kit 0.2.23 → 0.2.24

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/dist/index.d.mts CHANGED
@@ -613,6 +613,12 @@ interface KimeshViteConfig extends Omit<UserConfig, 'root' | 'configFile'> {
613
613
  * Default aliases provided by Kimesh.
614
614
  * Templates use placeholders: <srcDir>, <rootDir>
615
615
  * These are replaced at build time with actual paths.
616
+ *
617
+ * NOTE: `~` and `@` are NOT included here because they are handled by
618
+ * the layer-alias-plugin for context-aware resolution. The plugin resolves
619
+ * these aliases relative to the importing file's layer, not globally.
620
+ *
621
+ * @see packages/kit/src/vite/layer-alias-plugin.ts
616
622
  */
617
623
  declare const DEFAULT_ALIASES: Record<string, string>;
618
624
  /**
@@ -1146,7 +1152,11 @@ interface KimeshConfig extends KimeshModuleOptions {
1146
1152
  * Aliases are automatically added to the generated TypeScript configurations
1147
1153
  * for full type support and path auto-complete.
1148
1154
  *
1149
- * @default { "~": "/<srcDir>", "@": "/<srcDir>", "~~": "/<rootDir>", "@@": "/<rootDir>", "#build": "/<rootDir>/.kimesh" }
1155
+ * NOTE: `~` and `@` are NOT included in default aliases. They are handled
1156
+ * by layer-alias-plugin for context-aware resolution - resolving to the
1157
+ * current layer's path, not the global host app path.
1158
+ *
1159
+ * @default { "~~": "/<rootDir>", "@@": "/<rootDir>", "#build": "/<rootDir>/.kimesh" }
1150
1160
  *
1151
1161
  * @example
1152
1162
  * ```ts
package/dist/index.mjs CHANGED
@@ -8,7 +8,7 @@ import consola from "consola";
8
8
  import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
9
9
  import { dirname, isAbsolute, join, resolve } from "node:path";
10
10
  import fg from "fast-glob";
11
- import { basename, dirname as dirname$1, extname, isAbsolute as isAbsolute$1, join as join$1, relative, resolve as resolve$1 } from "pathe";
11
+ import { basename, dirname as dirname$1, extname, isAbsolute as isAbsolute$1, join as join$1, normalize, relative, resolve as resolve$1 } from "pathe";
12
12
  import { resolveModulePath } from "exsolve";
13
13
  import ignore from "ignore";
14
14
  import pc from "picocolors";
@@ -1310,10 +1310,14 @@ function buildInternalAliasMap(buildDir) {
1310
1310
  * Default aliases provided by Kimesh.
1311
1311
  * Templates use placeholders: <srcDir>, <rootDir>
1312
1312
  * These are replaced at build time with actual paths.
1313
+ *
1314
+ * NOTE: `~` and `@` are NOT included here because they are handled by
1315
+ * the layer-alias-plugin for context-aware resolution. The plugin resolves
1316
+ * these aliases relative to the importing file's layer, not globally.
1317
+ *
1318
+ * @see packages/kit/src/vite/layer-alias-plugin.ts
1313
1319
  */
1314
1320
  const DEFAULT_ALIASES = {
1315
- "~": "<srcDir>",
1316
- "@": "<srcDir>",
1317
1321
  "~~": "<rootDir>",
1318
1322
  "@@": "<rootDir>",
1319
1323
  "#build": "<rootDir>/.kimesh",
@@ -1974,6 +1978,54 @@ declare module '*?route' {
1974
1978
  `;
1975
1979
  }
1976
1980
 
1981
+ //#endregion
1982
+ //#region src/vite/module-types-generator.ts
1983
+ /**
1984
+ * @kimesh/kit - Module Types Generation
1985
+ *
1986
+ * Generates modules.ts for type augmentation from configured modules.
1987
+ * Extracted from plugin.ts for better maintainability.
1988
+ */
1989
+ /**
1990
+ * Extract module names from config (filters out inline module objects)
1991
+ */
1992
+ function extractModuleNames(modules) {
1993
+ if (!modules) return [];
1994
+ const names = [];
1995
+ for (const mod of modules) if (typeof mod === "string") names.push(mod);
1996
+ else if (Array.isArray(mod) && typeof mod[0] === "string") names.push(mod[0]);
1997
+ return names;
1998
+ }
1999
+ /**
2000
+ * Generate modules.ts that imports all configured modules
2001
+ * AND references their augment.d.ts files for type declarations
2002
+ */
2003
+ function generateModulesTypeDeclaration(modules, buildDir, rootDir) {
2004
+ const moduleNames = extractModuleNames(modules);
2005
+ if (moduleNames.length === 0) return;
2006
+ const imports = moduleNames.map((name) => `import "${name}";`).join("\n");
2007
+ const content = `/**
2008
+ * Module type declarations - Auto-generated by Kimesh
2009
+ * This file imports all modules to enable their type augmentations.
2010
+ *
2011
+ * DO NOT EDIT - This file is regenerated when modules change.
2012
+ */
2013
+
2014
+ ${moduleNames.map((name) => {
2015
+ const pkgDir = resolvePackageDir(name, rootDir);
2016
+ if (pkgDir) {
2017
+ const relativePath = relative(buildDir, pkgDir);
2018
+ return `/// <reference path="${relativePath.startsWith("..") ? relativePath : "./" + relativePath}/augment.d.ts" />`;
2019
+ }
2020
+ return `/// <reference path="../node_modules/${name}/augment.d.ts" />`;
2021
+ }).join("\n")}
2022
+
2023
+ ${imports}
2024
+ `;
2025
+ mkdirSync(buildDir, { recursive: true });
2026
+ writeFileSync(join$1(buildDir, "modules.ts"), content, "utf-8");
2027
+ }
2028
+
1977
2029
  //#endregion
1978
2030
  //#region src/kit/phase2-utils.ts
1979
2031
  /**
@@ -2538,31 +2590,267 @@ function capitalize(str) {
2538
2590
  }
2539
2591
 
2540
2592
  //#endregion
2541
- //#region src/vite/plugin.ts
2593
+ //#region src/vite/layer-alias-plugin.ts
2542
2594
  /**
2543
- * Find workspace root by traversing up and looking for workspaces config
2595
+ * @kimesh/kit - Context-Aware Layer Alias Plugin
2596
+ *
2597
+ * This Vite plugin intercepts `~/` and `@/` alias resolution and resolves them
2598
+ * relative to the **importing file's layer** instead of globally to the host app.
2599
+ *
2600
+ * Problem it solves:
2601
+ * ```typescript
2602
+ * // In layer: 1s-km/apps/remotes/okr/src/routes/okr.vue
2603
+ * import OkrSidebar from '~/components/okr-sidebar/OkrSidebar.vue'
2604
+ * // Without this plugin: Resolves to 1s-km/apps/host/src/components/... ❌
2605
+ * // With this plugin: Resolves to 1s-km/apps/remotes/okr/src/components/... ✅
2606
+ * ```
2607
+ *
2608
+ * Resolution rules:
2609
+ * - `~` → layer root path (e.g., `/path/to/layer`)
2610
+ * - `@` → layer source path (e.g., `/path/to/layer/src` or layer root if no src)
2611
+ * - Falls back to app/host layer if file is not in any layer
2544
2612
  */
2545
- function findWorkspaceRoot(startDir) {
2546
- let current = startDir;
2547
- for (let i = 0; i < 10; i++) {
2548
- const pkgPath = join$1(current, "package.json");
2549
- if (existsSync(pkgPath)) try {
2550
- if (JSON.parse(readFileSync(pkgPath, "utf-8")).workspaces || existsSync(join$1(current, "pnpm-workspace.yaml"))) return current;
2551
- } catch {}
2552
- const parent = resolve$1(current, "..");
2553
- if (parent === current) break;
2554
- current = parent;
2613
+ /**
2614
+ * Cache for resolved real paths to avoid repeated syscalls
2615
+ */
2616
+ const realPathCache = /* @__PURE__ */ new Map();
2617
+ /**
2618
+ * Get the real path (resolving symlinks) with caching
2619
+ */
2620
+ function getRealPath(filePath) {
2621
+ const cached = realPathCache.get(filePath);
2622
+ if (cached !== void 0) return cached;
2623
+ try {
2624
+ const realPath = realpathSync(filePath);
2625
+ realPathCache.set(filePath, realPath);
2626
+ return realPath;
2627
+ } catch {
2628
+ realPathCache.set(filePath, filePath);
2629
+ return filePath;
2555
2630
  }
2556
- return startDir;
2557
2631
  }
2558
2632
  /**
2559
- * Try to resolve a package or subpath export using exsolve.
2560
- * This handles various package manager layouts (npm, pnpm, bun, yarn)
2561
- * and properly resolves subpath exports like "@kimesh/head/plugin"
2633
+ * Strip any virtual module prefixes from the file path
2634
+ * Handles cases like '\0kimesh-route:' prefix added by router generator
2635
+ * Note: Vite uses \0 prefix to mark virtual modules internally
2636
+ */
2637
+ function stripVirtualPrefixes(filePath) {
2638
+ let result = filePath;
2639
+ if (result.charCodeAt(0) === 0 || result.startsWith("\0")) result = result.slice(1);
2640
+ const kimeshRouteIdx = result.indexOf("kimesh-route:");
2641
+ if (kimeshRouteIdx !== -1 && kimeshRouteIdx <= 1) result = result.slice(kimeshRouteIdx + 13);
2642
+ if (result.startsWith("virtual:")) result = result.slice(8);
2643
+ if (result.includes("/@fs/")) result = result.split("/@fs/").pop() || result;
2644
+ return result;
2645
+ }
2646
+ /**
2647
+ * Find which layer a file belongs to by checking if the file path starts with
2648
+ * any layer's path. Sorts layers by path length (longest first) to match the
2649
+ * most specific layer.
2650
+ *
2651
+ * This function handles both symlinked and real paths by comparing both:
2652
+ * - Original paths (for symlink matching)
2653
+ * - Real paths (for resolved symlink matching)
2654
+ *
2655
+ * @param filePath - Absolute path of the importing file (may be symlink or real)
2656
+ * @param layers - Array of resolved layers
2657
+ * @returns The layer the file belongs to, or null if not in any layer
2658
+ */
2659
+ function findLayerForFile(filePath, layers) {
2660
+ if (!filePath || layers.length === 0) return null;
2661
+ const strippedPath = stripVirtualPrefixes(filePath);
2662
+ const normalizedFilePath = normalize(strippedPath);
2663
+ const realFilePath = normalize(getRealPath(strippedPath));
2664
+ const sortedLayers = [...layers].sort((a, b) => b.path.length - a.path.length);
2665
+ for (const layer of sortedLayers) {
2666
+ const normalizedLayerPath = normalize(layer.path);
2667
+ const realLayerPath = normalize(getRealPath(layer.path));
2668
+ if (normalizedFilePath.startsWith(normalizedLayerPath + "/") || normalizedFilePath === normalizedLayerPath || realFilePath.startsWith(realLayerPath + "/") || realFilePath === realLayerPath || normalizedFilePath.startsWith(realLayerPath + "/") || realFilePath.startsWith(normalizedLayerPath + "/")) return layer;
2669
+ }
2670
+ return null;
2671
+ }
2672
+ /**
2673
+ * Get the source path for a layer (src/ if exists, otherwise layer root)
2674
+ *
2675
+ * @param layerPath - Absolute path to the layer root
2676
+ * @returns Path to src/ directory or layer root
2677
+ */
2678
+ function getLayerSourcePath(layerPath) {
2679
+ const srcPath = join$1(layerPath, "src");
2680
+ return existsSync(srcPath) ? srcPath : layerPath;
2681
+ }
2682
+ /**
2683
+ * Resolve a `~/` or `@/` alias relative to the given layer
2684
+ * Tries common extensions if the exact path doesn't exist
2562
2685
  *
2563
- * @param specifier - Full package specifier (e.g., "@kimesh/head" or "@kimesh/head/plugin")
2564
- * @param fromDir - Directory to resolve from
2565
- * @returns Resolved file path or null if not found
2686
+ * @param id - The import specifier (e.g., '~/components/Foo.vue')
2687
+ * @param layer - The layer to resolve relative to
2688
+ * @returns The resolved absolute path, or null if not an alias
2689
+ */
2690
+ function resolveLayerAlias(id, layer) {
2691
+ let basePath = null;
2692
+ if (id.startsWith("~/")) {
2693
+ const relativePath = id.slice(2);
2694
+ basePath = join$1(layer.path, relativePath);
2695
+ }
2696
+ if (id.startsWith("@/")) {
2697
+ const relativePath = id.slice(2);
2698
+ basePath = join$1(getLayerSourcePath(layer.path), relativePath);
2699
+ }
2700
+ if (!basePath) return null;
2701
+ if (existsSync(basePath)) return basePath;
2702
+ for (const ext of [
2703
+ ".ts",
2704
+ ".js",
2705
+ ".vue",
2706
+ ".tsx",
2707
+ ".jsx",
2708
+ ".mjs",
2709
+ ".mts"
2710
+ ]) {
2711
+ const withExt = basePath + ext;
2712
+ if (existsSync(withExt)) return withExt;
2713
+ }
2714
+ for (const indexFile of [
2715
+ "index.ts",
2716
+ "index.js",
2717
+ "index.vue",
2718
+ "index.mjs",
2719
+ "index.mts"
2720
+ ]) {
2721
+ const indexPath = join$1(basePath, indexFile);
2722
+ if (existsSync(indexPath)) return indexPath;
2723
+ }
2724
+ return basePath;
2725
+ }
2726
+ /**
2727
+ * Create the context-aware layer alias Vite plugin
2728
+ *
2729
+ * This plugin intercepts `~/` and `@/` imports and resolves them based on
2730
+ * which layer the importing file belongs to.
2731
+ */
2732
+ /**
2733
+ * Regex to match ~/ or @/ imports in code
2734
+ * Captures: full import statement, specifier, import path
2735
+ */
2736
+ const ALIAS_IMPORT_RE = /from\s+['"]([~@]\/[^'"]+)['"]/g;
2737
+ /**
2738
+ * Create the context-aware layer alias Vite plugin
2739
+ *
2740
+ * This plugin intercepts `~/` and `@/` imports and resolves them based on
2741
+ * which layer the importing file belongs to.
2742
+ *
2743
+ * It uses both resolveId (for regular imports) and transform (for virtual modules)
2744
+ * to handle all cases.
2745
+ */
2746
+ function layerAliasPlugin(options) {
2747
+ const { getLayers, debug = false } = options;
2748
+ /**
2749
+ * Resolve an alias import given the importer path
2750
+ */
2751
+ function resolveAliasImport(id, importer) {
2752
+ const layers = getLayers();
2753
+ if (layers.length === 0) return null;
2754
+ const targetLayer = findLayerForFile(importer, layers) || layers.find((l) => l.isApp) || layers[0];
2755
+ if (!targetLayer) return null;
2756
+ return resolveLayerAlias(id, targetLayer);
2757
+ }
2758
+ return {
2759
+ name: "kimesh:layer-alias",
2760
+ enforce: "pre",
2761
+ resolveId(id, importer) {
2762
+ if (!id.startsWith("~/") && !id.startsWith("@/")) return null;
2763
+ if (!importer) {
2764
+ if (debug) consola.debug(`[layer-alias] No importer for ${id}, skipping`);
2765
+ return null;
2766
+ }
2767
+ const resolved = resolveAliasImport(id, importer);
2768
+ if (resolved) {
2769
+ if (debug) consola.debug(`[layer-alias] resolveId: ${id} -> ${resolved} (from: ${importer.slice(-60)})`);
2770
+ return resolved;
2771
+ }
2772
+ return null;
2773
+ },
2774
+ transform(code, id) {
2775
+ if (!id.includes("kimesh-route:")) return null;
2776
+ if (!code.includes("~/") && !code.includes("@/")) return null;
2777
+ const filePath = stripVirtualPrefixes(id);
2778
+ if (debug) {
2779
+ const layers = getLayers();
2780
+ consola.debug(`[layer-alias] transform: processing ${filePath.slice(-80)}`);
2781
+ consola.debug(`[layer-alias] transform: layers=${layers.map((l) => l.name).join(", ")}`);
2782
+ }
2783
+ const transformedCode = code.replace(ALIAS_IMPORT_RE, (match, importPath) => {
2784
+ const resolved = resolveAliasImport(importPath, filePath);
2785
+ if (resolved) {
2786
+ if (debug) consola.debug(`[layer-alias] transform: ${importPath} -> ${resolved}`);
2787
+ return `from '${resolved}'`;
2788
+ }
2789
+ return match;
2790
+ });
2791
+ if (transformedCode !== code) return {
2792
+ code: transformedCode,
2793
+ map: null
2794
+ };
2795
+ return null;
2796
+ }
2797
+ };
2798
+ }
2799
+
2800
+ //#endregion
2801
+ //#region src/vite/kimesh-package-resolver.ts
2802
+ /**
2803
+ * @kimesh/kit - Kimesh Package Resolution
2804
+ *
2805
+ * Handles resolution of @kimesh/* packages across different package managers.
2806
+ * Extracted from plugin.ts for better maintainability.
2807
+ */
2808
+ /**
2809
+ * Map from @kimesh/* package name to the directory name in kimesh/packages/
2810
+ */
2811
+ const PACKAGE_DIR_MAP = {
2812
+ "@kimesh/router-runtime": "router-runtime",
2813
+ "@kimesh/head": "head",
2814
+ "@kimesh/query": "query",
2815
+ "@kimesh/kit": "kit",
2816
+ "@kimesh/layers": "layers",
2817
+ "@kimesh/auto-import": "auto-import",
2818
+ "@kimesh/router-generator": "router-generator"
2819
+ };
2820
+ /**
2821
+ * Map subpath exports to their actual dist file paths
2822
+ *
2823
+ * Used for workspace development (link:kimesh) where symlinked packages
2824
+ * don't resolve subpath exports correctly via Vite's alias system.
2825
+ */
2826
+ const SUBPATH_EXPORT_MAP = {
2827
+ "@kimesh/head/plugin": {
2828
+ pkg: "head",
2829
+ file: "dist/plugin.mjs"
2830
+ },
2831
+ "@kimesh/router-runtime/default-app": {
2832
+ pkg: "router-runtime",
2833
+ file: "dist/default-app.mjs"
2834
+ },
2835
+ "@kimesh/router-runtime/middleware/plugin": {
2836
+ pkg: "router-runtime",
2837
+ file: "dist/middleware/plugin.mjs"
2838
+ },
2839
+ "kimesh/head/plugin": {
2840
+ pkg: "head",
2841
+ file: "dist/plugin.mjs"
2842
+ },
2843
+ "kimesh/router-runtime/default-app": {
2844
+ pkg: "router-runtime",
2845
+ file: "dist/default-app.mjs"
2846
+ },
2847
+ "kimesh/router-runtime/middleware/plugin": {
2848
+ pkg: "router-runtime",
2849
+ file: "dist/middleware/plugin.mjs"
2850
+ }
2851
+ };
2852
+ /**
2853
+ * Try to resolve a package or subpath export using exsolve
2566
2854
  */
2567
2855
  function tryResolveModule(specifier, fromUrl) {
2568
2856
  try {
@@ -2579,8 +2867,7 @@ function tryResolveModule(specifier, fromUrl) {
2579
2867
  }
2580
2868
  }
2581
2869
  /**
2582
- * Try to resolve a package directory (not a specific file).
2583
- * Used for aliasing the base package.
2870
+ * Try to resolve a package directory (not a specific file)
2584
2871
  */
2585
2872
  function tryResolvePackage(packageName, fromUrl) {
2586
2873
  try {
@@ -2601,14 +2888,23 @@ function tryResolvePackage(packageName, fromUrl) {
2601
2888
  return null;
2602
2889
  }
2603
2890
  /**
2604
- * Find the kimesh packages location.
2605
- * This handles both installed packages (in node_modules) and workspace development.
2606
- *
2607
- * The search strategy:
2608
- * 1. Check if we're in a workspace development setup (kimesh/packages/*)
2609
- * 2. From @kimesh/kit's location, traverse up to find the parent "kimesh" package
2610
- * 3. In pnpm/bun workspace: kit -> ../kimesh/node_modules
2611
- * 4. In npm flat layout: kit is in node_modules/@kimesh/kit, umbrella is in node_modules/kimesh
2891
+ * Find workspace root by traversing up and looking for workspaces config
2892
+ */
2893
+ function findWorkspaceRoot(startDir) {
2894
+ let current = startDir;
2895
+ for (let i = 0; i < 10; i++) {
2896
+ const pkgPath = join$1(current, "package.json");
2897
+ if (existsSync(pkgPath)) try {
2898
+ if (JSON.parse(readFileSync(pkgPath, "utf-8")).workspaces || existsSync(join$1(current, "pnpm-workspace.yaml"))) return current;
2899
+ } catch {}
2900
+ const parent = resolve$1(current, "..");
2901
+ if (parent === current) break;
2902
+ current = parent;
2903
+ }
2904
+ return startDir;
2905
+ }
2906
+ /**
2907
+ * Find the kimesh packages location
2612
2908
  */
2613
2909
  function findKimeshPackagesLocation(kitPath, debug) {
2614
2910
  const kitDir = dirname$1(dirname$1(kitPath.replace(/^file:\/\//, "")));
@@ -2665,70 +2961,7 @@ function findKimeshPackagesLocation(kitPath, debug) {
2665
2961
  return null;
2666
2962
  }
2667
2963
  /**
2668
- * Map from @kimesh/* package name to the directory name in kimesh/packages/
2669
- * Most follow the pattern @kimesh/{name} -> {name}, but some differ
2670
- */
2671
- const PACKAGE_DIR_MAP = {
2672
- "@kimesh/router-runtime": "router-runtime",
2673
- "@kimesh/head": "head",
2674
- "@kimesh/query": "query",
2675
- "@kimesh/kit": "kit",
2676
- "@kimesh/layers": "layers",
2677
- "@kimesh/auto-import": "auto-import",
2678
- "@kimesh/router-generator": "router-generator"
2679
- };
2680
- /**
2681
- * Map subpath exports to their actual dist file paths
2682
- *
2683
- * This map is used for workspace development (link:kimesh) where we need to
2684
- * resolve subpath exports directly to dist files since symlinked packages
2685
- * don't resolve subpath exports correctly via Vite's alias system.
2686
- *
2687
- * NOTE: We now prefer using `kimesh/head/plugin` (umbrella package subpath)
2688
- * over `@kimesh/head/plugin` (direct internal package) because the umbrella
2689
- * package re-exports work correctly in PR preview packages and all setups.
2690
- */
2691
- const SUBPATH_EXPORT_MAP = {
2692
- "@kimesh/head/plugin": {
2693
- pkg: "head",
2694
- file: "dist/plugin.mjs"
2695
- },
2696
- "@kimesh/router-runtime/default-app": {
2697
- pkg: "router-runtime",
2698
- file: "dist/default-app.mjs"
2699
- },
2700
- "@kimesh/router-runtime/middleware/plugin": {
2701
- pkg: "router-runtime",
2702
- file: "dist/middleware/plugin.mjs"
2703
- },
2704
- "kimesh/head/plugin": {
2705
- pkg: "head",
2706
- file: "dist/plugin.mjs"
2707
- },
2708
- "kimesh/router-runtime/default-app": {
2709
- pkg: "router-runtime",
2710
- file: "dist/default-app.mjs"
2711
- },
2712
- "kimesh/router-runtime/middleware/plugin": {
2713
- pkg: "router-runtime",
2714
- file: "dist/middleware/plugin.mjs"
2715
- }
2716
- };
2717
- /**
2718
- * Build aliases for @kimesh/* packages and their subpath exports.
2719
- * This ensures Vite can resolve these packages regardless of package manager.
2720
- *
2721
- * KEY INSIGHT: We find the "kimesh" umbrella package's node_modules, then
2722
- * resolve all @kimesh/* packages from there. This works because:
2723
- * 1. User's project depends on the umbrella "kimesh" package
2724
- * 2. The umbrella package has @kimesh/kit, @kimesh/head, etc. as dependencies
2725
- * 3. Those dependencies are installed in the umbrella's node_modules
2726
- *
2727
- * For workspace development (link:kimesh), we directly resolve from sibling packages.
2728
- *
2729
- * Unlike simple package aliases, this also handles subpath exports like:
2730
- * - "@kimesh/head/plugin" -> actual file path
2731
- * - "@kimesh/router-runtime/default-app" -> actual file path
2964
+ * Build aliases for @kimesh/* packages and their subpath exports
2732
2965
  */
2733
2966
  function buildKimeshPackageAliases(_rootDir, debug) {
2734
2967
  const kitUrl = import.meta.url;
@@ -2784,8 +3017,178 @@ function buildKimeshPackageAliases(_rootDir, debug) {
2784
3017
  }
2785
3018
  return aliases;
2786
3019
  }
3020
+
3021
+ //#endregion
3022
+ //#region src/vite/middleware-generator.ts
3023
+ /**
3024
+ * @kimesh/kit - Middleware Generation
3025
+ *
3026
+ * Handles middleware scanning and code generation.
3027
+ * Extracted from plugin.ts for better maintainability.
3028
+ */
2787
3029
  /**
2788
- * Process all configured modules using the new v2 system
3030
+ * Scan and generate middleware from host app and all layers
3031
+ */
3032
+ async function generateMiddleware(kimesh, resolvedDirs, resolvedLayers, debug) {
3033
+ const middlewareDir = join$1(resolvedDirs.srcDir, "middleware");
3034
+ try {
3035
+ const middlewareResult = await scanLayerMiddleware([{
3036
+ layerName: "app",
3037
+ layerPath: resolvedDirs.rootDir,
3038
+ middlewareDir,
3039
+ priority: 0
3040
+ }, ...resolvedLayers.filter((l) => !l.isApp).map((layer) => ({
3041
+ layerName: layer.name,
3042
+ layerPath: layer.path,
3043
+ middlewareDir: join$1(layer.path, "src", "middleware"),
3044
+ priority: layer.priority
3045
+ }))], { orderStrategy: "alphabetical" });
3046
+ if (middlewareResult.middleware.length === 0) return {
3047
+ hasMiddleware: false,
3048
+ pluginPath: null
3049
+ };
3050
+ if (!existsSync(resolvedDirs.buildDir)) mkdirSync(resolvedDirs.buildDir, { recursive: true });
3051
+ const registryCode = generateMiddlewareRegistry(middlewareResult);
3052
+ writeFileSync(join$1(resolvedDirs.buildDir, "middleware.gen.ts"), registryCode, "utf-8");
3053
+ const typesCode = generateMiddlewareTypes(middlewareResult);
3054
+ writeFileSync(join$1(resolvedDirs.buildDir, "middleware.d.ts"), typesCode, "utf-8");
3055
+ const middlewarePluginCode = `/**
3056
+ * Kimesh Middleware Plugin - Auto-generated
3057
+ * DO NOT EDIT - This file is regenerated when middleware files change
3058
+ */
3059
+
3060
+ import { createMiddlewarePlugin } from '@kimesh/router-runtime'
3061
+ import { globalMiddleware, namedMiddleware } from './middleware.gen'
3062
+
3063
+ export default createMiddlewarePlugin({
3064
+ globalMiddleware,
3065
+ namedMiddleware,
3066
+ })
3067
+ `;
3068
+ const middlewarePluginPath = join$1(resolvedDirs.buildDir, "middleware-plugin.mjs");
3069
+ writeFileSync(middlewarePluginPath, middlewarePluginCode, "utf-8");
3070
+ if (debug) consola.info(`[Kimesh] Middleware: ${middlewareResult.globalMiddleware.length} global, ${middlewareResult.namedMiddleware.length} named`);
3071
+ if (!kimesh._registries.runtimePlugins.some((p) => p.name === "middleware")) {
3072
+ kimesh._registries.runtimePlugins.push({
3073
+ src: middlewarePluginPath,
3074
+ name: "middleware"
3075
+ });
3076
+ if (debug) consola.info(`[Kimesh] Auto-registered middleware plugin`);
3077
+ }
3078
+ return {
3079
+ hasMiddleware: true,
3080
+ pluginPath: middlewarePluginPath
3081
+ };
3082
+ } catch (error) {
3083
+ if (debug) consola.warn(`[Kimesh] Middleware scanning failed:`, error);
3084
+ return {
3085
+ hasMiddleware: false,
3086
+ pluginPath: null
3087
+ };
3088
+ }
3089
+ }
3090
+
3091
+ //#endregion
3092
+ //#region src/vite/spa-middleware.ts
3093
+ /**
3094
+ * @kimesh/kit - SPA Fallback Middleware
3095
+ *
3096
+ * Handles SPA routing in development mode.
3097
+ * Extracted from plugin.ts for better maintainability.
3098
+ */
3099
+ /**
3100
+ * Known static file extensions that should NOT be served as HTML
3101
+ */
3102
+ const STATIC_EXTENSIONS = new Set([
3103
+ "js",
3104
+ "mjs",
3105
+ "cjs",
3106
+ "ts",
3107
+ "mts",
3108
+ "cts",
3109
+ "jsx",
3110
+ "tsx",
3111
+ "css",
3112
+ "scss",
3113
+ "sass",
3114
+ "less",
3115
+ "styl",
3116
+ "json",
3117
+ "xml",
3118
+ "yaml",
3119
+ "yml",
3120
+ "toml",
3121
+ "html",
3122
+ "htm",
3123
+ "png",
3124
+ "jpg",
3125
+ "jpeg",
3126
+ "gif",
3127
+ "svg",
3128
+ "ico",
3129
+ "webp",
3130
+ "avif",
3131
+ "woff",
3132
+ "woff2",
3133
+ "ttf",
3134
+ "eot",
3135
+ "otf",
3136
+ "mp3",
3137
+ "mp4",
3138
+ "webm",
3139
+ "ogg",
3140
+ "wav",
3141
+ "pdf",
3142
+ "zip",
3143
+ "tar",
3144
+ "gz",
3145
+ "map",
3146
+ "wasm"
3147
+ ]);
3148
+ /**
3149
+ * Check if a URL should skip SPA handling
3150
+ */
3151
+ function shouldSkipSpaHandling(url) {
3152
+ if (url.startsWith("/@") || url.startsWith("/__") || url.startsWith("/node_modules")) return true;
3153
+ if (url.split("?")[0].startsWith("/.")) return true;
3154
+ return false;
3155
+ }
3156
+ /**
3157
+ * Check if pathname looks like a static file
3158
+ */
3159
+ function isStaticFile(pathname) {
3160
+ const extMatch = (pathname.split("/").pop() || "").match(/\.([a-zA-Z0-9]+)$/);
3161
+ if (extMatch) {
3162
+ const ext = extMatch[1].toLowerCase();
3163
+ return STATIC_EXTENSIONS.has(ext);
3164
+ }
3165
+ return false;
3166
+ }
3167
+ /**
3168
+ * Create SPA fallback middleware for Vite dev server
3169
+ */
3170
+ function createSpaFallbackMiddleware(server, generatedDir) {
3171
+ return async (req, res, next) => {
3172
+ const url = req.url || "/";
3173
+ if (shouldSkipSpaHandling(url)) return next();
3174
+ if (!(req.headers.accept || "").includes("text/html")) return next();
3175
+ const pathname = url.split("?")[0];
3176
+ if (isStaticFile(pathname)) return next();
3177
+ try {
3178
+ const html = readFileSync(`${generatedDir}/index.html`, "utf-8");
3179
+ const transformed = await server.transformIndexHtml(url, html);
3180
+ res.setHeader("Content-Type", "text/html");
3181
+ res.end(transformed);
3182
+ } catch (e) {
3183
+ next(e);
3184
+ }
3185
+ };
3186
+ }
3187
+
3188
+ //#endregion
3189
+ //#region src/vite/plugin.ts
3190
+ /**
3191
+ * Process all configured modules using the v2 system
2789
3192
  */
2790
3193
  async function processModules(kimesh, debug) {
2791
3194
  const modules = kimesh.options.config.modules;
@@ -2800,45 +3203,6 @@ async function processModules(kimesh, debug) {
2800
3203
  }
2801
3204
  }
2802
3205
  /**
2803
- * Extract module names from config (filters out inline module objects)
2804
- */
2805
- function extractModuleNames$1(modules) {
2806
- if (!modules) return [];
2807
- const names = [];
2808
- for (const mod of modules) if (typeof mod === "string") names.push(mod);
2809
- else if (Array.isArray(mod) && typeof mod[0] === "string") names.push(mod[0]);
2810
- return names;
2811
- }
2812
- /**
2813
- * Generate modules.ts that imports all configured modules
2814
- * AND references their augment.d.ts files for type declarations
2815
- */
2816
- function generateModulesTypeDeclaration(modules, buildDir, rootDir) {
2817
- const moduleNames = extractModuleNames$1(modules);
2818
- if (moduleNames.length === 0) return;
2819
- const imports = moduleNames.map((name) => `import "${name}";`).join("\n");
2820
- const content = `/**
2821
- * Module type declarations - Auto-generated by Kimesh
2822
- * This file imports all modules to enable their type augmentations.
2823
- *
2824
- * DO NOT EDIT - This file is regenerated when modules change.
2825
- */
2826
-
2827
- ${moduleNames.map((name) => {
2828
- const pkgDir = resolvePackageDir(name, rootDir);
2829
- if (pkgDir) {
2830
- const relativePath = relative(buildDir, pkgDir);
2831
- return `/// <reference path="${relativePath.startsWith("..") ? relativePath : "./" + relativePath}/augment.d.ts" />`;
2832
- }
2833
- return `/// <reference path="../node_modules/${name}/augment.d.ts" />`;
2834
- }).join("\n")}
2835
-
2836
- ${imports}
2837
- `;
2838
- mkdirSync(buildDir, { recursive: true });
2839
- writeFileSync(join$1(buildDir, "modules.ts"), content, "utf-8");
2840
- }
2841
- /**
2842
3206
  * Kimesh Vite plugin
2843
3207
  * Sets up Vue with Vite 8 + Rolldown + File-based routing + Layers + Auto-Import
2844
3208
  */
@@ -2951,54 +3315,7 @@ function kimeshPlugin(options = {}) {
2951
3315
  consola.info(`[Kimesh] Found ${state.kimesh._registries.runtimePlugins.length} module-registered plugins`);
2952
3316
  }
2953
3317
  }
2954
- const middlewareDir = join$1(resolvedDirs.srcDir, "middleware");
2955
- let hasMiddleware = false;
2956
- try {
2957
- const middlewareResult = await scanLayerMiddleware([{
2958
- layerName: "app",
2959
- layerPath: configRoot,
2960
- middlewareDir,
2961
- priority: 0
2962
- }, ...state.resolvedLayers.filter((l) => !l.isApp).map((layer) => ({
2963
- layerName: layer.name,
2964
- layerPath: layer.path,
2965
- middlewareDir: join$1(layer.path, "src", "middleware"),
2966
- priority: layer.priority
2967
- }))], { orderStrategy: "alphabetical" });
2968
- hasMiddleware = middlewareResult.middleware.length > 0;
2969
- if (hasMiddleware) {
2970
- const registryCode = generateMiddlewareRegistry(middlewareResult);
2971
- const registryPath = join$1(resolvedDirs.buildDir, "middleware.gen.ts");
2972
- if (!existsSync(resolvedDirs.buildDir)) mkdirSync(resolvedDirs.buildDir, { recursive: true });
2973
- writeFileSync(registryPath, registryCode, "utf-8");
2974
- const typesCode = generateMiddlewareTypes(middlewareResult);
2975
- writeFileSync(join$1(resolvedDirs.buildDir, "middleware.d.ts"), typesCode, "utf-8");
2976
- writeFileSync(join$1(resolvedDirs.buildDir, "middleware-plugin.mjs"), `/**
2977
- * Kimesh Middleware Plugin - Auto-generated
2978
- * DO NOT EDIT - This file is regenerated when middleware files change
2979
- */
2980
-
2981
- import { createMiddlewarePlugin } from '@kimesh/router-runtime'
2982
- import { globalMiddleware, namedMiddleware } from './middleware.gen'
2983
-
2984
- export default createMiddlewarePlugin({
2985
- globalMiddleware,
2986
- namedMiddleware,
2987
- })
2988
- `, "utf-8");
2989
- if (debug) consola.info(`[Kimesh] Middleware: ${middlewareResult.globalMiddleware.length} global, ${middlewareResult.namedMiddleware.length} named`);
2990
- const middlewarePluginSrc = join$1(resolvedDirs.buildDir, "middleware-plugin.mjs");
2991
- if (!state.kimesh._registries.runtimePlugins.some((p) => p.name === "middleware")) {
2992
- state.kimesh._registries.runtimePlugins.push({
2993
- src: middlewarePluginSrc,
2994
- name: "middleware"
2995
- });
2996
- if (debug) consola.info(`[Kimesh] Auto-registered middleware plugin`);
2997
- }
2998
- }
2999
- } catch (error) {
3000
- if (debug) consola.warn(`[Kimesh] Middleware scanning failed:`, error);
3001
- }
3318
+ await generateMiddleware(state.kimesh, resolvedDirs, state.resolvedLayers, debug);
3002
3319
  generateModulesTypeDeclaration(config.modules, state.generatedDir, configRoot);
3003
3320
  await writeTemplates(state.kimesh);
3004
3321
  const userAliases = buildAliases(config, resolvedDirs.srcDir, configRoot);
@@ -3014,11 +3331,18 @@ export default createMiddlewarePlugin({
3014
3331
  "#kimesh/plugins": join$1(resolvedDirs.buildDir, "plugins.mjs"),
3015
3332
  "#kimesh/middleware": join$1(resolvedDirs.buildDir, "middleware.gen.ts")
3016
3333
  };
3334
+ const hostAppAliases = {
3335
+ "@": resolvedDirs.srcDir,
3336
+ "~": configRoot
3337
+ };
3017
3338
  writeTsConfig({
3018
3339
  rootDir: configRoot,
3019
3340
  srcDir: resolvedDirs.srcDir,
3020
3341
  buildDir: state.generatedDir,
3021
- aliases: userAliases,
3342
+ aliases: {
3343
+ ...userAliases,
3344
+ ...hostAppAliases
3345
+ },
3022
3346
  layerAliases,
3023
3347
  moduleAliases,
3024
3348
  internalAliases,
@@ -3145,7 +3469,14 @@ export default createMiddlewarePlugin({
3145
3469
  esModuleInterop: true,
3146
3470
  lib: ["ESNext", "DOM"],
3147
3471
  skipLibCheck: true,
3148
- noEmit: true
3472
+ noEmit: true,
3473
+ baseUrl: "..",
3474
+ paths: {
3475
+ "~": ["."],
3476
+ "~/*": ["./*"],
3477
+ "@": ["./src"],
3478
+ "@/*": ["./src/*"]
3479
+ }
3149
3480
  },
3150
3481
  include: ["../src/**/*", "./**/*"],
3151
3482
  exclude: ["../node_modules"]
@@ -3173,70 +3504,7 @@ export default createMiddlewarePlugin({
3173
3504
  server.watcher.on("change", handleWatchEvent("change"));
3174
3505
  server.watcher.on("add", handleWatchEvent("add"));
3175
3506
  server.watcher.on("unlink", handleWatchEvent("unlink"));
3176
- server.middlewares.use(async (req, res, next) => {
3177
- const url = req.url || "/";
3178
- const pathname = url.split("?")[0];
3179
- if (url.startsWith("/@") || url.startsWith("/__") || url.startsWith("/node_modules") || pathname.startsWith("/.")) return next();
3180
- if (!(req.headers.accept || "").includes("text/html")) return next();
3181
- const extMatch = (pathname.split("/").pop() || "").match(/\.([a-zA-Z0-9]+)$/);
3182
- if (extMatch) {
3183
- const ext = extMatch[1].toLowerCase();
3184
- if ([
3185
- "js",
3186
- "mjs",
3187
- "cjs",
3188
- "ts",
3189
- "mts",
3190
- "cts",
3191
- "jsx",
3192
- "tsx",
3193
- "css",
3194
- "scss",
3195
- "sass",
3196
- "less",
3197
- "styl",
3198
- "json",
3199
- "xml",
3200
- "yaml",
3201
- "yml",
3202
- "toml",
3203
- "html",
3204
- "htm",
3205
- "png",
3206
- "jpg",
3207
- "jpeg",
3208
- "gif",
3209
- "svg",
3210
- "ico",
3211
- "webp",
3212
- "avif",
3213
- "woff",
3214
- "woff2",
3215
- "ttf",
3216
- "eot",
3217
- "otf",
3218
- "mp3",
3219
- "mp4",
3220
- "webm",
3221
- "ogg",
3222
- "wav",
3223
- "pdf",
3224
- "zip",
3225
- "tar",
3226
- "gz",
3227
- "map",
3228
- "wasm"
3229
- ].includes(ext)) return next();
3230
- }
3231
- try {
3232
- const html = readFileSync(join$1(state.generatedDir, "index.html"), "utf-8");
3233
- const transformed = await server.transformIndexHtml(url, html);
3234
- res.setHeader("Content-Type", "text/html");
3235
- res.end(transformed);
3236
- } catch (e) {
3237
- next(e);
3238
- }
3239
- });
3507
+ server.middlewares.use(createSpaFallbackMiddleware(server, state.generatedDir));
3240
3508
  }
3241
3509
  };
3242
3510
  const routerPlugin = kimeshRouterGenerator({
@@ -3257,41 +3525,46 @@ export default createMiddlewarePlugin({
3257
3525
  });
3258
3526
  }
3259
3527
  });
3528
+ const autoImportPlugin = kimeshAutoImport$1({
3529
+ getSources: () => {
3530
+ return state.resolvedLayers.map((layer) => ({
3531
+ layer: layer.name,
3532
+ priority: layer.priority,
3533
+ layerPath: layer.path,
3534
+ config: {
3535
+ composables: { dirs: layer.config.composables?.dirs || ["composables"] },
3536
+ components: {
3537
+ dirs: layer.config.components?.dirs || ["components"],
3538
+ prefix: layer.isApp ? void 0 : layer.config.components?.prefix
3539
+ },
3540
+ utils: { dirs: layer.config.utils?.dirs || ["utils"] },
3541
+ stores: { dirs: layer.config.stores?.dirs || ["stores"] },
3542
+ presets: layer.isApp ? config.autoImport?.presets || [
3543
+ "vue",
3544
+ "vue-router",
3545
+ "kimesh"
3546
+ ] : void 0
3547
+ }
3548
+ }));
3549
+ },
3550
+ getLayers: () => {
3551
+ return state.resolvedLayers.map((layer) => ({
3552
+ name: layer.name,
3553
+ path: layer.path,
3554
+ isApp: layer.isApp
3555
+ }));
3556
+ },
3557
+ getKimesh: () => state.kimesh,
3558
+ dts: config.autoImport?.dts !== false ? state.generatedDir || ".kimesh" : false,
3559
+ debug
3560
+ });
3260
3561
  const internalPlugins = [
3261
- mainPlugin,
3262
- kimeshAutoImport$1({
3263
- getSources: () => {
3264
- return state.resolvedLayers.map((layer) => ({
3265
- layer: layer.name,
3266
- priority: layer.priority,
3267
- layerPath: layer.path,
3268
- config: {
3269
- composables: { dirs: layer.config.composables?.dirs || ["composables"] },
3270
- components: {
3271
- dirs: layer.config.components?.dirs || ["components"],
3272
- prefix: layer.isApp ? void 0 : layer.config.components?.prefix
3273
- },
3274
- utils: { dirs: layer.config.utils?.dirs || ["utils"] },
3275
- stores: { dirs: layer.config.stores?.dirs || ["stores"] },
3276
- presets: layer.isApp ? config.autoImport?.presets || [
3277
- "vue",
3278
- "vue-router",
3279
- "kimesh"
3280
- ] : void 0
3281
- }
3282
- }));
3283
- },
3284
- getLayers: () => {
3285
- return state.resolvedLayers.map((layer) => ({
3286
- name: layer.name,
3287
- path: layer.path,
3288
- isApp: layer.isApp
3289
- }));
3290
- },
3291
- getKimesh: () => state.kimesh,
3292
- dts: config.autoImport?.dts !== false ? state.generatedDir || ".kimesh" : false,
3562
+ layerAliasPlugin({
3563
+ getLayers: () => state.resolvedLayers,
3293
3564
  debug
3294
3565
  }),
3566
+ mainPlugin,
3567
+ autoImportPlugin,
3295
3568
  vue(),
3296
3569
  routerPlugin
3297
3570
  ];
@@ -3450,11 +3723,18 @@ async function prepare(options = {}) {
3450
3723
  "#kimesh/context": join$1(root, "src", "app.context.ts"),
3451
3724
  "#kimesh/plugins": join$1(kimeshDir, "plugins.mjs")
3452
3725
  };
3726
+ const appAliases = {
3727
+ "@": srcDir,
3728
+ "~": root
3729
+ };
3453
3730
  writeTsConfig({
3454
3731
  rootDir: root,
3455
3732
  srcDir,
3456
3733
  buildDir,
3457
- aliases: userAliases,
3734
+ aliases: {
3735
+ ...userAliases,
3736
+ ...appAliases
3737
+ },
3458
3738
  layerAliases,
3459
3739
  moduleAliases: {},
3460
3740
  internalAliases,
@@ -3655,16 +3935,6 @@ declare module 'vue' {
3655
3935
  };
3656
3936
  }
3657
3937
  /**
3658
- * Extract module names from config (filters out inline module objects)
3659
- */
3660
- function extractModuleNames(modules) {
3661
- if (!modules) return [];
3662
- const names = [];
3663
- for (const mod of modules) if (typeof mod === "string") names.push(mod);
3664
- else if (Array.isArray(mod) && typeof mod[0] === "string") names.push(mod[0]);
3665
- return names;
3666
- }
3667
- /**
3668
3938
  * Generate stub routes.gen.ts when route scanning is not available
3669
3939
  */
3670
3940
  function generateRoutesStub(buildDir, generatedFiles, verbose) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kimesh/kit",
3
- "version": "0.2.23",
3
+ "version": "0.2.24",
4
4
  "description": "Build-time engine for Kimesh framework",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,9 +27,9 @@
27
27
  "test:watch": "vitest"
28
28
  },
29
29
  "dependencies": {
30
- "@kimesh/auto-import": "0.2.23",
31
- "@kimesh/layers": "0.2.23",
32
- "@kimesh/router-generator": "0.2.23",
30
+ "@kimesh/auto-import": "0.2.24",
31
+ "@kimesh/layers": "0.2.24",
32
+ "@kimesh/router-generator": "0.2.24",
33
33
  "@vitejs/plugin-vue": "^6.0.3",
34
34
  "c12": "^3.3.3",
35
35
  "consola": "^3.4.2",