@qlover/create-app 0.7.15 → 0.8.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/CHANGELOG.md +4 -0
- package/dist/configs/_common/.gitignore.template +6 -0
- package/dist/configs/_common/.prettierignore +17 -5
- package/dist/configs/_common/.vscode/settings.json +6 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/.env.template +1 -1
- package/dist/templates/next-app/README.en.md +0 -1
- package/dist/templates/next-app/README.md +0 -1
- package/dist/templates/next-app/config/Identifier/api.ts +5 -5
- package/dist/templates/next-app/config/Identifier/common/admint.table.ts +69 -0
- package/dist/templates/next-app/config/Identifier/common/common.ts +76 -0
- package/dist/templates/next-app/config/Identifier/common/index.ts +3 -0
- package/dist/templates/next-app/config/Identifier/{validator.ts → common/validators.ts} +5 -5
- package/dist/templates/next-app/config/Identifier/index.ts +2 -12
- package/dist/templates/next-app/config/Identifier/pages/index.ts +6 -0
- package/dist/templates/next-app/config/Identifier/pages/page.admin.home.ts +27 -0
- package/dist/templates/next-app/config/Identifier/pages/page.admin.locales.ts +266 -0
- package/dist/templates/next-app/config/Identifier/pages/page.admin.user.ts +293 -0
- package/dist/templates/{react-app/config/Identifier → next-app/config/Identifier/pages}/page.home.ts +15 -22
- package/dist/templates/next-app/config/Identifier/{page.login.ts → pages/page.login.ts} +28 -34
- package/dist/templates/next-app/config/Identifier/{page.register.ts → pages/page.register.ts} +30 -29
- package/dist/templates/next-app/config/adminNavs.ts +19 -0
- package/dist/templates/next-app/config/common.ts +22 -13
- package/dist/templates/next-app/config/i18n/HomeI18n.ts +5 -5
- package/dist/templates/next-app/config/i18n/admin18n.ts +61 -19
- package/dist/templates/next-app/config/i18n/i18nConfig.ts +2 -0
- package/dist/templates/next-app/config/i18n/i18nKeyScheam.ts +36 -0
- package/dist/templates/next-app/config/i18n/loginI18n.ts +22 -22
- package/dist/templates/next-app/config/i18n/register18n.ts +23 -24
- package/dist/templates/next-app/docs/en/index.md +0 -1
- package/dist/templates/next-app/docs/en/project-structure.md +0 -1
- package/dist/templates/next-app/docs/zh/index.md +0 -1
- package/dist/templates/next-app/docs/zh/project-structure.md +0 -1
- package/dist/templates/next-app/make/generateLocales.ts +19 -12
- package/dist/templates/next-app/migrations/schema/LocalesSchema.ts +15 -0
- package/dist/templates/next-app/migrations/sql/1694244000000.sql +11 -0
- package/dist/templates/next-app/package.json +7 -3
- package/dist/templates/next-app/public/locales/en.json +172 -207
- package/dist/templates/next-app/public/locales/zh.json +172 -207
- package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +153 -0
- package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +48 -50
- package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +2 -2
- package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +34 -0
- package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +40 -0
- package/dist/templates/next-app/src/app/api/admin/locales/route.ts +42 -0
- package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +32 -0
- package/dist/templates/next-app/src/app/api/locales/json/route.ts +44 -0
- package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +1 -13
- package/dist/templates/next-app/src/base/cases/Datetime.ts +18 -0
- package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +12 -6
- package/dist/templates/next-app/src/base/cases/ResourceState.ts +17 -0
- package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +25 -0
- package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +200 -0
- package/dist/templates/next-app/src/base/port/ZodBuilderInterface.ts +8 -0
- package/dist/templates/next-app/src/base/services/AdminLocalesService.ts +20 -0
- package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +26 -0
- package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +42 -0
- package/dist/templates/next-app/src/base/services/ResourceService.ts +122 -0
- package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +104 -0
- package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +38 -5
- package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +1 -1
- package/dist/templates/next-app/src/i18n/request.ts +30 -1
- package/dist/templates/next-app/src/server/PageParams.ts +2 -10
- package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +5 -0
- package/dist/templates/next-app/src/server/port/DBTableInterface.ts +2 -0
- package/dist/templates/next-app/src/server/port/LocalesRepositoryInterface.ts +43 -0
- package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +197 -0
- package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +122 -0
- package/dist/templates/next-app/src/server/sqlBridges/SupabaseBridge.ts +60 -11
- package/dist/templates/next-app/src/server/validators/ExtendedExecutorError.ts +6 -0
- package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +131 -0
- package/dist/templates/next-app/src/server/validators/LoginValidator.ts +2 -5
- package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +32 -16
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +2 -1
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +28 -29
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +2 -1
- package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +17 -3
- package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +5 -4
- package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +5 -4
- package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +3 -2
- package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +1 -1
- package/dist/templates/next-app/src/uikit/components/EditableCell.tsx +118 -0
- package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +5 -6
- package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +1 -1
- package/dist/templates/next-app/src/uikit/components/With.tsx +2 -2
- package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportButton.tsx +62 -0
- package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +28 -0
- package/dist/templates/next-app/src/uikit/components/localesImportButton/import.module.css +6 -0
- package/dist/templates/next-app/src/uikit/hook/useI18nInterface.ts +8 -14
- package/dist/templates/next-app/src/uikit/hook/useWarnTranslations.ts +25 -0
- package/dist/templates/react-app/.prettierignore +17 -0
- package/dist/templates/react-app/README.en.md +71 -54
- package/dist/templates/react-app/README.md +35 -18
- package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +14 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +2 -2
- package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +45 -0
- package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +34 -0
- package/dist/templates/react-app/__tests__/__mocks__/components/TestRouter.tsx +46 -0
- package/dist/templates/react-app/__tests__/__mocks__/components/index.ts +12 -0
- package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +1 -2
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +51 -0
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +69 -0
- package/dist/templates/react-app/__tests__/setup/index.ts +1 -51
- package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +51 -0
- package/dist/templates/react-app/__tests__/src/App.structure.test.tsx +115 -0
- package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +2 -2
- package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +1 -1
- package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +3 -5
- package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +13 -2
- package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +1 -1
- package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +1 -1
- package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +5 -5
- package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +1 -2
- package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +25 -15
- package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +29 -15
- package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +19 -9
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +153 -0
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +9 -7
- package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +4 -5
- package/dist/templates/react-app/__tests__/src/main.test.tsx +4 -4
- package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +68 -59
- package/dist/templates/react-app/config/IOCIdentifier.ts +8 -8
- package/dist/templates/react-app/config/Identifier/{common.error.ts → common/common.error.ts} +5 -5
- package/dist/templates/react-app/config/Identifier/{common.ts → common/common.ts} +9 -9
- package/dist/templates/react-app/config/Identifier/common/index.ts +2 -0
- package/dist/templates/react-app/config/Identifier/index.ts +1 -9
- package/dist/templates/react-app/config/Identifier/pages/index.ts +8 -0
- package/dist/templates/react-app/config/Identifier/{page.about.ts → pages/page.about.ts} +34 -26
- package/dist/templates/react-app/config/Identifier/{page.executor.ts → pages/page.executor.ts} +47 -39
- package/dist/templates/{next-app/config/Identifier → react-app/config/Identifier/pages}/page.home.ts +24 -23
- package/dist/templates/react-app/config/Identifier/pages/page.identifiter.ts +102 -0
- package/dist/templates/react-app/config/Identifier/{page.jsonStorage.ts → pages/page.jsonStorage.ts} +18 -11
- package/dist/templates/react-app/config/Identifier/{page.login.ts → pages/page.login.ts} +37 -27
- package/dist/templates/react-app/config/Identifier/{page.register.ts → pages/page.register.ts} +37 -25
- package/dist/templates/react-app/config/Identifier/{page.request.ts → pages/page.request.ts} +34 -44
- package/dist/templates/react-app/config/app.router.ts +66 -69
- package/dist/templates/react-app/config/i18n/PageI18nInterface.ts +51 -0
- package/dist/templates/react-app/config/i18n/aboutI18n.ts +42 -0
- package/dist/templates/react-app/config/i18n/executorI18n.ts +51 -0
- package/dist/templates/react-app/config/i18n/homeI18n.ts +24 -0
- package/dist/templates/react-app/config/i18n/i18nConfig.ts +30 -0
- package/dist/templates/react-app/config/i18n/identifiter18n.ts +30 -0
- package/dist/templates/react-app/config/i18n/jsonStorage18n.ts +27 -0
- package/dist/templates/react-app/config/i18n/login18n.ts +42 -0
- package/dist/templates/react-app/config/i18n/notFoundI18n.ts +34 -0
- package/dist/templates/react-app/config/i18n/register18n.ts +40 -0
- package/dist/templates/react-app/config/i18n/request18n.ts +41 -0
- package/dist/templates/react-app/config/theme.ts +14 -4
- package/dist/templates/react-app/docs/en/bootstrap.md +1670 -341
- package/dist/templates/react-app/docs/en/development-guide.md +1021 -345
- package/dist/templates/react-app/docs/en/env.md +1132 -278
- package/dist/templates/react-app/docs/en/i18n.md +858 -147
- package/dist/templates/react-app/docs/en/index.md +733 -104
- package/dist/templates/react-app/docs/en/ioc.md +1228 -287
- package/dist/templates/react-app/docs/en/playwright/e2e-tests.md +321 -0
- package/dist/templates/react-app/docs/en/playwright/index.md +19 -0
- package/dist/templates/react-app/docs/en/playwright/installation-summary.md +332 -0
- package/dist/templates/react-app/docs/en/playwright/overview.md +222 -0
- package/dist/templates/react-app/docs/en/playwright/quickstart.md +325 -0
- package/dist/templates/react-app/docs/en/playwright/reorganization-notes.md +340 -0
- package/dist/templates/react-app/docs/en/playwright/setup-complete.md +290 -0
- package/dist/templates/react-app/docs/en/playwright/testing-guide.md +565 -0
- package/dist/templates/react-app/docs/en/store.md +1194 -184
- package/dist/templates/react-app/docs/en/why-no-globals.md +797 -0
- package/dist/templates/react-app/docs/zh/bootstrap.md +1670 -341
- package/dist/templates/react-app/docs/zh/development-guide.md +1021 -345
- package/dist/templates/react-app/docs/zh/env.md +1132 -275
- package/dist/templates/react-app/docs/zh/i18n.md +858 -147
- package/dist/templates/react-app/docs/zh/index.md +717 -104
- package/dist/templates/react-app/docs/zh/ioc.md +1229 -287
- package/dist/templates/react-app/docs/zh/playwright/e2e-tests.md +321 -0
- package/dist/templates/react-app/docs/zh/playwright/index.md +19 -0
- package/dist/templates/react-app/docs/zh/playwright/installation-summary.md +332 -0
- package/dist/templates/react-app/docs/zh/playwright/overview.md +222 -0
- package/dist/templates/react-app/docs/zh/playwright/quickstart.md +325 -0
- package/dist/templates/react-app/docs/zh/playwright/reorganization-notes.md +340 -0
- package/dist/templates/react-app/docs/zh/playwright/setup-complete.md +290 -0
- package/dist/templates/react-app/docs/zh/playwright/testing-guide.md +565 -0
- package/dist/templates/react-app/docs/zh/store.md +1192 -184
- package/dist/templates/react-app/docs/zh/why-no-globals.md +797 -0
- package/dist/templates/react-app/e2e/App.spec.ts +319 -0
- package/dist/templates/react-app/e2e/fixtures/base.fixture.ts +40 -0
- package/dist/templates/react-app/e2e/main.spec.ts +20 -0
- package/dist/templates/react-app/e2e/utils/test-helpers.ts +19 -0
- package/dist/templates/react-app/eslint.config.mjs +247 -0
- package/dist/templates/react-app/makes/eslint-utils.mjs +195 -0
- package/dist/templates/react-app/makes/generateTs2LocalesOptions.ts +26 -0
- package/dist/templates/react-app/package.json +31 -3
- package/dist/templates/react-app/playwright.config.ts +79 -0
- package/dist/templates/react-app/public/locales/en/common.json +190 -179
- package/dist/templates/react-app/public/locales/zh/common.json +190 -179
- package/dist/templates/react-app/src/App.tsx +15 -42
- package/dist/templates/react-app/src/base/apis/AiApi.ts +5 -5
- package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +1 -1
- package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +1 -1
- package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +8 -8
- package/dist/templates/react-app/src/base/apis/feApi/FeApiType.ts +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +6 -6
- package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +12 -14
- package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +1 -1
- package/dist/templates/react-app/src/base/cases/DialogHandler.ts +5 -2
- package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +3 -3
- package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +3 -3
- package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +2 -2
- package/dist/templates/react-app/src/base/cases/RequestLogger.ts +4 -4
- package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +1 -1
- package/dist/templates/react-app/src/base/cases/ResourceState.ts +23 -0
- package/dist/templates/react-app/src/base/cases/RouterLoader.ts +4 -4
- package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +26 -0
- package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +2 -3
- package/dist/templates/react-app/src/base/port/I18nServiceInterface.ts +1 -1
- package/dist/templates/react-app/src/base/port/IOCInterface.ts +36 -0
- package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +2 -1
- package/dist/templates/react-app/src/base/port/ProcesserExecutorInterface.ts +1 -1
- package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +2 -2
- package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +9 -5
- package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +1 -1
- package/dist/templates/react-app/src/base/services/I18nService.ts +29 -29
- package/dist/templates/react-app/src/base/services/IdentifierService.ts +143 -0
- package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +3 -3
- package/dist/templates/react-app/src/base/services/RouteService.ts +27 -8
- package/dist/templates/react-app/src/base/services/UserService.ts +8 -8
- package/dist/templates/react-app/src/base/types/Page.ts +14 -2
- package/dist/templates/react-app/src/base/types/global.d.ts +1 -1
- package/dist/templates/react-app/src/core/IOC.ts +5 -46
- package/dist/templates/react-app/src/core/bootstraps/{BootstrapApp.ts → BootstrapClient.ts} +44 -17
- package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +14 -7
- package/dist/templates/react-app/src/core/bootstraps/IocIdentifierTest.ts +1 -1
- package/dist/templates/react-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
- package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +40 -0
- package/dist/templates/react-app/src/core/{IocRegisterImpl.ts → clientIoc/ClientIOCRegister.ts} +35 -24
- package/dist/templates/react-app/src/core/globals.ts +9 -9
- package/dist/templates/react-app/src/main.tsx +4 -4
- package/dist/templates/react-app/src/pages/404.tsx +6 -3
- package/dist/templates/react-app/src/pages/500.tsx +5 -2
- package/dist/templates/react-app/src/pages/NoRouteFound.tsx +5 -0
- package/dist/templates/react-app/src/pages/auth/Layout.tsx +9 -6
- package/dist/templates/react-app/src/pages/auth/LoginPage.tsx +46 -56
- package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +46 -58
- package/dist/templates/react-app/src/pages/base/AboutPage.tsx +35 -40
- package/dist/templates/react-app/src/pages/base/ExecutorPage.tsx +51 -51
- package/dist/templates/react-app/src/pages/base/HomePage.tsx +14 -15
- package/dist/templates/react-app/src/pages/base/IdentifierPage.tsx +70 -11
- package/dist/templates/react-app/src/pages/base/JSONStoragePage.tsx +24 -25
- package/dist/templates/react-app/src/pages/base/Layout.tsx +2 -2
- package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -2
- package/dist/templates/react-app/src/pages/base/RequestPage.tsx +41 -59
- package/dist/templates/react-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +85 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +99 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/_common/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +86 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/index.css +4 -3
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/_default.css +108 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/dark.css +67 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/pink.css +67 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/_default.css +34 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/dark.css +31 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/pink.css +36 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/_default.css +44 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/dark.css +43 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/pink.css +43 -0
- package/dist/templates/react-app/src/styles/css/page.css +4 -3
- package/dist/templates/react-app/src/styles/css/themes/_default.css +1 -0
- package/dist/templates/react-app/src/styles/css/themes/dark.css +1 -0
- package/dist/templates/react-app/src/styles/css/themes/pink.css +1 -0
- package/dist/templates/react-app/src/styles/css/zIndex.css +1 -1
- package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +3 -3
- package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +2 -2
- package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +1 -1
- package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +3 -3
- package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +35 -0
- package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +15 -11
- package/dist/templates/react-app/src/uikit/components/BaseRouteProvider.tsx +14 -11
- package/dist/templates/react-app/src/uikit/components/BaseRouteSeo.tsx +18 -0
- package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +13 -0
- package/dist/templates/react-app/src/uikit/components/ClientSeo.tsx +62 -0
- package/dist/templates/react-app/src/uikit/components/ComboProvider.tsx +38 -0
- package/dist/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +48 -27
- package/dist/templates/react-app/src/uikit/components/Loading.tsx +4 -2
- package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +4 -5
- package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +34 -11
- package/dist/templates/react-app/src/uikit/components/ProcessExecutorProvider.tsx +9 -5
- package/dist/templates/react-app/src/uikit/components/RouterRenderComponent.tsx +6 -3
- package/dist/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +97 -40
- package/dist/templates/react-app/src/uikit/components/UserAuthProvider.tsx +5 -5
- package/dist/templates/react-app/src/uikit/components/With.tsx +17 -0
- package/dist/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +17 -11
- package/dist/templates/react-app/src/uikit/contexts/IOCContext.ts +13 -0
- package/dist/templates/react-app/src/uikit/hooks/useAppTranslation.ts +26 -0
- package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +8 -11
- package/dist/templates/react-app/src/uikit/hooks/useI18nInterface.ts +25 -0
- package/dist/templates/react-app/src/uikit/hooks/useIOC.ts +35 -0
- package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +3 -3
- package/dist/templates/react-app/src/uikit/hooks/useStrictEffect.ts +0 -1
- package/dist/templates/react-app/tsconfig.e2e.json +21 -0
- package/dist/templates/react-app/tsconfig.json +8 -1
- package/dist/templates/react-app/tsconfig.node.json +1 -1
- package/dist/templates/react-app/tsconfig.test.json +3 -1
- package/dist/templates/react-app/vite.config.ts +50 -34
- package/package.json +2 -1
- package/dist/configs/react-app/eslint.config.js +0 -94
- package/dist/templates/next-app/config/Identifier/common.error.ts +0 -41
- package/dist/templates/next-app/config/Identifier/common.ts +0 -69
- package/dist/templates/next-app/config/Identifier/page.about.ts +0 -181
- package/dist/templates/next-app/config/Identifier/page.admin.ts +0 -48
- package/dist/templates/next-app/config/Identifier/page.executor.ts +0 -272
- package/dist/templates/next-app/config/Identifier/page.identifiter.ts +0 -39
- package/dist/templates/next-app/config/Identifier/page.jsonStorage.ts +0 -72
- package/dist/templates/next-app/config/Identifier/page.request.ts +0 -182
- package/dist/templates/next-app/src/base/cases/ChatAction.ts +0 -21
- package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +0 -36
- package/dist/templates/next-app/src/base/cases/RequestState.ts +0 -20
- package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +0 -85
- package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +0 -7
- package/dist/templates/next-app/src/base/services/AdminUserService.ts +0 -45
- package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +0 -17
- package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +0 -30
- package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +0 -65
- package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +0 -59
- package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +0 -28
- package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +0 -19
- package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +0 -17
- package/dist/templates/next-app/src/uikit/hook/useStore.ts +0 -15
- package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +0 -13
- package/dist/templates/react-app/__tests__/src/App.test.tsx +0 -139
- package/dist/templates/react-app/config/Identifier/page.identifiter.ts +0 -39
- package/dist/templates/react-app/config/i18n.ts +0 -15
- package/dist/templates/react-app/docs/en/project-structure.md +0 -434
- package/dist/templates/react-app/docs/zh/project-structure.md +0 -434
- package/dist/templates/react-app/src/base/cases/RequestState.ts +0 -20
- package/dist/templates/react-app/src/base/port/AsyncStateInterface.ts +0 -7
- package/dist/templates/react-app/src/uikit/hooks/useDocumentTitle.ts +0 -15
- package/dist/templates/react-app/src/uikit/hooks/useStore.ts +0 -15
|
@@ -1,120 +1,328 @@
|
|
|
1
|
-
# Environment
|
|
1
|
+
# Environment Variable Management
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 📋 Table of Contents
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- [What is Environment Variable Management](#-what-is-environment-variable-management)
|
|
6
|
+
- [Why Environment Variables are Needed](#-why-environment-variables-are-needed)
|
|
7
|
+
- [How It Works](#-how-it-works)
|
|
8
|
+
- [Implementation in the Project](#-implementation-in-the-project)
|
|
9
|
+
- [Multi-Environment Configuration](#-multi-environment-configuration)
|
|
10
|
+
- [Environment Variable Injection](#-environment-variable-injection)
|
|
11
|
+
- [AppConfig Usage](#-appconfig-usage)
|
|
12
|
+
- [Advanced Usage](#-advanced-usage)
|
|
13
|
+
- [Testing Configuration](#-testing-configuration)
|
|
14
|
+
- [Best Practices](#-best-practices)
|
|
15
|
+
- [FAQ](#-faq)
|
|
6
16
|
|
|
7
|
-
|
|
17
|
+
---
|
|
8
18
|
|
|
9
|
-
##
|
|
19
|
+
## 🎯 What is Environment Variable Management
|
|
10
20
|
|
|
11
|
-
|
|
21
|
+
Environment variable management is a core component of the Bootstrap architecture, responsible for using different configurations in different environments (development, testing, production) and passing configurations to the application through **automatic injection**.
|
|
22
|
+
|
|
23
|
+
### Core Workflow
|
|
12
24
|
|
|
13
25
|
```
|
|
14
|
-
App
|
|
26
|
+
Start App → Vite loads .env files → Bootstrap initializes → Inject env vars to AppConfig → IOC container → App uses them
|
|
15
27
|
```
|
|
16
28
|
|
|
17
|
-
###
|
|
29
|
+
### Key Concepts
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
┌─────────────────────────────────────────────────┐
|
|
33
|
+
│ Environment Variable Management System │
|
|
34
|
+
│ ┌───────────────────────────────────────────┐ │
|
|
35
|
+
│ │ 1. Vite --mode selects environment │ │
|
|
36
|
+
│ │ 2. Load corresponding .env file │ │
|
|
37
|
+
│ │ 3. envConfig plugin preprocessing │ │
|
|
38
|
+
│ │ 4. Bootstrap injects to AppConfig │ │
|
|
39
|
+
│ │ 5. Register to IOC container │ │
|
|
40
|
+
│ │ 6. App gets config through IOC │ │
|
|
41
|
+
│ └───────────────────────────────────────────┘ │
|
|
42
|
+
└─────────────────────────────────────────────────┘
|
|
43
|
+
```
|
|
18
44
|
|
|
19
|
-
|
|
20
|
-
- **@qlover/corekit-bridge/vite-env-config**: Vite environment variables configuration plugin
|
|
21
|
-
- **dotenv**: .env file parser
|
|
22
|
-
- **Vite**: Frontend build tool
|
|
45
|
+
---
|
|
23
46
|
|
|
24
|
-
|
|
47
|
+
## 🤔 Why Environment Variables are Needed
|
|
25
48
|
|
|
49
|
+
### Problem: Pain Points of Hard-coded Configuration
|
|
50
|
+
|
|
51
|
+
#### ❌ Traditional Approach: Configuration Scattered Everywhere
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// ❌ Problem 1: API address hard-coded in code
|
|
55
|
+
function fetchUserInfo() {
|
|
56
|
+
// 😰 Dev and prod API addresses are different, need to change code every time
|
|
57
|
+
return fetch('http://localhost:3000/api/user');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ❌ Problem 2: Configuration scattered across files
|
|
61
|
+
function saveToken(token: string) {
|
|
62
|
+
// 😰 Storage key hard-coded, difficult to manage uniformly
|
|
63
|
+
localStorage.setItem('user_token', token);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ❌ Problem 3: Sensitive information directly in code
|
|
67
|
+
function callAI(prompt: string) {
|
|
68
|
+
// 😰 API Key directly exposed in code, security risk
|
|
69
|
+
return fetch('https://api.openai.com/v1/chat', {
|
|
70
|
+
headers: {
|
|
71
|
+
Authorization: 'Bearer sk-xxxxxxxxxxxx' // 😰 Dangerous!
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ❌ Problem 4: Difficult to switch environments
|
|
77
|
+
if (window.location.host === 'localhost:3000') {
|
|
78
|
+
// 😰 Need to manually determine environment
|
|
79
|
+
apiUrl = 'http://localhost:3000/api';
|
|
80
|
+
} else if (window.location.host === 'staging.example.com') {
|
|
81
|
+
apiUrl = 'https://api.staging.example.com';
|
|
82
|
+
} else {
|
|
83
|
+
apiUrl = 'https://api.production.com';
|
|
84
|
+
}
|
|
26
85
|
```
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
86
|
+
|
|
87
|
+
**Problem Summary:**
|
|
88
|
+
|
|
89
|
+
- 😰 **Scattered configuration** - Config spread across multiple files, hard to manage
|
|
90
|
+
- 😰 **Difficult to switch environments** - Need to manually modify code or use complex conditions
|
|
91
|
+
- 😰 **Security risks** - Sensitive info may be committed to code repository
|
|
92
|
+
- 😰 **Hard to test** - Need to mock many hard-coded values when testing
|
|
93
|
+
- 😰 **Team collaboration difficulties** - Each developer may have different local configs
|
|
94
|
+
|
|
95
|
+
#### ✅ Solution: Environment Variables + AppConfig
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// ✅ 1. Environment variable files (different config for different environments)
|
|
99
|
+
// .env.localhost
|
|
100
|
+
VITE_API_BASE_URL=http://localhost:3000/api
|
|
101
|
+
VITE_AI_API_TOKEN=sk-dev-xxxxx
|
|
102
|
+
|
|
103
|
+
// .env.staging
|
|
104
|
+
VITE_API_BASE_URL=https://api.staging.example.com
|
|
105
|
+
VITE_AI_API_TOKEN=sk-staging-xxxxx
|
|
106
|
+
|
|
107
|
+
// .env.production
|
|
108
|
+
VITE_API_BASE_URL=https://api.production.com
|
|
109
|
+
VITE_AI_API_TOKEN=sk-prod-xxxxx
|
|
110
|
+
|
|
111
|
+
// ✅ 2. AppConfig manages configuration uniformly
|
|
112
|
+
export class AppConfig {
|
|
113
|
+
readonly feApiBaseUrl = ''; // ← Auto-injected
|
|
114
|
+
readonly aiApiToken = ''; // ← Auto-injected
|
|
115
|
+
readonly userTokenStorageKey = '__fe_user_token__';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ✅ 3. Get config through IOC container
|
|
119
|
+
@injectable()
|
|
120
|
+
export class UserService {
|
|
121
|
+
constructor(
|
|
122
|
+
@inject(IOCIdentifier.AppConfig) private config: AppConfig
|
|
123
|
+
) {}
|
|
124
|
+
|
|
125
|
+
async fetchUserInfo() {
|
|
126
|
+
// ✅ Get API address from config, automatically adapts to environment
|
|
127
|
+
return fetch(`${this.config.feApiBaseUrl}/user`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ✅ 4. Switch environments at runtime (no code changes needed)
|
|
132
|
+
npm run dev # localhost environment
|
|
133
|
+
npm run dev:staging # staging environment
|
|
134
|
+
npm run build:production # production environment
|
|
38
135
|
```
|
|
39
136
|
|
|
40
|
-
|
|
137
|
+
**Advantages:**
|
|
138
|
+
|
|
139
|
+
- ✅ **Centralized management** - All config managed uniformly in AppConfig
|
|
140
|
+
- ✅ **Easy environment switching** - Just switch run command
|
|
141
|
+
- ✅ **Secure** - Sensitive info managed through `.env.local`, not committed to repo
|
|
142
|
+
- ✅ **Easy to test** - Can easily mock AppConfig when testing
|
|
143
|
+
- ✅ **Team-friendly** - Each developer can have their own `.env.local`
|
|
144
|
+
|
|
145
|
+
---
|
|
41
146
|
|
|
42
|
-
|
|
147
|
+
## ⚙️ How It Works
|
|
43
148
|
|
|
44
|
-
|
|
149
|
+
### Environment Variable Loading Flow
|
|
45
150
|
|
|
46
151
|
```
|
|
47
|
-
|
|
152
|
+
┌────────────────────────────────────────────────────────────┐
|
|
153
|
+
│ 1. package.json: Define startup commands │
|
|
154
|
+
│ npm run dev → vite --mode localhost │
|
|
155
|
+
│ npm run dev:staging → vite --mode staging │
|
|
156
|
+
│ npm run build:production → vite build --mode production │
|
|
157
|
+
└──────────────────┬─────────────────────────────────────────┘
|
|
158
|
+
↓
|
|
159
|
+
┌────────────────────────────────────────────────────────────┐
|
|
160
|
+
│ 2. Vite: Load corresponding .env file based on --mode │
|
|
161
|
+
│ --mode localhost → .env.localhost │
|
|
162
|
+
│ --mode staging → .env.staging │
|
|
163
|
+
│ --mode production → .env.production │
|
|
164
|
+
│ │
|
|
165
|
+
│ Loading priority: .env.local > .env.[mode] > .env │
|
|
166
|
+
└──────────────────┬─────────────────────────────────────────┘
|
|
167
|
+
↓
|
|
168
|
+
┌────────────────────────────────────────────────────────────┐
|
|
169
|
+
│ 3. vite.config.ts: envConfig plugin preprocessing │
|
|
170
|
+
│ - Inject APP_NAME, APP_VERSION, etc. │
|
|
171
|
+
│ - Set environment variable prefix (VITE_) │
|
|
172
|
+
└──────────────────┬─────────────────────────────────────────┘
|
|
173
|
+
↓
|
|
174
|
+
┌────────────────────────────────────────────────────────────┐
|
|
175
|
+
│ 4. BootstrapClient: Initialize Bootstrap │
|
|
176
|
+
│ envOptions: { │
|
|
177
|
+
│ target: appConfig, // Injection target │
|
|
178
|
+
│ source: import.meta.env, // Env var source │
|
|
179
|
+
│ prefix: 'VITE_', // Prefix │
|
|
180
|
+
│ blackList: ['env', 'userNodeEnv'] // Blacklist │
|
|
181
|
+
│ } │
|
|
182
|
+
└──────────────────┬─────────────────────────────────────────┘
|
|
183
|
+
↓
|
|
184
|
+
┌────────────────────────────────────────────────────────────┐
|
|
185
|
+
│ 5. InjectEnv plugin: Auto-inject env vars to AppConfig │
|
|
186
|
+
│ - VITE_APP_NAME → appConfig.appName │
|
|
187
|
+
│ - VITE_FE_API_BASE_URL → appConfig.feApiBaseUrl │
|
|
188
|
+
│ - VITE_AI_API_TOKEN → appConfig.aiApiToken │
|
|
189
|
+
└──────────────────┬─────────────────────────────────────────┘
|
|
190
|
+
↓
|
|
191
|
+
┌────────────────────────────────────────────────────────────┐
|
|
192
|
+
│ 6. IOC container: Register AppConfig │
|
|
193
|
+
│ container.bind(IOCIdentifier.AppConfig).toConstantValue │
|
|
194
|
+
└──────────────────┬─────────────────────────────────────────┘
|
|
195
|
+
↓
|
|
196
|
+
┌────────────────────────────────────────────────────────────┐
|
|
197
|
+
│ 7. App usage: Get config through IOC │
|
|
198
|
+
│ const config = useIOC('AppConfig'); │
|
|
199
|
+
│ console.log(config.feApiBaseUrl); │
|
|
200
|
+
└────────────────────────────────────────────────────────────┘
|
|
48
201
|
```
|
|
49
202
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
# Development mode
|
|
54
|
-
vite dev --mode development
|
|
55
|
-
# Loading order: .env.local > .env.development > .env
|
|
203
|
+
### Naming Conversion Rules
|
|
56
204
|
|
|
57
|
-
|
|
58
|
-
vite build --mode production
|
|
59
|
-
# Loading order: .env.local > .env.production > .env
|
|
205
|
+
Environment variable names are automatically converted to AppConfig property names:
|
|
60
206
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
207
|
+
```
|
|
208
|
+
VITE_APP_NAME → appName
|
|
209
|
+
VITE_FE_API_BASE_URL → feApiBaseUrl
|
|
210
|
+
VITE_AI_API_TOKEN → aiApiToken
|
|
211
|
+
VITE_USER_TOKEN_STORAGE_KEY → userTokenStorageKey
|
|
64
212
|
```
|
|
65
213
|
|
|
66
|
-
|
|
214
|
+
**Conversion Rules:**
|
|
67
215
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
VITE_API_BASE_URL=http://api.example.com
|
|
72
|
-
VITE_USER_TOKEN_KEY=user_token
|
|
216
|
+
1. Remove prefix (`VITE_`)
|
|
217
|
+
2. Convert underscore-separated to camelCase
|
|
218
|
+
3. Match property name in AppConfig
|
|
73
219
|
|
|
74
|
-
|
|
75
|
-
VITE_API_BASE_URL=http://localhost:3000/api
|
|
76
|
-
VITE_DEBUG=true
|
|
220
|
+
---
|
|
77
221
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
222
|
+
## 🛠️ Implementation in the Project
|
|
223
|
+
|
|
224
|
+
### 1. Define Startup Commands
|
|
81
225
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
226
|
+
```json
|
|
227
|
+
// package.json
|
|
228
|
+
{
|
|
229
|
+
"scripts": {
|
|
230
|
+
"dev": "vite --mode localhost",
|
|
231
|
+
"dev:staging": "vite --mode staging",
|
|
232
|
+
"dev:prod": "vite --mode production",
|
|
233
|
+
"build": "npm run lint && vite build",
|
|
234
|
+
"build:staging": "npm run lint && vite build --mode staging",
|
|
235
|
+
"build:production": "npm run lint && vite build --mode production"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
85
238
|
```
|
|
86
239
|
|
|
87
|
-
|
|
240
|
+
**Explanation:**
|
|
241
|
+
|
|
242
|
+
- `--mode` parameter determines which `.env` file to load
|
|
243
|
+
- Development environment: use `localhost` mode
|
|
244
|
+
- Staging environment: use `staging` mode
|
|
245
|
+
- Production environment: use `production` mode
|
|
88
246
|
|
|
89
|
-
###
|
|
247
|
+
### 2. Configure Vite
|
|
90
248
|
|
|
91
|
-
```
|
|
249
|
+
```typescript
|
|
92
250
|
// vite.config.ts
|
|
93
|
-
import
|
|
251
|
+
import { defineConfig } from 'vitest/config';
|
|
252
|
+
import { envPrefix } from './config/common';
|
|
253
|
+
import { name, version } from './package.json';
|
|
254
|
+
import envConfig from '@qlover/corekit-bridge/build/vite-env-config';
|
|
94
255
|
|
|
95
256
|
export default defineConfig({
|
|
96
257
|
plugins: [
|
|
258
|
+
// ✅ envConfig plugin: preprocess environment variables
|
|
97
259
|
envConfig({
|
|
98
|
-
envPops: true, // Enable
|
|
99
|
-
envPrefix
|
|
260
|
+
envPops: true, // Enable env var processing
|
|
261
|
+
envPrefix, // Env var prefix: 'VITE_'
|
|
100
262
|
records: [
|
|
101
|
-
['APP_NAME', name], // Inject
|
|
102
|
-
['APP_VERSION', version] // Inject
|
|
263
|
+
['APP_NAME', name], // Inject app name
|
|
264
|
+
['APP_VERSION', version] // Inject app version
|
|
103
265
|
]
|
|
104
266
|
})
|
|
267
|
+
// ... other plugins
|
|
105
268
|
],
|
|
106
|
-
envPrefix:
|
|
269
|
+
envPrefix: envPrefix, // Vite env var prefix
|
|
107
270
|
server: {
|
|
108
271
|
port: Number(process.env.VITE_SERVER_PORT || 3200)
|
|
109
272
|
}
|
|
110
273
|
});
|
|
111
274
|
```
|
|
112
275
|
|
|
113
|
-
|
|
276
|
+
**Key Configuration:**
|
|
277
|
+
|
|
278
|
+
- `envConfig` plugin handles preprocessing of environment variables
|
|
279
|
+
- `records` can inject additional variables (like info from package.json)
|
|
280
|
+
- `envPrefix` set to `'VITE_'`, only variables with this prefix are exposed to client
|
|
281
|
+
|
|
282
|
+
### 3. Define Common Configuration
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// config/common.ts
|
|
286
|
+
export const envPrefix = 'VITE_';
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Bootstrap env var injection blacklist
|
|
290
|
+
* These properties won't be injected from env vars
|
|
291
|
+
*/
|
|
292
|
+
export const envBlackList = ['env', 'userNodeEnv'];
|
|
293
|
+
|
|
294
|
+
export const browserGlobalsName = 'feGlobals';
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 4. Define AppConfig
|
|
114
298
|
|
|
115
|
-
```
|
|
299
|
+
```typescript
|
|
116
300
|
// src/base/cases/AppConfig.ts
|
|
301
|
+
import type { EnvConfigInterface } from '@qlover/corekit-bridge';
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Application configuration class
|
|
305
|
+
*
|
|
306
|
+
* All properties will be automatically injected with corresponding env var values during Bootstrap initialization
|
|
307
|
+
*
|
|
308
|
+
* Environment variable naming rules:
|
|
309
|
+
* - Property names are converted to UPPER_SNAKE_CASE
|
|
310
|
+
* - Add VITE_ prefix
|
|
311
|
+
*
|
|
312
|
+
* Examples:
|
|
313
|
+
* - appName → VITE_APP_NAME
|
|
314
|
+
* - feApiBaseUrl → VITE_FE_API_BASE_URL
|
|
315
|
+
* - aiApiToken → VITE_AI_API_TOKEN
|
|
316
|
+
*/
|
|
117
317
|
export class AppConfig implements EnvConfigInterface {
|
|
318
|
+
constructor(
|
|
319
|
+
/**
|
|
320
|
+
* Current environment mode
|
|
321
|
+
* @description Automatically set from Vite's MODE
|
|
322
|
+
*/
|
|
323
|
+
readonly env: string = import.meta.env.VITE_USER_NODE_ENV
|
|
324
|
+
) {}
|
|
325
|
+
|
|
118
326
|
/**
|
|
119
327
|
* Application name
|
|
120
328
|
* @description Injected from VITE_APP_NAME environment variable
|
|
@@ -127,18 +335,30 @@ export class AppConfig implements EnvConfigInterface {
|
|
|
127
335
|
*/
|
|
128
336
|
readonly appVersion = '';
|
|
129
337
|
|
|
130
|
-
/**
|
|
131
|
-
* Current environment mode
|
|
132
|
-
* @description Retrieved from Vite's mode
|
|
133
|
-
*/
|
|
134
|
-
readonly env: string = import.meta.env.MODE;
|
|
135
|
-
|
|
136
338
|
/**
|
|
137
339
|
* User token storage key
|
|
138
340
|
* @description Injected from VITE_USER_TOKEN_STORAGE_KEY environment variable
|
|
139
341
|
*/
|
|
140
342
|
readonly userTokenStorageKey = '__fe_user_token__';
|
|
141
343
|
|
|
344
|
+
/**
|
|
345
|
+
* User info storage key
|
|
346
|
+
* @description Injected from VITE_USER_INFO_STORAGE_KEY environment variable
|
|
347
|
+
*/
|
|
348
|
+
readonly userInfoStorageKey = '__fe_user_info__';
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Frontend API base URL
|
|
352
|
+
* @description Injected from VITE_FE_API_BASE_URL environment variable
|
|
353
|
+
*/
|
|
354
|
+
readonly feApiBaseUrl = '';
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* User API base URL
|
|
358
|
+
* @description Injected from VITE_USER_API_BASE_URL environment variable
|
|
359
|
+
*/
|
|
360
|
+
readonly userApiBaseUrl = '';
|
|
361
|
+
|
|
142
362
|
/**
|
|
143
363
|
* AI API base URL
|
|
144
364
|
* @description Injected from VITE_AI_API_BASE_URL environment variable
|
|
@@ -151,332 +371,966 @@ export class AppConfig implements EnvConfigInterface {
|
|
|
151
371
|
*/
|
|
152
372
|
readonly aiApiToken = '';
|
|
153
373
|
|
|
154
|
-
|
|
374
|
+
/**
|
|
375
|
+
* AI API token prefix
|
|
376
|
+
* @description Injected from VITE_AI_API_TOKEN_PREFIX environment variable
|
|
377
|
+
*/
|
|
378
|
+
readonly aiApiTokenPrefix = 'Bearer';
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Whether AI API token is required
|
|
382
|
+
* @description Injected from VITE_AI_API_REQUIRE_TOKEN environment variable
|
|
383
|
+
*/
|
|
384
|
+
readonly aiApiRequireToken = true;
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Default login username
|
|
388
|
+
* @description Injected from VITE_LOGIN_USER environment variable
|
|
389
|
+
*/
|
|
390
|
+
readonly loginUser = '';
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Default login password
|
|
394
|
+
* @description Injected from VITE_LOGIN_PASSWORD environment variable
|
|
395
|
+
*/
|
|
396
|
+
readonly loginPassword = '';
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* OpenAI available models list
|
|
400
|
+
*/
|
|
401
|
+
readonly openAiModels = [
|
|
402
|
+
'gpt-4o-mini',
|
|
403
|
+
'gpt-3.5-turbo',
|
|
404
|
+
'gpt-3.5-turbo-2',
|
|
405
|
+
'gpt-4',
|
|
406
|
+
'gpt-4-32k'
|
|
407
|
+
];
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* OpenAI API base URL
|
|
411
|
+
* @description Injected from VITE_OPEN_AI_BASE_URL environment variable
|
|
412
|
+
*/
|
|
413
|
+
readonly openAiBaseUrl = '';
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* OpenAI API token
|
|
417
|
+
* @description Injected from VITE_OPEN_AI_TOKEN environment variable
|
|
418
|
+
*/
|
|
419
|
+
readonly openAiToken = '';
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* OpenAI API token prefix
|
|
423
|
+
* @description Injected from VITE_OPEN_AI_TOKEN_PREFIX environment variable
|
|
424
|
+
*/
|
|
425
|
+
readonly openAiTokenPrefix = '';
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Whether OpenAI API token is required
|
|
429
|
+
* @description Injected from VITE_OPEN_AI_REQUIRE_TOKEN environment variable
|
|
430
|
+
*/
|
|
431
|
+
readonly openAiRequireToken = true;
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Project startup URL
|
|
435
|
+
* @description Injected from Bootstrap's BOOT_HREF
|
|
436
|
+
*/
|
|
437
|
+
readonly bootHref = '';
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Whether is production environment
|
|
441
|
+
*/
|
|
442
|
+
get isProduction(): boolean {
|
|
443
|
+
return this.env === 'production';
|
|
444
|
+
}
|
|
155
445
|
}
|
|
156
446
|
```
|
|
157
447
|
|
|
158
|
-
###
|
|
448
|
+
### 5. Bootstrap Configuration
|
|
159
449
|
|
|
160
|
-
```
|
|
161
|
-
// src/core/bootstraps/
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
450
|
+
```typescript
|
|
451
|
+
// src/core/bootstraps/BootstrapClient.ts
|
|
452
|
+
import { envBlackList, envPrefix, browserGlobalsName } from '@config/common';
|
|
453
|
+
import { Bootstrap } from '@qlover/corekit-bridge';
|
|
454
|
+
import * as globals from '../globals';
|
|
455
|
+
|
|
456
|
+
export class BootstrapClient {
|
|
457
|
+
static async main(args: BootstrapClientArgs) {
|
|
458
|
+
const { root, bootHref, ioc, iocRegister } = args;
|
|
459
|
+
const { logger, appConfig } = globals;
|
|
460
|
+
|
|
461
|
+
// Create IOC container
|
|
462
|
+
const IOC = ioc.create({
|
|
463
|
+
pathname: bootHref,
|
|
464
|
+
appConfig: appConfig
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Create Bootstrap instance
|
|
468
|
+
const bootstrap = new Bootstrap({
|
|
469
|
+
root,
|
|
470
|
+
logger,
|
|
471
|
+
ioc: {
|
|
472
|
+
manager: IOC,
|
|
473
|
+
register: iocRegister
|
|
474
|
+
},
|
|
475
|
+
// ✅ Environment variable injection configuration
|
|
476
|
+
envOptions: {
|
|
477
|
+
target: appConfig, // Injection target: AppConfig instance
|
|
478
|
+
source: Object.assign({}, import.meta.env, {
|
|
479
|
+
[envPrefix + 'BOOT_HREF']: bootHref // Additionally inject startup URL
|
|
480
|
+
}),
|
|
481
|
+
prefix: envPrefix, // Env var prefix: 'VITE_'
|
|
482
|
+
blackList: envBlackList // Blacklist: ['env', 'userNodeEnv']
|
|
483
|
+
},
|
|
484
|
+
// Global variable encapsulation configuration
|
|
485
|
+
globalOptions: {
|
|
486
|
+
sources: globals,
|
|
487
|
+
target: browserGlobalsName
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
logger.info('bootstrap start...');
|
|
493
|
+
|
|
494
|
+
// ✅ Initialize Bootstrap (env var injection executes here)
|
|
495
|
+
await bootstrap.initialize();
|
|
496
|
+
|
|
497
|
+
// Register business plugins
|
|
498
|
+
const bootstrapsRegistry = new BootstrapsRegistry(IOC);
|
|
499
|
+
await bootstrap.use(bootstrapsRegistry.register()).start();
|
|
500
|
+
|
|
501
|
+
logger.info('bootstrap completed successfully');
|
|
502
|
+
} catch (error) {
|
|
503
|
+
logger.error(`${appConfig.appName} startup error:`, error);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return args;
|
|
177
507
|
}
|
|
178
|
-
}
|
|
508
|
+
}
|
|
179
509
|
```
|
|
180
510
|
|
|
181
|
-
|
|
511
|
+
**Key Flow:**
|
|
182
512
|
|
|
183
|
-
|
|
513
|
+
1. `appConfig` is an AppConfig instance with default values for all properties
|
|
514
|
+
2. `bootstrap.initialize()` executes the `InjectEnv` plugin
|
|
515
|
+
3. `InjectEnv` plugin iterates through `appConfig` properties, looking for corresponding env vars in `import.meta.env`
|
|
516
|
+
4. If an env var is found and value is not empty, it overwrites the default value
|
|
517
|
+
5. Finally `appConfig` is registered to the IOC container
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## 🌍 Multi-Environment Configuration
|
|
522
|
+
|
|
523
|
+
### Environment File Structure
|
|
524
|
+
|
|
525
|
+
```
|
|
526
|
+
Project root/
|
|
527
|
+
├── .env # Default config (shared across all environments)
|
|
528
|
+
├── .env.localhost # Local development environment
|
|
529
|
+
├── .env.staging # Staging environment
|
|
530
|
+
├── .env.production # Production environment
|
|
531
|
+
├── .env.local # Local override config (not committed to git)
|
|
532
|
+
└── .env.template # Env var template (committed to git)
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Loading Priority
|
|
536
|
+
|
|
537
|
+
```
|
|
538
|
+
.env.local > .env.[mode] > .env
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
**Example:**
|
|
184
542
|
|
|
185
543
|
```bash
|
|
186
|
-
#
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
544
|
+
# Run: npm run dev (vite --mode localhost)
|
|
545
|
+
# Loading order:
|
|
546
|
+
# 1. .env.local # Highest priority
|
|
547
|
+
# 2. .env.localhost # Second
|
|
548
|
+
# 3. .env # Last
|
|
549
|
+
|
|
550
|
+
# Run: npm run build:production (vite build --mode production)
|
|
551
|
+
# Loading order:
|
|
552
|
+
# 1. .env.local
|
|
553
|
+
# 2. .env.production
|
|
554
|
+
# 3. .env
|
|
194
555
|
```
|
|
195
556
|
|
|
557
|
+
### Example 1: Default Configuration
|
|
558
|
+
|
|
196
559
|
```bash
|
|
197
|
-
# .env
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
560
|
+
# .env
|
|
561
|
+
# Configuration shared across all environments
|
|
562
|
+
|
|
563
|
+
VITE_APP_NAME=MyApp
|
|
564
|
+
VITE_USER_TOKEN_STORAGE_KEY=__fe_user_token__
|
|
565
|
+
VITE_USER_INFO_STORAGE_KEY=__fe_user_info__
|
|
566
|
+
VITE_AI_API_TOKEN_PREFIX=Bearer
|
|
567
|
+
VITE_AI_API_REQUIRE_TOKEN=true
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Example 2: Local Development Environment
|
|
571
|
+
|
|
572
|
+
```bash
|
|
573
|
+
# .env.localhost
|
|
574
|
+
# Local development environment config
|
|
575
|
+
|
|
576
|
+
# API configuration
|
|
577
|
+
VITE_FE_API_BASE_URL=http://localhost:3000/api
|
|
578
|
+
VITE_USER_API_BASE_URL=http://localhost:3000/api/user
|
|
579
|
+
VITE_AI_API_BASE_URL=http://localhost:3001/v1
|
|
580
|
+
|
|
581
|
+
# AI configuration (dev environment may use local Mock)
|
|
582
|
+
VITE_AI_API_TOKEN=sk-dev-xxxxx
|
|
583
|
+
VITE_AI_API_REQUIRE_TOKEN=false
|
|
584
|
+
|
|
585
|
+
# Debug configuration
|
|
201
586
|
VITE_LOG_LEVEL=debug
|
|
587
|
+
VITE_DEBUG=true
|
|
588
|
+
|
|
589
|
+
# Default login info (convenient for development)
|
|
590
|
+
VITE_LOGIN_USER=admin
|
|
591
|
+
VITE_LOGIN_PASSWORD=admin123
|
|
202
592
|
```
|
|
203
593
|
|
|
204
|
-
###
|
|
594
|
+
### Example 3: Staging Environment
|
|
205
595
|
|
|
206
596
|
```bash
|
|
207
597
|
# .env.staging
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
598
|
+
# Staging environment config
|
|
599
|
+
|
|
600
|
+
# API configuration
|
|
601
|
+
VITE_FE_API_BASE_URL=https://api.staging.example.com
|
|
602
|
+
VITE_USER_API_BASE_URL=https://api.staging.example.com/user
|
|
603
|
+
VITE_AI_API_BASE_URL=https://api.staging.example.com/ai
|
|
604
|
+
|
|
605
|
+
# AI configuration
|
|
606
|
+
VITE_AI_API_TOKEN=sk-staging-xxxxx
|
|
607
|
+
VITE_AI_API_REQUIRE_TOKEN=true
|
|
608
|
+
|
|
609
|
+
# Debug configuration
|
|
211
610
|
VITE_LOG_LEVEL=info
|
|
611
|
+
VITE_DEBUG=true
|
|
212
612
|
```
|
|
213
613
|
|
|
214
|
-
###
|
|
614
|
+
### Example 4: Production Environment
|
|
215
615
|
|
|
216
616
|
```bash
|
|
217
617
|
# .env.production
|
|
218
|
-
|
|
219
|
-
|
|
618
|
+
# Production environment config
|
|
619
|
+
|
|
620
|
+
# API configuration
|
|
621
|
+
VITE_FE_API_BASE_URL=https://api.example.com
|
|
622
|
+
VITE_USER_API_BASE_URL=https://api.example.com/user
|
|
623
|
+
VITE_AI_API_BASE_URL=https://api.openai.com/v1
|
|
624
|
+
|
|
625
|
+
# AI configuration
|
|
626
|
+
VITE_AI_API_TOKEN=sk-prod-xxxxx
|
|
627
|
+
VITE_AI_API_REQUIRE_TOKEN=true
|
|
628
|
+
|
|
629
|
+
# Debug configuration
|
|
630
|
+
VITE_LOG_LEVEL=error
|
|
220
631
|
VITE_DEBUG=false
|
|
221
|
-
VITE_LOG_LEVEL=warn
|
|
222
632
|
```
|
|
223
633
|
|
|
224
|
-
###
|
|
634
|
+
### Example 5: Local Override Configuration
|
|
225
635
|
|
|
226
636
|
```bash
|
|
227
|
-
# .env.local
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
637
|
+
# .env.local
|
|
638
|
+
# Local personal config, not committed to git
|
|
639
|
+
|
|
640
|
+
# Override AI API Token (use your own key)
|
|
641
|
+
VITE_AI_API_TOKEN=sk-my-personal-key
|
|
642
|
+
|
|
643
|
+
# Override API address (connect to your local service)
|
|
644
|
+
VITE_FE_API_BASE_URL=http://192.168.1.100:3000/api
|
|
645
|
+
|
|
646
|
+
# Enable specific features
|
|
647
|
+
VITE_ENABLE_EXPERIMENTAL_FEATURES=true
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Example 6: Environment Variable Template
|
|
651
|
+
|
|
652
|
+
```bash
|
|
653
|
+
# .env.template
|
|
654
|
+
# Env var template, committed to git for team reference
|
|
655
|
+
|
|
656
|
+
# ===== Required Configuration =====
|
|
657
|
+
VITE_FE_API_BASE_URL=https://your-api-url.com
|
|
658
|
+
VITE_AI_API_TOKEN=your-ai-api-token-here
|
|
659
|
+
|
|
660
|
+
# ===== Optional Configuration =====
|
|
661
|
+
VITE_LOGIN_USER=your-default-username
|
|
662
|
+
VITE_LOGIN_PASSWORD=your-default-password
|
|
663
|
+
|
|
664
|
+
# ===== Instructions =====
|
|
665
|
+
# 1. Copy this file as .env.local
|
|
666
|
+
# 2. Fill in actual configuration values
|
|
667
|
+
# 3. .env.local won't be committed to git
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
## 🔄 Environment Variable Injection
|
|
673
|
+
|
|
674
|
+
### InjectEnv Plugin Working Principle
|
|
675
|
+
|
|
676
|
+
```typescript
|
|
677
|
+
// @qlover/corekit-bridge/src/core/bootstrap/plugins/InjectEnv.ts (simplified)
|
|
678
|
+
export class InjectEnv implements BootstrapExecutorPlugin {
|
|
679
|
+
readonly pluginName = 'InjectEnv';
|
|
680
|
+
|
|
681
|
+
constructor(protected options: InjectEnvConfig) {}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Execute before Bootstrap initialization
|
|
685
|
+
*/
|
|
686
|
+
onBefore(): void {
|
|
687
|
+
const { target, source, prefix, blackList } = this.options;
|
|
688
|
+
|
|
689
|
+
// Iterate through all properties of target object
|
|
690
|
+
for (const key in target) {
|
|
691
|
+
// Skip properties in blacklist
|
|
692
|
+
if (blackList.includes(key)) {
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const currentValue = target[key as keyof typeof target];
|
|
697
|
+
|
|
698
|
+
// Get corresponding env var value
|
|
699
|
+
const envValue = this.getEnvValue(key, currentValue);
|
|
700
|
+
|
|
701
|
+
// If env var exists and differs from default value, inject it
|
|
702
|
+
if (!this.isEmpty(envValue) && envValue !== currentValue) {
|
|
703
|
+
target[key as keyof typeof target] = envValue;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Get environment variable value
|
|
710
|
+
*/
|
|
711
|
+
private getEnvValue<D>(key: string, defaultValue?: D): D {
|
|
712
|
+
const { prefix = '', source = {} } = this.options;
|
|
713
|
+
|
|
714
|
+
// Convert camelCase to UPPER_SNAKE_CASE
|
|
715
|
+
// appName → APP_NAME
|
|
716
|
+
// feApiBaseUrl → FE_API_BASE_URL
|
|
717
|
+
const formattedKey = key.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase();
|
|
718
|
+
|
|
719
|
+
// Add prefix
|
|
720
|
+
const envKey = `${prefix}${formattedKey}`;
|
|
721
|
+
|
|
722
|
+
// Get env var value
|
|
723
|
+
const value = source[envKey];
|
|
724
|
+
|
|
725
|
+
// If it's a JSON string, parse it
|
|
726
|
+
if (typeof value === 'string' && this.isJSONString(value)) {
|
|
727
|
+
return JSON.parse(value);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return (value ?? defaultValue) as D;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Check if value is empty
|
|
735
|
+
*/
|
|
736
|
+
private isEmpty(value: any): boolean {
|
|
737
|
+
return value === undefined || value === null || value === '';
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Check if it's a JSON string
|
|
742
|
+
*/
|
|
743
|
+
private isJSONString(str: string): boolean {
|
|
744
|
+
try {
|
|
745
|
+
JSON.parse(str);
|
|
746
|
+
return true;
|
|
747
|
+
} catch {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Injection Example
|
|
755
|
+
|
|
756
|
+
```typescript
|
|
757
|
+
// Assuming the following environment variables:
|
|
758
|
+
VITE_APP_NAME=MyApp
|
|
759
|
+
VITE_FE_API_BASE_URL=https://api.example.com
|
|
760
|
+
VITE_AI_API_TOKEN=sk-xxxxx
|
|
761
|
+
|
|
762
|
+
// AppConfig initial state:
|
|
763
|
+
const appConfig = new AppConfig();
|
|
764
|
+
console.log(appConfig.appName); // ''
|
|
765
|
+
console.log(appConfig.feApiBaseUrl); // ''
|
|
766
|
+
console.log(appConfig.aiApiToken); // ''
|
|
767
|
+
|
|
768
|
+
// After Bootstrap initialization (after InjectEnv plugin execution):
|
|
769
|
+
await bootstrap.initialize();
|
|
770
|
+
|
|
771
|
+
console.log(appConfig.appName); // 'MyApp'
|
|
772
|
+
console.log(appConfig.feApiBaseUrl); // 'https://api.example.com'
|
|
773
|
+
console.log(appConfig.aiApiToken); // 'sk-xxxxx'
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
---
|
|
777
|
+
|
|
778
|
+
## 📦 AppConfig Usage
|
|
779
|
+
|
|
780
|
+
### 1. Use in Services (Recommended) ⭐
|
|
781
|
+
|
|
782
|
+
```typescript
|
|
783
|
+
// src/base/services/UserService.ts
|
|
784
|
+
import { injectable, inject } from 'inversify';
|
|
785
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
786
|
+
import type { AppConfig } from '@/base/cases/AppConfig';
|
|
787
|
+
|
|
788
|
+
@injectable()
|
|
789
|
+
export class UserService {
|
|
790
|
+
constructor(
|
|
791
|
+
@inject(IOCIdentifier.AppConfig) private config: AppConfig,
|
|
792
|
+
@inject(UserApi) private api: UserApi
|
|
793
|
+
) {}
|
|
794
|
+
|
|
795
|
+
async login(username: string, password: string) {
|
|
796
|
+
// ✅ Use API address from config
|
|
797
|
+
const response = await this.api.post(
|
|
798
|
+
`${this.config.userApiBaseUrl}/login`,
|
|
799
|
+
{ username, password }
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
// ✅ Use storage key from config
|
|
803
|
+
this.storage.setItem(this.config.userTokenStorageKey, response.token);
|
|
804
|
+
|
|
805
|
+
return response.user;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
231
808
|
```
|
|
232
809
|
|
|
233
|
-
|
|
810
|
+
### 2. Use in UI Components
|
|
234
811
|
|
|
235
|
-
|
|
812
|
+
```typescript
|
|
813
|
+
// src/pages/base/HomePage.tsx
|
|
814
|
+
import { useIOC } from '@/uikit/hooks/useIOC';
|
|
236
815
|
|
|
237
|
-
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
const apiUrl = import.meta.env.VITE_API_BASE_URL;
|
|
241
|
-
const isDebug = import.meta.env.VITE_DEBUG === 'true';
|
|
816
|
+
function HomePage() {
|
|
817
|
+
// ✅ Get config through Hook
|
|
818
|
+
const config = useIOC('AppConfig');
|
|
242
819
|
|
|
243
820
|
return (
|
|
244
821
|
<div>
|
|
245
|
-
<
|
|
246
|
-
|
|
822
|
+
<h1>{config.appName}</h1>
|
|
823
|
+
<p>Version: {config.appVersion}</p>
|
|
824
|
+
<p>Environment: {config.env}</p>
|
|
825
|
+
{!config.isProduction && <p>🚧 Development Mode</p>}
|
|
247
826
|
</div>
|
|
248
827
|
);
|
|
249
828
|
}
|
|
250
829
|
```
|
|
251
830
|
|
|
252
|
-
###
|
|
831
|
+
### 3. Use in Plugins
|
|
832
|
+
|
|
833
|
+
```typescript
|
|
834
|
+
// src/base/apis/feApi/FeApiBootstrap.ts
|
|
835
|
+
export class FeApiBootstarp implements BootstrapExecutorPlugin {
|
|
836
|
+
readonly pluginName = 'FeApiBootstarp';
|
|
837
|
+
|
|
838
|
+
onBefore({ parameters: { ioc } }: BootstrapContext): void {
|
|
839
|
+
const feApi = ioc.get<FeApi>(FeApi);
|
|
840
|
+
// ✅ Get config from IOC
|
|
841
|
+
const config = ioc.get<AppConfig>(IOCIdentifier.AppConfig);
|
|
842
|
+
|
|
843
|
+
// ✅ Use config to set API base URL
|
|
844
|
+
feApi.setBaseURL(config.feApiBaseUrl);
|
|
845
|
+
|
|
846
|
+
// Add other plugins
|
|
847
|
+
feApi.usePlugin(
|
|
848
|
+
new AuthTokenPlugin({
|
|
849
|
+
getToken: () => {
|
|
850
|
+
const storage = ioc.get(IOCIdentifier.LocalStorageEncrypt);
|
|
851
|
+
return storage.getItem(config.userTokenStorageKey);
|
|
852
|
+
}
|
|
853
|
+
})
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
```
|
|
253
858
|
|
|
254
|
-
|
|
255
|
-
// Getting configuration through IOC
|
|
256
|
-
function UserService() {
|
|
257
|
-
const appConfig = IOC(IOCIdentifier.AppConfig);
|
|
859
|
+
### 4. Use Environment Variables Directly in Code
|
|
258
860
|
|
|
259
|
-
|
|
260
|
-
|
|
861
|
+
```typescript
|
|
862
|
+
// Note: Using import.meta.env directly is not recommended as it can't be managed by IOC
|
|
261
863
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
864
|
+
// ❌ Not recommended: Direct use (bypasses AppConfig)
|
|
865
|
+
function MyComponent() {
|
|
866
|
+
const apiUrl = import.meta.env.VITE_FE_API_BASE_URL;
|
|
867
|
+
// ...
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// ✅ Recommended: Use through AppConfig
|
|
871
|
+
function MyComponent() {
|
|
872
|
+
const config = useIOC('AppConfig');
|
|
873
|
+
const apiUrl = config.feApiBaseUrl;
|
|
874
|
+
// ...
|
|
268
875
|
}
|
|
269
876
|
```
|
|
270
877
|
|
|
271
|
-
|
|
878
|
+
---
|
|
272
879
|
|
|
273
|
-
|
|
274
|
-
@injectable()
|
|
275
|
-
export class ApiService {
|
|
276
|
-
constructor(@inject(IOCIdentifier.AppConfig) private appConfig: AppConfig) {}
|
|
880
|
+
## 🚀 Advanced Usage
|
|
277
881
|
|
|
278
|
-
|
|
279
|
-
const baseUrl = this.appConfig.aiApiBaseUrl;
|
|
280
|
-
const token = this.appConfig.aiApiToken;
|
|
882
|
+
### 1. Dynamically Modify Configuration
|
|
281
883
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
884
|
+
Sometimes you may need to dynamically modify configuration at runtime (rather than through environment variables):
|
|
885
|
+
|
|
886
|
+
```typescript
|
|
887
|
+
// ✅ Method 1: Modify before Bootstrap initialization
|
|
888
|
+
const appConfig = new AppConfig();
|
|
889
|
+
|
|
890
|
+
// Dynamically modify config
|
|
891
|
+
if (window.location.hostname.includes('localhost')) {
|
|
892
|
+
// Use different API address for local development
|
|
893
|
+
(appConfig as any).feApiBaseUrl = 'http://localhost:3000/api';
|
|
288
894
|
}
|
|
289
|
-
```
|
|
290
895
|
|
|
291
|
-
|
|
896
|
+
// Then pass to Bootstrap
|
|
897
|
+
const bootstrap = new Bootstrap({
|
|
898
|
+
envOptions: {
|
|
899
|
+
target: appConfig, // Use modified config
|
|
900
|
+
source: import.meta.env,
|
|
901
|
+
prefix: 'VITE_',
|
|
902
|
+
blackList: envBlackList
|
|
903
|
+
}
|
|
904
|
+
});
|
|
292
905
|
|
|
293
|
-
|
|
906
|
+
await bootstrap.initialize();
|
|
907
|
+
```
|
|
294
908
|
|
|
295
|
-
```
|
|
296
|
-
//
|
|
297
|
-
export
|
|
298
|
-
|
|
909
|
+
```typescript
|
|
910
|
+
// ✅ Method 2: Create config factory function
|
|
911
|
+
export function createAppConfig(): AppConfig {
|
|
912
|
+
const config = new AppConfig();
|
|
299
913
|
|
|
300
|
-
|
|
914
|
+
// Dynamically set config based on specific conditions
|
|
915
|
+
if (someCondition) {
|
|
916
|
+
(config as any).aiApiBaseUrl = 'https://custom-api.com';
|
|
917
|
+
}
|
|
301
918
|
|
|
302
|
-
|
|
303
|
-
|
|
919
|
+
return config;
|
|
920
|
+
}
|
|
304
921
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
continue; // Skip properties in blacklist
|
|
309
|
-
}
|
|
922
|
+
// Use in Bootstrap
|
|
923
|
+
const appConfig = createAppConfig();
|
|
924
|
+
```
|
|
310
925
|
|
|
311
|
-
|
|
312
|
-
const envValue = this.env(key, value); // Get environment variable value
|
|
926
|
+
### 2. Configuration Validation
|
|
313
927
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
928
|
+
```typescript
|
|
929
|
+
// src/base/cases/AppConfig.ts
|
|
930
|
+
export class AppConfig implements EnvConfigInterface {
|
|
931
|
+
// ... property definitions
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Validate required configuration items
|
|
935
|
+
*/
|
|
936
|
+
validate(): void {
|
|
937
|
+
const required: (keyof AppConfig)[] = [
|
|
938
|
+
'appName',
|
|
939
|
+
'feApiBaseUrl',
|
|
940
|
+
'userTokenStorageKey'
|
|
941
|
+
];
|
|
942
|
+
|
|
943
|
+
for (const key of required) {
|
|
944
|
+
if (!this[key]) {
|
|
945
|
+
throw new Error(`Missing required configuration: ${key}`);
|
|
317
946
|
}
|
|
318
947
|
}
|
|
319
948
|
}
|
|
320
949
|
}
|
|
950
|
+
|
|
951
|
+
// Use in Bootstrap
|
|
952
|
+
const appConfig = new AppConfig();
|
|
953
|
+
|
|
954
|
+
await bootstrap.initialize();
|
|
955
|
+
|
|
956
|
+
// Validate after initialization
|
|
957
|
+
appConfig.validate();
|
|
321
958
|
```
|
|
322
959
|
|
|
323
|
-
###
|
|
960
|
+
### 3. Configuration Composition
|
|
961
|
+
|
|
962
|
+
```typescript
|
|
963
|
+
// ✅ Method 3: Compose config from multiple sources
|
|
964
|
+
const appConfig = new AppConfig();
|
|
965
|
+
|
|
966
|
+
const bootstrap = new Bootstrap({
|
|
967
|
+
envOptions: {
|
|
968
|
+
target: appConfig,
|
|
969
|
+
// Merge multiple config sources
|
|
970
|
+
source: Object.assign(
|
|
971
|
+
{},
|
|
972
|
+
import.meta.env, // Vite environment variables
|
|
973
|
+
{ VITE_BOOT_HREF: window.location.href }, // Runtime info
|
|
974
|
+
window.__APP_CONFIG__ // Server-injected config
|
|
975
|
+
),
|
|
976
|
+
prefix: 'VITE_',
|
|
977
|
+
blackList: envBlackList
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
```
|
|
324
981
|
|
|
325
|
-
|
|
326
|
-
env<D>(key: string, defaultValue?: D): D {
|
|
327
|
-
const { prefix = '', source = {} } = this.options;
|
|
982
|
+
### 4. Conditional Configuration
|
|
328
983
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
984
|
+
```typescript
|
|
985
|
+
// src/core/bootstraps/BootstrapClient.ts
|
|
986
|
+
const appConfig = new AppConfig();
|
|
332
987
|
|
|
333
|
-
|
|
988
|
+
// ✅ Set different config sources based on environment
|
|
989
|
+
const configSource =
|
|
990
|
+
import.meta.env.VITE_USER_NODE_ENV === 'production'
|
|
991
|
+
? import.meta.env // Production: only use env vars
|
|
992
|
+
: {
|
|
993
|
+
...import.meta.env,
|
|
994
|
+
...window.__DEV_CONFIG__ // Development: allow window injection
|
|
995
|
+
};
|
|
334
996
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
997
|
+
const bootstrap = new Bootstrap({
|
|
998
|
+
envOptions: {
|
|
999
|
+
target: appConfig,
|
|
1000
|
+
source: configSource,
|
|
1001
|
+
prefix: 'VITE_',
|
|
1002
|
+
blackList: envBlackList
|
|
338
1003
|
}
|
|
1004
|
+
});
|
|
1005
|
+
```
|
|
339
1006
|
|
|
340
|
-
|
|
341
|
-
|
|
1007
|
+
---
|
|
1008
|
+
|
|
1009
|
+
## 🧪 Testing Configuration
|
|
1010
|
+
|
|
1011
|
+
### 1. Mock AppConfig in Tests
|
|
1012
|
+
|
|
1013
|
+
```typescript
|
|
1014
|
+
// __tests__/src/base/services/UserService.test.ts
|
|
1015
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
1016
|
+
import { UserService } from '@/base/services/UserService';
|
|
1017
|
+
import type { AppConfig } from '@/base/cases/AppConfig';
|
|
1018
|
+
|
|
1019
|
+
describe('UserService', () => {
|
|
1020
|
+
let userService: UserService;
|
|
1021
|
+
let mockConfig: AppConfig;
|
|
1022
|
+
|
|
1023
|
+
beforeEach(() => {
|
|
1024
|
+
// ✅ Create mock config
|
|
1025
|
+
mockConfig = {
|
|
1026
|
+
userApiBaseUrl: 'http://localhost:3000/api',
|
|
1027
|
+
userTokenStorageKey: '__test_token__',
|
|
1028
|
+
userInfoStorageKey: '__test_user__',
|
|
1029
|
+
isProduction: false
|
|
1030
|
+
} as AppConfig;
|
|
1031
|
+
|
|
1032
|
+
// Create service
|
|
1033
|
+
userService = new UserService(mockConfig, mockApi, mockStorage);
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
it('should use config values', async () => {
|
|
1037
|
+
await userService.login('user', 'pass');
|
|
1038
|
+
|
|
1039
|
+
// ✅ Verify config values were used
|
|
1040
|
+
expect(mockApi.post).toHaveBeenCalledWith(
|
|
1041
|
+
`${mockConfig.userApiBaseUrl}/login`,
|
|
1042
|
+
expect.any(Object)
|
|
1043
|
+
);
|
|
1044
|
+
});
|
|
1045
|
+
});
|
|
342
1046
|
```
|
|
343
1047
|
|
|
344
|
-
|
|
1048
|
+
### 2. Test Different Environment Configurations
|
|
1049
|
+
|
|
1050
|
+
```typescript
|
|
1051
|
+
// __tests__/src/base/cases/AppConfig.test.ts
|
|
1052
|
+
import { describe, it, expect } from 'vitest';
|
|
1053
|
+
import { AppConfig } from '@/base/cases/AppConfig';
|
|
1054
|
+
|
|
1055
|
+
describe('AppConfig', () => {
|
|
1056
|
+
it('should detect production environment', () => {
|
|
1057
|
+
const config = new AppConfig('production');
|
|
1058
|
+
expect(config.isProduction).toBe(true);
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
it('should detect non-production environment', () => {
|
|
1062
|
+
const config = new AppConfig('localhost');
|
|
1063
|
+
expect(config.isProduction).toBe(false);
|
|
1064
|
+
});
|
|
345
1065
|
|
|
346
|
-
|
|
1066
|
+
it('should have default values', () => {
|
|
1067
|
+
const config = new AppConfig();
|
|
1068
|
+
expect(config.appName).toBe('');
|
|
1069
|
+
expect(config.userTokenStorageKey).toBe('__fe_user_token__');
|
|
1070
|
+
});
|
|
1071
|
+
});
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
### 3. Test Environment Variable Injection
|
|
1075
|
+
|
|
1076
|
+
```typescript
|
|
1077
|
+
// __tests__/src/core/bootstraps/BootstrapClient.test.ts
|
|
1078
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
1079
|
+
import { BootstrapClient } from '@/core/bootstraps/BootstrapClient';
|
|
1080
|
+
|
|
1081
|
+
describe('BootstrapClient', () => {
|
|
1082
|
+
it('should inject environment variables to AppConfig', async () => {
|
|
1083
|
+
const mockArgs = {
|
|
1084
|
+
root: {},
|
|
1085
|
+
bootHref: 'http://localhost:3000',
|
|
1086
|
+
ioc: {
|
|
1087
|
+
create: vi.fn().mockReturnValue(mockIOC)
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
// Execute startup
|
|
1092
|
+
await BootstrapClient.main(mockArgs);
|
|
1093
|
+
|
|
1094
|
+
// ✅ Verify config was injected
|
|
1095
|
+
const globals = (mockArgs.root as any).feGlobals;
|
|
1096
|
+
expect(globals.appConfig).toBeDefined();
|
|
1097
|
+
expect(globals.appConfig.appName).toBeTruthy();
|
|
1098
|
+
});
|
|
1099
|
+
});
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
---
|
|
1103
|
+
|
|
1104
|
+
## 💎 Best Practices
|
|
1105
|
+
|
|
1106
|
+
### 1. ✅ Use Environment Variable Prefix
|
|
347
1107
|
|
|
348
1108
|
```bash
|
|
349
|
-
# ✅ Good naming
|
|
1109
|
+
# ✅ Good naming: Use VITE_ prefix
|
|
350
1110
|
VITE_APP_NAME=MyApp
|
|
351
1111
|
VITE_API_BASE_URL=https://api.example.com
|
|
352
|
-
VITE_USER_TOKEN_STORAGE_KEY=user_token
|
|
353
|
-
VITE_DEBUG=true
|
|
354
1112
|
|
|
355
|
-
# ❌
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
VITE_USER_TOKEN_STORAGE_KEY=user_token
|
|
359
|
-
VITE_DEBUG=true
|
|
1113
|
+
# ❌ Wrong naming: No prefix
|
|
1114
|
+
APP_NAME=MyApp
|
|
1115
|
+
API_BASE_URL=https://api.example.com
|
|
360
1116
|
```
|
|
361
1117
|
|
|
362
|
-
### 2. Sensitive Information
|
|
1118
|
+
### 2. ✅ Use .env.local for Sensitive Information
|
|
363
1119
|
|
|
364
1120
|
```bash
|
|
365
1121
|
# .env.local (not committed to git)
|
|
366
|
-
|
|
367
|
-
VITE_DATABASE_PASSWORD=
|
|
1122
|
+
VITE_AI_API_TOKEN=sk-your-secret-key
|
|
1123
|
+
VITE_DATABASE_PASSWORD=your-password
|
|
368
1124
|
|
|
369
|
-
# .
|
|
370
|
-
|
|
371
|
-
VITE_DATABASE_PASSWORD=your_password_here
|
|
1125
|
+
# .gitignore
|
|
1126
|
+
.env.local
|
|
372
1127
|
```
|
|
373
1128
|
|
|
374
|
-
### 3.
|
|
1129
|
+
### 3. ✅ Provide .env.template
|
|
375
1130
|
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
VITE_APP_NAME: string;
|
|
380
|
-
VITE_API_BASE_URL: string;
|
|
381
|
-
VITE_DEBUG: boolean;
|
|
382
|
-
VITE_PORT: number;
|
|
383
|
-
}
|
|
1131
|
+
```bash
|
|
1132
|
+
# .env.template (committed to git)
|
|
1133
|
+
# Team members can copy this file as .env.local and fill in actual values
|
|
384
1134
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
readonly appName: string = '';
|
|
388
|
-
readonly apiBaseUrl: string = '';
|
|
389
|
-
readonly debug: boolean = false;
|
|
390
|
-
readonly port: number = 3000;
|
|
391
|
-
}
|
|
1135
|
+
VITE_AI_API_TOKEN=your-api-token-here
|
|
1136
|
+
VITE_DATABASE_PASSWORD=your-password-here
|
|
392
1137
|
```
|
|
393
1138
|
|
|
394
|
-
### 4.
|
|
1139
|
+
### 4. ✅ Use Type-safe Configuration
|
|
395
1140
|
|
|
396
|
-
```
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
this.validateRequiredEnvVars();
|
|
401
|
-
}
|
|
1141
|
+
```typescript
|
|
1142
|
+
// ✅ Good practice: Access through AppConfig
|
|
1143
|
+
const config = useIOC('AppConfig');
|
|
1144
|
+
const apiUrl = config.feApiBaseUrl; // ✅ Type-safe
|
|
402
1145
|
|
|
403
|
-
|
|
404
|
-
|
|
1146
|
+
// ❌ Bad practice: Direct access to env vars
|
|
1147
|
+
const apiUrl = import.meta.env.VITE_FE_API_BASE_URL; // ❌ May be undefined
|
|
1148
|
+
```
|
|
405
1149
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
1150
|
+
### 5. ✅ Add Comments to AppConfig
|
|
1151
|
+
|
|
1152
|
+
```typescript
|
|
1153
|
+
export class AppConfig {
|
|
1154
|
+
/**
|
|
1155
|
+
* AI API base URL
|
|
1156
|
+
* @description Injected from VITE_AI_API_BASE_URL environment variable
|
|
1157
|
+
* @default 'https://api.openai.com/v1'
|
|
1158
|
+
* @example 'https://api.openai.com/v1'
|
|
1159
|
+
*/
|
|
1160
|
+
readonly aiApiBaseUrl = 'https://api.openai.com/v1';
|
|
412
1161
|
}
|
|
413
1162
|
```
|
|
414
1163
|
|
|
415
|
-
|
|
1164
|
+
### 6. ✅ Avoid Environment Checks in Code
|
|
416
1165
|
|
|
417
|
-
|
|
1166
|
+
```typescript
|
|
1167
|
+
// ❌ Bad: Check environment in code
|
|
1168
|
+
if (process.env.NODE_ENV === 'production') {
|
|
1169
|
+
apiUrl = 'https://api.production.com';
|
|
1170
|
+
} else {
|
|
1171
|
+
apiUrl = 'http://localhost:3000';
|
|
1172
|
+
}
|
|
418
1173
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
console.log('AppConfig:', IOC(IOCIdentifier.AppConfig));
|
|
1174
|
+
// ✅ Good: Manage through config
|
|
1175
|
+
const config = useIOC('AppConfig');
|
|
1176
|
+
const apiUrl = config.feApiBaseUrl; // Automatically uses correct value based on environment
|
|
423
1177
|
```
|
|
424
1178
|
|
|
425
|
-
###
|
|
426
|
-
|
|
427
|
-
**Issue 1: Environment Variables Not Injected**
|
|
1179
|
+
### 7. ✅ Configuration Naming Conventions
|
|
428
1180
|
|
|
429
1181
|
```bash
|
|
430
|
-
#
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
1182
|
+
# ✅ Good naming: Clear and specific
|
|
1183
|
+
VITE_FE_API_BASE_URL=https://api.example.com
|
|
1184
|
+
VITE_USER_TOKEN_STORAGE_KEY=__fe_user_token__
|
|
1185
|
+
VITE_AI_API_REQUIRE_TOKEN=true
|
|
1186
|
+
|
|
1187
|
+
# ❌ Bad naming: Vague, abbreviated
|
|
1188
|
+
VITE_API=https://api.example.com
|
|
1189
|
+
VITE_KEY=__token__
|
|
1190
|
+
VITE_REQ=true
|
|
434
1191
|
```
|
|
435
1192
|
|
|
436
|
-
|
|
1193
|
+
---
|
|
1194
|
+
|
|
1195
|
+
## ❓ FAQ
|
|
1196
|
+
|
|
1197
|
+
### Q1: Why aren't my environment variables being injected?
|
|
1198
|
+
|
|
1199
|
+
**A:** Check the following points:
|
|
1200
|
+
|
|
1201
|
+
1. **Environment variable prefix**
|
|
437
1202
|
|
|
438
1203
|
```bash
|
|
439
|
-
#
|
|
440
|
-
|
|
441
|
-
|
|
1204
|
+
# ✅ Correct: Use VITE_ prefix
|
|
1205
|
+
VITE_APP_NAME=MyApp
|
|
1206
|
+
|
|
1207
|
+
# ❌ Wrong: No prefix
|
|
1208
|
+
APP_NAME=MyApp
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
2. **AppConfig property name**
|
|
1212
|
+
|
|
1213
|
+
```typescript
|
|
1214
|
+
// ✅ Correct: Property exists
|
|
1215
|
+
export class AppConfig {
|
|
1216
|
+
readonly appName = ''; // ← VITE_APP_NAME will inject here
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// ❌ Wrong: No corresponding property
|
|
1220
|
+
export class AppConfig {
|
|
1221
|
+
// No appName property, VITE_APP_NAME won't be injected
|
|
1222
|
+
}
|
|
442
1223
|
```
|
|
443
1224
|
|
|
444
|
-
|
|
1225
|
+
3. **Blacklist configuration**
|
|
445
1226
|
|
|
446
|
-
```
|
|
447
|
-
//
|
|
1227
|
+
```typescript
|
|
1228
|
+
// config/common.ts
|
|
448
1229
|
export const envBlackList = ['env', 'userNodeEnv'];
|
|
449
|
-
// Make sure your
|
|
1230
|
+
// Make sure your property is not in the blacklist
|
|
450
1231
|
```
|
|
451
1232
|
|
|
452
|
-
###
|
|
1233
|
+
### Q2: How to use different configurations in different environments?
|
|
453
1234
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
console.groupEnd();
|
|
1235
|
+
**A:** Use the `--mode` parameter:
|
|
1236
|
+
|
|
1237
|
+
```json
|
|
1238
|
+
{
|
|
1239
|
+
"scripts": {
|
|
1240
|
+
"dev": "vite --mode localhost", // Load .env.localhost
|
|
1241
|
+
"dev:staging": "vite --mode staging", // Load .env.staging
|
|
1242
|
+
"build:prod": "vite build --mode production" // Load .env.production
|
|
463
1243
|
}
|
|
464
1244
|
}
|
|
1245
|
+
```
|
|
465
1246
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
1247
|
+
### Q3: How to handle sensitive information?
|
|
1248
|
+
|
|
1249
|
+
**A:** Use `.env.local`:
|
|
1250
|
+
|
|
1251
|
+
```bash
|
|
1252
|
+
# .env.local (not committed to git)
|
|
1253
|
+
VITE_AI_API_TOKEN=sk-your-secret-key
|
|
1254
|
+
|
|
1255
|
+
# .gitignore
|
|
1256
|
+
.env.local
|
|
1257
|
+
```
|
|
1258
|
+
|
|
1259
|
+
### Q4: Can configuration be dynamically modified at runtime?
|
|
1260
|
+
|
|
1261
|
+
**A:** Yes, but it needs to be done before Bootstrap initialization:
|
|
1262
|
+
|
|
1263
|
+
```typescript
|
|
1264
|
+
const appConfig = new AppConfig();
|
|
1265
|
+
|
|
1266
|
+
// ✅ Modify before Bootstrap initialization
|
|
1267
|
+
(appConfig as any).feApiBaseUrl = 'https://custom-api.com';
|
|
1268
|
+
|
|
1269
|
+
const bootstrap = new Bootstrap({
|
|
1270
|
+
envOptions: {
|
|
1271
|
+
target: appConfig, // Use modified config
|
|
1272
|
+
source: import.meta.env,
|
|
1273
|
+
prefix: 'VITE_',
|
|
1274
|
+
blackList: envBlackList
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
await bootstrap.initialize();
|
|
470
1279
|
```
|
|
471
1280
|
|
|
472
|
-
|
|
1281
|
+
### Q5: Why recommend using AppConfig instead of import.meta.env directly?
|
|
1282
|
+
|
|
1283
|
+
**A:**
|
|
1284
|
+
|
|
1285
|
+
| Feature | import.meta.env | AppConfig |
|
|
1286
|
+
| -------------------------- | ------------------- | ---------------------------- |
|
|
1287
|
+
| **Type Safety** | ❌ May be undefined | ✅ Complete type definitions |
|
|
1288
|
+
| **Default Values** | ❌ None | ✅ Has default values |
|
|
1289
|
+
| **Testability** | ❌ Hard to mock | ✅ Easy to mock |
|
|
1290
|
+
| **Centralized Management** | ❌ Scattered | ✅ Unified management |
|
|
1291
|
+
| **Runtime Modification** | ❌ Not possible | ✅ Possible |
|
|
1292
|
+
|
|
1293
|
+
### Q6: What's the difference between environment variables and config files?
|
|
1294
|
+
|
|
1295
|
+
**A:**
|
|
1296
|
+
|
|
1297
|
+
**Environment variables:** Suitable for:
|
|
1298
|
+
|
|
1299
|
+
- Configuration that differs by environment (API addresses, tokens, etc.)
|
|
1300
|
+
- Sensitive information
|
|
1301
|
+
- Configuration that needs to be modified during deployment
|
|
1302
|
+
|
|
1303
|
+
**Config files (config/):** Suitable for:
|
|
1304
|
+
|
|
1305
|
+
- Application logic configuration (routes, themes, i18n, etc.)
|
|
1306
|
+
- Configuration that doesn't change with environment
|
|
1307
|
+
- Code-level configuration
|
|
1308
|
+
|
|
1309
|
+
---
|
|
1310
|
+
|
|
1311
|
+
## 📚 Related Documentation
|
|
1312
|
+
|
|
1313
|
+
- [Project Architecture Design](./index.md) - Understand overall architecture
|
|
1314
|
+
- [Bootstrap Initializer](./bootstrap.md) - Bootstrap details
|
|
1315
|
+
- [IOC Container](./ioc.md) - Dependency injection details
|
|
1316
|
+
- [Global Variable Encapsulation](./global.md) - Browser API encapsulation
|
|
1317
|
+
|
|
1318
|
+
---
|
|
1319
|
+
|
|
1320
|
+
## 🎉 Summary
|
|
1321
|
+
|
|
1322
|
+
The environment variable management system, through the combination of **Bootstrap + AppConfig + IOC**, provides:
|
|
1323
|
+
|
|
1324
|
+
1. **Environment Isolation** 🌍 - Different environments use different configs, no code changes needed
|
|
1325
|
+
2. **Type Safety** 🔒 - Complete type checking through TypeScript
|
|
1326
|
+
3. **Centralized Management** 📦 - All config managed uniformly in AppConfig
|
|
1327
|
+
4. **Auto Injection** ⚡ - Bootstrap automatically injects env vars to AppConfig
|
|
1328
|
+
5. **Easy to Test** 🧪 - Can easily mock AppConfig for testing
|
|
1329
|
+
6. **Flexible Extension** 🚀 - Supports multiple config sources and dynamic modification
|
|
473
1330
|
|
|
474
|
-
|
|
1331
|
+
Through proper use of environment variable management, you can build a more robust, flexible, and maintainable application architecture.
|
|
475
1332
|
|
|
476
|
-
|
|
477
|
-
2. **Type Safety**: Type checking through TypeScript
|
|
478
|
-
3. **Centralized Management**: All configurations are managed uniformly in AppConfig
|
|
479
|
-
4. **Flexible Configuration**: Support for multiple environment variable files
|
|
480
|
-
5. **Security Handling**: Sensitive information can be managed locally through .env.local
|
|
1333
|
+
---
|
|
481
1334
|
|
|
482
|
-
|
|
1335
|
+
**Feedback:**
|
|
1336
|
+
If you have any questions or suggestions about environment variable management, please discuss in the team channel or submit an Issue.
|