{"version":3,"sources":["../../src/config/app-runtime.ts","../../src/config/umi-base.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * UmiJS App Runtime 配置工厂\n *\n * 抽象三个项目中 app.tsx 的通用模式:\n * - getInitialState: 获取用户信息 + 认证状态\n * - layout: 头像菜单、页面切换守卫、错误边界\n * - rootContainer: 全局 Provider 包装\n *\n * 将三个项目中 app.tsx 的重复模式抽象为统一的运行时初始化工厂。\n */\n\nimport type React from 'react';\nimport type { BaseUser } from '../types/auth';\n\n// ===== 类型定义 =====\n\n/**\n * 应用用户信息 — 基于 BaseUser 扩展\n *\n * 将 `BaseUser` 所有字段设为可选，以适配用户信息未完全加载的场景。\n */\nexport interface AppUser extends Partial<BaseUser> {}\n\n/**\n * InitialState 结构\n */\nexport interface AppInitialState<\n  U extends AppUser = AppUser,\n  E = Record<string, unknown>,\n> {\n  /** 当前用户信息 */\n  currentUser?: U;\n  /** 正在加载用户信息 */\n  loading?: boolean;\n  /** 额外的初始状态 (系统配置、组织信息等) */\n  extra?: E;\n  /** 重新获取用户信息 */\n  fetchUserInfo: () => Promise<U | undefined>;\n}\n\n/**\n * 头像菜单项\n */\nexport interface AvatarMenuItem {\n  key: string;\n  label: string;\n  icon?: React.ReactNode;\n  danger?: boolean;\n  onClick?: () => void;\n}\n\n/**\n * 应用运行时配置选项\n */\nexport interface AppRuntimeOptions<\n  U extends AppUser = AppUser,\n  E = Record<string, unknown>,\n> {\n  /**\n   * 认证配置\n   */\n  auth: {\n    /** 获取当前用户信息的 API */\n    fetchUserInfo: () => Promise<U | undefined>;\n    /** 登录页路径, 默认 '/login' */\n    loginPath?: string;\n    /** 不需要认证的页面路径列表 (默认含 loginPath) */\n    guestPaths?: string[];\n    /** 检测是否已登录 (如检查 token) */\n    isAuthenticated?: () => boolean;\n    /** 获取失败重试次数, 默认 0 */\n    retryCount?: number;\n    /** 重试延迟 (ms), 默认 1000 */\n    retryDelay?: number;\n  };\n\n  /**\n   * 布局配置\n   */\n  layout?: {\n    /** 应用标题 */\n    title?: string | ((initialState?: AppInitialState<U, E>) => string);\n    /** 应用 Logo URL 或组件 */\n    logo?: string | React.ReactNode;\n    /** 头像菜单项 */\n    avatarMenuItems?: AvatarMenuItem[];\n    /** 显示用户名, 默认 true */\n    showAvatarName?: boolean;\n    /** 页脚 */\n    footer?: React.ReactNode | (() => React.ReactNode);\n    /** 页面切换回调 (可用于路由守卫) */\n    onPageChange?: (\n      initialState: AppInitialState<U, E> | undefined,\n      location: { pathname: string },\n      navigate: (path: string) => void,\n    ) => void;\n    /** 自定义 childrenRender 包装 (如水印) */\n    childrenRender?: (\n      children: React.ReactNode,\n      initialState?: AppInitialState<U, E>,\n    ) => React.ReactNode;\n    /** 额外的 ProLayout 配置 */\n    layoutProps?: Record<string, unknown>;\n  };\n\n  /**\n   * 额外初始数据获取 (与 fetchUserInfo 并行执行)\n   */\n  fetchExtra?: () => Promise<E>;\n\n  /**\n   * 导航函数 (用于路由跳转)\n   *\n   * SDK 不假设路由模式，由消费方注入。\n   * 如不提供，onPageChange 中的 navigate 回调将无操作。\n   *\n   * @example\n   * ```ts\n   * import { history } from '@umijs/max';\n   * navigate: (path) => history.push(path),\n   * ```\n   */\n  navigate?: (path: string) => void;\n\n  /**\n   * 初始化完成回调\n   */\n  onReady?: (state: AppInitialState<U, E>) => void;\n\n  /**\n   * 初始化失败回调\n   */\n  onError?: (error: Error) => void;\n}\n\n// ===== 内部工具 =====\n\nfunction normalizeError(error: unknown): Error {\n  return error instanceof Error ? error : new Error(String(error));\n}\n\nfunction normalizePath(pathname: string): string {\n  const pathWithoutQuery = pathname.split('?')[0] || '/';\n  return pathWithoutQuery.startsWith('/')\n    ? pathWithoutQuery\n    : `/${pathWithoutQuery}`;\n}\n\nfunction buildGuestPathList(\n  loginPath: string,\n  guestPaths?: string[],\n): string[] {\n  const normalizedLoginPath = normalizePath(loginPath);\n  const sourcePaths =\n    guestPaths && guestPaths.length > 0 ? guestPaths : [normalizedLoginPath];\n\n  return [...new Set([normalizedLoginPath, ...sourcePaths.map(normalizePath)])];\n}\n\n/**\n * 判断路径是否匹配免认证路径列表（精确匹配或前缀匹配）\n */\nfunction matchGuestPath(pathname: string, guestPaths: string[]): boolean {\n  const currentPath = normalizePath(pathname);\n  return guestPaths.some(\n    (p) => currentPath === p || currentPath.startsWith(`${p}/`),\n  );\n}\n\n// ===== 工厂函数 =====\n\n/**\n * 创建 UmiJS App Runtime 配置\n *\n * 返回 `getInitialState` 和 `layout` 函数，直接导出供 UmiJS 使用。\n *\n * @example\n * ```ts\n * // app.tsx\n * import { createAppRuntime } from '@aspect/shared/config';\n *\n * const appRuntime = createAppRuntime({\n *   auth: {\n *     fetchUserInfo: () => getCurrentUser().then(res =>\n *       isApiSuccess(res) ? res.data : undefined\n *     ),\n *     loginPath: '/login',\n *     guestPaths: ['/login', '/register', '/agreement'],\n *   },\n *   layout: {\n *     title: 'My App',\n *     showAvatarName: true,\n *     avatarMenuItems: [\n *       { key: 'settings', label: '个人设置', onClick: () => history.push('/settings') },\n *       { key: 'logout', label: '退出登录', danger: true, onClick: handleLogout },\n *     ],\n *     onPageChange: (state, location, navigate) => {\n *       if (!state?.currentUser && location.pathname !== '/login') {\n *         navigate('/login');\n *       }\n *     },\n *   },\n * });\n *\n * export const getInitialState = appRuntime.getInitialState;\n * export const layout = appRuntime.layout;\n * ```\n */\nexport function createAppRuntime<\n  U extends AppUser = AppUser,\n  E = Record<string, unknown>,\n>(options: AppRuntimeOptions<U, E>) {\n  const {\n    auth,\n    layout: layoutCfg,\n    fetchExtra,\n    navigate: injectedNavigate,\n    onReady,\n    onError,\n  } = options;\n\n  const loginPath = normalizePath(auth.loginPath ?? '/login');\n  const guestPaths = buildGuestPathList(loginPath, auth.guestPaths);\n\n  /**\n   * 获取当前路径 (支持 hash 和 history 模式)\n   */\n  function getCurrentPath(): string {\n    if (typeof window === 'undefined') return '/';\n    const hash = window.location.hash;\n    if (hash && hash.length > 1) {\n      return normalizePath(hash.replace(/^#/, '').split('?')[0] || '/');\n    }\n    return normalizePath(window.location.pathname);\n  }\n\n  /**\n   * 带重试的用户信息获取\n   */\n  async function fetchUserWithRetry(): Promise<U | undefined> {\n    const maxRetries = auth.retryCount ?? 0;\n    const retryDelay = auth.retryDelay ?? 1000;\n\n    for (let attempt = 0; attempt <= maxRetries; attempt++) {\n      try {\n        const user = await auth.fetchUserInfo();\n        if (user) return user;\n      } catch {\n        // 请求失败时等待后重试，最后一次失败后跳出循环\n        if (attempt < maxRetries) {\n          await new Promise((resolve) => setTimeout(resolve, retryDelay));\n        }\n      }\n    }\n    return undefined;\n  }\n\n  /**\n   * 判断路径是否为免认证页面\n   */\n  function isGuestPath(pathname: string): boolean {\n    return matchGuestPath(pathname, guestPaths);\n  }\n\n  // ----- getInitialState -----\n\n  async function getInitialState(): Promise<AppInitialState<U, E>> {\n    const fetchUserInfo = fetchUserWithRetry;\n    const anonymousState = (): AppInitialState<U, E> => {\n      return {\n        currentUser: undefined,\n        loading: false,\n        extra: undefined,\n        fetchUserInfo,\n      };\n    };\n\n    // 免认证页面 — 跳过用户信息获取\n    const currentPath = getCurrentPath();\n\n    if (isGuestPath(currentPath)) {\n      return anonymousState();\n    }\n\n    // 非免认证页面 — 如果提供了 isAuthenticated, 先检查\n    if (auth.isAuthenticated && !auth.isAuthenticated()) {\n      return anonymousState();\n    }\n\n    // 并行获取用户信息和额外数据\n    try {\n      const promises: [Promise<U | undefined>, Promise<E | undefined>] = [\n        fetchUserWithRetry(),\n        fetchExtra ? fetchExtra() : Promise.resolve(undefined),\n      ];\n\n      const [currentUser, extra] = await Promise.all(promises);\n\n      const state: AppInitialState<U, E> = {\n        currentUser,\n        loading: false,\n        extra,\n        fetchUserInfo,\n      };\n\n      onReady?.(state);\n      return state;\n    } catch (error) {\n      onError?.(normalizeError(error));\n      return anonymousState();\n    }\n  }\n\n  // ----- layout -----\n\n  function layout(params: {\n    initialState?: AppInitialState<U, E>;\n    location?: { pathname: string };\n    // UmiJS 注入\n    [key: string]: unknown;\n  }) {\n    const { initialState } = params;\n    const currentUser = initialState?.currentUser;\n\n    // 标题\n    const title =\n      typeof layoutCfg?.title === 'function'\n        ? layoutCfg.title(initialState)\n        : layoutCfg?.title;\n\n    // 头像菜单\n    const avatarProps: Record<string, unknown> = {};\n    if (currentUser) {\n      avatarProps.src = currentUser.avatar;\n      if (layoutCfg?.showAvatarName !== false) {\n        avatarProps.title = currentUser.nickname || currentUser.username || '';\n      }\n    }\n\n    // 将 avatarMenuItems 作为数据附加到返回值，由调用方决定如何渲染\n    const avatarMenuItems = layoutCfg?.avatarMenuItems;\n\n    // 页面切换\n    const navigateFn = injectedNavigate ?? (() => {});\n    const pageChangeHandler = layoutCfg?.onPageChange;\n    const onPageChange = pageChangeHandler\n      ? () => {\n          const pathname = normalizePath(\n            params.location?.pathname || getCurrentPath(),\n          );\n          pageChangeHandler(initialState, { pathname }, navigateFn);\n        }\n      : undefined;\n\n    // 页脚\n    const footerValue = layoutCfg?.footer;\n    const footerRender = footerValue\n      ? () =>\n          typeof footerValue === 'function'\n            ? (footerValue as () => React.ReactNode)()\n            : footerValue\n      : undefined;\n\n    // children 包装 (水印等)\n    const layoutChildrenRender = layoutCfg?.childrenRender;\n    const childrenRender = layoutChildrenRender\n      ? (children: React.ReactNode) =>\n          layoutChildrenRender(children, initialState)\n      : undefined;\n\n    return {\n      title,\n      logo: layoutCfg?.logo,\n      avatarProps,\n      avatarMenuItems,\n      onPageChange,\n      footerRender,\n      childrenRender,\n      ...layoutCfg?.layoutProps,\n    };\n  }\n\n  return {\n    getInitialState,\n    layout,\n    /** 工具方法: 判断是否为免认证路径 */\n    isGuestPath,\n  };\n}\n\n/**\n * 创建默认的页面切换守卫\n *\n * 未登录用户在访问需要认证的页面时，自动跳转到登录页。\n *\n * @example\n * ```ts\n * const appRuntime = createAppRuntime({\n *   // ...\n *   layout: {\n *     onPageChange: createPageChangeGuard({\n *       loginPath: '/login',\n *       guestPaths: ['/login', '/register'],\n *     }),\n *   },\n * });\n * ```\n */\nexport function createPageChangeGuard<\n  U extends AppUser = AppUser,\n  E = Record<string, unknown>,\n>(options?: {\n  loginPath?: string;\n  guestPaths?: string[];\n}): (\n  initialState: AppInitialState<U, E> | undefined,\n  location: { pathname: string },\n  navigate: (path: string) => void,\n) => void {\n  const loginPath = normalizePath(options?.loginPath ?? '/login');\n  const guestPaths = buildGuestPathList(loginPath, options?.guestPaths);\n\n  return (\n    initialState: AppInitialState<U, E> | undefined,\n    location: { pathname: string },\n    navigate: (path: string) => void,\n  ) => {\n    if (\n      !initialState?.currentUser &&\n      !matchGuestPath(normalizePath(location.pathname), guestPaths)\n    ) {\n      navigate(loginPath);\n    }\n  };\n}\n","/**\n * UmiJS 通用配置工厂\n */\n\n/**\n * UmiJS 配置返回类型\n *\n * 列出工厂函数可能输出的已知字段，同时允许 UmiJS 插件扩展的未知字段。\n */\nexport interface UmiBaseConfig {\n  routes: Record<string, unknown>[];\n  hash: boolean;\n  history: { type: 'hash' | 'browser' };\n  publicPath: string;\n  model: Record<string, unknown>;\n  initialState: Record<string, unknown>;\n  access: Record<string, unknown>;\n  request: Record<string, unknown>;\n  mako: Record<string, unknown>;\n  fastRefresh: boolean;\n  esbuildMinifyIIFE: boolean;\n  ignoreMomentLocale: boolean;\n  moment2dayjs: { preset: string; plugins: string[] };\n  antd: Record<string, unknown>;\n  locale?: false | { default: string; antd: boolean; baseNavigator: boolean };\n  layout?: Record<string, unknown>;\n  title?: string;\n  alias?: Record<string, string>;\n  define?: Record<string, unknown>;\n  proxy?: Record<string, unknown>;\n  codeSplitting?: { jsStrategy: string };\n  headScripts?: Record<string, unknown>[];\n  favicons?: string[];\n  [key: string]: unknown;\n}\n\n/**\n * UmiJS 基础配置选项\n */\nexport interface UmiBaseConfigOptions {\n  /** 路由配置 */\n  routes: Record<string, unknown>[];\n  /** 国际化配置，传 false 关闭 */\n  locale?:\n    | false\n    | {\n        default: string;\n        antd?: boolean;\n        baseNavigator?: boolean;\n      };\n  /** 公共路径，默认 '/' */\n  publicPath?: string;\n  /** 路径别名 */\n  alias?: Record<string, string>;\n  /** 环境变量定义 */\n  define?: Record<string, unknown>;\n  /** 代理配置 */\n  proxy?: Record<string, unknown>;\n  /** 应用标题 */\n  title?: string;\n  /** 是否使用 ProLayout，默认 false */\n  useProLayout?: boolean;\n  /** ProLayout 配置 */\n  proLayoutSettings?: Record<string, unknown>;\n  /** 是否使用 hash 路由，默认 true */\n  hashRouting?: boolean;\n  /** 是否启用代码分割，默认 false */\n  codeSplitting?: boolean;\n  /** head 中的额外脚本 */\n  headScripts?: Record<string, unknown>[];\n  /** 网站图标 */\n  favicons?: string[];\n  /** antd 主题配置 */\n  antdTheme?: Record<string, unknown>;\n}\n\n/**\n * 创建 UmiJS 基础配置\n *\n * 输出统一的:\n * - hash routing\n * - model + initialState + access 插件\n * - mako 构建\n * - fastRefresh 热更新\n * - moment2dayjs\n * - antd CSS 变量模式\n * - esbuildMinifyIIFE\n *\n * @param options - 配置选项\n * @returns UmiJS 配置对象\n *\n * @example\n * ```ts\n * // config/config.ts\n * import { createUmiBaseConfig } from '@aspect/shared/config';\n * import routes from './routes';\n *\n * export default createUmiBaseConfig({\n *   routes,\n *   locale: { default: 'zh-CN', antd: true },\n *   title: 'My App',\n *   define: {\n *     'process.env.API_BASE_URL': 'https://api.example.com',\n *   },\n * });\n * ```\n */\nexport function createUmiBaseConfig(\n  options: UmiBaseConfigOptions,\n): UmiBaseConfig {\n  const {\n    routes,\n    locale,\n    publicPath = '/',\n    alias,\n    define,\n    proxy,\n    title,\n    useProLayout = false,\n    proLayoutSettings,\n    hashRouting = true,\n    codeSplitting = false,\n    headScripts,\n    favicons,\n    antdTheme,\n  } = options;\n\n  const config: UmiBaseConfig = {\n    // ===== 路由 =====\n    routes,\n    hash: hashRouting,\n    history: { type: hashRouting ? 'hash' : 'browser' },\n    publicPath,\n\n    // ===== 插件 =====\n    model: {},\n    initialState: {},\n    access: {},\n    request: {},\n\n    // ===== 构建优化 =====\n    mako: {},\n    fastRefresh: true,\n    esbuildMinifyIIFE: true,\n    ignoreMomentLocale: true,\n\n    // ===== moment -> dayjs =====\n    moment2dayjs: {\n      preset: 'antd',\n      plugins: ['duration'],\n    },\n\n    // ===== Ant Design =====\n    antd: {\n      import: false,\n      appConfig: {},\n      configProvider: {\n        theme: {\n          cssVar: true,\n          ...(antdTheme || {}),\n        },\n      },\n    },\n  };\n\n  // 国际化\n  if (locale === false) {\n    config.locale = false;\n  } else if (locale) {\n    config.locale = {\n      default: locale.default,\n      antd: locale.antd ?? true,\n      baseNavigator: locale.baseNavigator ?? false,\n    };\n  }\n\n  // ProLayout\n  if (useProLayout) {\n    config.layout = {\n      locale: locale !== false,\n      ...(proLayoutSettings || {}),\n    };\n  }\n\n  // 标题\n  if (title) {\n    config.title = title;\n  }\n\n  // 别名\n  if (alias) {\n    config.alias = alias;\n  }\n\n  // 环境变量\n  if (define) {\n    config.define = define;\n  }\n\n  // 代理\n  if (proxy) {\n    config.proxy = proxy;\n  }\n\n  // 代码分割\n  if (codeSplitting) {\n    config.codeSplitting = {\n      jsStrategy: 'granularChunks',\n    };\n  }\n\n  // Head Scripts\n  if (headScripts) {\n    config.headScripts = headScripts;\n  }\n\n  // Favicons\n  if (favicons) {\n    config.favicons = favicons;\n  }\n\n  return config;\n}\n"]}