@skyroc/scripts 2.5.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/LICENSE +21 -0
- package/README.md +508 -0
- package/dist/cli.mjs +78 -0
- package/dist/config-BKkdncJS.mjs +987 -0
- package/dist/index.mjs +14 -0
- package/package.json +83 -0
- package/templates/admin/.env +62 -0
- package/templates/admin/.env.prod +7 -0
- package/templates/admin/.env.test +7 -0
- package/templates/admin/.oxlintrc.json +6 -0
- package/templates/admin/README.md +508 -0
- package/templates/admin/index.html +14 -0
- package/templates/admin/package.json +105 -0
- package/templates/admin/public/favicon.svg +3 -0
- package/templates/admin/src/App.tsx +71 -0
- package/templates/admin/src/assets/audio/wechat-style-notification.wav +0 -0
- package/templates/admin/src/assets/imgs/skyroc.jpg +0 -0
- package/templates/admin/src/assets/svg-icon/activity.svg +1 -0
- package/templates/admin/src/assets/svg-icon/at-sign.svg +1 -0
- package/templates/admin/src/assets/svg-icon/avatar.svg +1 -0
- package/templates/admin/src/assets/svg-icon/banner.svg +1 -0
- package/templates/admin/src/assets/svg-icon/cast.svg +1 -0
- package/templates/admin/src/assets/svg-icon/chrome.svg +1 -0
- package/templates/admin/src/assets/svg-icon/copy.svg +1 -0
- package/templates/admin/src/assets/svg-icon/custom-icon.svg +1 -0
- package/templates/admin/src/assets/svg-icon/empty-data.svg +1 -0
- package/templates/admin/src/assets/svg-icon/error.svg +1 -0
- package/templates/admin/src/assets/svg-icon/expectation.svg +1 -0
- package/templates/admin/src/assets/svg-icon/heart.svg +1 -0
- package/templates/admin/src/assets/svg-icon/logo.svg +15 -0
- package/templates/admin/src/assets/svg-icon/network-error.svg +1 -0
- package/templates/admin/src/assets/svg-icon/no-icon.svg +1 -0
- package/templates/admin/src/assets/svg-icon/no-permission.svg +1 -0
- package/templates/admin/src/assets/svg-icon/not-found.svg +1 -0
- package/templates/admin/src/assets/svg-icon/service-error.svg +1 -0
- package/templates/admin/src/bootstrap.tsx +60 -0
- package/templates/admin/src/components/SkyrocAvatar.tsx +13 -0
- package/templates/admin/src/components/SystemLogo.tsx +7 -0
- package/templates/admin/src/config.ts +125 -0
- package/templates/admin/src/features/antd/AntdProvider.tsx +21 -0
- package/templates/admin/src/features/auth/components/UserAvatar.tsx +76 -0
- package/templates/admin/src/features/auth/use-auth-form-rules.ts +83 -0
- package/templates/admin/src/features/auth/use-auth.ts +96 -0
- package/templates/admin/src/features/auth/use-login.ts +72 -0
- package/templates/admin/src/features/effects/GlobalEffect.tsx +15 -0
- package/templates/admin/src/features/menus/dynamic-routes.ts +10 -0
- package/templates/admin/src/features/menus/extras.ts +3 -0
- package/templates/admin/src/features/menus/menu-category.ts +14 -0
- package/templates/admin/src/features/menus/menu-config.ts +5 -0
- package/templates/admin/src/features/router/RouterProvider.tsx +18 -0
- package/templates/admin/src/features/router/guard.ts +97 -0
- package/templates/admin/src/features/router/index.ts +52 -0
- package/templates/admin/src/features/router/routeTree.gen.ts +237 -0
- package/templates/admin/src/features/router/router-ref.ts +24 -0
- package/templates/admin/src/locales/antd.ts +8 -0
- package/templates/admin/src/locales/index.ts +22 -0
- package/templates/admin/src/locales/sync.ts +14 -0
- package/templates/admin/src/main.tsx +9 -0
- package/templates/admin/src/pages/(admin)/home/index.tsx +46 -0
- package/templates/admin/src/pages/(admin)/layout.tsx +44 -0
- package/templates/admin/src/pages/(auth)/login/index.tsx +102 -0
- package/templates/admin/src/pages/(auth)/login/layout.tsx +71 -0
- package/templates/admin/src/pages/(auth)/login-out.tsx +27 -0
- package/templates/admin/src/pages/(errors)/403.tsx +16 -0
- package/templates/admin/src/pages/(errors)/404.tsx +16 -0
- package/templates/admin/src/pages/(errors)/500.tsx +16 -0
- package/templates/admin/src/pages/__root.tsx +54 -0
- package/templates/admin/src/pages/error.tsx +19 -0
- package/templates/admin/src/pages/index.tsx +14 -0
- package/templates/admin/src/pages/loading.tsx +45 -0
- package/templates/admin/src/pages/not-found.tsx +9 -0
- package/templates/admin/src/plugins/app.tsx +47 -0
- package/templates/admin/src/plugins/assets.ts +4 -0
- package/templates/admin/src/plugins/index.ts +32 -0
- package/templates/admin/src/service/adapter.ts +63 -0
- package/templates/admin/src/service/api/auth/api.ts +31 -0
- package/templates/admin/src/service/api/auth/hooks.ts +28 -0
- package/templates/admin/src/service/api/auth/index.ts +10 -0
- package/templates/admin/src/service/api/auth/keys.ts +7 -0
- package/templates/admin/src/service/api/auth/urls.ts +7 -0
- package/templates/admin/src/service/api/index.ts +2 -0
- package/templates/admin/src/service/api/route/api.ts +7 -0
- package/templates/admin/src/service/api/route/hooks.ts +21 -0
- package/templates/admin/src/service/api/route/index.ts +10 -0
- package/templates/admin/src/service/api/route/keys.ts +3 -0
- package/templates/admin/src/service/api/route/urls.ts +5 -0
- package/templates/admin/src/service/queryClient.ts +13 -0
- package/templates/admin/src/service/request/index.ts +62 -0
- package/templates/admin/src/styles/scss/global.scss +1 -0
- package/templates/admin/src/styles/scss/scrollbar.scss +28 -0
- package/templates/admin/src/types/auto-imports.d.ts +186 -0
- package/templates/admin/src/types/global.d.ts +16 -0
- package/templates/admin/src/types/router.d.ts +14 -0
- package/templates/admin/src/types/storage.d.ts +17 -0
- package/templates/admin/src/types/vite-env.d.ts +117 -0
- package/templates/admin/src/utils/common.tsx +38 -0
- package/templates/admin/src/utils/icon.ts +9 -0
- package/templates/admin/src/utils/service.ts +75 -0
- package/templates/admin/src/utils/storage.ts +7 -0
- package/templates/admin/tsconfig.json +22 -0
- package/templates/admin/uno.config.ts +18 -0
- package/templates/admin/vite.config.ts +13 -0
- package/templates/admin-shell/i18n/atoms/lang.ts +20 -0
- package/templates/admin-shell/i18n/config/default.ts +11 -0
- package/templates/admin-shell/i18n/features/lang/LangEffect.tsx +25 -0
- package/templates/admin-shell/i18n/features/lang/LangSwitch.tsx +49 -0
- package/templates/admin-shell/i18n/hooks/use-lang.ts +47 -0
- package/templates/admin-shell/i18n/i18n.ts +116 -0
- package/templates/admin-shell/i18n/index.ts +23 -0
- package/templates/admin-shell/i18n/langs/en-us/common.json +49 -0
- package/templates/admin-shell/i18n/langs/en-us/datatable.json +8 -0
- package/templates/admin-shell/i18n/langs/en-us/dropdown.json +9 -0
- package/templates/admin-shell/i18n/langs/en-us/form.json +27 -0
- package/templates/admin-shell/i18n/langs/en-us/icon.json +13 -0
- package/templates/admin-shell/i18n/langs/en-us/notification.json +20 -0
- package/templates/admin-shell/i18n/langs/en-us/page.json +442 -0
- package/templates/admin-shell/i18n/langs/en-us/request.json +8 -0
- package/templates/admin-shell/i18n/langs/en-us/route.json +84 -0
- package/templates/admin-shell/i18n/langs/en-us/system.json +9 -0
- package/templates/admin-shell/i18n/langs/en-us/theme.json +173 -0
- package/templates/admin-shell/i18n/langs/index.ts +63 -0
- package/templates/admin-shell/i18n/langs/zh-cn/common.json +49 -0
- package/templates/admin-shell/i18n/langs/zh-cn/datatable.json +8 -0
- package/templates/admin-shell/i18n/langs/zh-cn/dropdown.json +9 -0
- package/templates/admin-shell/i18n/langs/zh-cn/form.json +27 -0
- package/templates/admin-shell/i18n/langs/zh-cn/icon.json +13 -0
- package/templates/admin-shell/i18n/langs/zh-cn/notification.json +20 -0
- package/templates/admin-shell/i18n/langs/zh-cn/page.json +442 -0
- package/templates/admin-shell/i18n/langs/zh-cn/request.json +8 -0
- package/templates/admin-shell/i18n/langs/zh-cn/route.json +84 -0
- package/templates/admin-shell/i18n/langs/zh-cn/system.json +9 -0
- package/templates/admin-shell/i18n/langs/zh-cn/theme.json +173 -0
- package/templates/admin-shell/i18n/types/i18n.ts +29 -0
- package/templates/admin-shell/i18n/types.ts +53 -0
- package/templates/admin-shell/i18n/utils/helpers.ts +18 -0
- package/templates/admin-shell/layouts/AdminLayout.tsx +161 -0
- package/templates/admin-shell/layouts/constant.ts +56 -0
- package/templates/admin-shell/layouts/context.tsx +101 -0
- package/templates/admin-shell/layouts/features/menus/MenuBadge.tsx +76 -0
- package/templates/admin-shell/layouts/features/menus/dynamic-routes.ts +146 -0
- package/templates/admin-shell/layouts/features/menus/menu-generator.tsx +444 -0
- package/templates/admin-shell/layouts/features/menus/menu-renderer.tsx +149 -0
- package/templates/admin-shell/layouts/features/menus/permissions.ts +28 -0
- package/templates/admin-shell/layouts/features/menus/use-menus.ts +98 -0
- package/templates/admin-shell/layouts/features/use-route.ts +30 -0
- package/templates/admin-shell/layouts/index.ts +44 -0
- package/templates/admin-shell/layouts/materials/index.ts +6 -0
- package/templates/admin-shell/layouts/materials/libs/admin-layout/index.module.css +63 -0
- package/templates/admin-shell/layouts/materials/libs/admin-layout/index.module.css.d.ts +18 -0
- package/templates/admin-shell/layouts/materials/libs/admin-layout/index.tsx +238 -0
- package/templates/admin-shell/layouts/materials/libs/admin-layout/shared.ts +67 -0
- package/templates/admin-shell/layouts/materials/libs/page-tab/ButtonTab.tsx +44 -0
- package/templates/admin-shell/layouts/materials/libs/page-tab/ChromeTab.tsx +54 -0
- package/templates/admin-shell/layouts/materials/libs/page-tab/ChromeTabBg.tsx +30 -0
- package/templates/admin-shell/layouts/materials/libs/page-tab/SliderTab.tsx +44 -0
- package/templates/admin-shell/layouts/materials/libs/page-tab/SvgClose.tsx +51 -0
- package/templates/admin-shell/layouts/materials/libs/page-tab/hook.ts +34 -0
- package/templates/admin-shell/layouts/materials/libs/page-tab/index.module.css +120 -0
- package/templates/admin-shell/layouts/materials/libs/page-tab/index.module.css.d.ts +18 -0
- package/templates/admin-shell/layouts/materials/libs/page-tab/index.tsx +76 -0
- package/templates/admin-shell/layouts/materials/libs/page-tab/shared.ts +32 -0
- package/templates/admin-shell/layouts/materials/types/index.ts +322 -0
- package/templates/admin-shell/layouts/modules/AdminContent.tsx +285 -0
- package/templates/admin-shell/layouts/modules/AdminFooter.tsx +13 -0
- package/templates/admin-shell/layouts/modules/AdminLogo.tsx +28 -0
- package/templates/admin-shell/layouts/modules/AdminSider.tsx +50 -0
- package/templates/admin-shell/layouts/modules/admin-header/AdminHeader.tsx +132 -0
- package/templates/admin-shell/layouts/modules/admin-header/components/Breadcrumb.tsx +62 -0
- package/templates/admin-shell/layouts/modules/admin-header/components/ThemeButton.tsx +24 -0
- package/templates/admin-shell/layouts/modules/admin-menu/AdminMenu.tsx +37 -0
- package/templates/admin-shell/layouts/modules/admin-menu/components/FirstLevelMenu.tsx +156 -0
- package/templates/admin-shell/layouts/modules/admin-menu/components/HorizontalMenu.tsx +88 -0
- package/templates/admin-shell/layouts/modules/admin-menu/components/MenuPortal.tsx +28 -0
- package/templates/admin-shell/layouts/modules/admin-menu/components/VerticalMenu.tsx +123 -0
- package/templates/admin-shell/layouts/modules/admin-menu/enum.ts +47 -0
- package/templates/admin-shell/layouts/modules/admin-menu/modules/Horizontal.tsx +22 -0
- package/templates/admin-shell/layouts/modules/admin-menu/modules/TopHybridHeaderFirst.tsx +15 -0
- package/templates/admin-shell/layouts/modules/admin-menu/modules/TopHybridSidebarFirst.tsx +18 -0
- package/templates/admin-shell/layouts/modules/admin-menu/modules/Vertical.tsx +14 -0
- package/templates/admin-shell/layouts/modules/admin-menu/modules/VerticalHybridHeaderFirst.tsx +18 -0
- package/templates/admin-shell/layouts/modules/admin-menu/modules/VerticalMix.tsx +132 -0
- package/templates/admin-shell/layouts/modules/admin-search/AdminSearch.tsx +36 -0
- package/templates/admin-shell/layouts/modules/admin-search/components/SearchFooter.tsx +38 -0
- package/templates/admin-shell/layouts/modules/admin-search/components/SearchModal.tsx +171 -0
- package/templates/admin-shell/layouts/modules/admin-search/components/SearchResult.tsx +43 -0
- package/templates/admin-shell/layouts/modules/admin-tab/AdminTab.tsx +125 -0
- package/templates/admin-shell/layouts/modules/admin-tab/components/TabContextMenu.tsx +107 -0
- package/templates/admin-shell/layouts/modules/admin-tab/hooks/useTabScroll.ts +86 -0
- package/templates/admin-shell/layouts/modules/shared/logo.ts +42 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/ThemeDrawer.tsx +68 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/components/AnimatedCollapse.tsx +36 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/components/AnimatedItem.tsx +35 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/components/LayoutModeCard.tsx +110 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/components/SettingItem.tsx +31 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/ConfigOperation.tsx +53 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/appearance/CustomPicker.tsx +105 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/appearance/ThemeColor.tsx +58 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/appearance/ThemeSchema.tsx +42 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/appearance/ThemeToken.tsx +44 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/appearance/index.tsx +23 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/general/GlobalSettings.tsx +57 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/general/ThemeGeneral.tsx +20 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/general/WatermarkSettings.tsx +103 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/ContentSettings.tsx +100 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/FooterSettings.tsx +71 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/HeaderSettings.tsx +63 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/LayoutMode.tsx +73 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/SiderSettings.tsx +124 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/TabSettings.tsx +95 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/ThemeLayout.tsx +32 -0
- package/templates/admin-shell/layouts/modules/theme-drawer/modules/preset/ThemePreset.tsx +132 -0
- package/templates/admin-shell/layouts/options.tsx +79 -0
- package/templates/admin-shell/layouts/setup.ts +102 -0
- package/templates/admin-shell/layouts/state/AdminEffect.tsx +56 -0
- package/templates/admin-shell/layouts/state/AdminTabEffect.tsx +28 -0
- package/templates/admin-shell/layouts/state/index.ts +3 -0
- package/templates/admin-shell/layouts/state/menus/MenuToggler.tsx +53 -0
- package/templates/admin-shell/layouts/state/menus/use-admin-menu-badges.ts +46 -0
- package/templates/admin-shell/layouts/state/menus/use-admin-menus.tsx +164 -0
- package/templates/admin-shell/layouts/state/tabs/index.ts +1 -0
- package/templates/admin-shell/layouts/state/tabs/shared.tsx +148 -0
- package/templates/admin-shell/layouts/state/tabs/use-admin-tab.ts +377 -0
- package/templates/admin-shell/layouts/state/use-admin-state.ts +118 -0
- package/templates/admin-shell/layouts/types/router.d.ts +10 -0
- package/templates/admin-shell/layouts/types/storage.ts +18 -0
- package/templates/admin-shell/notification/NotificationButton.tsx +91 -0
- package/templates/admin-shell/notification/NotificationContext.ts +8 -0
- package/templates/admin-shell/notification/NotificationPanel.tsx +194 -0
- package/templates/admin-shell/notification/NotificationProvider.tsx +59 -0
- package/templates/admin-shell/notification/index.ts +12 -0
- package/templates/admin-shell/notification/mock.ts +59 -0
- package/templates/admin-shell/notification/notification-store.ts +438 -0
- package/templates/admin-shell/notification/notification.css +60 -0
- package/templates/admin-shell/notification/types.ts +104 -0
- package/templates/admin-shell/notification/use-notification.ts +51 -0
- package/templates/admin-shell/notification/useNotificationContext.ts +14 -0
- package/templates/admin-shell/runtime/app-update.ts +156 -0
- package/templates/admin-shell/runtime/dayjs.ts +20 -0
- package/templates/admin-shell/runtime/iconify.ts +17 -0
- package/templates/admin-shell/runtime/index.ts +10 -0
- package/templates/admin-shell/runtime/nprogress.ts +25 -0
- package/templates/admin-shell/runtime/plugins.ts +62 -0
- package/templates/admin-shell/styles/global.css +54 -0
- package/templates/admin-shell/styles/nprogress.css +85 -0
- package/templates/admin-shell/styles/reset.css +378 -0
- package/templates/admin-shell/theme/antd/AntdProvider.tsx +106 -0
- package/templates/admin-shell/theme/antd/index.ts +23 -0
- package/templates/admin-shell/theme/antd/shared.ts +80 -0
- package/templates/admin-shell/theme/antd/ui.ts +250 -0
- package/templates/admin-shell/theme/antd-algorithm/algorithm/dark/colors.ts +99 -0
- package/templates/admin-shell/theme/antd-algorithm/algorithm/dark/index.ts +87 -0
- package/templates/admin-shell/theme/antd-algorithm/algorithm/default/colors.ts +91 -0
- package/templates/admin-shell/theme/antd-algorithm/algorithm/default/index.ts +77 -0
- package/templates/admin-shell/theme/antd-algorithm/index.ts +80 -0
- package/templates/admin-shell/theme/antd-algorithm/seed/index.ts +41 -0
- package/templates/admin-shell/theme/antd-algorithm/shared/colorAlgorithm.ts +10 -0
- package/templates/admin-shell/theme/antd-algorithm/shared/genColorMapToken.ts +165 -0
- package/templates/admin-shell/theme/antd-algorithm/shared/genCommonMapToken.ts +23 -0
- package/templates/admin-shell/theme/antd-algorithm/shared/genControlHeight.ts +14 -0
- package/templates/admin-shell/theme/antd-algorithm/shared/genFontMapToken.ts +274 -0
- package/templates/admin-shell/theme/antd-algorithm/shared/genRadiusMapToken.ts +139 -0
- package/templates/admin-shell/theme/antd-algorithm/shared/genSizeMapToken.ts +114 -0
- package/templates/admin-shell/theme/antd-algorithm/types/index.ts +20 -0
- package/templates/admin-shell/theme/components/ThemeEffect.tsx +55 -0
- package/templates/admin-shell/theme/components/ThemeSchemaSegmented.tsx +27 -0
- package/templates/admin-shell/theme/components/ThemeSchemaSwitch.tsx +77 -0
- package/templates/admin-shell/theme/components/shared.ts +5 -0
- package/templates/admin-shell/theme/config/default.ts +108 -0
- package/templates/admin-shell/theme/config/index.ts +1 -0
- package/templates/admin-shell/theme/hooks/index.ts +2 -0
- package/templates/admin-shell/theme/hooks/use-theme.ts +244 -0
- package/templates/admin-shell/theme/index.ts +78 -0
- package/templates/admin-shell/theme/presets/azir.json +39 -0
- package/templates/admin-shell/theme/presets/compact.json +38 -0
- package/templates/admin-shell/theme/presets/dark.json +93 -0
- package/templates/admin-shell/theme/presets/default.json +95 -0
- package/templates/admin-shell/theme/presets/index.ts +29 -0
- package/templates/admin-shell/theme/presets/shadcn.json +99 -0
- package/templates/admin-shell/theme/setup.ts +115 -0
- package/templates/admin-shell/theme/types/index.ts +27 -0
- package/templates/admin-shell/theme/types/theme.d.ts +316 -0
- package/templates/admin-shell/theme/utils/dark-mode.ts +46 -0
- package/templates/admin-shell/theme/utils/filters.ts +20 -0
- package/templates/admin-shell/theme/utils/index.ts +3 -0
- package/templates/admin-shell/theme/utils/settings.ts +43 -0
- package/templates/admin-shell/types/api/auth.d.ts +148 -0
- package/templates/admin-shell/types/api/common.d.ts +54 -0
- package/templates/admin-shell/types/api/route.d.ts +104 -0
- package/templates/admin-shell/types/api/service.d.ts +51 -0
- package/templates/admin-shell/types/api/system-manage.d.ts +160 -0
- package/templates/admin-shell/types/app/common.d.ts +31 -0
- package/templates/admin-shell/types/app/global.d.ts +71 -0
- package/templates/admin-shell/types/app/menu.d.ts +57 -0
- package/templates/admin-shell/types/app/router.d.ts +201 -0
- package/templates/admin-shell/types/app/storage.d.ts +10 -0
- package/templates/admin-shell/types/app/union-key.d.ts +156 -0
- package/templates/admin-shell/types/locales/i18n.d.ts +135 -0
- package/templates/admin-shell/ui/antd/components/AppTree.tsx +62 -0
- package/templates/admin-shell/ui/antd/components/BeyondHiding.tsx +42 -0
- package/templates/admin-shell/ui/antd/components/ButtonIcon.tsx +123 -0
- package/templates/admin-shell/ui/antd/components/ButtonLink.tsx +30 -0
- package/templates/admin-shell/ui/antd/components/ErrorBoundary.tsx +45 -0
- package/templates/admin-shell/ui/antd/components/ExceptionBase.tsx +69 -0
- package/templates/admin-shell/ui/antd/components/FullScreen.tsx +33 -0
- package/templates/admin-shell/ui/antd/components/PinToggler.tsx +35 -0
- package/templates/admin-shell/ui/antd/components/ReloadButton.tsx +20 -0
- package/templates/admin-shell/ui/antd/components/index.ts +12 -0
- package/templates/admin-shell/ui/antd/components/styles/button-icon-animations.css +246 -0
- package/templates/admin-shell/ui/antd/index.ts +1 -0
- package/templates/admin-shell/ui/antd/styles.d.ts +1 -0
- package/templates/admin-shell/ui/compose/animate-feature.ts +5 -0
- package/templates/admin-shell/ui/compose/components/BetterScroll.tsx +51 -0
- package/templates/admin-shell/ui/compose/components/DarkModeContainer.tsx +22 -0
- package/templates/admin-shell/ui/compose/components/FlipText.tsx +41 -0
- package/templates/admin-shell/ui/compose/components/I18nLabel.tsx +18 -0
- package/templates/admin-shell/ui/compose/components/LazyAnimate.tsx +10 -0
- package/templates/admin-shell/ui/compose/components/LookForward.tsx +24 -0
- package/templates/admin-shell/ui/compose/components/NumberTicker.tsx +105 -0
- package/templates/admin-shell/ui/compose/components/Portal.tsx +74 -0
- package/templates/admin-shell/ui/compose/components/SvgIcon.tsx +39 -0
- package/templates/admin-shell/ui/compose/components/TypingAnimation.tsx +83 -0
- package/templates/admin-shell/ui/compose/components/WaveBg.tsx +56 -0
- package/templates/admin-shell/ui/compose/components/index.ts +11 -0
- package/templates/admin-shell/ui/compose/index.ts +2 -0
- package/templates/admin-shell/ui/compose/table/DragContent.tsx +239 -0
- package/templates/admin-shell/ui/compose/table/TableHeaderOperation.tsx +210 -0
- package/templates/admin-shell/ui/compose/table/hooks.ts +196 -0
- package/templates/admin-shell/ui/compose/table/index.ts +16 -0
- package/templates/admin-shell/ui/compose/table/types.ts +244 -0
- package/templates/admin-shell/ui/compose/table/use-table-operate.ts +136 -0
- package/templates/admin-shell/ui/compose/table/use-table-scroll.ts +49 -0
- package/templates/admin-shell/ui/compose/table/use-table.ts +303 -0
- package/templates/admin.meta.json +756 -0
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { generateChangelog, generateTotalChangelog } from "@soybeanjs/changelog";
|
|
3
|
+
import { rimraf } from "rimraf";
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { cp, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import process from "node:process";
|
|
8
|
+
import { bgRed, cyan, green, red, yellow } from "kolorist";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import enquirer from "enquirer";
|
|
11
|
+
import { versionBump } from "bumpp";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { parseYAML } from "confbox";
|
|
14
|
+
import { loadConfig } from "c12";
|
|
15
|
+
|
|
16
|
+
//#region src/commands/changelog.ts
|
|
17
|
+
async function genChangelog(options, total = false) {
|
|
18
|
+
if (total) await generateTotalChangelog(options);
|
|
19
|
+
else await generateChangelog(options);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/commands/cleanup.ts
|
|
24
|
+
async function cleanup(paths) {
|
|
25
|
+
await rimraf(paths, { glob: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/shared/index.ts
|
|
30
|
+
async function execCommand(cmd, args, options) {
|
|
31
|
+
const { execa } = await import("execa");
|
|
32
|
+
return ((await execa(cmd, args, options))?.stdout)?.trim() || "";
|
|
33
|
+
}
|
|
34
|
+
/** 从当前模块位置向上找到 `@skyroc/scripts` 自身的包根,用于定位内置 templates。 */
|
|
35
|
+
function getPackageRoot() {
|
|
36
|
+
let currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
38
|
+
if (existsSync(path.join(currentDir, "package.json"))) return currentDir;
|
|
39
|
+
currentDir = path.dirname(currentDir);
|
|
40
|
+
}
|
|
41
|
+
throw new Error("Cannot resolve @skyroc/scripts package root.");
|
|
42
|
+
}
|
|
43
|
+
/** 从 cwd 向上找到 monorepo 根目录。只有仓库内的命令才需要它。 */
|
|
44
|
+
function getWorkspaceRoot(startDir = process.cwd()) {
|
|
45
|
+
let currentDir = path.resolve(startDir);
|
|
46
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
47
|
+
if (existsSync(path.join(currentDir, "pnpm-workspace.yaml"))) return currentDir;
|
|
48
|
+
currentDir = path.dirname(currentDir);
|
|
49
|
+
}
|
|
50
|
+
throw new Error("Cannot resolve workspace root. Run this command inside the monorepo.");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/template/materialize.ts
|
|
55
|
+
/**
|
|
56
|
+
* 只在 monorepo 内成立的 package.json 字段。生成的是一个全新的私有应用,继承 skyroc-admin 的 homepage / repository /
|
|
57
|
+
* author 只会让新项目一开始就带着错误的身份信息。
|
|
58
|
+
*/
|
|
59
|
+
const DROPPED_PACKAGE_FIELDS = [
|
|
60
|
+
"author",
|
|
61
|
+
"bugs",
|
|
62
|
+
"homepage",
|
|
63
|
+
"keywords",
|
|
64
|
+
"repository",
|
|
65
|
+
"website"
|
|
66
|
+
];
|
|
67
|
+
/**
|
|
68
|
+
* 这些 script 靠 `pnpm --filter` 去构建同一个 workspace 里的 `@skyroc/web-admin-vite`。独立工程里该包直接来自 registry,
|
|
69
|
+
* 已经是构建产物,pre* 钩子只会因为 filter 不到包而失败。
|
|
70
|
+
*/
|
|
71
|
+
const DROPPED_PACKAGE_SCRIPTS = [
|
|
72
|
+
"build:admin-vite",
|
|
73
|
+
"prebuild",
|
|
74
|
+
"prebuild:test",
|
|
75
|
+
"predev",
|
|
76
|
+
"predev:prod",
|
|
77
|
+
"prepreview"
|
|
78
|
+
];
|
|
79
|
+
const STANDALONE_REWRITES = [{
|
|
80
|
+
file: "vite.config.ts",
|
|
81
|
+
hint: "vite.config.ts 里没找到 monorepo 的 shellAlias 覆盖块,请同步更新 STANDALONE_REWRITES。",
|
|
82
|
+
pattern: /,\s*\/\/ monorepo 内 shell 源码[^\n]*\n\s*resolve:\s*\{\s*shellAlias:[^}]*\}/,
|
|
83
|
+
replacement: ""
|
|
84
|
+
}, {
|
|
85
|
+
file: "uno.config.ts",
|
|
86
|
+
hint: "uno.config.ts 的 filesystem 不再是预期的 monorepo 相对路径,请同步更新 STANDALONE_REWRITES。",
|
|
87
|
+
pattern: /filesystem:\s*\[[^\]]*\]/,
|
|
88
|
+
replacement: "filesystem: ['./node_modules/@skyroc/web-ui/dist/**/*.{js,mjs,cjs}']"
|
|
89
|
+
}];
|
|
90
|
+
const GITIGNORE = `node_modules
|
|
91
|
+
dist
|
|
92
|
+
dist-ssr
|
|
93
|
+
coverage
|
|
94
|
+
|
|
95
|
+
.turbo
|
|
96
|
+
.tanstack
|
|
97
|
+
*.tsbuildinfo
|
|
98
|
+
|
|
99
|
+
.env.local
|
|
100
|
+
.env.*.local
|
|
101
|
+
|
|
102
|
+
.DS_Store
|
|
103
|
+
*.log
|
|
104
|
+
`;
|
|
105
|
+
function createReadme(packageName, description) {
|
|
106
|
+
return `# ${packageName}
|
|
107
|
+
|
|
108
|
+
${description}
|
|
109
|
+
|
|
110
|
+
基于 [skyroc-admin](https://github.com/Ohh-889/skyroc-admin) 模板,由 \`sa create-admin\` 生成。
|
|
111
|
+
|
|
112
|
+
## 开始
|
|
113
|
+
|
|
114
|
+
\`\`\`bash
|
|
115
|
+
pnpm install
|
|
116
|
+
pnpm dev
|
|
117
|
+
\`\`\`
|
|
118
|
+
|
|
119
|
+
## 命令
|
|
120
|
+
|
|
121
|
+
| 命令 | 说明 |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| \`pnpm dev\` | 启动开发服务器(test 环境变量) |
|
|
124
|
+
| \`pnpm build\` | 生产构建 |
|
|
125
|
+
| \`pnpm preview\` | 预览构建产物 |
|
|
126
|
+
| \`pnpm lint\` | oxlint 检查并自动修复 |
|
|
127
|
+
| \`pnpm typecheck\` | TypeScript 类型检查 |
|
|
128
|
+
|
|
129
|
+
## 目录
|
|
130
|
+
|
|
131
|
+
业务页面放在 \`src/pages/(admin)\` 下,路由由 TanStack Router 基于文件结构自动生成。
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
async function materializePackageJson(options) {
|
|
135
|
+
const { description, meta, packageName, targetDir } = options;
|
|
136
|
+
const packageJsonPath = path.join(targetDir, "package.json");
|
|
137
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
138
|
+
packageJson.name = packageName;
|
|
139
|
+
packageJson.description = description;
|
|
140
|
+
packageJson.version = "0.0.0";
|
|
141
|
+
packageJson.private = true;
|
|
142
|
+
packageJson.dependencies = meta.dependencies;
|
|
143
|
+
packageJson.devDependencies = meta.devDependencies;
|
|
144
|
+
for (const field of DROPPED_PACKAGE_FIELDS) delete packageJson[field];
|
|
145
|
+
const scripts = packageJson.scripts;
|
|
146
|
+
if (scripts) for (const script of DROPPED_PACKAGE_SCRIPTS) delete scripts[script];
|
|
147
|
+
if (Object.keys(meta.overrides).length > 0) packageJson.pnpm = { overrides: meta.overrides };
|
|
148
|
+
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
149
|
+
}
|
|
150
|
+
async function materializeJsonConfig(targetDir, config) {
|
|
151
|
+
const content = {
|
|
152
|
+
$schema: config.schema,
|
|
153
|
+
...config.content
|
|
154
|
+
};
|
|
155
|
+
await writeFile(path.join(targetDir, config.file), `${JSON.stringify(content, null, 2)}\n`);
|
|
156
|
+
}
|
|
157
|
+
/** 返回未命中的重写规则,让调用方决定是告警还是失败。 */
|
|
158
|
+
async function applyStandaloneRewrites(targetDir) {
|
|
159
|
+
const missed = [];
|
|
160
|
+
await Promise.all(STANDALONE_REWRITES.map(async (rewrite) => {
|
|
161
|
+
const filePath = path.join(targetDir, rewrite.file);
|
|
162
|
+
const content = await readFile(filePath, "utf8");
|
|
163
|
+
if (!rewrite.pattern.test(content)) {
|
|
164
|
+
missed.push(rewrite);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
await writeFile(filePath, content.replace(rewrite.pattern, rewrite.replacement));
|
|
168
|
+
}));
|
|
169
|
+
return missed;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* shell 源码在生成项目里位于 `src/framework`,monorepo 里位于 `packages/web/admin`。
|
|
173
|
+
* 展平后的 tsconfig 还带着 monorepo 的映射,这里改写成独立工程的路径。
|
|
174
|
+
*/
|
|
175
|
+
function rewriteShellTsconfig(tsconfig) {
|
|
176
|
+
const rewritten = structuredClone(tsconfig);
|
|
177
|
+
const paths = (rewritten.compilerOptions ?? {}).paths;
|
|
178
|
+
if (paths?.["@shell/*"]) paths["@shell/*"] = ["./src/framework/*"];
|
|
179
|
+
if (Array.isArray(rewritten.include)) rewritten.include = rewritten.include.filter((entry) => typeof entry !== "string" || !entry.includes("packages/web/admin"));
|
|
180
|
+
return rewritten;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 把同步期解析好的数据套用到生成目录,产出一个脱离 monorepo 也能 install 的应用。
|
|
184
|
+
*
|
|
185
|
+
* 返回需要提醒用户的问题:命中失败的重写规则、以及依赖里仍然指向未发布 workspace 包的部分。这两类问题都不应该静默——
|
|
186
|
+
* 它们最终会以一句莫名其妙的 install 报错砸到用户脸上。
|
|
187
|
+
*/
|
|
188
|
+
async function materializeStandaloneApp(options) {
|
|
189
|
+
const { description, meta, packageName, targetDir } = options;
|
|
190
|
+
await materializePackageJson(options);
|
|
191
|
+
await materializeJsonConfig(targetDir, {
|
|
192
|
+
content: rewriteShellTsconfig(meta.tsconfig),
|
|
193
|
+
file: "tsconfig.json",
|
|
194
|
+
schema: "https://json.schemastore.org/tsconfig"
|
|
195
|
+
});
|
|
196
|
+
await materializeJsonConfig(targetDir, {
|
|
197
|
+
content: meta.oxlintConfig,
|
|
198
|
+
file: ".oxlintrc.json",
|
|
199
|
+
schema: "./node_modules/oxlint/configuration_schema.json"
|
|
200
|
+
});
|
|
201
|
+
const missedRewrites = await applyStandaloneRewrites(targetDir);
|
|
202
|
+
await writeFile(path.join(targetDir, ".gitignore"), GITIGNORE);
|
|
203
|
+
await writeFile(path.join(targetDir, "README.md"), createReadme(packageName, description));
|
|
204
|
+
return {
|
|
205
|
+
missedRewrites: missedRewrites.map((rewrite) => `${rewrite.file}: ${rewrite.hint}`),
|
|
206
|
+
unpublishedPackages: meta.unpublishedPackages
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/template/meta.ts
|
|
212
|
+
/** sidecar 文件名,与 `templates/admin/` 平级存放,避免污染快照目录的逐字节比对。 */
|
|
213
|
+
const TEMPLATE_META_FILE = "admin.meta.json";
|
|
214
|
+
/** 按 key 排序,保证同样的输入产出同样的字节,`--check` 才能靠内容比对判断是否落后。 */
|
|
215
|
+
function sortRecord(record) {
|
|
216
|
+
return Object.fromEntries(Object.entries(record).toSorted(([a], [b]) => a.localeCompare(b)));
|
|
217
|
+
}
|
|
218
|
+
function stringifyTemplateMeta(meta) {
|
|
219
|
+
const normalized = {
|
|
220
|
+
dependencies: sortRecord(meta.dependencies),
|
|
221
|
+
devDependencies: sortRecord(meta.devDependencies),
|
|
222
|
+
oxlintConfig: meta.oxlintConfig,
|
|
223
|
+
overrides: sortRecord(meta.overrides),
|
|
224
|
+
tsconfig: meta.tsconfig,
|
|
225
|
+
unpublishedPackages: meta.unpublishedPackages.toSorted((a, b) => a.localeCompare(b))
|
|
226
|
+
};
|
|
227
|
+
return `${JSON.stringify(normalized, null, 2)}\n`;
|
|
228
|
+
}
|
|
229
|
+
async function readTemplateMeta(metaPath) {
|
|
230
|
+
const content = await readFile(metaPath, "utf8");
|
|
231
|
+
return JSON.parse(content);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/commands/create-admin.ts
|
|
236
|
+
const TEMPLATE_NAME$1 = "admin";
|
|
237
|
+
function normalizePackageName(name) {
|
|
238
|
+
const normalizedName = name.trim().replaceAll("_", "-").replace(/\s+/g, "-").replace(/[^a-zA-Z0-9-@/.]/g, "").toLowerCase();
|
|
239
|
+
if (!normalizedName) throw new Error("Admin app name is required.");
|
|
240
|
+
if (!/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(normalizedName)) throw new Error(`Invalid package name: ${name}`);
|
|
241
|
+
return normalizedName;
|
|
242
|
+
}
|
|
243
|
+
function toTitle(name) {
|
|
244
|
+
return name.replace(/^@[^/]+\//, "").split(/[-_\s]+/).filter(Boolean).map((item) => `${item.charAt(0).toUpperCase()}${item.slice(1)}`).join(" ");
|
|
245
|
+
}
|
|
246
|
+
function toStoragePrefix(name) {
|
|
247
|
+
return `${name.replace(/^@[^/]+\//, "").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase() || "ADMIN"}_`;
|
|
248
|
+
}
|
|
249
|
+
function getTemplateDir() {
|
|
250
|
+
return path.join(getPackageRoot(), "templates", TEMPLATE_NAME$1);
|
|
251
|
+
}
|
|
252
|
+
function getShellTemplateDir() {
|
|
253
|
+
return path.join(getPackageRoot(), "templates", "admin-shell");
|
|
254
|
+
}
|
|
255
|
+
function getTemplateMetaPath() {
|
|
256
|
+
return path.join(getPackageRoot(), "templates", TEMPLATE_META_FILE);
|
|
257
|
+
}
|
|
258
|
+
async function isDirectoryEmpty(dir) {
|
|
259
|
+
if (!existsSync(dir)) return true;
|
|
260
|
+
return (await readdir(dir)).length === 0;
|
|
261
|
+
}
|
|
262
|
+
function replaceEnvValue(content, key, value) {
|
|
263
|
+
const escapedValue = value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
|
264
|
+
const regexp = new RegExp(`^${key}=.*$`, "m");
|
|
265
|
+
if (!regexp.test(content)) return `${content.trimEnd()}\n${key}=${escapedValue}\n`;
|
|
266
|
+
return content.replace(regexp, `${key}=${escapedValue}`);
|
|
267
|
+
}
|
|
268
|
+
async function updatePackageJson(targetDir, packageName, description) {
|
|
269
|
+
const packageJsonPath = path.join(targetDir, "package.json");
|
|
270
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
271
|
+
packageJson.name = packageName;
|
|
272
|
+
packageJson.description = description;
|
|
273
|
+
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
274
|
+
}
|
|
275
|
+
async function updateEnvFile(options) {
|
|
276
|
+
const { description, storagePrefix, targetDir, title } = options;
|
|
277
|
+
const envPath = path.join(targetDir, ".env");
|
|
278
|
+
let env = await readFile(envPath, "utf8");
|
|
279
|
+
env = replaceEnvValue(env, "VITE_APP_TITLE", title);
|
|
280
|
+
env = replaceEnvValue(env, "VITE_APP_DESC", description);
|
|
281
|
+
env = replaceEnvValue(env, "VITE_STORAGE_PREFIX", storagePrefix);
|
|
282
|
+
await writeFile(envPath, env);
|
|
283
|
+
}
|
|
284
|
+
async function installDependencies(cwd) {
|
|
285
|
+
const { execa } = await import("execa");
|
|
286
|
+
await execa("pnpm", ["install"], {
|
|
287
|
+
cwd,
|
|
288
|
+
stdio: "inherit"
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/** 独立模式下把 monorepo 协议物化掉,并把物化过程中发现的隐患打印出来。 */
|
|
292
|
+
async function applyStandaloneMaterialization(targetDir, packageName, description) {
|
|
293
|
+
const metaPath = getTemplateMetaPath();
|
|
294
|
+
if (!existsSync(metaPath)) throw new Error(`Template metadata is missing: ${metaPath}\nRun "pnpm sa sync-admin-template" to regenerate it, or pass --workspace to keep the monorepo protocols.`);
|
|
295
|
+
const { missedRewrites, unpublishedPackages } = await materializeStandaloneApp({
|
|
296
|
+
description,
|
|
297
|
+
meta: await readTemplateMeta(metaPath),
|
|
298
|
+
packageName,
|
|
299
|
+
targetDir
|
|
300
|
+
});
|
|
301
|
+
for (const missed of missedRewrites) console.log(yellow(`warning ${missed}`));
|
|
302
|
+
if (unpublishedPackages.length > 0) console.log(yellow(`warning these workspace packages are marked "private": true and cannot be installed from the registry: ${unpublishedPackages.join(", ")}`));
|
|
303
|
+
}
|
|
304
|
+
async function createAdminTemplate(name, options = {}) {
|
|
305
|
+
const packageName = normalizePackageName(name);
|
|
306
|
+
const title = options.title || toTitle(packageName);
|
|
307
|
+
const description = options.description || `${title} admin application`;
|
|
308
|
+
const storagePrefix = toStoragePrefix(packageName);
|
|
309
|
+
const directoryName = packageName.replace(/^@[^/]+\//, "");
|
|
310
|
+
const targetDir = path.resolve(process.cwd(), options.target || path.join("apps", directoryName));
|
|
311
|
+
const templateDir = getTemplateDir();
|
|
312
|
+
if (!existsSync(templateDir)) throw new Error(`Admin template is missing: ${templateDir}`);
|
|
313
|
+
if (existsSync(targetDir) && !await isDirectoryEmpty(targetDir)) {
|
|
314
|
+
if (!options.force) throw new Error(`Target directory is not empty: ${targetDir}. Use --force to overwrite it.`);
|
|
315
|
+
await rm(targetDir, {
|
|
316
|
+
force: true,
|
|
317
|
+
recursive: true
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
await mkdir(path.dirname(targetDir), { recursive: true });
|
|
321
|
+
await cp(templateDir, targetDir, { recursive: true });
|
|
322
|
+
if (options.workspace) await updatePackageJson(targetDir, packageName, description);
|
|
323
|
+
else {
|
|
324
|
+
const shellTemplateDir = getShellTemplateDir();
|
|
325
|
+
if (!existsSync(shellTemplateDir)) throw new Error(`Admin shell template is missing: ${shellTemplateDir}\nRun "pnpm sa sync-admin-template" to regenerate it.`);
|
|
326
|
+
await cp(shellTemplateDir, path.join(targetDir, "src", "framework"), { recursive: true });
|
|
327
|
+
await applyStandaloneMaterialization(targetDir, packageName, description);
|
|
328
|
+
}
|
|
329
|
+
await updateEnvFile({
|
|
330
|
+
description,
|
|
331
|
+
storagePrefix,
|
|
332
|
+
targetDir,
|
|
333
|
+
title
|
|
334
|
+
});
|
|
335
|
+
if (options.install) await installDependencies(options.workspace ? process.cwd() : targetDir);
|
|
336
|
+
console.log(green(`Created admin app: ${packageName}`));
|
|
337
|
+
console.log(`${cyan("mode")} ${options.workspace ? "workspace (monorepo protocols kept)" : "standalone"}`);
|
|
338
|
+
console.log(`${cyan("target")} ${targetDir}`);
|
|
339
|
+
if (!options.install) console.log(`${cyan("next")} ${options.workspace ? "pnpm install (from the workspace root)" : `pnpm --dir ${targetDir} install`}`);
|
|
340
|
+
console.log(yellow("The template starts with a minimal Home page; add product routes under src/pages/(admin)."));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/locales/index.ts
|
|
345
|
+
const locales = {
|
|
346
|
+
"en-us": {
|
|
347
|
+
gitCommitMessages: {
|
|
348
|
+
description: `Please enter a description (add prefix ${yellow("!")} to indicate breaking change)`,
|
|
349
|
+
emptyDescription: `${bgRed(" ERROR ")} ${red("commit description must not be empty.")}`,
|
|
350
|
+
scopes: "Please select a scope",
|
|
351
|
+
types: "Please select a type"
|
|
352
|
+
},
|
|
353
|
+
gitCommitScopes: [
|
|
354
|
+
["projects", "project"],
|
|
355
|
+
["packages", "packages"],
|
|
356
|
+
["components", "components"],
|
|
357
|
+
["hooks", "hook functions"],
|
|
358
|
+
["utils", "utils functions"],
|
|
359
|
+
["types", "TS declaration"],
|
|
360
|
+
["styles", "style"],
|
|
361
|
+
["deps", "project dependencies"],
|
|
362
|
+
["release", "release project"],
|
|
363
|
+
["other", "other changes"]
|
|
364
|
+
],
|
|
365
|
+
gitCommitTypes: [
|
|
366
|
+
["feat", "A new feature"],
|
|
367
|
+
["feat-wip", "Features in development, such as partial code for a certain feature"],
|
|
368
|
+
["fix", "A bug fix"],
|
|
369
|
+
["docs", "Documentation only changes"],
|
|
370
|
+
["typo", "Code or document corrections, such as spelling errors"],
|
|
371
|
+
["style", "Changes that do not affect the meaning of the code"],
|
|
372
|
+
["refactor", "A code change that neither fixes a bug nor adds a feature"],
|
|
373
|
+
["perf", "A code change that improves performance"],
|
|
374
|
+
["optimize", "A code change that optimizes code quality"],
|
|
375
|
+
["test", "Adding missing tests or correcting existing tests"],
|
|
376
|
+
["build", "Changes that affect the build system or external dependencies"],
|
|
377
|
+
["ci", "Changes to our CI configuration files and scripts"],
|
|
378
|
+
["chore", "Other changes that don't modify src or test files"],
|
|
379
|
+
["revert", "Reverts a previous commit"]
|
|
380
|
+
],
|
|
381
|
+
gitCommitVerify: `${bgRed(" ERROR ")} ${red("git commit message must match the Conventional Commits standard!")}\n\n${green("Recommended to use the command `pnpm commit` to generate Conventional Commits compliant commit information.\nGet more info about Conventional Commits, follow this link: https://conventionalcommits.org")}`
|
|
382
|
+
},
|
|
383
|
+
"zh-cn": {
|
|
384
|
+
gitCommitMessages: {
|
|
385
|
+
description: `请输入描述信息(${yellow("!")}开头表示破坏性改动`,
|
|
386
|
+
emptyDescription: `${bgRed(" 错误 ")} ${red("提交描述不能为空。")}`,
|
|
387
|
+
scopes: "请选择提交范围",
|
|
388
|
+
types: "请选择提交类型"
|
|
389
|
+
},
|
|
390
|
+
gitCommitScopes: [
|
|
391
|
+
["projects", "项目"],
|
|
392
|
+
["packages", "包"],
|
|
393
|
+
["components", "组件"],
|
|
394
|
+
["hooks", "钩子函数"],
|
|
395
|
+
["utils", "工具函数"],
|
|
396
|
+
["types", "TS类型声明"],
|
|
397
|
+
["styles", "代码风格"],
|
|
398
|
+
["deps", "项目依赖"],
|
|
399
|
+
["release", "发布项目新版本"],
|
|
400
|
+
["other", "其他的变更"]
|
|
401
|
+
],
|
|
402
|
+
gitCommitTypes: [
|
|
403
|
+
["feat", "新功能"],
|
|
404
|
+
["feat-wip", "开发中的功能,比如某功能的部分代码"],
|
|
405
|
+
["fix", "修复Bug"],
|
|
406
|
+
["docs", "只涉及文档更新"],
|
|
407
|
+
["typo", "代码或文档勘误,比如错误拼写"],
|
|
408
|
+
["style", "修改代码风格,不影响代码含义的变更"],
|
|
409
|
+
["refactor", "代码重构,既不修复 bug 也不添加功能的代码变更"],
|
|
410
|
+
["perf", "可提高性能的代码更改"],
|
|
411
|
+
["optimize", "优化代码质量的代码更改"],
|
|
412
|
+
["test", "添加缺失的测试或更正现有测试"],
|
|
413
|
+
["build", "影响构建系统或外部依赖项的更改"],
|
|
414
|
+
["ci", "对 CI 配置文件和脚本的更改"],
|
|
415
|
+
["chore", "没有修改src或测试文件的其他变更"],
|
|
416
|
+
["revert", "还原先前的提交"]
|
|
417
|
+
],
|
|
418
|
+
gitCommitVerify: `${bgRed(" 错误 ")} ${red("git 提交信息必须符合 Conventional Commits 标准!")}\n\n${green("推荐使用命令 `pnpm commit` 生成符合 Conventional Commits 标准的提交信息。\n获取有关 Conventional Commits 的更多信息,请访问此链接: https://conventionalcommits.org")}`
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region src/commands/git-commit.ts
|
|
424
|
+
const { prompt } = enquirer;
|
|
425
|
+
/**
|
|
426
|
+
* 构造 Conventional Commits 的校验正则。
|
|
427
|
+
*
|
|
428
|
+
* 必须锚定首尾并把 type 限定成白名单:不锚定的话任何含有 `xx: yy` 的句子都能蒙混过关,不限白名单的话
|
|
429
|
+
* `banana: 修点东西` 也算合规——那这个校验就等于没有。
|
|
430
|
+
*/
|
|
431
|
+
function createCommitMessageRegExp(types) {
|
|
432
|
+
const alternatives = types.toSorted((a, b) => b.length - a.length).join("|");
|
|
433
|
+
return /* @__PURE__ */ new RegExp(`^(?:${alternatives})(?:\\([^()]+\\))?!?: .+$`);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Git commit with Conventional Commits standard
|
|
437
|
+
*
|
|
438
|
+
* @param lang
|
|
439
|
+
*/
|
|
440
|
+
async function gitCommit(lang = "en-us") {
|
|
441
|
+
const { gitCommitMessages, gitCommitScopes, gitCommitTypes } = locales[lang];
|
|
442
|
+
const typesChoices = gitCommitTypes.map(([value, msg]) => {
|
|
443
|
+
return {
|
|
444
|
+
message: `${`${value}:`.padEnd(12)}${msg}`,
|
|
445
|
+
name: value
|
|
446
|
+
};
|
|
447
|
+
});
|
|
448
|
+
const scopesChoices = gitCommitScopes.map(([value, msg]) => ({
|
|
449
|
+
message: `${value.padEnd(30)} (${msg})`,
|
|
450
|
+
name: value
|
|
451
|
+
}));
|
|
452
|
+
const result = await prompt([
|
|
453
|
+
{
|
|
454
|
+
choices: typesChoices,
|
|
455
|
+
message: gitCommitMessages.types,
|
|
456
|
+
name: "types",
|
|
457
|
+
type: "select"
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
choices: scopesChoices,
|
|
461
|
+
message: gitCommitMessages.scopes,
|
|
462
|
+
name: "scopes",
|
|
463
|
+
type: "select"
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
message: gitCommitMessages.description,
|
|
467
|
+
name: "description",
|
|
468
|
+
type: "text"
|
|
469
|
+
}
|
|
470
|
+
]);
|
|
471
|
+
const breaking = result.description.startsWith("!") ? "!" : "";
|
|
472
|
+
const description = result.description.replace(/^!/, "").trim();
|
|
473
|
+
if (!description) throw new Error(gitCommitMessages.emptyDescription);
|
|
474
|
+
await execCommand("git", [
|
|
475
|
+
"commit",
|
|
476
|
+
"-m",
|
|
477
|
+
`${result.types}(${result.scopes})${breaking}: ${description}`
|
|
478
|
+
], { stdio: "inherit" });
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* 读取本次提交的信息文件。
|
|
482
|
+
*
|
|
483
|
+
* 走 `git rev-parse --git-path` 而不是手工拼 `<toplevel>/.git/COMMIT_EDITMSG`:在 worktree 里 `.git` 是文件不是目录,
|
|
484
|
+
* 手工拼出来的路径根本不存在。
|
|
485
|
+
*/
|
|
486
|
+
async function readCommitMessage() {
|
|
487
|
+
const gitMsgPath = await execCommand("git", [
|
|
488
|
+
"rev-parse",
|
|
489
|
+
"--git-path",
|
|
490
|
+
"COMMIT_EDITMSG"
|
|
491
|
+
]);
|
|
492
|
+
return readFileSync(path.resolve(process.cwd(), gitMsgPath), "utf8").trim();
|
|
493
|
+
}
|
|
494
|
+
/** Git commit message verify */
|
|
495
|
+
async function gitCommitVerify(lang = "en-us", ignores = []) {
|
|
496
|
+
const commitMsg = await readCommitMessage();
|
|
497
|
+
if (ignores.some((regExp) => regExp.test(commitMsg))) return;
|
|
498
|
+
const { gitCommitTypes, gitCommitVerify: errorMsg } = locales[lang];
|
|
499
|
+
const [subject = ""] = commitMsg.split("\n");
|
|
500
|
+
if (!createCommitMessageRegExp(gitCommitTypes.map(([value]) => value)).test(subject)) throw new Error(errorMsg);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
//#endregion
|
|
504
|
+
//#region src/commands/release.ts
|
|
505
|
+
async function release(execute = "pnpm sa changelog", push = true) {
|
|
506
|
+
await versionBump({
|
|
507
|
+
all: true,
|
|
508
|
+
commit: "chore(projects): release v%s",
|
|
509
|
+
execute,
|
|
510
|
+
files: ["**/package.json", "!**/node_modules/**"],
|
|
511
|
+
push,
|
|
512
|
+
tag: true
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/template/resolve.ts
|
|
518
|
+
const SKIP_DIRS = new Set([
|
|
519
|
+
".git",
|
|
520
|
+
".turbo",
|
|
521
|
+
"dist",
|
|
522
|
+
"node_modules"
|
|
523
|
+
]);
|
|
524
|
+
/**
|
|
525
|
+
* `@skyroc/tsconfig` 的内容已经被展平进生成应用的 tsconfig.json,再留一条依赖只会指向一个装不上的私有包。
|
|
526
|
+
* 其余 workspace 依赖都必须保留——vite.config、uno.config 真的会 import 它们。
|
|
527
|
+
*/
|
|
528
|
+
const INLINED_DEV_DEPENDENCIES = new Set(["@skyroc/tsconfig"]);
|
|
529
|
+
/**
|
|
530
|
+
* shell 源码会被 `sa create-admin` 原样复制进生成项目的 `src/framework`(经 `@shell` 别名访问),
|
|
531
|
+
* 依赖里不能再指向这个私有 workspace 包。
|
|
532
|
+
*/
|
|
533
|
+
const INLINED_DEPENDENCIES = new Set(["@skyroc/web-admin-shell"]);
|
|
534
|
+
/**
|
|
535
|
+
* 把 pnpm 的依赖协议解析成 registry 能识别的版本区间。
|
|
536
|
+
*
|
|
537
|
+
* `workspace:*` 走 `^version` 而不是 pnpm publish 时的精确版本——脚手架产出的是一个新项目,它应该能跟着上游收补丁。
|
|
538
|
+
*/
|
|
539
|
+
function resolveDependencySpecifier(name, specifier, context) {
|
|
540
|
+
if (specifier.startsWith("workspace:")) {
|
|
541
|
+
const range = specifier.slice(10);
|
|
542
|
+
const workspacePackage = context.workspacePackages.get(name);
|
|
543
|
+
if (!workspacePackage) throw new Error(`Cannot resolve workspace dependency "${name}": no such package in the workspace.`);
|
|
544
|
+
if (range === "*") return `^${workspacePackage.version}`;
|
|
545
|
+
if (range === "^" || range === "~") return `${range}${workspacePackage.version}`;
|
|
546
|
+
return range;
|
|
547
|
+
}
|
|
548
|
+
if (specifier.startsWith("catalog:")) {
|
|
549
|
+
const catalogName = specifier.slice(8).trim();
|
|
550
|
+
const catalog = catalogName ? context.catalog.catalogs[catalogName] : context.catalog.defaultCatalog;
|
|
551
|
+
if (!catalog) throw new Error(`Cannot resolve "${name}": catalog "${catalogName || "default"}" is not defined.`);
|
|
552
|
+
const version = catalog[name];
|
|
553
|
+
if (!version) throw new Error(`Cannot resolve "${name}": not listed in catalog "${catalogName || "default"}".`);
|
|
554
|
+
return version;
|
|
555
|
+
}
|
|
556
|
+
return specifier;
|
|
557
|
+
}
|
|
558
|
+
function resolveDependencies(dependencies, context, omit = /* @__PURE__ */ new Set()) {
|
|
559
|
+
const resolved = {};
|
|
560
|
+
const unpublished = [];
|
|
561
|
+
const entries = Object.entries(dependencies ?? {}).filter(([name]) => !omit.has(name));
|
|
562
|
+
for (const [name, specifier] of entries) {
|
|
563
|
+
resolved[name] = resolveDependencySpecifier(name, specifier, context);
|
|
564
|
+
if (specifier.startsWith("workspace:") && context.workspacePackages.get(name)?.isPrivate) unpublished.push(name);
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
resolved,
|
|
568
|
+
unpublished
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
/** tsconfig 的 extends 是「compilerOptions 浅合并、其余字段子覆盖父」。 */
|
|
572
|
+
function mergeTsconfig(base, child) {
|
|
573
|
+
const merged = {
|
|
574
|
+
...base,
|
|
575
|
+
...child
|
|
576
|
+
};
|
|
577
|
+
merged.compilerOptions = {
|
|
578
|
+
...base.compilerOptions ?? {},
|
|
579
|
+
...child.compilerOptions ?? {}
|
|
580
|
+
};
|
|
581
|
+
delete merged.extends;
|
|
582
|
+
delete merged.$schema;
|
|
583
|
+
delete merged.display;
|
|
584
|
+
return merged;
|
|
585
|
+
}
|
|
586
|
+
/** oxlint 的 extends 是「rules 浅合并、overrides 按父→子拼接、其余字段子覆盖父」。 */
|
|
587
|
+
function mergeOxlintConfig(base, child) {
|
|
588
|
+
const merged = {
|
|
589
|
+
...base,
|
|
590
|
+
...child
|
|
591
|
+
};
|
|
592
|
+
merged.rules = {
|
|
593
|
+
...base.rules ?? {},
|
|
594
|
+
...child.rules ?? {}
|
|
595
|
+
};
|
|
596
|
+
const baseOverrides = base.overrides ?? [];
|
|
597
|
+
const childOverrides = child.overrides ?? [];
|
|
598
|
+
if (baseOverrides.length > 0 || childOverrides.length > 0) merged.overrides = [...baseOverrides, ...childOverrides];
|
|
599
|
+
delete merged.extends;
|
|
600
|
+
delete merged.$schema;
|
|
601
|
+
return merged;
|
|
602
|
+
}
|
|
603
|
+
async function readJsonFile(filePath) {
|
|
604
|
+
const withoutComments = (await readFile(filePath, "utf8")).replaceAll(/^\s*\/\/.*$/gm, "").replaceAll(/,(\s*[\]}])/g, "$1");
|
|
605
|
+
return JSON.parse(withoutComments);
|
|
606
|
+
}
|
|
607
|
+
function resolveExtendsPath(specifier, fromDir) {
|
|
608
|
+
if (specifier.startsWith(".")) return path.resolve(fromDir, specifier);
|
|
609
|
+
return createRequire(path.join(fromDir, "package.json")).resolve(specifier);
|
|
610
|
+
}
|
|
611
|
+
/** 沿 extends 链自底向上展平一份 JSON 配置。 */
|
|
612
|
+
async function flattenJsonConfig(filePath, merge) {
|
|
613
|
+
const config = await readJsonFile(filePath);
|
|
614
|
+
const extendsField = config.extends;
|
|
615
|
+
const parents = Array.isArray(extendsField) ? extendsField : [extendsField].filter(Boolean);
|
|
616
|
+
let merged = {};
|
|
617
|
+
for (const parent of parents) {
|
|
618
|
+
const parentConfig = await flattenJsonConfig(resolveExtendsPath(parent, path.dirname(filePath)), merge);
|
|
619
|
+
merged = merge(merged, parentConfig);
|
|
620
|
+
}
|
|
621
|
+
return merge(merged, config);
|
|
622
|
+
}
|
|
623
|
+
function segmentToRegExp(segment) {
|
|
624
|
+
const source = segment.split("*").map((part) => part.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`)).join("[^/]*");
|
|
625
|
+
return /* @__PURE__ */ new RegExp(`^${source}$`);
|
|
626
|
+
}
|
|
627
|
+
/** 逐段匹配 pnpm-workspace.yaml 的 packages glob,支持 `*` 与 `**`。 */
|
|
628
|
+
function matchesWorkspacePattern(relativePath, pattern) {
|
|
629
|
+
const pathSegments = relativePath.split("/");
|
|
630
|
+
const patternSegments = pattern.split("/");
|
|
631
|
+
function match(pathIndex, patternIndex) {
|
|
632
|
+
if (patternIndex === patternSegments.length) return pathIndex === pathSegments.length;
|
|
633
|
+
const patternSegment = patternSegments[patternIndex];
|
|
634
|
+
if (patternSegment === "**") {
|
|
635
|
+
for (let skip = pathIndex; skip <= pathSegments.length; skip += 1) if (match(skip, patternIndex + 1)) return true;
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
if (pathIndex === pathSegments.length) return false;
|
|
639
|
+
return segmentToRegExp(patternSegment).test(pathSegments[pathIndex]) && match(pathIndex + 1, patternIndex + 1);
|
|
640
|
+
}
|
|
641
|
+
return match(0, 0);
|
|
642
|
+
}
|
|
643
|
+
async function readWorkspaceCatalog(workspaceRoot) {
|
|
644
|
+
const workspaceFile = parseYAML(await readFile(path.join(workspaceRoot, "pnpm-workspace.yaml"), "utf8"));
|
|
645
|
+
return {
|
|
646
|
+
catalogs: workspaceFile.catalogs ?? {},
|
|
647
|
+
defaultCatalog: workspaceFile.catalog ?? {},
|
|
648
|
+
overrides: workspaceFile.overrides ?? {},
|
|
649
|
+
packages: workspaceFile.packages ?? []
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
async function collectWorkspacePackages(workspaceRoot, patterns) {
|
|
653
|
+
const isWorkspaceDir = (relativePath) => patterns.some((pattern) => matchesWorkspacePattern(relativePath, pattern));
|
|
654
|
+
const maxDepth = Math.max(...patterns.map((pattern) => pattern.split("/").length), 1);
|
|
655
|
+
const packages = /* @__PURE__ */ new Map();
|
|
656
|
+
async function walk(currentDir, depth) {
|
|
657
|
+
const directories = (await readdir(currentDir, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !SKIP_DIRS.has(entry.name));
|
|
658
|
+
await Promise.all(directories.map(async (entry) => {
|
|
659
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
660
|
+
if (isWorkspaceDir(path.relative(workspaceRoot, fullPath).split(path.sep).join("/"))) {
|
|
661
|
+
const packageJsonPath = path.join(fullPath, "package.json");
|
|
662
|
+
if (existsSync(packageJsonPath)) {
|
|
663
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
664
|
+
if (packageJson.name) packages.set(packageJson.name, {
|
|
665
|
+
isPrivate: Boolean(packageJson.private),
|
|
666
|
+
version: packageJson.version ?? "0.0.0"
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (depth < maxDepth) await walk(fullPath, depth + 1);
|
|
671
|
+
}));
|
|
672
|
+
}
|
|
673
|
+
await walk(workspaceRoot, 1);
|
|
674
|
+
return packages;
|
|
675
|
+
}
|
|
676
|
+
/** 在 monorepo 内解析出模板的全部物化数据。只能在仓库内调用。 */
|
|
677
|
+
async function resolveTemplateMeta(options) {
|
|
678
|
+
const { sourceDir, workspaceRoot } = options;
|
|
679
|
+
const catalog = await readWorkspaceCatalog(workspaceRoot);
|
|
680
|
+
const context = {
|
|
681
|
+
catalog,
|
|
682
|
+
workspacePackages: await collectWorkspacePackages(workspaceRoot, catalog.packages)
|
|
683
|
+
};
|
|
684
|
+
const packageJson = JSON.parse(await readFile(path.join(sourceDir, "package.json"), "utf8"));
|
|
685
|
+
const dependencies = resolveDependencies(packageJson.dependencies, context, INLINED_DEPENDENCIES);
|
|
686
|
+
const devDependencies = resolveDependencies(packageJson.devDependencies, context, INLINED_DEV_DEPENDENCIES);
|
|
687
|
+
const [tsconfig, oxlintConfig] = await Promise.all([flattenJsonConfig(path.join(sourceDir, "tsconfig.json"), mergeTsconfig), flattenJsonConfig(path.join(sourceDir, ".oxlintrc.json"), mergeOxlintConfig)]);
|
|
688
|
+
return {
|
|
689
|
+
dependencies: dependencies.resolved,
|
|
690
|
+
devDependencies: devDependencies.resolved,
|
|
691
|
+
oxlintConfig,
|
|
692
|
+
overrides: catalog.overrides,
|
|
693
|
+
tsconfig,
|
|
694
|
+
unpublishedPackages: Array.from(new Set([...dependencies.unpublished, ...devDependencies.unpublished]))
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
//#endregion
|
|
699
|
+
//#region src/commands/sync-admin-template.ts
|
|
700
|
+
const TEMPLATE_NAME = "admin";
|
|
701
|
+
/** shell 源码模板目录名,与 templates/admin 平级,保持 admin/ 仍是 apps/admin 的逐字节镜像。 */
|
|
702
|
+
const SHELL_TEMPLATE_NAME = "admin-shell";
|
|
703
|
+
/** shell 里会被复制进生成项目 src/framework 的运行时目录;配置、测试与文档不属于产物。 */
|
|
704
|
+
const SHELL_RUNTIME_DIRS = [
|
|
705
|
+
"i18n",
|
|
706
|
+
"layouts",
|
|
707
|
+
"notification",
|
|
708
|
+
"runtime",
|
|
709
|
+
"styles",
|
|
710
|
+
"theme",
|
|
711
|
+
"types",
|
|
712
|
+
"ui"
|
|
713
|
+
];
|
|
714
|
+
const MAX_REPORTED_DIFFERENCES = 20;
|
|
715
|
+
/** 构建产物与工具缓存目录,同步时既不复制也不参与比对。 */
|
|
716
|
+
const TECHNICAL_DIRS = new Set([
|
|
717
|
+
".tanstack",
|
|
718
|
+
".turbo",
|
|
719
|
+
"coverage",
|
|
720
|
+
"dist",
|
|
721
|
+
"node_modules"
|
|
722
|
+
]);
|
|
723
|
+
function normalizeRelativePath(filePath) {
|
|
724
|
+
return filePath.split(path.sep).join("/");
|
|
725
|
+
}
|
|
726
|
+
function isLocalEnvFile(relativePath) {
|
|
727
|
+
const basename = path.posix.basename(relativePath);
|
|
728
|
+
return basename === ".env.local" || /^\.env\..*\.local$/.test(basename);
|
|
729
|
+
}
|
|
730
|
+
function isTechnicalGeneratedPath(relativePath) {
|
|
731
|
+
if (!relativePath) return false;
|
|
732
|
+
const segments = relativePath.split("/");
|
|
733
|
+
const basename = segments.at(-1);
|
|
734
|
+
return basename === ".DS_Store" || basename?.endsWith(".tsbuildinfo") || basename?.endsWith(".log") || isLocalEnvFile(relativePath) || segments.some((segment) => TECHNICAL_DIRS.has(segment));
|
|
735
|
+
}
|
|
736
|
+
function isRouteTreeFile(relativePath) {
|
|
737
|
+
return relativePath === "src/features/router/routeTree.gen.ts";
|
|
738
|
+
}
|
|
739
|
+
function shouldCopyPath(relativePath) {
|
|
740
|
+
return !isTechnicalGeneratedPath(relativePath) && !isRouteTreeFile(relativePath);
|
|
741
|
+
}
|
|
742
|
+
function shouldComparePath(relativePath) {
|
|
743
|
+
return !isTechnicalGeneratedPath(relativePath);
|
|
744
|
+
}
|
|
745
|
+
function resolveSourceDir(workspaceRoot, source) {
|
|
746
|
+
return path.resolve(workspaceRoot, source || "apps/admin");
|
|
747
|
+
}
|
|
748
|
+
function resolveTargetDir(target) {
|
|
749
|
+
return path.resolve(process.cwd(), target || path.join(getPackageRoot(), "templates", TEMPLATE_NAME));
|
|
750
|
+
}
|
|
751
|
+
async function copyAdminSource(sourceDir, targetDir) {
|
|
752
|
+
await rm(targetDir, {
|
|
753
|
+
force: true,
|
|
754
|
+
recursive: true
|
|
755
|
+
});
|
|
756
|
+
await cp(sourceDir, targetDir, {
|
|
757
|
+
filter: (source) => {
|
|
758
|
+
return shouldCopyPath(normalizeRelativePath(path.relative(sourceDir, source)));
|
|
759
|
+
},
|
|
760
|
+
recursive: true
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
async function copyShellSource(shellSourceDir, shellTargetDir) {
|
|
764
|
+
await rm(shellTargetDir, {
|
|
765
|
+
force: true,
|
|
766
|
+
recursive: true
|
|
767
|
+
});
|
|
768
|
+
for (const dir of SHELL_RUNTIME_DIRS) {
|
|
769
|
+
const from = path.join(shellSourceDir, dir);
|
|
770
|
+
if (!existsSync(from)) throw new Error(`Admin shell source dir is missing: ${from}`);
|
|
771
|
+
await cp(from, path.join(shellTargetDir, dir), {
|
|
772
|
+
filter: (source) => !source.split(path.sep).some((segment) => TECHNICAL_DIRS.has(segment)),
|
|
773
|
+
recursive: true
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
async function generateRouteTree(targetDir) {
|
|
778
|
+
const { configSchema, Generator } = await import("@tanstack/router-generator").catch(() => {
|
|
779
|
+
throw new Error("@tanstack/router-generator is not installed. sync-admin-template only runs inside the skyroc-admin monorepo.");
|
|
780
|
+
});
|
|
781
|
+
const routeTreeTmpDir = await mkdtemp(path.join(tmpdir(), "skyroc-route-tree-"));
|
|
782
|
+
try {
|
|
783
|
+
await new Generator({
|
|
784
|
+
config: configSchema.parse({
|
|
785
|
+
autoCodeSplitting: true,
|
|
786
|
+
disableLogging: true,
|
|
787
|
+
generatedRouteTree: path.join(targetDir, "src/features/router/routeTree.gen.ts"),
|
|
788
|
+
routeFileIgnorePattern: "(?:^|/)(components|modules)(?:/|$)|(?:^|/)(loading|error|not-found)(?:.tsx?|$)",
|
|
789
|
+
routesDirectory: path.join(targetDir, "src/pages"),
|
|
790
|
+
routeToken: "layout",
|
|
791
|
+
target: "react",
|
|
792
|
+
tmpDir: routeTreeTmpDir
|
|
793
|
+
}),
|
|
794
|
+
root: targetDir
|
|
795
|
+
}).run();
|
|
796
|
+
} finally {
|
|
797
|
+
await rm(routeTreeTmpDir, {
|
|
798
|
+
force: true,
|
|
799
|
+
recursive: true
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
async function collectFiles(dir) {
|
|
804
|
+
const files = [];
|
|
805
|
+
async function walk(currentDir) {
|
|
806
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
807
|
+
for (const entry of entries) {
|
|
808
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
809
|
+
const relativePath = normalizeRelativePath(path.relative(dir, fullPath));
|
|
810
|
+
if (!shouldComparePath(relativePath)) continue;
|
|
811
|
+
if (entry.isDirectory()) {
|
|
812
|
+
await walk(fullPath);
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (entry.isFile()) files.push(relativePath);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
if (existsSync(dir)) await walk(dir);
|
|
819
|
+
return files.toSorted((a, b) => a.localeCompare(b));
|
|
820
|
+
}
|
|
821
|
+
async function compareFiles(leftFile, rightFile) {
|
|
822
|
+
const [leftStat, rightStat] = await Promise.all([stat(leftFile), stat(rightFile)]);
|
|
823
|
+
if (leftStat.size !== rightStat.size) return false;
|
|
824
|
+
const [leftContent, rightContent] = await Promise.all([readFile(leftFile), readFile(rightFile)]);
|
|
825
|
+
return leftContent.equals(rightContent);
|
|
826
|
+
}
|
|
827
|
+
async function compareDirectories(leftDir, rightDir) {
|
|
828
|
+
const [leftFiles, rightFiles] = await Promise.all([collectFiles(leftDir), collectFiles(rightDir)]);
|
|
829
|
+
const leftFileSet = new Set(leftFiles);
|
|
830
|
+
const rightFileSet = new Set(rightFiles);
|
|
831
|
+
const allFiles = Array.from(new Set([...leftFiles, ...rightFiles])).toSorted((a, b) => a.localeCompare(b));
|
|
832
|
+
const differences = [];
|
|
833
|
+
for (const file of allFiles) {
|
|
834
|
+
if (!leftFileSet.has(file)) {
|
|
835
|
+
differences.push({
|
|
836
|
+
path: file,
|
|
837
|
+
type: "added"
|
|
838
|
+
});
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (!rightFileSet.has(file)) {
|
|
842
|
+
differences.push({
|
|
843
|
+
path: file,
|
|
844
|
+
type: "removed"
|
|
845
|
+
});
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (!await compareFiles(path.join(leftDir, file), path.join(rightDir, file))) differences.push({
|
|
849
|
+
path: file,
|
|
850
|
+
type: "changed"
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
return differences;
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* 产出一份完整快照:模板目录 + 物化元数据。
|
|
857
|
+
*
|
|
858
|
+
* 元数据放在模板目录外层,这样 `templates/admin/` 依然是 `apps/admin` 的逐字节镜像,比对逻辑不需要为 sidecar 开特例。
|
|
859
|
+
*/
|
|
860
|
+
async function generateSnapshot(workspaceRoot, sourceDir, targetDir) {
|
|
861
|
+
if (!existsSync(sourceDir)) throw new Error(`Admin source is missing: ${sourceDir}`);
|
|
862
|
+
const shellSourceDir = path.join(workspaceRoot, "packages/web/admin");
|
|
863
|
+
const shellTargetDir = path.join(path.dirname(targetDir), SHELL_TEMPLATE_NAME);
|
|
864
|
+
await copyAdminSource(sourceDir, targetDir);
|
|
865
|
+
await copyShellSource(shellSourceDir, shellTargetDir);
|
|
866
|
+
await generateRouteTree(targetDir);
|
|
867
|
+
const meta = await resolveTemplateMeta({
|
|
868
|
+
sourceDir,
|
|
869
|
+
workspaceRoot
|
|
870
|
+
});
|
|
871
|
+
const metaContent = stringifyTemplateMeta(meta);
|
|
872
|
+
await writeFile(path.join(path.dirname(targetDir), TEMPLATE_META_FILE), metaContent);
|
|
873
|
+
return {
|
|
874
|
+
meta,
|
|
875
|
+
metaContent
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function formatDifferences(differences) {
|
|
879
|
+
const detail = differences.slice(0, MAX_REPORTED_DIFFERENCES).map((item) => ` ${item.type.padEnd(7)} ${item.path}`).join("\n");
|
|
880
|
+
if (differences.length <= MAX_REPORTED_DIFFERENCES) return detail;
|
|
881
|
+
return `${detail}\n ...and ${differences.length - MAX_REPORTED_DIFFERENCES} more`;
|
|
882
|
+
}
|
|
883
|
+
async function readMetaContent(metaPath) {
|
|
884
|
+
if (!existsSync(metaPath)) return "";
|
|
885
|
+
return readFile(metaPath, "utf8");
|
|
886
|
+
}
|
|
887
|
+
async function checkAdminTemplate(workspaceRoot, sourceDir, targetDir) {
|
|
888
|
+
const tempDir = await mkdtemp(path.join(tmpdir(), "skyroc-admin-template-"));
|
|
889
|
+
const generatedDir = path.join(tempDir, TEMPLATE_NAME);
|
|
890
|
+
try {
|
|
891
|
+
const { metaContent } = await generateSnapshot(workspaceRoot, sourceDir, generatedDir);
|
|
892
|
+
const differences = await compareDirectories(targetDir, generatedDir);
|
|
893
|
+
const shellDifferences = await compareDirectories(path.join(path.dirname(targetDir), SHELL_TEMPLATE_NAME), path.join(tempDir, SHELL_TEMPLATE_NAME));
|
|
894
|
+
differences.push(...shellDifferences.map((item) => ({
|
|
895
|
+
...item,
|
|
896
|
+
path: `${SHELL_TEMPLATE_NAME}/${item.path}`
|
|
897
|
+
})));
|
|
898
|
+
const currentMetaContent = await readMetaContent(path.join(path.dirname(targetDir), TEMPLATE_META_FILE));
|
|
899
|
+
if (currentMetaContent !== metaContent) differences.push({
|
|
900
|
+
path: TEMPLATE_META_FILE,
|
|
901
|
+
type: currentMetaContent ? "changed" : "added"
|
|
902
|
+
});
|
|
903
|
+
if (differences.length > 0) throw new Error([
|
|
904
|
+
red("Admin template is out of date."),
|
|
905
|
+
`${cyan("source")} ${sourceDir}`,
|
|
906
|
+
`${cyan("target")} ${targetDir}`,
|
|
907
|
+
yellow("Run pnpm sa sync-admin-template to regenerate it."),
|
|
908
|
+
formatDifferences(differences)
|
|
909
|
+
].join("\n"));
|
|
910
|
+
console.log(green("Admin template is up to date."));
|
|
911
|
+
} finally {
|
|
912
|
+
await rm(tempDir, {
|
|
913
|
+
force: true,
|
|
914
|
+
recursive: true
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async function syncAdminTemplate(options = {}) {
|
|
919
|
+
const workspaceRoot = getWorkspaceRoot();
|
|
920
|
+
const sourceDir = resolveSourceDir(workspaceRoot, options.source);
|
|
921
|
+
const targetDir = resolveTargetDir(options.target);
|
|
922
|
+
if (options.check) {
|
|
923
|
+
await checkAdminTemplate(workspaceRoot, sourceDir, targetDir);
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
const { meta } = await generateSnapshot(workspaceRoot, sourceDir, targetDir);
|
|
927
|
+
console.log(green("Synced admin template."));
|
|
928
|
+
console.log(`${cyan("source")} ${sourceDir}`);
|
|
929
|
+
console.log(`${cyan("target")} ${targetDir}`);
|
|
930
|
+
console.log(`${cyan("meta")} ${path.join(path.dirname(targetDir), TEMPLATE_META_FILE)}`);
|
|
931
|
+
if (meta.unpublishedPackages.length > 0) console.log(yellow(`warning standalone apps will not install these private workspace packages: ${meta.unpublishedPackages.join(", ")}`));
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
//#endregion
|
|
935
|
+
//#region src/commands/update-pkg.ts
|
|
936
|
+
async function updatePkg(args = ["--deep", "-u"]) {
|
|
937
|
+
await execCommand("npx", ["npm-check-updates", ...args], { stdio: "inherit" });
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
//#endregion
|
|
941
|
+
//#region src/config/index.ts
|
|
942
|
+
const defaultOptions = {
|
|
943
|
+
cwd: process.cwd(),
|
|
944
|
+
cleanupDirs: [
|
|
945
|
+
"**/dist",
|
|
946
|
+
"**/package-lock.json",
|
|
947
|
+
"**/yarn.lock",
|
|
948
|
+
"**/pnpm-lock.yaml",
|
|
949
|
+
"**/node_modules"
|
|
950
|
+
],
|
|
951
|
+
ncuCommandArgs: ["--deep", "-u"],
|
|
952
|
+
changelogOptions: {},
|
|
953
|
+
gitCommitVerifyIgnores: [
|
|
954
|
+
/^((Merge pull request)|(Merge (.*?) into (.*?)|(Merge branch (.*?)))(?:\r?\n)*$)/m,
|
|
955
|
+
/^(Merge tag (.*?))(?:\r?\n)*$/m,
|
|
956
|
+
/^(R|r)evert (.*)/,
|
|
957
|
+
/^(amend|fixup|squash)!/,
|
|
958
|
+
/^(Merged (.*?)(in|into) (.*)|Merged PR (.*): (.*))/,
|
|
959
|
+
/^Merge remote-tracking branch(\s*)(.*)/,
|
|
960
|
+
/^Automatic merge(.*)/,
|
|
961
|
+
/^Auto-merged (.*?) into (.*)/
|
|
962
|
+
]
|
|
963
|
+
};
|
|
964
|
+
/**
|
|
965
|
+
* 加载 CLI 配置。
|
|
966
|
+
*
|
|
967
|
+
* 先读历史遗留的 `soybean.*`,再让 `skyroc.*` 覆盖在上面——包名早已改成 `@skyroc/scripts`,但旧配置文件不该一升级就失效。
|
|
968
|
+
*/
|
|
969
|
+
async function loadCliOptions(overrides, cwd = process.cwd()) {
|
|
970
|
+
const { config: legacyConfig } = await loadConfig({
|
|
971
|
+
name: "soybean",
|
|
972
|
+
defaults: defaultOptions,
|
|
973
|
+
cwd,
|
|
974
|
+
packageJson: true
|
|
975
|
+
});
|
|
976
|
+
const { config } = await loadConfig({
|
|
977
|
+
name: "skyroc",
|
|
978
|
+
defaults: legacyConfig,
|
|
979
|
+
overrides,
|
|
980
|
+
cwd,
|
|
981
|
+
packageJson: true
|
|
982
|
+
});
|
|
983
|
+
return config;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
//#endregion
|
|
987
|
+
export { createCommitMessageRegExp as a, locales as c, replaceEnvValue as d, toStoragePrefix as f, genChangelog as h, release as i, createAdminTemplate as l, cleanup as m, updatePkg as n, gitCommit as o, toTitle as p, syncAdminTemplate as r, gitCommitVerify as s, loadCliOptions as t, normalizePackageName as u };
|