@pattern-stack/frontend-patterns 0.0.6 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/LICENSE +19 -0
- package/cli/cli/commands/generate-hooks.js +291 -0
- package/cli/cli/commands/init.js +25 -0
- package/cli/cli/commands/scaffold.js +201 -0
- package/cli/cli/index.js +113 -0
- package/cli/commands/generate-hooks.js +288 -0
- package/cli/commands/generate-hooks.ts +316 -0
- package/cli/commands/init.js +22 -0
- package/cli/commands/init.ts +33 -0
- package/cli/commands/scaffold.js +198 -0
- package/cli/commands/scaffold.ts +224 -0
- package/cli/index.js +3210 -0
- package/cli/index.ts +122 -0
- package/cli/src/codegen/openapi/bulk-hook-generator.js +252 -0
- package/cli/src/codegen/openapi/bulk-types.js +89 -0
- package/cli/src/codegen/openapi/client-generator.js +672 -0
- package/cli/src/codegen/openapi/confidence-scorer.js +204 -0
- package/cli/src/codegen/openapi/hook-config.js +66 -0
- package/cli/src/codegen/openapi/hook-generator.js +1057 -0
- package/cli/src/codegen/openapi/parser.js +279 -0
- package/cli/src/codegen/openapi/type-generator.js +339 -0
- package/dist/atoms/components/core/Avatar/Avatar.d.ts +41 -0
- package/dist/atoms/components/core/Avatar/Avatar.d.ts.map +1 -0
- package/dist/atoms/components/core/Avatar/index.d.ts +2 -0
- package/dist/atoms/components/core/Avatar/index.d.ts.map +1 -0
- package/dist/atoms/components/core/Badge/Badge.d.ts +38 -0
- package/dist/atoms/components/core/Badge/Badge.d.ts.map +1 -0
- package/dist/atoms/components/core/Badge/index.d.ts +2 -0
- package/dist/atoms/components/core/Badge/index.d.ts.map +1 -0
- package/dist/atoms/components/core/Button/Button.d.ts +28 -0
- package/dist/atoms/components/core/Button/Button.d.ts.map +1 -0
- package/dist/atoms/components/core/Button/index.d.ts +3 -0
- package/dist/atoms/components/core/Button/index.d.ts.map +1 -0
- package/dist/atoms/components/core/Card/Card.d.ts +41 -0
- package/dist/atoms/components/core/Card/Card.d.ts.map +1 -0
- package/dist/atoms/components/core/Card/index.d.ts +3 -0
- package/dist/atoms/components/core/Card/index.d.ts.map +1 -0
- package/dist/atoms/components/core/Checkbox/Checkbox.d.ts +28 -0
- package/dist/atoms/components/core/Checkbox/Checkbox.d.ts.map +1 -0
- package/dist/atoms/components/core/Checkbox/index.d.ts +3 -0
- package/dist/atoms/components/core/Checkbox/index.d.ts.map +1 -0
- package/dist/atoms/components/core/Input/Input.d.ts +37 -0
- package/dist/atoms/components/core/Input/Input.d.ts.map +1 -0
- package/dist/atoms/components/core/Input/index.d.ts +3 -0
- package/dist/atoms/components/core/Input/index.d.ts.map +1 -0
- package/dist/atoms/components/core/Label/Label.d.ts +23 -0
- package/dist/atoms/components/core/Label/Label.d.ts.map +1 -0
- package/dist/atoms/components/core/Label/index.d.ts +3 -0
- package/dist/atoms/components/core/Label/index.d.ts.map +1 -0
- package/dist/atoms/components/core/Select/Select.d.ts +42 -0
- package/dist/atoms/components/core/Select/Select.d.ts.map +1 -0
- package/dist/atoms/components/core/Select/index.d.ts +3 -0
- package/dist/atoms/components/core/Select/index.d.ts.map +1 -0
- package/dist/atoms/components/core/Spinner/Spinner.d.ts +25 -0
- package/dist/atoms/components/core/Spinner/Spinner.d.ts.map +1 -0
- package/dist/atoms/components/core/Spinner/index.d.ts +3 -0
- package/dist/atoms/components/core/Spinner/index.d.ts.map +1 -0
- package/dist/atoms/components/core/Switch/Switch.d.ts +35 -0
- package/dist/atoms/components/core/Switch/Switch.d.ts.map +1 -0
- package/dist/atoms/components/core/Switch/index.d.ts +2 -0
- package/dist/atoms/components/core/Switch/index.d.ts.map +1 -0
- package/dist/atoms/components/core/index.d.ts +11 -0
- package/dist/atoms/components/core/index.d.ts.map +1 -0
- package/dist/atoms/components/data/ActivityFeed/ActivityFeed.d.ts +4 -0
- package/dist/atoms/components/data/ActivityFeed/ActivityFeed.d.ts.map +1 -0
- package/dist/atoms/components/data/ActivityFeed/ActivityFeed.stories.d.ts +38 -0
- package/dist/atoms/components/data/ActivityFeed/ActivityFeed.stories.d.ts.map +1 -0
- package/dist/atoms/components/data/ActivityFeed/ActivityFeedItem.d.ts +9 -0
- package/dist/atoms/components/data/ActivityFeed/ActivityFeedItem.d.ts.map +1 -0
- package/dist/atoms/components/data/ActivityFeed/index.d.ts +4 -0
- package/dist/atoms/components/data/ActivityFeed/index.d.ts.map +1 -0
- package/dist/atoms/components/data/ActivityFeed/types.d.ts +26 -0
- package/dist/atoms/components/data/ActivityFeed/types.d.ts.map +1 -0
- package/dist/atoms/components/data/ActivityFeed/utils.d.ts +5 -0
- package/dist/atoms/components/data/ActivityFeed/utils.d.ts.map +1 -0
- package/dist/atoms/{composed → components/data}/Chart/Chart.d.ts +2 -2
- package/dist/atoms/components/data/Chart/Chart.d.ts.map +1 -0
- package/dist/atoms/components/data/Chart/index.d.ts.map +1 -0
- package/dist/atoms/components/data/DataBadge/DataBadge.d.ts +18 -0
- package/dist/atoms/components/data/DataBadge/DataBadge.d.ts.map +1 -0
- package/dist/atoms/components/data/DataBadge/index.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/DataTable.d.ts +5 -0
- package/dist/atoms/components/data/DataTable/DataTable.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/DataTable.types.d.ts +51 -0
- package/dist/atoms/components/data/DataTable/DataTable.types.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/TableCellWithTooltip.d.ts.map +1 -0
- package/dist/atoms/components/data/DataTable/index.d.ts.map +1 -0
- package/dist/atoms/components/data/DetailedCard/DetailedCard.d.ts.map +1 -0
- package/dist/atoms/components/data/DetailedCard/index.d.ts.map +1 -0
- package/dist/atoms/components/data/EntityIcon/EntityIcon.d.ts +24 -0
- package/dist/atoms/components/data/EntityIcon/EntityIcon.d.ts.map +1 -0
- package/dist/atoms/components/data/EntityIcon/index.d.ts +2 -0
- package/dist/atoms/components/data/EntityIcon/index.d.ts.map +1 -0
- package/dist/atoms/{composed → components/data}/IconBadge/IconBadge.d.ts +2 -1
- package/dist/atoms/components/data/IconBadge/IconBadge.d.ts.map +1 -0
- package/dist/atoms/components/data/IconBadge/index.d.ts.map +1 -0
- package/dist/atoms/components/data/ListCard/ListCard.d.ts +32 -0
- package/dist/atoms/components/data/ListCard/ListCard.d.ts.map +1 -0
- package/dist/atoms/components/data/ListCard/index.d.ts +2 -0
- package/dist/atoms/components/data/ListCard/index.d.ts.map +1 -0
- package/dist/atoms/components/data/ProgressBar/ProgressBar.d.ts.map +1 -0
- package/dist/atoms/components/data/ProgressBar/index.d.ts.map +1 -0
- package/dist/atoms/{composed → components/data}/StatCard/StatCard.d.ts +1 -1
- package/dist/atoms/components/data/StatCard/StatCard.d.ts.map +1 -0
- package/dist/atoms/components/data/StatCard/index.d.ts.map +1 -0
- package/dist/atoms/components/data/Table/Table.d.ts +41 -0
- package/dist/atoms/components/data/Table/Table.d.ts.map +1 -0
- package/dist/atoms/components/data/Table/index.d.ts +2 -0
- package/dist/atoms/components/data/Table/index.d.ts.map +1 -0
- package/dist/atoms/components/data/TruncatedText/TruncatedText.d.ts +26 -0
- package/dist/atoms/components/data/TruncatedText/TruncatedText.d.ts.map +1 -0
- package/dist/atoms/components/data/TruncatedText/index.d.ts +2 -0
- package/dist/atoms/components/data/TruncatedText/index.d.ts.map +1 -0
- package/dist/atoms/components/data/index.d.ts +13 -0
- package/dist/atoms/components/data/index.d.ts.map +1 -0
- package/dist/atoms/components/domain/SalesPanel/SalesPanel.d.ts.map +1 -0
- package/dist/atoms/components/domain/SalesPanel/index.d.ts.map +1 -0
- package/dist/atoms/components/domain/SalesPanel/mockSalesData.d.ts.map +1 -0
- package/dist/atoms/components/domain/index.d.ts +2 -0
- package/dist/atoms/components/domain/index.d.ts.map +1 -0
- package/dist/atoms/components/feedback/Alert/Alert.d.ts.map +1 -0
- package/dist/atoms/components/feedback/Alert/index.d.ts.map +1 -0
- package/dist/atoms/components/feedback/EmptyState/EmptyState.d.ts.map +1 -0
- package/dist/atoms/components/feedback/EmptyState/index.d.ts.map +1 -0
- package/dist/atoms/components/feedback/ErrorBoundary/ErrorBoundary.d.ts +61 -0
- package/dist/atoms/components/feedback/ErrorBoundary/ErrorBoundary.d.ts.map +1 -0
- package/dist/atoms/components/feedback/ErrorBoundary/index.d.ts +2 -0
- package/dist/atoms/components/feedback/ErrorBoundary/index.d.ts.map +1 -0
- package/dist/atoms/components/feedback/Skeleton/Skeleton.d.ts +41 -0
- package/dist/atoms/components/feedback/Skeleton/Skeleton.d.ts.map +1 -0
- package/dist/atoms/components/feedback/Skeleton/index.d.ts +2 -0
- package/dist/atoms/components/feedback/Skeleton/index.d.ts.map +1 -0
- package/dist/atoms/components/feedback/Toast/Toast.d.ts.map +1 -0
- package/dist/atoms/components/feedback/Toast/index.d.ts.map +1 -0
- package/dist/atoms/components/feedback/index.d.ts +6 -0
- package/dist/atoms/components/feedback/index.d.ts.map +1 -0
- package/dist/atoms/components/forms/DateTimePicker/DateTimePicker.d.ts.map +1 -0
- package/dist/atoms/components/forms/DateTimePicker/index.d.ts.map +1 -0
- package/dist/atoms/components/forms/FileUpload/FileUpload.d.ts.map +1 -0
- package/dist/atoms/components/forms/FileUpload/index.d.ts.map +1 -0
- package/dist/atoms/components/forms/FormField/FormField.d.ts.map +1 -0
- package/dist/atoms/components/forms/FormField/index.d.ts.map +1 -0
- package/dist/atoms/components/forms/index.d.ts +4 -0
- package/dist/atoms/components/forms/index.d.ts.map +1 -0
- package/dist/atoms/components/index.d.ts +10 -0
- package/dist/atoms/components/index.d.ts.map +1 -0
- package/dist/atoms/components/layout/Accordion/Accordion.d.ts.map +1 -0
- package/dist/atoms/components/layout/Accordion/index.d.ts.map +1 -0
- package/dist/atoms/components/layout/Breadcrumb/Breadcrumb.d.ts.map +1 -0
- package/dist/atoms/components/layout/Breadcrumb/index.d.ts.map +1 -0
- package/dist/atoms/components/layout/Dialog/index.d.ts +3 -0
- package/dist/atoms/components/layout/Dialog/index.d.ts.map +1 -0
- package/dist/atoms/components/layout/Dropdown/Dropdown.d.ts +40 -0
- package/dist/atoms/components/layout/Dropdown/Dropdown.d.ts.map +1 -0
- package/dist/atoms/components/layout/Dropdown/index.d.ts +3 -0
- package/dist/atoms/components/layout/Dropdown/index.d.ts.map +1 -0
- package/dist/atoms/components/layout/Modal/Modal.d.ts.map +1 -0
- package/dist/atoms/components/layout/Modal/index.d.ts.map +1 -0
- package/dist/atoms/components/layout/Tabs/index.d.ts +2 -0
- package/dist/atoms/components/layout/Tabs/index.d.ts.map +1 -0
- package/dist/atoms/components/layout/Tooltip/Tooltip.d.ts.map +1 -0
- package/dist/atoms/components/layout/Tooltip/index.d.ts +2 -0
- package/dist/atoms/components/layout/Tooltip/index.d.ts.map +1 -0
- package/dist/atoms/components/layout/index.d.ts +8 -0
- package/dist/atoms/components/layout/index.d.ts.map +1 -0
- package/dist/atoms/components/navigation/GlobalSearch/GlobalSearch.d.ts.map +1 -0
- package/dist/atoms/components/navigation/GlobalSearch/index.d.ts.map +1 -0
- package/dist/atoms/components/navigation/index.d.ts +2 -0
- package/dist/atoms/components/navigation/index.d.ts.map +1 -0
- package/dist/atoms/components/theme/ColorSwatch/ColorSwatch.d.ts.map +1 -0
- package/dist/atoms/components/theme/ColorSwatch/index.d.ts.map +1 -0
- package/dist/atoms/components/theme/DarkModeToggle.d.ts.map +1 -0
- package/dist/atoms/components/theme/PaletteSwitcher.d.ts.map +1 -0
- package/dist/atoms/components/theme/StyleGuide.d.ts.map +1 -0
- package/dist/atoms/components/theme/index.d.ts +5 -0
- package/dist/atoms/components/theme/index.d.ts.map +1 -0
- package/dist/atoms/components/user/UserAvatar/UserAvatar.d.ts.map +1 -0
- package/dist/atoms/components/user/UserAvatar/index.d.ts.map +1 -0
- package/dist/atoms/components/user/UserMenu/UserMenu.d.ts.map +1 -0
- package/dist/atoms/components/user/UserMenu/index.d.ts.map +1 -0
- package/dist/atoms/components/user/index.d.ts +3 -0
- package/dist/atoms/components/user/index.d.ts.map +1 -0
- package/dist/atoms/config/responsive.d.ts +147 -0
- package/dist/atoms/config/responsive.d.ts.map +1 -0
- package/dist/atoms/hooks/index.d.ts +5 -0
- package/dist/atoms/hooks/index.d.ts.map +1 -0
- package/dist/atoms/hooks/use-toast.d.ts +16 -0
- package/dist/atoms/hooks/use-toast.d.ts.map +1 -0
- package/dist/atoms/hooks/useResponsive.d.ts +42 -0
- package/dist/atoms/hooks/useResponsive.d.ts.map +1 -0
- package/dist/atoms/index.d.ts +4 -5
- package/dist/atoms/index.d.ts.map +1 -1
- package/dist/atoms/primitives/Badge.d.ts.map +1 -0
- package/dist/atoms/{ui → primitives}/ErrorBoundary.d.ts +1 -1
- package/dist/atoms/primitives/ErrorBoundary.d.ts.map +1 -0
- package/dist/atoms/primitives/Select.d.ts.map +1 -0
- package/dist/atoms/primitives/Switch.d.ts.map +1 -0
- package/dist/atoms/primitives/Tabs.d.ts.map +1 -0
- package/dist/atoms/primitives/avatar.d.ts.map +1 -0
- package/dist/atoms/{ui → primitives}/button.d.ts +2 -2
- package/dist/atoms/primitives/button.d.ts.map +1 -0
- package/dist/atoms/primitives/card.d.ts.map +1 -0
- package/dist/atoms/primitives/checkbox.d.ts +12 -0
- package/dist/atoms/primitives/checkbox.d.ts.map +1 -0
- package/dist/atoms/primitives/dialog.d.ts +34 -0
- package/dist/atoms/primitives/dialog.d.ts.map +1 -0
- package/dist/atoms/primitives/dropdown-menu.d.ts.map +1 -0
- package/dist/atoms/{ui → primitives}/index.d.ts +2 -0
- package/dist/atoms/primitives/index.d.ts.map +1 -0
- package/dist/atoms/primitives/input.d.ts.map +1 -0
- package/dist/atoms/primitives/label.d.ts.map +1 -0
- package/dist/atoms/primitives/skeleton.d.ts.map +1 -0
- package/dist/atoms/primitives/spinner.d.ts.map +1 -0
- package/dist/atoms/primitives/table.d.ts.map +1 -0
- package/dist/atoms/shared/index.d.ts +1 -0
- package/dist/atoms/shared/index.d.ts.map +1 -1
- package/dist/atoms/types/index.d.ts +1 -0
- package/dist/atoms/types/index.d.ts.map +1 -1
- package/dist/atoms/types/navigation.d.ts +1 -1
- package/dist/atoms/types/navigation.d.ts.map +1 -1
- package/dist/atoms/types/ui-config.d.ts +50 -0
- package/dist/atoms/types/ui-config.d.ts.map +1 -0
- package/dist/atoms/utils/color-manager.d.ts +68 -0
- package/dist/atoms/utils/color-manager.d.ts.map +1 -0
- package/dist/atoms/utils/debounce.d.ts +6 -0
- package/dist/atoms/utils/debounce.d.ts.map +1 -0
- package/dist/atoms/utils/field-detection.d.ts +15 -0
- package/dist/atoms/utils/field-detection.d.ts.map +1 -0
- package/dist/atoms/utils/icon-resolver.d.ts +5 -1
- package/dist/atoms/utils/icon-resolver.d.ts.map +1 -1
- package/dist/atoms/utils/index.d.ts +5 -0
- package/dist/atoms/utils/index.d.ts.map +1 -0
- package/dist/atoms/utils/ui-mapping.d.ts +17 -0
- package/dist/atoms/utils/ui-mapping.d.ts.map +1 -0
- package/dist/atoms/utils/utils.d.ts +3 -0
- package/dist/atoms/utils/utils.d.ts.map +1 -1
- package/dist/codegen/index.d.ts +7 -0
- package/dist/codegen/index.d.ts.map +1 -0
- package/dist/codegen/openapi/bulk-hook-generator.d.ts +40 -0
- package/dist/codegen/openapi/bulk-hook-generator.d.ts.map +1 -0
- package/dist/codegen/openapi/bulk-types.d.ts +142 -0
- package/dist/codegen/openapi/bulk-types.d.ts.map +1 -0
- package/dist/codegen/openapi/client-generator.d.ts +52 -0
- package/dist/codegen/openapi/client-generator.d.ts.map +1 -0
- package/dist/codegen/openapi/confidence-scorer.d.ts +30 -0
- package/dist/codegen/openapi/confidence-scorer.d.ts.map +1 -0
- package/dist/codegen/openapi/hook-config.d.ts +50 -0
- package/dist/codegen/openapi/hook-config.d.ts.map +1 -0
- package/dist/codegen/openapi/hook-generator.d.ts +108 -0
- package/dist/codegen/openapi/hook-generator.d.ts.map +1 -0
- package/dist/codegen/openapi/index.d.ts +27 -0
- package/dist/codegen/openapi/index.d.ts.map +1 -0
- package/dist/codegen/openapi/parser.d.ts +107 -0
- package/dist/codegen/openapi/parser.d.ts.map +1 -0
- package/dist/codegen/openapi/type-generator.d.ts +53 -0
- package/dist/codegen/openapi/type-generator.d.ts.map +1 -0
- package/dist/features/auth/components/LoginForm.d.ts.map +1 -1
- package/dist/features/auth/components/ProtectedRoute.d.ts +1 -1
- package/dist/features/auth/hooks/index.d.ts +1 -0
- package/dist/features/auth/hooks/index.d.ts.map +1 -1
- package/dist/features/auth/hooks/useAuthContext.d.ts +7 -0
- package/dist/features/auth/hooks/useAuthContext.d.ts.map +1 -0
- package/dist/features/auth/index.d.ts +1 -0
- package/dist/features/auth/index.d.ts.map +1 -1
- package/dist/features/auth/providers/MockAuthProvider.d.ts +9 -0
- package/dist/features/auth/providers/MockAuthProvider.d.ts.map +1 -0
- package/dist/features/auth/providers/index.d.ts +2 -0
- package/dist/features/auth/providers/index.d.ts.map +1 -0
- package/dist/features/auth/services/mock-auth-service.d.ts +17 -0
- package/dist/features/auth/services/mock-auth-service.d.ts.map +1 -0
- package/dist/frontend-patterns.css +4417 -1
- package/dist/generated/client/client.d.ts +23 -0
- package/dist/generated/client/client.d.ts.map +1 -0
- package/dist/generated/client/config.d.ts +10 -0
- package/dist/generated/client/config.d.ts.map +1 -0
- package/dist/generated/client/index.d.ts +12 -0
- package/dist/generated/client/index.d.ts.map +1 -0
- package/dist/generated/client/methods.d.ts +591 -0
- package/dist/generated/client/methods.d.ts.map +1 -0
- package/dist/generated/client/types.d.ts +37 -0
- package/dist/generated/client/types.d.ts.map +1 -0
- package/dist/generated/example.d.ts +8 -0
- package/dist/generated/example.d.ts.map +1 -0
- package/dist/generated/hooks/index.d.ts +11 -0
- package/dist/generated/hooks/index.d.ts.map +1 -0
- package/dist/generated/hooks/keys.d.ts +59 -0
- package/dist/generated/hooks/keys.d.ts.map +1 -0
- package/dist/generated/hooks/mutations.d.ts +551 -0
- package/dist/generated/hooks/mutations.d.ts.map +1 -0
- package/dist/generated/hooks/queries.d.ts +426 -0
- package/dist/generated/hooks/queries.d.ts.map +1 -0
- package/dist/generated/hooks/types.d.ts +318 -0
- package/dist/generated/hooks/types.d.ts.map +1 -0
- package/dist/generated/index.d.ts +13 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/types/endpoints.d.ts +1364 -0
- package/dist/generated/types/endpoints.d.ts.map +1 -0
- package/dist/generated/types/index.d.ts +11 -0
- package/dist/generated/types/index.d.ts.map +1 -0
- package/dist/generated/types/parameters.d.ts +8 -0
- package/dist/generated/types/parameters.d.ts.map +1 -0
- package/dist/generated/types/responses.d.ts +8 -0
- package/dist/generated/types/responses.d.ts.map +1 -0
- package/dist/generated/types/schemas.d.ts +652 -0
- package/dist/generated/types/schemas.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +27049 -8420
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +25314 -6664
- package/dist/index.js.map +1 -1
- package/dist/molecules/feedback/index.d.ts +2 -0
- package/dist/molecules/feedback/index.d.ts.map +1 -0
- package/dist/molecules/forms/SearchInput.d.ts.map +1 -1
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +1 -1
- package/dist/molecules/layout/BulkSelectionBar.d.ts +15 -0
- package/dist/molecules/layout/BulkSelectionBar.d.ts.map +1 -0
- package/dist/molecules/layout/NavigationContext.d.ts.map +1 -1
- package/dist/molecules/layout/index.d.ts +1 -0
- package/dist/molecules/layout/index.d.ts.map +1 -1
- package/dist/organisms/showcase/ComponentShowcasePage.d.ts.map +1 -1
- package/dist/templates/DataTemplate.d.ts +1 -1
- package/dist/templates/DataTemplate.d.ts.map +1 -1
- package/dist/templates/EnhancedDataTemplate.d.ts +188 -0
- package/dist/templates/EnhancedDataTemplate.d.ts.map +1 -0
- package/dist/templates/EnhancedDataTemplate.hooks.bulk.d.ts +18 -0
- package/dist/templates/EnhancedDataTemplate.hooks.bulk.d.ts.map +1 -0
- package/dist/templates/EnhancedDataTemplate.hooks.d.ts +22 -0
- package/dist/templates/EnhancedDataTemplate.hooks.d.ts.map +1 -0
- package/dist/templates/admin/AdminCRUDTemplate.d.ts.map +1 -1
- package/dist/templates/admin/AdminDashboardTemplate.d.ts +6 -9
- package/dist/templates/admin/AdminDashboardTemplate.d.ts.map +1 -1
- package/dist/templates/admin/AdminDetailTemplate.d.ts +1 -1
- package/dist/templates/admin/AdminDetailTemplate.d.ts.map +1 -1
- package/dist/templates/api/APIDataTemplate.d.ts +66 -0
- package/dist/templates/api/APIDataTemplate.d.ts.map +1 -0
- package/dist/templates/api/index.d.ts +8 -0
- package/dist/templates/api/index.d.ts.map +1 -0
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/index.d.ts.map +1 -1
- package/package.json +29 -8
- package/dist/atoms/composed/Accordion/Accordion.d.ts.map +0 -1
- package/dist/atoms/composed/Accordion/index.d.ts.map +0 -1
- package/dist/atoms/composed/Alert/Alert.d.ts.map +0 -1
- package/dist/atoms/composed/Alert/index.d.ts.map +0 -1
- package/dist/atoms/composed/Breadcrumb/Breadcrumb.d.ts.map +0 -1
- package/dist/atoms/composed/Breadcrumb/index.d.ts.map +0 -1
- package/dist/atoms/composed/Chart/Chart.d.ts.map +0 -1
- package/dist/atoms/composed/Chart/index.d.ts.map +0 -1
- package/dist/atoms/composed/ColorSwatch/ColorSwatch.d.ts.map +0 -1
- package/dist/atoms/composed/ColorSwatch/index.d.ts.map +0 -1
- package/dist/atoms/composed/DarkModeToggle.d.ts.map +0 -1
- package/dist/atoms/composed/DataBadge/DataBadge.d.ts +0 -13
- package/dist/atoms/composed/DataBadge/DataBadge.d.ts.map +0 -1
- package/dist/atoms/composed/DataBadge/index.d.ts.map +0 -1
- package/dist/atoms/composed/DataTable/DataTable.d.ts +0 -28
- package/dist/atoms/composed/DataTable/DataTable.d.ts.map +0 -1
- package/dist/atoms/composed/DataTable/TableCellWithTooltip.d.ts.map +0 -1
- package/dist/atoms/composed/DataTable/index.d.ts.map +0 -1
- package/dist/atoms/composed/DateTimePicker/DateTimePicker.d.ts.map +0 -1
- package/dist/atoms/composed/DateTimePicker/index.d.ts.map +0 -1
- package/dist/atoms/composed/DetailedCard/DetailedCard.d.ts.map +0 -1
- package/dist/atoms/composed/DetailedCard/index.d.ts.map +0 -1
- package/dist/atoms/composed/EmptyState/EmptyState.d.ts.map +0 -1
- package/dist/atoms/composed/EmptyState/index.d.ts.map +0 -1
- package/dist/atoms/composed/FileUpload/FileUpload.d.ts.map +0 -1
- package/dist/atoms/composed/FileUpload/index.d.ts.map +0 -1
- package/dist/atoms/composed/FormField/FormField.d.ts.map +0 -1
- package/dist/atoms/composed/FormField/index.d.ts.map +0 -1
- package/dist/atoms/composed/GlobalSearch/GlobalSearch.d.ts.map +0 -1
- package/dist/atoms/composed/GlobalSearch/index.d.ts.map +0 -1
- package/dist/atoms/composed/IconBadge/IconBadge.d.ts.map +0 -1
- package/dist/atoms/composed/IconBadge/index.d.ts.map +0 -1
- package/dist/atoms/composed/Modal/Modal.d.ts.map +0 -1
- package/dist/atoms/composed/Modal/index.d.ts.map +0 -1
- package/dist/atoms/composed/PaletteSwitcher.d.ts.map +0 -1
- package/dist/atoms/composed/ProgressBar/ProgressBar.d.ts.map +0 -1
- package/dist/atoms/composed/ProgressBar/index.d.ts.map +0 -1
- package/dist/atoms/composed/SalesPanel/SalesPanel.d.ts.map +0 -1
- package/dist/atoms/composed/SalesPanel/index.d.ts.map +0 -1
- package/dist/atoms/composed/SalesPanel/mockSalesData.d.ts.map +0 -1
- package/dist/atoms/composed/StatCard/StatCard.d.ts.map +0 -1
- package/dist/atoms/composed/StatCard/index.d.ts.map +0 -1
- package/dist/atoms/composed/StyleGuide.d.ts.map +0 -1
- package/dist/atoms/composed/Toast/Toast.d.ts.map +0 -1
- package/dist/atoms/composed/Toast/index.d.ts.map +0 -1
- package/dist/atoms/composed/Tooltip/Tooltip.d.ts.map +0 -1
- package/dist/atoms/composed/Tooltip/index.d.ts +0 -2
- package/dist/atoms/composed/Tooltip/index.d.ts.map +0 -1
- package/dist/atoms/composed/UserAvatar/UserAvatar.d.ts.map +0 -1
- package/dist/atoms/composed/UserAvatar/index.d.ts.map +0 -1
- package/dist/atoms/composed/UserMenu/UserMenu.d.ts.map +0 -1
- package/dist/atoms/composed/UserMenu/index.d.ts.map +0 -1
- package/dist/atoms/composed/index.d.ts +0 -26
- package/dist/atoms/composed/index.d.ts.map +0 -1
- package/dist/atoms/ui/Badge.d.ts.map +0 -1
- package/dist/atoms/ui/ErrorBoundary.d.ts.map +0 -1
- package/dist/atoms/ui/Select.d.ts.map +0 -1
- package/dist/atoms/ui/Switch.d.ts.map +0 -1
- package/dist/atoms/ui/Tabs.d.ts.map +0 -1
- package/dist/atoms/ui/avatar.d.ts.map +0 -1
- package/dist/atoms/ui/button.d.ts.map +0 -1
- package/dist/atoms/ui/card.d.ts.map +0 -1
- package/dist/atoms/ui/dropdown-menu.d.ts.map +0 -1
- package/dist/atoms/ui/index.d.ts.map +0 -1
- package/dist/atoms/ui/input.d.ts.map +0 -1
- package/dist/atoms/ui/label.d.ts.map +0 -1
- package/dist/atoms/ui/skeleton.d.ts.map +0 -1
- package/dist/atoms/ui/spinner.d.ts.map +0 -1
- package/dist/atoms/ui/table.d.ts.map +0 -1
- package/src/App.css +0 -42
- package/src/App.tsx +0 -64
- package/src/__tests__/README.md +0 -221
- package/src/__tests__/atoms/composed/databadge.test.tsx +0 -106
- package/src/__tests__/atoms/composed/statcard.test.tsx +0 -133
- package/src/__tests__/atoms/hooks/simple-hooks.test.ts +0 -44
- package/src/__tests__/atoms/ui/button.test.tsx +0 -68
- package/src/__tests__/atoms/utils/icon-resolver.test.tsx +0 -140
- package/src/__tests__/atoms/utils/simple.test.ts +0 -18
- package/src/__tests__/atoms/utils/utils.test.ts +0 -77
- package/src/__tests__/features/auth/simple-auth.test.tsx +0 -40
- package/src/__tests__/molecules/layout/simple-layout.test.tsx +0 -81
- package/src/__tests__/organisms/showcase/simple-showcase.test.tsx +0 -167
- package/src/__tests__/setup.ts +0 -51
- package/src/__tests__/utils.tsx +0 -123
- package/src/atoms/composed/Accordion/Accordion.tsx +0 -271
- package/src/atoms/composed/Accordion/index.ts +0 -1
- package/src/atoms/composed/Alert/Alert.tsx +0 -132
- package/src/atoms/composed/Alert/index.ts +0 -1
- package/src/atoms/composed/Breadcrumb/Breadcrumb.tsx +0 -83
- package/src/atoms/composed/Breadcrumb/index.ts +0 -1
- package/src/atoms/composed/Chart/Chart.tsx +0 -425
- package/src/atoms/composed/Chart/index.ts +0 -2
- package/src/atoms/composed/ColorSwatch/ColorSwatch.tsx +0 -72
- package/src/atoms/composed/ColorSwatch/index.ts +0 -1
- package/src/atoms/composed/DarkModeToggle.tsx +0 -66
- package/src/atoms/composed/DataBadge/DataBadge.tsx +0 -81
- package/src/atoms/composed/DataBadge/index.ts +0 -1
- package/src/atoms/composed/DataTable/DataTable.tsx +0 -394
- package/src/atoms/composed/DataTable/TableCellWithTooltip.tsx +0 -41
- package/src/atoms/composed/DataTable/index.ts +0 -2
- package/src/atoms/composed/DateTimePicker/DateTimePicker.tsx +0 -611
- package/src/atoms/composed/DateTimePicker/index.ts +0 -2
- package/src/atoms/composed/DetailedCard/DetailedCard.tsx +0 -181
- package/src/atoms/composed/DetailedCard/index.ts +0 -2
- package/src/atoms/composed/EmptyState/EmptyState.tsx +0 -90
- package/src/atoms/composed/EmptyState/index.ts +0 -1
- package/src/atoms/composed/FileUpload/FileUpload.tsx +0 -477
- package/src/atoms/composed/FileUpload/index.ts +0 -2
- package/src/atoms/composed/FormField/FormField.tsx +0 -92
- package/src/atoms/composed/FormField/index.ts +0 -1
- package/src/atoms/composed/GlobalSearch/GlobalSearch.tsx +0 -37
- package/src/atoms/composed/GlobalSearch/index.ts +0 -1
- package/src/atoms/composed/IconBadge/IconBadge.tsx +0 -95
- package/src/atoms/composed/IconBadge/index.ts +0 -2
- package/src/atoms/composed/Modal/Modal.tsx +0 -223
- package/src/atoms/composed/Modal/index.ts +0 -2
- package/src/atoms/composed/PaletteSwitcher.tsx +0 -386
- package/src/atoms/composed/ProgressBar/ProgressBar.tsx +0 -116
- package/src/atoms/composed/ProgressBar/index.ts +0 -1
- package/src/atoms/composed/SalesPanel/SalesPanel.tsx +0 -116
- package/src/atoms/composed/SalesPanel/index.ts +0 -1
- package/src/atoms/composed/SalesPanel/mockSalesData.ts +0 -151
- package/src/atoms/composed/StatCard/StatCard.tsx +0 -219
- package/src/atoms/composed/StatCard/index.ts +0 -1
- package/src/atoms/composed/StyleGuide.tsx +0 -717
- package/src/atoms/composed/Toast/Toast.tsx +0 -219
- package/src/atoms/composed/Toast/index.ts +0 -1
- package/src/atoms/composed/Tooltip/Tooltip.tsx +0 -213
- package/src/atoms/composed/Tooltip/index.ts +0 -1
- package/src/atoms/composed/UserAvatar/UserAvatar.tsx +0 -139
- package/src/atoms/composed/UserAvatar/index.ts +0 -1
- package/src/atoms/composed/UserMenu/UserMenu.tsx +0 -16
- package/src/atoms/composed/UserMenu/index.ts +0 -1
- package/src/atoms/composed/index.ts +0 -30
- package/src/atoms/hooks/useApi.ts +0 -80
- package/src/atoms/hooks/useHealth.ts +0 -17
- package/src/atoms/index.ts +0 -13
- package/src/atoms/services/api/client.ts +0 -134
- package/src/atoms/services/auth-service.ts +0 -248
- package/src/atoms/services/health.ts +0 -15
- package/src/atoms/services/index.ts +0 -3
- package/src/atoms/shared/config/constants.ts +0 -17
- package/src/atoms/shared/config/dashboard-sizes.ts +0 -111
- package/src/atoms/shared/config/environment.ts +0 -10
- package/src/atoms/shared/index.ts +0 -4
- package/src/atoms/shared/styles/color-palettes.css +0 -566
- package/src/atoms/types/auth.ts +0 -62
- package/src/atoms/types/entity-config.ts +0 -127
- package/src/atoms/types/generated.ts +0 -1469
- package/src/atoms/types/index.ts +0 -6
- package/src/atoms/types/loading.ts +0 -28
- package/src/atoms/types/navigation.ts +0 -43
- package/src/atoms/ui/Badge.tsx +0 -30
- package/src/atoms/ui/ErrorBoundary.tsx +0 -59
- package/src/atoms/ui/Select.tsx +0 -53
- package/src/atoms/ui/Switch.tsx +0 -42
- package/src/atoms/ui/Tabs.tsx +0 -118
- package/src/atoms/ui/avatar.tsx +0 -48
- package/src/atoms/ui/button.tsx +0 -70
- package/src/atoms/ui/card.tsx +0 -76
- package/src/atoms/ui/dropdown-menu.tsx +0 -199
- package/src/atoms/ui/index.ts +0 -39
- package/src/atoms/ui/input.tsx +0 -23
- package/src/atoms/ui/label.tsx +0 -23
- package/src/atoms/ui/skeleton.tsx +0 -13
- package/src/atoms/ui/spinner.tsx +0 -49
- package/src/atoms/ui/table.tsx +0 -116
- package/src/atoms/utils/animations.ts +0 -135
- package/src/atoms/utils/icon-resolver.tsx +0 -54
- package/src/atoms/utils/metric-engine.ts +0 -236
- package/src/atoms/utils/tooltip-helpers.ts +0 -140
- package/src/atoms/utils/utils.ts +0 -11
- package/src/features/auth/components/LoginForm.tsx +0 -168
- package/src/features/auth/components/LogoutButton.tsx +0 -19
- package/src/features/auth/components/ProtectedRoute.tsx +0 -60
- package/src/features/auth/components/index.ts +0 -4
- package/src/features/auth/hooks/index.ts +0 -2
- package/src/features/auth/hooks/useAuth.tsx +0 -205
- package/src/features/auth/hooks/usePermissions.ts +0 -35
- package/src/features/auth/index.ts +0 -2
- package/src/features/index.ts +0 -2
- package/src/index.css +0 -704
- package/src/index.ts +0 -13
- package/src/main.tsx +0 -48
- package/src/molecules/.gitkeep +0 -0
- package/src/molecules/forms/FormGroup.tsx +0 -75
- package/src/molecules/forms/SearchInput.tsx +0 -259
- package/src/molecules/forms/index.ts +0 -4
- package/src/molecules/index.ts +0 -4
- package/src/molecules/layout/AppHeader/AppHeader.tsx +0 -42
- package/src/molecules/layout/AppHeader/index.ts +0 -1
- package/src/molecules/layout/AppLayout.tsx +0 -29
- package/src/molecules/layout/DashboardWithSidePanel/DashboardWithSidePanel.tsx +0 -42
- package/src/molecules/layout/DashboardWithSidePanel/index.ts +0 -1
- package/src/molecules/layout/NavigationContext.tsx +0 -63
- package/src/molecules/layout/PageTemplate.tsx +0 -87
- package/src/molecules/layout/SectionHeader/SectionHeader.tsx +0 -87
- package/src/molecules/layout/SectionHeader/index.ts +0 -1
- package/src/molecules/layout/ShowcaseSection.tsx +0 -57
- package/src/molecules/layout/Sidebar.tsx +0 -131
- package/src/molecules/layout/SidebarButton/SidebarButton.tsx +0 -121
- package/src/molecules/layout/SidebarButton/index.ts +0 -1
- package/src/molecules/layout/SidebarContext.tsx +0 -31
- package/src/molecules/layout/index.ts +0 -10
- package/src/molecules/navigation/NavMenu.tsx +0 -188
- package/src/molecules/navigation/Pagination.tsx +0 -172
- package/src/molecules/navigation/index.ts +0 -4
- package/src/organisms/entity/CategoryBreakdownPanel.tsx +0 -427
- package/src/organisms/entity/EntityListPanel.tsx +0 -339
- package/src/organisms/entity/MetricsOverviewPanel.tsx +0 -236
- package/src/organisms/entity/TrendAnalysisPanel.tsx +0 -337
- package/src/organisms/entity/index.ts +0 -4
- package/src/organisms/index.ts +0 -9
- package/src/organisms/showcase/ComponentShowcasePage.tsx +0 -2496
- package/src/organisms/showcase/index.ts +0 -1
- package/src/pages/AdminShowcase/AdminCRUDShowcase.tsx +0 -242
- package/src/pages/AdminShowcase/AdminDashboardShowcase.tsx +0 -173
- package/src/pages/AdminShowcase/AdminDetailShowcase.tsx +0 -385
- package/src/pages/AdminShowcase/SalesPerformanceDashboard.tsx +0 -158
- package/src/pages/AdminShowcase/index.tsx +0 -4
- package/src/pages/ComponentShowcase/BadgesShowcase.tsx +0 -188
- package/src/pages/ComponentShowcase/CardsShowcase.tsx +0 -392
- package/src/pages/ComponentShowcase/PalettesShowcase.tsx +0 -207
- package/src/pages/ComponentShowcase/StatesShowcase.tsx +0 -485
- package/src/pages/ComponentShowcase/TablesShowcase.tsx +0 -134
- package/src/pages/ComponentShowcase/TypographyShowcase.tsx +0 -255
- package/src/pages/ComponentShowcase/index.tsx +0 -188
- package/src/pages/EntityShowcase/EntityManagementShowcase.tsx +0 -137
- package/src/pages/EntityShowcase/EntityPerformanceShowcase.tsx +0 -117
- package/src/pages/EntityShowcase/index.ts +0 -2
- package/src/pages/EntityTemplateExample.tsx +0 -229
- package/src/pages/TestEntityTemplate.tsx +0 -40
- package/src/pages/index.ts +0 -3
- package/src/templates/AuthTemplate.tsx +0 -216
- package/src/templates/ComponentShowcaseTemplate.tsx +0 -173
- package/src/templates/DashboardTemplate.tsx +0 -232
- package/src/templates/DataTemplate.tsx +0 -319
- package/src/templates/admin/AdminCRUDTemplate.tsx +0 -630
- package/src/templates/admin/AdminDashboardTemplate.tsx +0 -351
- package/src/templates/admin/AdminDetailTemplate.tsx +0 -563
- package/src/templates/admin/index.ts +0 -29
- package/src/templates/entity/EntityManagementTemplate.tsx +0 -430
- package/src/templates/entity/EntityPerformanceDashboardTemplate.tsx +0 -277
- package/src/templates/entity/configs/financial-config.ts +0 -141
- package/src/templates/entity/configs/index.ts +0 -1
- package/src/templates/entity/index.ts +0 -3
- package/src/templates/factory.tsx +0 -176
- package/src/templates/financial/FinancialDashboardTemplate.tsx +0 -326
- package/src/templates/index.ts +0 -41
- package/src/vite-env.d.ts +0 -1
- /package/dist/atoms/{composed → components/data}/Chart/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/data}/DataBadge/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/data}/DataTable/TableCellWithTooltip.d.ts +0 -0
- /package/dist/atoms/{composed → components/data}/DataTable/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/data}/DetailedCard/DetailedCard.d.ts +0 -0
- /package/dist/atoms/{composed → components/data}/DetailedCard/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/data}/IconBadge/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/data}/ProgressBar/ProgressBar.d.ts +0 -0
- /package/dist/atoms/{composed → components/data}/ProgressBar/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/data}/StatCard/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/domain}/SalesPanel/SalesPanel.d.ts +0 -0
- /package/dist/atoms/{composed → components/domain}/SalesPanel/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/domain}/SalesPanel/mockSalesData.d.ts +0 -0
- /package/dist/atoms/{composed → components/feedback}/Alert/Alert.d.ts +0 -0
- /package/dist/atoms/{composed → components/feedback}/Alert/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/feedback}/EmptyState/EmptyState.d.ts +0 -0
- /package/dist/atoms/{composed → components/feedback}/EmptyState/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/feedback}/Toast/Toast.d.ts +0 -0
- /package/dist/atoms/{composed → components/feedback}/Toast/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/forms}/DateTimePicker/DateTimePicker.d.ts +0 -0
- /package/dist/atoms/{composed → components/forms}/DateTimePicker/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/forms}/FileUpload/FileUpload.d.ts +0 -0
- /package/dist/atoms/{composed → components/forms}/FileUpload/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/forms}/FormField/FormField.d.ts +0 -0
- /package/dist/atoms/{composed → components/forms}/FormField/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/layout}/Accordion/Accordion.d.ts +0 -0
- /package/dist/atoms/{composed → components/layout}/Accordion/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/layout}/Breadcrumb/Breadcrumb.d.ts +0 -0
- /package/dist/atoms/{composed → components/layout}/Breadcrumb/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/layout}/Modal/Modal.d.ts +0 -0
- /package/dist/atoms/{composed → components/layout}/Modal/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/layout}/Tooltip/Tooltip.d.ts +0 -0
- /package/dist/atoms/{composed → components/navigation}/GlobalSearch/GlobalSearch.d.ts +0 -0
- /package/dist/atoms/{composed → components/navigation}/GlobalSearch/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/theme}/ColorSwatch/ColorSwatch.d.ts +0 -0
- /package/dist/atoms/{composed → components/theme}/ColorSwatch/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/theme}/DarkModeToggle.d.ts +0 -0
- /package/dist/atoms/{composed → components/theme}/PaletteSwitcher.d.ts +0 -0
- /package/dist/atoms/{composed → components/theme}/StyleGuide.d.ts +0 -0
- /package/dist/atoms/{composed → components/user}/UserAvatar/UserAvatar.d.ts +0 -0
- /package/dist/atoms/{composed → components/user}/UserAvatar/index.d.ts +0 -0
- /package/dist/atoms/{composed → components/user}/UserMenu/UserMenu.d.ts +0 -0
- /package/dist/atoms/{composed → components/user}/UserMenu/index.d.ts +0 -0
- /package/dist/atoms/{ui → primitives}/Badge.d.ts +0 -0
- /package/dist/atoms/{ui → primitives}/Select.d.ts +0 -0
- /package/dist/atoms/{ui → primitives}/Switch.d.ts +0 -0
- /package/dist/atoms/{ui → primitives}/Tabs.d.ts +0 -0
- /package/dist/atoms/{ui → primitives}/avatar.d.ts +0 -0
- /package/dist/atoms/{ui → primitives}/card.d.ts +0 -0
- /package/dist/atoms/{ui → primitives}/dropdown-menu.d.ts +0 -0
- /package/dist/atoms/{ui → primitives}/input.d.ts +0 -0
- /package/dist/atoms/{ui → primitives}/label.d.ts +0 -0
- /package/dist/atoms/{ui → primitives}/skeleton.d.ts +0 -0
- /package/dist/atoms/{ui → primitives}/spinner.d.ts +0 -0
- /package/dist/atoms/{ui → primitives}/table.d.ts +0 -0
package/cli/index.js
ADDED
|
@@ -0,0 +1,3210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// cli/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
default: () => index_default
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var import_commander = require("commander");
|
|
27
|
+
|
|
28
|
+
// cli/commands/generate-hooks.ts
|
|
29
|
+
var import_fs3 = require("fs");
|
|
30
|
+
var import_path3 = require("path");
|
|
31
|
+
|
|
32
|
+
// src/codegen/openapi/parser.js
|
|
33
|
+
var import_fs = require("fs");
|
|
34
|
+
var import_path = require("path");
|
|
35
|
+
var OpenAPIParser = class {
|
|
36
|
+
spec;
|
|
37
|
+
refs = /* @__PURE__ */ new Map();
|
|
38
|
+
constructor(spec) {
|
|
39
|
+
this.spec = spec;
|
|
40
|
+
this.buildRefsMap();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse the OpenAPI specification into our intermediate representation
|
|
44
|
+
*/
|
|
45
|
+
parse() {
|
|
46
|
+
return {
|
|
47
|
+
info: this.parseInfo(),
|
|
48
|
+
servers: this.parseServers(),
|
|
49
|
+
endpoints: this.parseEndpoints(),
|
|
50
|
+
schemas: this.parseSchemas(),
|
|
51
|
+
security: this.parseSecurity()
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
parseInfo() {
|
|
55
|
+
return {
|
|
56
|
+
title: this.spec.info.title,
|
|
57
|
+
version: this.spec.info.version,
|
|
58
|
+
description: this.spec.info.description
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
parseServers() {
|
|
62
|
+
if (!this.spec.servers)
|
|
63
|
+
return [];
|
|
64
|
+
return this.spec.servers.map((server) => ({
|
|
65
|
+
url: server.url,
|
|
66
|
+
description: server.description,
|
|
67
|
+
variables: server.variables ? Object.fromEntries(Object.entries(server.variables).map(([key, variable]) => [
|
|
68
|
+
key,
|
|
69
|
+
variable.default || ""
|
|
70
|
+
])) : void 0
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
parseEndpoints() {
|
|
74
|
+
const endpoints = [];
|
|
75
|
+
for (const [path, pathItem] of Object.entries(this.spec.paths || {})) {
|
|
76
|
+
if (!pathItem)
|
|
77
|
+
continue;
|
|
78
|
+
const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
79
|
+
for (const method of methods) {
|
|
80
|
+
const operation = pathItem[method];
|
|
81
|
+
if (!operation)
|
|
82
|
+
continue;
|
|
83
|
+
endpoints.push({
|
|
84
|
+
path,
|
|
85
|
+
method,
|
|
86
|
+
operationId: operation.operationId,
|
|
87
|
+
summary: operation.summary,
|
|
88
|
+
description: operation.description,
|
|
89
|
+
tags: operation.tags,
|
|
90
|
+
parameters: this.parseParameters(operation.parameters),
|
|
91
|
+
requestBody: operation.requestBody ? this.parseRequestBody(operation.requestBody) : void 0,
|
|
92
|
+
responses: this.parseResponses(operation.responses),
|
|
93
|
+
security: operation.security ? this.parseOperationSecurity(operation.security) : void 0
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return endpoints;
|
|
98
|
+
}
|
|
99
|
+
parseParameters(parameters) {
|
|
100
|
+
if (!parameters)
|
|
101
|
+
return [];
|
|
102
|
+
return parameters.map((param) => {
|
|
103
|
+
const resolved = this.resolveRef(param);
|
|
104
|
+
return {
|
|
105
|
+
name: resolved.name,
|
|
106
|
+
in: resolved.in,
|
|
107
|
+
required: resolved.required || false,
|
|
108
|
+
schema: this.parseSchema(resolved.schema),
|
|
109
|
+
description: resolved.description
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
parseRequestBody(requestBody) {
|
|
114
|
+
const resolved = this.resolveRef(requestBody);
|
|
115
|
+
return {
|
|
116
|
+
required: resolved.required || false,
|
|
117
|
+
description: resolved.description,
|
|
118
|
+
content: Object.fromEntries(Object.entries(resolved.content || {}).map(([mediaType, mediaTypeObj]) => [
|
|
119
|
+
mediaType,
|
|
120
|
+
{ schema: this.parseSchema(mediaTypeObj.schema) }
|
|
121
|
+
]))
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
parseResponses(responses) {
|
|
125
|
+
return Object.entries(responses).map(([statusCode, response]) => {
|
|
126
|
+
const resolved = this.resolveRef(response);
|
|
127
|
+
return {
|
|
128
|
+
statusCode,
|
|
129
|
+
description: resolved.description,
|
|
130
|
+
content: resolved.content ? Object.fromEntries(Object.entries(resolved.content).map(([mediaType, mediaTypeObj]) => [
|
|
131
|
+
mediaType,
|
|
132
|
+
{ schema: this.parseSchema(mediaTypeObj.schema) }
|
|
133
|
+
])) : void 0,
|
|
134
|
+
headers: resolved.headers ? Object.fromEntries(Object.entries(resolved.headers).map(([headerName, header]) => [
|
|
135
|
+
headerName,
|
|
136
|
+
this.parseSchema(this.resolveRef(header).schema)
|
|
137
|
+
])) : void 0
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
parseSchema(schema) {
|
|
142
|
+
if (!schema) {
|
|
143
|
+
return { type: "any" };
|
|
144
|
+
}
|
|
145
|
+
if ("$ref" in schema) {
|
|
146
|
+
return {
|
|
147
|
+
type: "any",
|
|
148
|
+
// Will be resolved later
|
|
149
|
+
ref: schema.$ref,
|
|
150
|
+
originalRef: schema.$ref
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const resolved = schema;
|
|
154
|
+
if (resolved.type === "array") {
|
|
155
|
+
return {
|
|
156
|
+
type: "array",
|
|
157
|
+
items: this.parseSchema(resolved.items),
|
|
158
|
+
description: resolved.description,
|
|
159
|
+
nullable: resolved.nullable
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (resolved.type === "object" || resolved.properties) {
|
|
163
|
+
return {
|
|
164
|
+
type: "object",
|
|
165
|
+
properties: resolved.properties ? Object.fromEntries(Object.entries(resolved.properties).map(([propName, propSchema]) => [
|
|
166
|
+
propName,
|
|
167
|
+
this.parseSchema(propSchema)
|
|
168
|
+
])) : void 0,
|
|
169
|
+
required: resolved.required,
|
|
170
|
+
description: resolved.description,
|
|
171
|
+
nullable: resolved.nullable
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const type = this.mapOpenAPIType(resolved.type);
|
|
175
|
+
return {
|
|
176
|
+
type,
|
|
177
|
+
format: resolved.format,
|
|
178
|
+
enum: resolved.enum,
|
|
179
|
+
description: resolved.description,
|
|
180
|
+
example: resolved.example,
|
|
181
|
+
nullable: resolved.nullable
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
parseSchemas() {
|
|
185
|
+
if (!this.spec.components?.schemas)
|
|
186
|
+
return [];
|
|
187
|
+
return Object.entries(this.spec.components.schemas).map(([name, schema]) => ({
|
|
188
|
+
...this.parseSchema(schema),
|
|
189
|
+
ref: `#/components/schemas/${name}`
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
parseSecurity() {
|
|
193
|
+
if (!this.spec.components?.securitySchemes)
|
|
194
|
+
return [];
|
|
195
|
+
return Object.entries(this.spec.components.securitySchemes).map(([_name, scheme]) => {
|
|
196
|
+
const resolved = this.resolveRef(scheme);
|
|
197
|
+
return {
|
|
198
|
+
type: resolved.type,
|
|
199
|
+
scheme: "scheme" in resolved ? resolved.scheme : void 0,
|
|
200
|
+
bearerFormat: "bearerFormat" in resolved ? resolved.bearerFormat : void 0,
|
|
201
|
+
in: "in" in resolved ? resolved.in : void 0,
|
|
202
|
+
name: "name" in resolved ? resolved.name : void 0,
|
|
203
|
+
flows: "flows" in resolved ? resolved.flows : void 0,
|
|
204
|
+
openIdConnectUrl: "openIdConnectUrl" in resolved ? resolved.openIdConnectUrl : void 0
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
parseOperationSecurity(_security) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
mapOpenAPIType(type) {
|
|
212
|
+
switch (type) {
|
|
213
|
+
case "string":
|
|
214
|
+
return "string";
|
|
215
|
+
case "number":
|
|
216
|
+
return "number";
|
|
217
|
+
case "integer":
|
|
218
|
+
return "integer";
|
|
219
|
+
case "boolean":
|
|
220
|
+
return "boolean";
|
|
221
|
+
case "array":
|
|
222
|
+
return "array";
|
|
223
|
+
case "object":
|
|
224
|
+
return "object";
|
|
225
|
+
case "null":
|
|
226
|
+
return "null";
|
|
227
|
+
default:
|
|
228
|
+
return "any";
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
buildRefsMap() {
|
|
232
|
+
this.traverseAndMapRefs(this.spec, "");
|
|
233
|
+
}
|
|
234
|
+
traverseAndMapRefs(obj, currentPath) {
|
|
235
|
+
if (typeof obj !== "object" || obj === null)
|
|
236
|
+
return;
|
|
237
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
238
|
+
const path = currentPath ? `${currentPath}/${key}` : key;
|
|
239
|
+
if (key === "$ref" && typeof value === "string") {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (typeof value === "object") {
|
|
243
|
+
if (currentPath.includes("/components/")) {
|
|
244
|
+
this.refs.set(`#/${path}`, value);
|
|
245
|
+
}
|
|
246
|
+
this.traverseAndMapRefs(value, path);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
resolveRef(item) {
|
|
251
|
+
if (typeof item === "object" && item !== null && "$ref" in item) {
|
|
252
|
+
const ref = item.$ref;
|
|
253
|
+
const resolved = this.refs.get(ref);
|
|
254
|
+
if (!resolved) {
|
|
255
|
+
throw new Error(`Could not resolve reference: ${ref}`);
|
|
256
|
+
}
|
|
257
|
+
return resolved;
|
|
258
|
+
}
|
|
259
|
+
return item;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
async function parseOpenAPI(spec) {
|
|
263
|
+
const parser = new OpenAPIParser(spec);
|
|
264
|
+
return parser.parse();
|
|
265
|
+
}
|
|
266
|
+
async function loadOpenAPISpec(source) {
|
|
267
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
268
|
+
const response = await fetch(source);
|
|
269
|
+
if (!response.ok) {
|
|
270
|
+
throw new Error(`Failed to load OpenAPI spec from ${source}: ${response.statusText}`);
|
|
271
|
+
}
|
|
272
|
+
return response.json();
|
|
273
|
+
} else {
|
|
274
|
+
try {
|
|
275
|
+
return JSON.parse(source);
|
|
276
|
+
} catch {
|
|
277
|
+
try {
|
|
278
|
+
const absolutePath = (0, import_path.resolve)(source);
|
|
279
|
+
const content = await import_fs.promises.readFile(absolutePath, "utf8");
|
|
280
|
+
return JSON.parse(content);
|
|
281
|
+
} catch (fileError) {
|
|
282
|
+
throw new Error(`Failed to load OpenAPI spec: Not a valid URL, JSON string, or file path. ${fileError}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// src/codegen/openapi/type-generator.js
|
|
289
|
+
var TypeScriptGenerator = class {
|
|
290
|
+
options;
|
|
291
|
+
generatedTypes = /* @__PURE__ */ new Set();
|
|
292
|
+
refMap = /* @__PURE__ */ new Map();
|
|
293
|
+
constructor(options = {}) {
|
|
294
|
+
this.options = {
|
|
295
|
+
prefix: options.prefix || "",
|
|
296
|
+
suffix: options.suffix || "",
|
|
297
|
+
enumStyle: options.enumStyle || "union",
|
|
298
|
+
nullableStyle: options.nullableStyle || "undefined",
|
|
299
|
+
includeJSDoc: options.includeJSDoc !== false,
|
|
300
|
+
includeExamples: options.includeExamples !== false
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
generate(parsedAPI) {
|
|
304
|
+
this.generatedTypes.clear();
|
|
305
|
+
this.refMap.clear();
|
|
306
|
+
this.buildRefMap(parsedAPI);
|
|
307
|
+
return {
|
|
308
|
+
schemas: this.generateSchemas(parsedAPI.schemas),
|
|
309
|
+
endpoints: this.generateEndpointTypes(parsedAPI.endpoints),
|
|
310
|
+
parameters: this.generateParameterTypes(parsedAPI.endpoints),
|
|
311
|
+
responses: this.generateResponseTypes(parsedAPI.endpoints),
|
|
312
|
+
index: this.generateIndexFile()
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
buildRefMap(parsedAPI) {
|
|
316
|
+
parsedAPI.schemas.forEach((schema) => {
|
|
317
|
+
if (schema.ref) {
|
|
318
|
+
const typeName = this.extractTypeNameFromRef(schema.ref);
|
|
319
|
+
this.refMap.set(schema.ref, typeName);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
generateSchemas(schemas) {
|
|
324
|
+
const types = [];
|
|
325
|
+
types.push(this.generateFileHeader("Schema Types"));
|
|
326
|
+
for (const schema of schemas) {
|
|
327
|
+
if (!schema.ref)
|
|
328
|
+
continue;
|
|
329
|
+
const typeName = this.refMap.get(schema.ref);
|
|
330
|
+
if (!typeName || this.generatedTypes.has(typeName))
|
|
331
|
+
continue;
|
|
332
|
+
const typeDefinition = this.generateSchemaType(schema, typeName);
|
|
333
|
+
types.push(typeDefinition);
|
|
334
|
+
this.generatedTypes.add(typeName);
|
|
335
|
+
}
|
|
336
|
+
return types.join("\n\n");
|
|
337
|
+
}
|
|
338
|
+
generateSchemaType(schema, typeName) {
|
|
339
|
+
const lines = [];
|
|
340
|
+
if (this.options.includeJSDoc && schema.description) {
|
|
341
|
+
lines.push(`/**`);
|
|
342
|
+
lines.push(` * ${schema.description}`);
|
|
343
|
+
if (this.options.includeExamples && schema.example) {
|
|
344
|
+
lines.push(` * @example ${JSON.stringify(schema.example)}`);
|
|
345
|
+
}
|
|
346
|
+
lines.push(` */`);
|
|
347
|
+
}
|
|
348
|
+
const typeContent = this.generateTypeContent(schema);
|
|
349
|
+
if (schema.type === "object" && !schema.enum) {
|
|
350
|
+
lines.push(`export interface ${typeName} ${typeContent}`);
|
|
351
|
+
} else {
|
|
352
|
+
lines.push(`export type ${typeName} = ${typeContent}`);
|
|
353
|
+
}
|
|
354
|
+
return lines.join("\n");
|
|
355
|
+
}
|
|
356
|
+
generateTypeContent(schema) {
|
|
357
|
+
switch (schema.type) {
|
|
358
|
+
case "object":
|
|
359
|
+
return this.generateObjectType(schema);
|
|
360
|
+
case "array":
|
|
361
|
+
return `${this.generateTypeContent(schema.items)}[]`;
|
|
362
|
+
case "string":
|
|
363
|
+
if (schema.enum) {
|
|
364
|
+
return this.generateEnumType(schema.enum);
|
|
365
|
+
}
|
|
366
|
+
return this.addNullable("string", schema.nullable);
|
|
367
|
+
case "number":
|
|
368
|
+
case "integer":
|
|
369
|
+
return this.addNullable("number", schema.nullable);
|
|
370
|
+
case "boolean":
|
|
371
|
+
return this.addNullable("boolean", schema.nullable);
|
|
372
|
+
case "null":
|
|
373
|
+
return "null";
|
|
374
|
+
case "any":
|
|
375
|
+
if (schema.ref) {
|
|
376
|
+
const refType = this.refMap.get(schema.ref);
|
|
377
|
+
return refType || "unknown";
|
|
378
|
+
}
|
|
379
|
+
return "unknown";
|
|
380
|
+
default:
|
|
381
|
+
return "unknown";
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
generateObjectType(schema) {
|
|
385
|
+
if (!schema.properties) {
|
|
386
|
+
return "{ [key: string]: unknown }";
|
|
387
|
+
}
|
|
388
|
+
const properties = [];
|
|
389
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
390
|
+
const isRequired = schema.required?.includes(propName) || false;
|
|
391
|
+
const isOptional = !isRequired ? "?" : "";
|
|
392
|
+
const propType = this.generateTypeContent(propSchema);
|
|
393
|
+
let propDoc = "";
|
|
394
|
+
if (this.options.includeJSDoc && propSchema.description) {
|
|
395
|
+
propDoc = ` /** ${propSchema.description} */
|
|
396
|
+
`;
|
|
397
|
+
}
|
|
398
|
+
properties.push(`${propDoc}${propName}${isOptional}: ${propType}`);
|
|
399
|
+
}
|
|
400
|
+
return `{
|
|
401
|
+
${properties.join("\n ")}
|
|
402
|
+
}`;
|
|
403
|
+
}
|
|
404
|
+
generateEnumType(enumValues) {
|
|
405
|
+
if (this.options.enumStyle === "enum") {
|
|
406
|
+
return enumValues.map((val) => `'${val}'`).join(" | ");
|
|
407
|
+
} else {
|
|
408
|
+
return enumValues.map((val) => `'${val}'`).join(" | ");
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
addNullable(type, nullable) {
|
|
412
|
+
if (!nullable)
|
|
413
|
+
return type;
|
|
414
|
+
switch (this.options.nullableStyle) {
|
|
415
|
+
case "null":
|
|
416
|
+
return `${type} | null`;
|
|
417
|
+
case "both":
|
|
418
|
+
return `${type} | null | undefined`;
|
|
419
|
+
case "undefined":
|
|
420
|
+
default:
|
|
421
|
+
return `${type} | undefined`;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
generateEndpointTypes(endpoints) {
|
|
425
|
+
const types = [];
|
|
426
|
+
types.push(this.generateFileHeader("Endpoint Types"));
|
|
427
|
+
const endpointTypes = /* @__PURE__ */ new Set();
|
|
428
|
+
for (const endpoint of endpoints) {
|
|
429
|
+
const operationName = this.getOperationName(endpoint);
|
|
430
|
+
if (endpointTypes.has(operationName))
|
|
431
|
+
continue;
|
|
432
|
+
endpointTypes.add(operationName);
|
|
433
|
+
const requestType = this.generateRequestType(endpoint, operationName);
|
|
434
|
+
if (requestType) {
|
|
435
|
+
types.push(requestType);
|
|
436
|
+
}
|
|
437
|
+
const responseType = this.generateResponseType(endpoint, operationName);
|
|
438
|
+
if (responseType) {
|
|
439
|
+
types.push(responseType);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return types.join("\n\n");
|
|
443
|
+
}
|
|
444
|
+
generateRequestType(endpoint, operationName) {
|
|
445
|
+
const hasParams = endpoint.parameters.length > 0;
|
|
446
|
+
const hasBody = endpoint.requestBody !== void 0;
|
|
447
|
+
if (!hasParams && !hasBody)
|
|
448
|
+
return null;
|
|
449
|
+
const lines = [];
|
|
450
|
+
if (this.options.includeJSDoc) {
|
|
451
|
+
lines.push(`/**`);
|
|
452
|
+
lines.push(` * Request type for ${endpoint.method.toUpperCase()} ${endpoint.path}`);
|
|
453
|
+
if (endpoint.summary) {
|
|
454
|
+
lines.push(` * ${endpoint.summary}`);
|
|
455
|
+
}
|
|
456
|
+
lines.push(` */`);
|
|
457
|
+
}
|
|
458
|
+
const properties = [];
|
|
459
|
+
if (hasParams) {
|
|
460
|
+
const paramsByType = this.groupParametersByType(endpoint.parameters);
|
|
461
|
+
for (const [paramType, params] of Object.entries(paramsByType)) {
|
|
462
|
+
if (params.length === 0)
|
|
463
|
+
continue;
|
|
464
|
+
const paramProps = params.map((param) => {
|
|
465
|
+
const optional = param.required ? "" : "?";
|
|
466
|
+
const type = this.generateTypeContent(param.schema);
|
|
467
|
+
return ` ${param.name}${optional}: ${type}`;
|
|
468
|
+
});
|
|
469
|
+
properties.push(` ${paramType}: {
|
|
470
|
+
${paramProps.join("\n")}
|
|
471
|
+
}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (hasBody && endpoint.requestBody) {
|
|
475
|
+
const bodyType = this.generateRequestBodyType(endpoint.requestBody);
|
|
476
|
+
const optional = endpoint.requestBody.required ? "" : "?";
|
|
477
|
+
properties.push(` body${optional}: ${bodyType}`);
|
|
478
|
+
}
|
|
479
|
+
const typeName = `${this.formatTypeName(operationName)}Request`;
|
|
480
|
+
lines.push(`export interface ${typeName} {`);
|
|
481
|
+
lines.push(properties.join("\n"));
|
|
482
|
+
lines.push(`}`);
|
|
483
|
+
return lines.join("\n");
|
|
484
|
+
}
|
|
485
|
+
generateResponseType(endpoint, operationName) {
|
|
486
|
+
if (endpoint.responses.length === 0)
|
|
487
|
+
return null;
|
|
488
|
+
const lines = [];
|
|
489
|
+
if (this.options.includeJSDoc) {
|
|
490
|
+
lines.push(`/**`);
|
|
491
|
+
lines.push(` * Response type for ${endpoint.method.toUpperCase()} ${endpoint.path}`);
|
|
492
|
+
if (endpoint.summary) {
|
|
493
|
+
lines.push(` * ${endpoint.summary}`);
|
|
494
|
+
}
|
|
495
|
+
lines.push(` */`);
|
|
496
|
+
}
|
|
497
|
+
const responseTypes = [];
|
|
498
|
+
for (const response of endpoint.responses) {
|
|
499
|
+
if (response.content) {
|
|
500
|
+
for (const [mediaType, content] of Object.entries(response.content)) {
|
|
501
|
+
if (mediaType.includes("json")) {
|
|
502
|
+
const type = this.generateTypeContent(content.schema);
|
|
503
|
+
responseTypes.push(`{ status: ${response.statusCode}; data: ${type} }`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
responseTypes.push(`{ status: ${response.statusCode}; data: void }`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const typeName = `${this.formatTypeName(operationName)}Response`;
|
|
511
|
+
const unionType = responseTypes.length > 1 ? responseTypes.join(" | ") : responseTypes[0] || "void";
|
|
512
|
+
lines.push(`export type ${typeName} = ${unionType}`);
|
|
513
|
+
return lines.join("\n");
|
|
514
|
+
}
|
|
515
|
+
generateRequestBodyType(requestBody) {
|
|
516
|
+
const jsonContent = requestBody.content["application/json"] || requestBody.content["application/vnd.api+json"] || Object.values(requestBody.content)[0];
|
|
517
|
+
if (!jsonContent)
|
|
518
|
+
return "unknown";
|
|
519
|
+
return this.generateTypeContent(jsonContent.schema);
|
|
520
|
+
}
|
|
521
|
+
generateParameterTypes(_endpoints) {
|
|
522
|
+
const types = [];
|
|
523
|
+
types.push(this.generateFileHeader("Parameter Types"));
|
|
524
|
+
return types.join("\n\n");
|
|
525
|
+
}
|
|
526
|
+
generateResponseTypes(_endpoints) {
|
|
527
|
+
const types = [];
|
|
528
|
+
types.push(this.generateFileHeader("Response Types"));
|
|
529
|
+
return types.join("\n\n");
|
|
530
|
+
}
|
|
531
|
+
generateIndexFile() {
|
|
532
|
+
const lines = [];
|
|
533
|
+
lines.push(this.generateFileHeader("Generated API Types"));
|
|
534
|
+
lines.push("");
|
|
535
|
+
lines.push("// Schema exports");
|
|
536
|
+
lines.push("export * from './schemas'");
|
|
537
|
+
lines.push("");
|
|
538
|
+
lines.push("// Endpoint exports");
|
|
539
|
+
lines.push("export * from './endpoints'");
|
|
540
|
+
lines.push("");
|
|
541
|
+
lines.push("// Parameter exports");
|
|
542
|
+
lines.push("export * from './parameters'");
|
|
543
|
+
lines.push("");
|
|
544
|
+
lines.push("// Response exports");
|
|
545
|
+
lines.push("export * from './responses'");
|
|
546
|
+
return lines.join("\n");
|
|
547
|
+
}
|
|
548
|
+
generateFileHeader(title) {
|
|
549
|
+
return `/**
|
|
550
|
+
* ${title}
|
|
551
|
+
*
|
|
552
|
+
* Auto-generated from OpenAPI specification
|
|
553
|
+
* Do not edit manually - regenerate using the OpenAPI type generator
|
|
554
|
+
*/`;
|
|
555
|
+
}
|
|
556
|
+
getOperationName(endpoint) {
|
|
557
|
+
if (endpoint.operationId) {
|
|
558
|
+
return endpoint.operationId;
|
|
559
|
+
}
|
|
560
|
+
const pathParts = endpoint.path.split("/").filter((part) => part && !part.startsWith("{")).map((part) => this.capitalize(part));
|
|
561
|
+
return `${endpoint.method}${pathParts.join("")}`;
|
|
562
|
+
}
|
|
563
|
+
formatTypeName(name) {
|
|
564
|
+
const formatted = this.capitalize(name);
|
|
565
|
+
return `${this.options.prefix}${formatted}${this.options.suffix}`;
|
|
566
|
+
}
|
|
567
|
+
extractTypeNameFromRef(ref) {
|
|
568
|
+
const parts = ref.split("/");
|
|
569
|
+
const name = parts[parts.length - 1];
|
|
570
|
+
return this.formatTypeName(name);
|
|
571
|
+
}
|
|
572
|
+
groupParametersByType(parameters) {
|
|
573
|
+
const grouped = {
|
|
574
|
+
path: [],
|
|
575
|
+
query: [],
|
|
576
|
+
header: [],
|
|
577
|
+
cookie: []
|
|
578
|
+
};
|
|
579
|
+
for (const param of parameters) {
|
|
580
|
+
grouped[param.in].push(param);
|
|
581
|
+
}
|
|
582
|
+
return grouped;
|
|
583
|
+
}
|
|
584
|
+
capitalize(str) {
|
|
585
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
function generateTypes(parsedAPI, options) {
|
|
589
|
+
const generator = new TypeScriptGenerator(options);
|
|
590
|
+
return generator.generate(parsedAPI);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/codegen/openapi/client-generator.js
|
|
594
|
+
var APIClientGenerator = class {
|
|
595
|
+
options;
|
|
596
|
+
constructor(options = {}) {
|
|
597
|
+
this.options = {
|
|
598
|
+
clientType: options.clientType || "axios",
|
|
599
|
+
baseUrl: options.baseUrl || "",
|
|
600
|
+
includeAuth: options.includeAuth !== false,
|
|
601
|
+
authType: options.authType || "bearer",
|
|
602
|
+
timeout: options.timeout || 1e4,
|
|
603
|
+
retries: options.retries || 3,
|
|
604
|
+
includeInterceptors: options.includeInterceptors !== false
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
generate(parsedAPI) {
|
|
608
|
+
return {
|
|
609
|
+
client: this.generateClientSetup(),
|
|
610
|
+
methods: this.generateApiMethods(parsedAPI.endpoints),
|
|
611
|
+
types: this.generateClientTypes(),
|
|
612
|
+
config: this.generateConfiguration(),
|
|
613
|
+
index: this.generateIndexFile()
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
generateClientSetup() {
|
|
617
|
+
if (this.options.clientType === "axios") {
|
|
618
|
+
return this.generateAxiosClient();
|
|
619
|
+
} else {
|
|
620
|
+
return this.generateFetchClient();
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
generateAxiosClient() {
|
|
624
|
+
return `/**
|
|
625
|
+
* Axios API Client
|
|
626
|
+
*
|
|
627
|
+
* Auto-generated API client with interceptors and error handling
|
|
628
|
+
*/
|
|
629
|
+
|
|
630
|
+
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
|
|
631
|
+
import { APIClientConfig, RequestOptions, APIError } from './types'
|
|
632
|
+
|
|
633
|
+
export class APIClient {
|
|
634
|
+
private client: AxiosInstance
|
|
635
|
+
private config: APIClientConfig
|
|
636
|
+
|
|
637
|
+
constructor(config: APIClientConfig) {
|
|
638
|
+
this.config = config
|
|
639
|
+
this.client = this.createAxiosInstance()
|
|
640
|
+
this.setupInterceptors()
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
private createAxiosInstance(): AxiosInstance {
|
|
644
|
+
return axios.create({
|
|
645
|
+
baseURL: this.config.baseUrl,
|
|
646
|
+
timeout: this.config.timeout || ${this.options.timeout},
|
|
647
|
+
headers: {
|
|
648
|
+
'Content-Type': 'application/json',
|
|
649
|
+
...this.config.defaultHeaders
|
|
650
|
+
}
|
|
651
|
+
})
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
${this.options.includeInterceptors ? this.generateAxiosInterceptors() : ""}
|
|
655
|
+
|
|
656
|
+
private async makeRequest<T>(
|
|
657
|
+
method: string,
|
|
658
|
+
url: string,
|
|
659
|
+
options: RequestOptions = {}
|
|
660
|
+
): Promise<T> {
|
|
661
|
+
try {
|
|
662
|
+
const config: AxiosRequestConfig = {
|
|
663
|
+
method: method as any,
|
|
664
|
+
url,
|
|
665
|
+
...options.config
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (options.params) {
|
|
669
|
+
config.params = options.params
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (options.data) {
|
|
673
|
+
config.data = options.data
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (options.headers) {
|
|
677
|
+
config.headers = { ...config.headers, ...options.headers }
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
${this.options.includeAuth ? this.generateAuthInjection() : ""}
|
|
681
|
+
|
|
682
|
+
const response: AxiosResponse<T> = await this.client.request(config)
|
|
683
|
+
return response.data
|
|
684
|
+
} catch (error) {
|
|
685
|
+
throw this.handleError(error as AxiosError)
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
private handleError(error: AxiosError): APIError {
|
|
690
|
+
const apiError: APIError = {
|
|
691
|
+
message: error.message,
|
|
692
|
+
status: error.response?.status,
|
|
693
|
+
statusText: error.response?.statusText,
|
|
694
|
+
data: error.response?.data,
|
|
695
|
+
config: {
|
|
696
|
+
url: error.config?.url,
|
|
697
|
+
method: error.config?.method?.toUpperCase(),
|
|
698
|
+
headers: error.config?.headers
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (this.config.onError) {
|
|
703
|
+
this.config.onError(apiError)
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return apiError
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
public get<T>(url: string, options?: RequestOptions): Promise<T> {
|
|
710
|
+
return this.makeRequest<T>('GET', url, options)
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
public post<T>(url: string, data?: any, options?: RequestOptions): Promise<T> {
|
|
714
|
+
return this.makeRequest<T>('POST', url, { ...options, data })
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
public put<T>(url: string, data?: any, options?: RequestOptions): Promise<T> {
|
|
718
|
+
return this.makeRequest<T>('PUT', url, { ...options, data })
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
public patch<T>(url: string, data?: any, options?: RequestOptions): Promise<T> {
|
|
722
|
+
return this.makeRequest<T>('PATCH', url, { ...options, data })
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
public delete<T>(url: string, options?: RequestOptions): Promise<T> {
|
|
726
|
+
return this.makeRequest<T>('DELETE', url, options)
|
|
727
|
+
}
|
|
728
|
+
}`;
|
|
729
|
+
}
|
|
730
|
+
generateFetchClient() {
|
|
731
|
+
return `/**
|
|
732
|
+
* Fetch API Client
|
|
733
|
+
*
|
|
734
|
+
* Auto-generated API client using native fetch with error handling
|
|
735
|
+
*/
|
|
736
|
+
|
|
737
|
+
import { APIClientConfig, RequestOptions, APIError } from './types'
|
|
738
|
+
|
|
739
|
+
export class APIClient {
|
|
740
|
+
private config: APIClientConfig
|
|
741
|
+
|
|
742
|
+
constructor(config: APIClientConfig) {
|
|
743
|
+
this.config = config
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private async makeRequest<T>(
|
|
747
|
+
method: string,
|
|
748
|
+
url: string,
|
|
749
|
+
options: RequestOptions = {}
|
|
750
|
+
): Promise<T> {
|
|
751
|
+
try {
|
|
752
|
+
const fullUrl = this.buildUrl(url, options.params)
|
|
753
|
+
|
|
754
|
+
const fetchOptions: RequestInit = {
|
|
755
|
+
method,
|
|
756
|
+
headers: {
|
|
757
|
+
'Content-Type': 'application/json',
|
|
758
|
+
...this.config.defaultHeaders,
|
|
759
|
+
...options.headers
|
|
760
|
+
},
|
|
761
|
+
signal: options.signal
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (options.data && method !== 'GET') {
|
|
765
|
+
fetchOptions.body = JSON.stringify(options.data)
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
${this.options.includeAuth ? this.generateFetchAuthInjection() : ""}
|
|
769
|
+
|
|
770
|
+
const response = await fetch(fullUrl, fetchOptions)
|
|
771
|
+
|
|
772
|
+
if (!response.ok) {
|
|
773
|
+
throw await this.createErrorFromResponse(response)
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const contentType = response.headers.get('content-type')
|
|
777
|
+
if (contentType && contentType.includes('application/json')) {
|
|
778
|
+
return await response.json()
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
return await response.text() as any
|
|
782
|
+
} catch (error) {
|
|
783
|
+
if (error instanceof APIError) {
|
|
784
|
+
throw error
|
|
785
|
+
}
|
|
786
|
+
throw this.createGenericError(error as Error)
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
private buildUrl(path: string, params?: Record<string, any>): string {
|
|
791
|
+
const url = new URL(path, this.config.baseUrl)
|
|
792
|
+
|
|
793
|
+
if (params) {
|
|
794
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
795
|
+
if (value !== undefined && value !== null) {
|
|
796
|
+
url.searchParams.append(key, String(value))
|
|
797
|
+
}
|
|
798
|
+
})
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return url.toString()
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
private async createErrorFromResponse(response: Response): Promise<APIError> {
|
|
805
|
+
let data: any
|
|
806
|
+
try {
|
|
807
|
+
data = await response.json()
|
|
808
|
+
} catch {
|
|
809
|
+
data = await response.text()
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const error: APIError = {
|
|
813
|
+
message: data?.message || response.statusText || 'Request failed',
|
|
814
|
+
status: response.status,
|
|
815
|
+
statusText: response.statusText,
|
|
816
|
+
data,
|
|
817
|
+
config: {
|
|
818
|
+
url: response.url,
|
|
819
|
+
method: 'Unknown'
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (this.config.onError) {
|
|
824
|
+
this.config.onError(error)
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
return error
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
private createGenericError(error: Error): APIError {
|
|
831
|
+
const apiError: APIError = {
|
|
832
|
+
message: error.message,
|
|
833
|
+
config: {}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (this.config.onError) {
|
|
837
|
+
this.config.onError(apiError)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
return apiError
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
public get<T>(url: string, options?: RequestOptions): Promise<T> {
|
|
844
|
+
return this.makeRequest<T>('GET', url, options)
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
public post<T>(url: string, data?: any, options?: RequestOptions): Promise<T> {
|
|
848
|
+
return this.makeRequest<T>('POST', url, { ...options, data })
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
public put<T>(url: string, data?: any, options?: RequestOptions): Promise<T> {
|
|
852
|
+
return this.makeRequest<T>('PUT', url, { ...options, data })
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
public patch<T>(url: string, data?: any, options?: RequestOptions): Promise<T> {
|
|
856
|
+
return this.makeRequest<T>('PATCH', url, { ...options, data })
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
public delete<T>(url: string, options?: RequestOptions): Promise<T> {
|
|
860
|
+
return this.makeRequest<T>('DELETE', url, options)
|
|
861
|
+
}
|
|
862
|
+
}`;
|
|
863
|
+
}
|
|
864
|
+
generateAxiosInterceptors() {
|
|
865
|
+
return `
|
|
866
|
+
private setupInterceptors(): void {
|
|
867
|
+
// Request interceptor
|
|
868
|
+
this.client.interceptors.request.use(
|
|
869
|
+
(config) => {
|
|
870
|
+
if (this.config.onRequest) {
|
|
871
|
+
return this.config.onRequest(config) || config
|
|
872
|
+
}
|
|
873
|
+
return config
|
|
874
|
+
},
|
|
875
|
+
(error) => {
|
|
876
|
+
if (this.config.onRequestError) {
|
|
877
|
+
this.config.onRequestError(error)
|
|
878
|
+
}
|
|
879
|
+
return Promise.reject(error)
|
|
880
|
+
}
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
// Response interceptor
|
|
884
|
+
this.client.interceptors.response.use(
|
|
885
|
+
(response) => {
|
|
886
|
+
if (this.config.onResponse) {
|
|
887
|
+
return this.config.onResponse(response) || response
|
|
888
|
+
}
|
|
889
|
+
return response
|
|
890
|
+
},
|
|
891
|
+
async (error) => {
|
|
892
|
+
if (this.config.onResponseError) {
|
|
893
|
+
const result = await this.config.onResponseError(error)
|
|
894
|
+
if (result) {
|
|
895
|
+
return result
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Auto-retry logic
|
|
900
|
+
if (this.shouldRetry(error)) {
|
|
901
|
+
return this.retryRequest(error.config)
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
return Promise.reject(error)
|
|
905
|
+
}
|
|
906
|
+
)
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
private shouldRetry(error: AxiosError): boolean {
|
|
910
|
+
const retryCount = (error.config as any)?._retryCount || 0
|
|
911
|
+
const maxRetries = this.config.retries || ${this.options.retries}
|
|
912
|
+
|
|
913
|
+
return (
|
|
914
|
+
retryCount < maxRetries &&
|
|
915
|
+
(!error.response || error.response.status >= 500) &&
|
|
916
|
+
error.code !== 'ECONNABORTED'
|
|
917
|
+
)
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
private async retryRequest(config: any): Promise<any> {
|
|
921
|
+
config._retryCount = (config._retryCount || 0) + 1
|
|
922
|
+
|
|
923
|
+
// Exponential backoff
|
|
924
|
+
const delay = Math.pow(2, config._retryCount) * 1000
|
|
925
|
+
await new Promise(resolve => setTimeout(resolve, delay))
|
|
926
|
+
|
|
927
|
+
return this.client.request(config)
|
|
928
|
+
}`;
|
|
929
|
+
}
|
|
930
|
+
generateAuthInjection() {
|
|
931
|
+
switch (this.options.authType) {
|
|
932
|
+
case "bearer":
|
|
933
|
+
return `
|
|
934
|
+
// Inject bearer token if available
|
|
935
|
+
if (this.config.getAuthToken) {
|
|
936
|
+
const token = await this.config.getAuthToken()
|
|
937
|
+
if (token) {
|
|
938
|
+
config.headers = {
|
|
939
|
+
...config.headers,
|
|
940
|
+
Authorization: \`Bearer \${token}\`
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}`;
|
|
944
|
+
case "apiKey":
|
|
945
|
+
return `
|
|
946
|
+
// Inject API key if available
|
|
947
|
+
if (this.config.getApiKey) {
|
|
948
|
+
const apiKey = await this.config.getApiKey()
|
|
949
|
+
if (apiKey) {
|
|
950
|
+
if (this.config.apiKeyHeader) {
|
|
951
|
+
config.headers = {
|
|
952
|
+
...config.headers,
|
|
953
|
+
[this.config.apiKeyHeader]: apiKey
|
|
954
|
+
}
|
|
955
|
+
} else {
|
|
956
|
+
config.params = { ...config.params, api_key: apiKey }
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}`;
|
|
960
|
+
case "basic":
|
|
961
|
+
return `
|
|
962
|
+
// Inject basic auth if available
|
|
963
|
+
if (this.config.getBasicAuth) {
|
|
964
|
+
const auth = await this.config.getBasicAuth()
|
|
965
|
+
if (auth) {
|
|
966
|
+
const encoded = btoa(\`\${auth.username}:\${auth.password}\`)
|
|
967
|
+
config.headers = {
|
|
968
|
+
...config.headers,
|
|
969
|
+
Authorization: \`Basic \${encoded}\`
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}`;
|
|
973
|
+
default:
|
|
974
|
+
return "";
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
generateFetchAuthInjection() {
|
|
978
|
+
switch (this.options.authType) {
|
|
979
|
+
case "bearer":
|
|
980
|
+
return `
|
|
981
|
+
// Inject bearer token if available
|
|
982
|
+
if (this.config.getAuthToken) {
|
|
983
|
+
const token = await this.config.getAuthToken()
|
|
984
|
+
if (token) {
|
|
985
|
+
fetchOptions.headers = {
|
|
986
|
+
...fetchOptions.headers,
|
|
987
|
+
Authorization: \`Bearer \${token}\`
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}`;
|
|
991
|
+
case "apiKey":
|
|
992
|
+
return `
|
|
993
|
+
// Inject API key if available
|
|
994
|
+
if (this.config.getApiKey) {
|
|
995
|
+
const apiKey = await this.config.getApiKey()
|
|
996
|
+
if (apiKey && this.config.apiKeyHeader) {
|
|
997
|
+
fetchOptions.headers = {
|
|
998
|
+
...fetchOptions.headers,
|
|
999
|
+
[this.config.apiKeyHeader]: apiKey
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}`;
|
|
1003
|
+
default:
|
|
1004
|
+
return "";
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
generateApiMethods(endpoints) {
|
|
1008
|
+
const methods = [];
|
|
1009
|
+
methods.push(this.generateFileHeader("API Methods"));
|
|
1010
|
+
methods.push("");
|
|
1011
|
+
methods.push("import { APIClient } from './client'");
|
|
1012
|
+
methods.push("import * as Types from './types'");
|
|
1013
|
+
methods.push("");
|
|
1014
|
+
methods.push("export class APIService {");
|
|
1015
|
+
methods.push(" constructor(private client: APIClient) {}");
|
|
1016
|
+
methods.push("");
|
|
1017
|
+
const groupedEndpoints = this.groupEndpoints(endpoints);
|
|
1018
|
+
for (const [group, groupEndpoints] of Object.entries(groupedEndpoints)) {
|
|
1019
|
+
methods.push(` // ${group} methods`);
|
|
1020
|
+
for (const endpoint of groupEndpoints) {
|
|
1021
|
+
const method = this.generateEndpointMethod(endpoint);
|
|
1022
|
+
methods.push(method);
|
|
1023
|
+
methods.push("");
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
methods.push("}");
|
|
1027
|
+
return methods.join("\n");
|
|
1028
|
+
}
|
|
1029
|
+
generateEndpointMethod(endpoint) {
|
|
1030
|
+
const methodName = this.getMethodName(endpoint);
|
|
1031
|
+
const pathWithParams = this.generatePathWithParams(endpoint);
|
|
1032
|
+
const httpMethod = endpoint.method.toLowerCase();
|
|
1033
|
+
const lines = [];
|
|
1034
|
+
lines.push(" /**");
|
|
1035
|
+
if (endpoint.summary) {
|
|
1036
|
+
lines.push(` * ${endpoint.summary}`);
|
|
1037
|
+
}
|
|
1038
|
+
if (endpoint.description) {
|
|
1039
|
+
lines.push(` * ${endpoint.description}`);
|
|
1040
|
+
}
|
|
1041
|
+
lines.push(` * @param options Request options`);
|
|
1042
|
+
lines.push(" */");
|
|
1043
|
+
const hasPathParams = endpoint.parameters.some((p) => p.in === "path");
|
|
1044
|
+
const hasQueryParams = endpoint.parameters.some((p) => p.in === "query");
|
|
1045
|
+
const hasBody = endpoint.requestBody !== void 0;
|
|
1046
|
+
let params = "options: RequestOptions = {}";
|
|
1047
|
+
if (hasPathParams) {
|
|
1048
|
+
const pathParams = endpoint.parameters.filter((p) => p.in === "path").map((p) => `${p.name}: ${this.getParameterType(p)}`).join(", ");
|
|
1049
|
+
params = `${pathParams}, ${params}`;
|
|
1050
|
+
}
|
|
1051
|
+
const responseType = this.getResponseType(endpoint);
|
|
1052
|
+
lines.push(` async ${methodName}(${params}): Promise<${responseType}> {`);
|
|
1053
|
+
const urlConstruction = hasPathParams ? `const url = \`${pathWithParams}\`` : `const url = '${endpoint.path}'`;
|
|
1054
|
+
lines.push(` ${urlConstruction}`);
|
|
1055
|
+
if (hasQueryParams) {
|
|
1056
|
+
lines.push(" const queryParams = {");
|
|
1057
|
+
endpoint.parameters.filter((p) => p.in === "query").forEach((p) => {
|
|
1058
|
+
lines.push(` ${p.name}: options.${p.name},`);
|
|
1059
|
+
});
|
|
1060
|
+
lines.push(" }");
|
|
1061
|
+
lines.push(" options.params = { ...queryParams, ...options.params }");
|
|
1062
|
+
}
|
|
1063
|
+
if (hasBody) {
|
|
1064
|
+
lines.push(` return this.client.${httpMethod}<${responseType}>(url, options.data, options)`);
|
|
1065
|
+
} else {
|
|
1066
|
+
lines.push(` return this.client.${httpMethod}<${responseType}>(url, options)`);
|
|
1067
|
+
}
|
|
1068
|
+
lines.push(" }");
|
|
1069
|
+
return lines.join("\n");
|
|
1070
|
+
}
|
|
1071
|
+
generateClientTypes() {
|
|
1072
|
+
const authTypes = this.generateAuthTypes();
|
|
1073
|
+
return `/**
|
|
1074
|
+
* API Client Types
|
|
1075
|
+
*
|
|
1076
|
+
* Configuration and utility types for the generated API client
|
|
1077
|
+
*/
|
|
1078
|
+
|
|
1079
|
+
${this.options.clientType === "axios" ? "import { AxiosRequestConfig, AxiosResponse } from 'axios'" : ""}
|
|
1080
|
+
|
|
1081
|
+
export interface APIClientConfig {
|
|
1082
|
+
baseUrl: string
|
|
1083
|
+
timeout?: number
|
|
1084
|
+
defaultHeaders?: Record<string, string>
|
|
1085
|
+
retries?: number
|
|
1086
|
+
${this.options.includeAuth ? authTypes : ""}
|
|
1087
|
+
onRequest?: (config: any) => any
|
|
1088
|
+
onRequestError?: (error: any) => void
|
|
1089
|
+
onResponse?: (response: any) => any
|
|
1090
|
+
onResponseError?: (error: any) => Promise<any> | any
|
|
1091
|
+
onError?: (error: APIError) => void
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
export interface RequestOptions {
|
|
1095
|
+
params?: Record<string, any>
|
|
1096
|
+
data?: any
|
|
1097
|
+
headers?: Record<string, string>
|
|
1098
|
+
config?: any
|
|
1099
|
+
signal?: AbortSignal
|
|
1100
|
+
[key: string]: any
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
export interface APIError {
|
|
1104
|
+
message: string
|
|
1105
|
+
status?: number
|
|
1106
|
+
statusText?: string
|
|
1107
|
+
data?: any
|
|
1108
|
+
config: {
|
|
1109
|
+
url?: string
|
|
1110
|
+
method?: string
|
|
1111
|
+
headers?: any
|
|
1112
|
+
}
|
|
1113
|
+
}`;
|
|
1114
|
+
}
|
|
1115
|
+
generateAuthTypes() {
|
|
1116
|
+
switch (this.options.authType) {
|
|
1117
|
+
case "bearer":
|
|
1118
|
+
return "getAuthToken?: () => Promise<string | null> | string | null";
|
|
1119
|
+
case "apiKey":
|
|
1120
|
+
return `getApiKey?: () => Promise<string | null> | string | null
|
|
1121
|
+
apiKeyHeader?: string`;
|
|
1122
|
+
case "basic":
|
|
1123
|
+
return "getBasicAuth?: () => Promise<{ username: string; password: string } | null> | { username: string; password: string } | null";
|
|
1124
|
+
default:
|
|
1125
|
+
return "";
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
generateConfiguration() {
|
|
1129
|
+
return `/**
|
|
1130
|
+
* API Client Configuration
|
|
1131
|
+
*
|
|
1132
|
+
* Default configuration and factory functions
|
|
1133
|
+
*/
|
|
1134
|
+
|
|
1135
|
+
import { APIClient } from './client'
|
|
1136
|
+
import { APIService } from './methods'
|
|
1137
|
+
import { APIClientConfig } from './types'
|
|
1138
|
+
|
|
1139
|
+
export function createAPIClient(config: APIClientConfig): APIService {
|
|
1140
|
+
const client = new APIClient(config)
|
|
1141
|
+
return new APIService(client)
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
export const defaultConfig: Partial<APIClientConfig> = {
|
|
1145
|
+
timeout: ${this.options.timeout},
|
|
1146
|
+
retries: ${this.options.retries},
|
|
1147
|
+
defaultHeaders: {
|
|
1148
|
+
'Content-Type': 'application/json'
|
|
1149
|
+
}
|
|
1150
|
+
}`;
|
|
1151
|
+
}
|
|
1152
|
+
generateIndexFile() {
|
|
1153
|
+
return `/**
|
|
1154
|
+
* Generated API Client
|
|
1155
|
+
*
|
|
1156
|
+
* Auto-generated from OpenAPI specification
|
|
1157
|
+
*/
|
|
1158
|
+
|
|
1159
|
+
export * from './client'
|
|
1160
|
+
export * from './methods'
|
|
1161
|
+
export * from './types'
|
|
1162
|
+
export * from './config'
|
|
1163
|
+
export { createAPIClient, defaultConfig } from './config'
|
|
1164
|
+
|
|
1165
|
+
// Singleton instance for convenient usage
|
|
1166
|
+
export const apiClient = createAPIClient({
|
|
1167
|
+
baseUrl: process.env.VITE_API_URL || '${this.options.baseUrl || "http://localhost:8080"}',
|
|
1168
|
+
timeout: ${this.options.timeout},
|
|
1169
|
+
defaultHeaders: {
|
|
1170
|
+
'Content-Type': 'application/json'
|
|
1171
|
+
}
|
|
1172
|
+
})`;
|
|
1173
|
+
}
|
|
1174
|
+
generateFileHeader(title) {
|
|
1175
|
+
return `/**
|
|
1176
|
+
* ${title}
|
|
1177
|
+
*
|
|
1178
|
+
* Auto-generated from OpenAPI specification
|
|
1179
|
+
* Do not edit manually
|
|
1180
|
+
*/`;
|
|
1181
|
+
}
|
|
1182
|
+
getMethodName(endpoint) {
|
|
1183
|
+
if (endpoint.operationId) {
|
|
1184
|
+
return this.camelCase(endpoint.operationId);
|
|
1185
|
+
}
|
|
1186
|
+
const pathParts = endpoint.path.split("/").filter((part) => part && !part.startsWith("{")).map((part) => this.capitalize(part));
|
|
1187
|
+
return this.camelCase(`${endpoint.method}_${pathParts.join("_")}`);
|
|
1188
|
+
}
|
|
1189
|
+
generatePathWithParams(endpoint) {
|
|
1190
|
+
return endpoint.path.replace(/{([^}]+)}/g, "${$1}");
|
|
1191
|
+
}
|
|
1192
|
+
getParameterType(param) {
|
|
1193
|
+
switch (param.schema.type) {
|
|
1194
|
+
case "string":
|
|
1195
|
+
return "string";
|
|
1196
|
+
case "number":
|
|
1197
|
+
case "integer":
|
|
1198
|
+
return "number";
|
|
1199
|
+
case "boolean":
|
|
1200
|
+
return "boolean";
|
|
1201
|
+
default:
|
|
1202
|
+
return "any";
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
getResponseType(endpoint) {
|
|
1206
|
+
const successResponse = endpoint.responses.find((r) => r.statusCode.startsWith("2") || r.statusCode === "default");
|
|
1207
|
+
if (!successResponse?.content) {
|
|
1208
|
+
return "void";
|
|
1209
|
+
}
|
|
1210
|
+
const jsonContent = successResponse.content["application/json"];
|
|
1211
|
+
if (!jsonContent) {
|
|
1212
|
+
return "any";
|
|
1213
|
+
}
|
|
1214
|
+
return "any";
|
|
1215
|
+
}
|
|
1216
|
+
groupEndpoints(endpoints) {
|
|
1217
|
+
const groups = {};
|
|
1218
|
+
for (const endpoint of endpoints) {
|
|
1219
|
+
const group = endpoint.tags?.[0] || "default";
|
|
1220
|
+
if (!groups[group]) {
|
|
1221
|
+
groups[group] = [];
|
|
1222
|
+
}
|
|
1223
|
+
groups[group].push(endpoint);
|
|
1224
|
+
}
|
|
1225
|
+
return groups;
|
|
1226
|
+
}
|
|
1227
|
+
camelCase(str) {
|
|
1228
|
+
return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase()).replace(/^./, (char) => char.toLowerCase());
|
|
1229
|
+
}
|
|
1230
|
+
capitalize(str) {
|
|
1231
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
function generateAPIClient(parsedAPI, options) {
|
|
1235
|
+
const generator = new APIClientGenerator(options);
|
|
1236
|
+
return generator.generate(parsedAPI);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// src/codegen/openapi/hook-config.ts
|
|
1240
|
+
var import_fs2 = require("fs");
|
|
1241
|
+
var import_path2 = require("path");
|
|
1242
|
+
var DEFAULT_CONFIG = {
|
|
1243
|
+
version: "1.0.0",
|
|
1244
|
+
nameOverrides: {},
|
|
1245
|
+
reviewedNames: [],
|
|
1246
|
+
patterns: {
|
|
1247
|
+
removeDoubleWords: true,
|
|
1248
|
+
smartCasing: true,
|
|
1249
|
+
deduplicateSegments: true
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
var HookConfigManager = class {
|
|
1253
|
+
configPath;
|
|
1254
|
+
config = DEFAULT_CONFIG;
|
|
1255
|
+
constructor(configPath = "./hooks.config.json") {
|
|
1256
|
+
this.configPath = (0, import_path2.resolve)(configPath);
|
|
1257
|
+
}
|
|
1258
|
+
async load() {
|
|
1259
|
+
try {
|
|
1260
|
+
const content = await import_fs2.promises.readFile(this.configPath, "utf8");
|
|
1261
|
+
this.config = { ...DEFAULT_CONFIG, ...JSON.parse(content) };
|
|
1262
|
+
} catch (error) {
|
|
1263
|
+
this.config = DEFAULT_CONFIG;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
async save() {
|
|
1267
|
+
const content = JSON.stringify(this.config, null, 2);
|
|
1268
|
+
await import_fs2.promises.writeFile(this.configPath, content, "utf8");
|
|
1269
|
+
}
|
|
1270
|
+
getOverride(operationId) {
|
|
1271
|
+
return this.config.nameOverrides[operationId];
|
|
1272
|
+
}
|
|
1273
|
+
addOverride(override) {
|
|
1274
|
+
this.config.nameOverrides[override.original] = override.override;
|
|
1275
|
+
this.config.reviewedNames.push({
|
|
1276
|
+
...override,
|
|
1277
|
+
approvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
getPatterns() {
|
|
1281
|
+
return this.config.patterns;
|
|
1282
|
+
}
|
|
1283
|
+
updateStatistics(stats) {
|
|
1284
|
+
this.config.statistics = {
|
|
1285
|
+
...this.config.statistics,
|
|
1286
|
+
...stats,
|
|
1287
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
getReviewedNames() {
|
|
1291
|
+
return this.config.reviewedNames;
|
|
1292
|
+
}
|
|
1293
|
+
hasBeenReviewed(operationId) {
|
|
1294
|
+
return operationId in this.config.nameOverrides;
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
// src/codegen/openapi/confidence-scorer.ts
|
|
1299
|
+
var ConfidenceScorer = class {
|
|
1300
|
+
/**
|
|
1301
|
+
* Score a generated hook name based on various quality indicators
|
|
1302
|
+
*/
|
|
1303
|
+
scoreHookName(generatedName, originalOperationId, path, method) {
|
|
1304
|
+
const issues = [];
|
|
1305
|
+
const suggestions = [];
|
|
1306
|
+
let score = 100;
|
|
1307
|
+
if (!this.isProperCamelCase(generatedName)) {
|
|
1308
|
+
score -= 20;
|
|
1309
|
+
issues.push("Improper camelCase formatting");
|
|
1310
|
+
}
|
|
1311
|
+
if (this.hasConsecutiveUppercase(generatedName)) {
|
|
1312
|
+
score -= 30;
|
|
1313
|
+
issues.push("Consecutive uppercase letters suggest concatenation issues");
|
|
1314
|
+
suggestions.push(this.suggestFixForConsecutiveUppercase(generatedName));
|
|
1315
|
+
}
|
|
1316
|
+
const nameLength = generatedName.length;
|
|
1317
|
+
if (nameLength > 30) {
|
|
1318
|
+
score -= 20;
|
|
1319
|
+
issues.push(`Name is too long (${nameLength} chars)`);
|
|
1320
|
+
}
|
|
1321
|
+
if (this.hasRepeatedWords(generatedName)) {
|
|
1322
|
+
score -= 25;
|
|
1323
|
+
issues.push("Contains repeated word segments");
|
|
1324
|
+
}
|
|
1325
|
+
if (this.matchesCommonPattern(generatedName, method)) {
|
|
1326
|
+
score += 10;
|
|
1327
|
+
score = Math.min(score, 100);
|
|
1328
|
+
}
|
|
1329
|
+
if (this.hasWordBoundaryIssues(generatedName)) {
|
|
1330
|
+
score -= 15;
|
|
1331
|
+
issues.push("Potential word boundary issues");
|
|
1332
|
+
}
|
|
1333
|
+
let confidence;
|
|
1334
|
+
if (score >= 80) {
|
|
1335
|
+
confidence = "high";
|
|
1336
|
+
} else if (score >= 60) {
|
|
1337
|
+
confidence = "medium";
|
|
1338
|
+
} else {
|
|
1339
|
+
confidence = "low";
|
|
1340
|
+
}
|
|
1341
|
+
return {
|
|
1342
|
+
name: generatedName,
|
|
1343
|
+
confidence,
|
|
1344
|
+
score,
|
|
1345
|
+
issues,
|
|
1346
|
+
suggestions: suggestions.filter((s) => s !== generatedName)
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
isProperCamelCase(name) {
|
|
1350
|
+
if (!name.startsWith("use")) return false;
|
|
1351
|
+
const afterUse = name.substring(3);
|
|
1352
|
+
return /^[A-Z][a-zA-Z0-9]*$/.test(afterUse);
|
|
1353
|
+
}
|
|
1354
|
+
hasConsecutiveUppercase(name) {
|
|
1355
|
+
return /[A-Z]{2,}[a-z]/.test(name) || /[a-z][A-Z]{2,}/.test(name);
|
|
1356
|
+
}
|
|
1357
|
+
hasRepeatedWords(name) {
|
|
1358
|
+
const words = this.camelCaseToWords(name).map((w) => w.toLowerCase());
|
|
1359
|
+
const uniqueWords = new Set(words);
|
|
1360
|
+
return words.length > uniqueWords.size + 1;
|
|
1361
|
+
}
|
|
1362
|
+
matchesCommonPattern(name, method) {
|
|
1363
|
+
const commonPatterns = {
|
|
1364
|
+
get: /^use[A-Z][a-zA-Z]+$/,
|
|
1365
|
+
post: /^useCreate[A-Z][a-zA-Z]+$/,
|
|
1366
|
+
put: /^useUpdate[A-Z][a-zA-Z]+$/,
|
|
1367
|
+
patch: /^useUpdate[A-Z][a-zA-Z]+$/,
|
|
1368
|
+
delete: /^useDelete[A-Z][a-zA-Z]+$/
|
|
1369
|
+
};
|
|
1370
|
+
const pattern = commonPatterns[method];
|
|
1371
|
+
return pattern ? pattern.test(name) : false;
|
|
1372
|
+
}
|
|
1373
|
+
hasWordBoundaryIssues(name) {
|
|
1374
|
+
const afterUse = name.substring(3);
|
|
1375
|
+
const knownPrefixes = ["Create", "Update", "Delete", "Get", "List", "Fetch", "Assign", "Reassign"];
|
|
1376
|
+
for (const prefix of knownPrefixes) {
|
|
1377
|
+
if (afterUse.startsWith(prefix)) {
|
|
1378
|
+
const remaining = afterUse.substring(prefix.length);
|
|
1379
|
+
if (/[a-z]{2,}[A-Z]/.test(remaining)) {
|
|
1380
|
+
return true;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
if (/[a-z]{3,}[A-Z]/.test(afterUse)) {
|
|
1385
|
+
return true;
|
|
1386
|
+
}
|
|
1387
|
+
if (/[A-Z][a-z]+[a-z][A-Z]/.test(afterUse)) {
|
|
1388
|
+
return true;
|
|
1389
|
+
}
|
|
1390
|
+
return false;
|
|
1391
|
+
}
|
|
1392
|
+
suggestFixForConsecutiveUppercase(name) {
|
|
1393
|
+
let suggestion = name;
|
|
1394
|
+
const knownFixes = {
|
|
1395
|
+
"useAssignRolesusersUserRoles": "useAssignUserRoles",
|
|
1396
|
+
"useReassignBudgettransactionsReassignBudget": "useReassignTransactionBudget",
|
|
1397
|
+
"useCategorySubcategories": "useCreateSubcategory"
|
|
1398
|
+
};
|
|
1399
|
+
if (knownFixes[name]) {
|
|
1400
|
+
return knownFixes[name];
|
|
1401
|
+
}
|
|
1402
|
+
suggestion = suggestion.replace(/([a-z]{2,})([A-Z])/g, (match, p1, p2) => {
|
|
1403
|
+
if (p1.endsWith("roles") || p1.endsWith("users") || p1.endsWith("budget")) {
|
|
1404
|
+
return p1 + p2;
|
|
1405
|
+
}
|
|
1406
|
+
return match;
|
|
1407
|
+
});
|
|
1408
|
+
const parts = this.camelCaseToWords(suggestion);
|
|
1409
|
+
if (parts.length > 4) {
|
|
1410
|
+
const action = parts[1];
|
|
1411
|
+
const resources = parts.slice(2);
|
|
1412
|
+
const uniqueResources = [...new Set(resources.map((r) => r.toLowerCase()))];
|
|
1413
|
+
if (uniqueResources.length === 1) {
|
|
1414
|
+
return `use${action}${resources[resources.length - 1]}`;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return suggestion;
|
|
1418
|
+
}
|
|
1419
|
+
camelCaseToWords(str) {
|
|
1420
|
+
return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(" ").filter((w) => w.length > 0);
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Generate a confidence report for review
|
|
1424
|
+
*/
|
|
1425
|
+
generateReport(scoredNames) {
|
|
1426
|
+
const byConfidence = {
|
|
1427
|
+
high: scoredNames.filter((s) => s.confidence === "high"),
|
|
1428
|
+
medium: scoredNames.filter((s) => s.confidence === "medium"),
|
|
1429
|
+
low: scoredNames.filter((s) => s.confidence === "low")
|
|
1430
|
+
};
|
|
1431
|
+
let report = "# Hook Name Generation Report\n\n";
|
|
1432
|
+
report += `Generated ${scoredNames.length} hooks
|
|
1433
|
+
|
|
1434
|
+
`;
|
|
1435
|
+
report += `- High confidence: ${byConfidence.high.length}
|
|
1436
|
+
`;
|
|
1437
|
+
report += `- Medium confidence: ${byConfidence.medium.length}
|
|
1438
|
+
`;
|
|
1439
|
+
report += `- Low confidence: ${byConfidence.low.length}
|
|
1440
|
+
|
|
1441
|
+
`;
|
|
1442
|
+
if (byConfidence.low.length > 0) {
|
|
1443
|
+
report += "## Low Confidence Names (Review Recommended)\n\n";
|
|
1444
|
+
for (const scored of byConfidence.low) {
|
|
1445
|
+
report += `### ${scored.name}
|
|
1446
|
+
`;
|
|
1447
|
+
report += `- Score: ${scored.score}/100
|
|
1448
|
+
`;
|
|
1449
|
+
report += `- Issues: ${scored.issues.join(", ")}
|
|
1450
|
+
`;
|
|
1451
|
+
if (scored.suggestions.length > 0) {
|
|
1452
|
+
report += `- Suggestions: ${scored.suggestions.join(", ")}
|
|
1453
|
+
`;
|
|
1454
|
+
}
|
|
1455
|
+
report += "\n";
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
if (byConfidence.medium.length > 0) {
|
|
1459
|
+
report += "## Medium Confidence Names (Optional Review)\n\n";
|
|
1460
|
+
for (const scored of byConfidence.medium) {
|
|
1461
|
+
report += `- ${scored.name} (${scored.score}/100)
|
|
1462
|
+
`;
|
|
1463
|
+
if (scored.issues.length > 0) {
|
|
1464
|
+
report += ` Issues: ${scored.issues.join(", ")}
|
|
1465
|
+
`;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
report += "\n";
|
|
1469
|
+
}
|
|
1470
|
+
return report;
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
|
|
1474
|
+
// src/codegen/openapi/bulk-types.ts
|
|
1475
|
+
function isBulkOperation(path, method, requestBody) {
|
|
1476
|
+
const bulkPatterns = [
|
|
1477
|
+
/\/bulk[-_]?delete$/i,
|
|
1478
|
+
/\/bulk[-_]?update$/i,
|
|
1479
|
+
/\/bulk[-_]?create$/i,
|
|
1480
|
+
/\/batch$/i,
|
|
1481
|
+
/\/bulk$/i,
|
|
1482
|
+
/\/multiple$/i
|
|
1483
|
+
];
|
|
1484
|
+
if (bulkPatterns.some((pattern) => pattern.test(path))) {
|
|
1485
|
+
return true;
|
|
1486
|
+
}
|
|
1487
|
+
if (requestBody && typeof requestBody === "object") {
|
|
1488
|
+
const body = requestBody;
|
|
1489
|
+
if (body.content?.["application/json"]?.schema) {
|
|
1490
|
+
const schema = body.content["application/json"].schema;
|
|
1491
|
+
return schema.type === "array" && !!schema.items;
|
|
1492
|
+
}
|
|
1493
|
+
if ("type" in requestBody) {
|
|
1494
|
+
const bodyType = requestBody;
|
|
1495
|
+
return bodyType.type === "array" && !!bodyType.items;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
return false;
|
|
1499
|
+
}
|
|
1500
|
+
function detectBulkOperationType2(path, method) {
|
|
1501
|
+
const lowercasePath = path.toLowerCase();
|
|
1502
|
+
const lowercaseMethod = method.toLowerCase();
|
|
1503
|
+
if (lowercasePath.includes("delete") || lowercaseMethod === "delete") {
|
|
1504
|
+
return "delete";
|
|
1505
|
+
}
|
|
1506
|
+
if (lowercasePath.includes("update") || lowercaseMethod === "put" || lowercaseMethod === "patch") {
|
|
1507
|
+
return "update";
|
|
1508
|
+
}
|
|
1509
|
+
if (lowercasePath.includes("create") || lowercaseMethod === "post") {
|
|
1510
|
+
return "create";
|
|
1511
|
+
}
|
|
1512
|
+
if (lowercasePath.includes("batch") || lowercasePath.includes("bulk")) {
|
|
1513
|
+
return "mixed";
|
|
1514
|
+
}
|
|
1515
|
+
return null;
|
|
1516
|
+
}
|
|
1517
|
+
function generateBulkOperationName(resource, operationType) {
|
|
1518
|
+
const cleanResource = resource.replace(/[^a-zA-Z0-9]/g, "");
|
|
1519
|
+
const singular = cleanResource.endsWith("s") ? cleanResource.slice(0, -1) : cleanResource;
|
|
1520
|
+
const capitalizedSingular = capitalize(singular);
|
|
1521
|
+
switch (operationType) {
|
|
1522
|
+
case "delete":
|
|
1523
|
+
return `bulkDelete${capitalizedSingular}s`;
|
|
1524
|
+
case "update":
|
|
1525
|
+
return `bulkUpdate${capitalizedSingular}s`;
|
|
1526
|
+
case "create":
|
|
1527
|
+
return `bulkCreate${capitalizedSingular}s`;
|
|
1528
|
+
case "mixed":
|
|
1529
|
+
return `bulk${capitalizedSingular}Operations`;
|
|
1530
|
+
default:
|
|
1531
|
+
return `bulk${capitalizedSingular}s`;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
function capitalize(str) {
|
|
1535
|
+
if (!str) return "";
|
|
1536
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// src/codegen/openapi/bulk-hook-generator.ts
|
|
1540
|
+
var BulkHookGenerator = class {
|
|
1541
|
+
/**
|
|
1542
|
+
* Generate a bulk mutation hook with progress tracking and retry logic
|
|
1543
|
+
*/
|
|
1544
|
+
generateBulkMutationHook(endpoint, hookName, operationName) {
|
|
1545
|
+
const bulkType = detectBulkOperationType2(endpoint.path, endpoint.method);
|
|
1546
|
+
const lines = [];
|
|
1547
|
+
lines.push("/**");
|
|
1548
|
+
if (endpoint.summary) {
|
|
1549
|
+
lines.push(` * ${endpoint.summary}`);
|
|
1550
|
+
}
|
|
1551
|
+
if (endpoint.description) {
|
|
1552
|
+
lines.push(` * ${endpoint.description}`);
|
|
1553
|
+
}
|
|
1554
|
+
lines.push(" * @param options Mutation options with bulk operation support");
|
|
1555
|
+
lines.push(" */");
|
|
1556
|
+
const resource = endpoint.tags?.[0] || "default";
|
|
1557
|
+
lines.push(`export function ${hookName}(`);
|
|
1558
|
+
lines.push(` options?: UseMutationOptions<BulkOperationResponse, any, BulkOperationRequest> & BulkMutationOptions`);
|
|
1559
|
+
lines.push(") {");
|
|
1560
|
+
lines.push(" const queryClient = useQueryClient()");
|
|
1561
|
+
lines.push(" ");
|
|
1562
|
+
lines.push(" return useMutation({");
|
|
1563
|
+
lines.push(" mutationFn: async (request: BulkOperationRequest) => {");
|
|
1564
|
+
lines.push(" // Transform request if needed");
|
|
1565
|
+
lines.push(" const transformedRequest = options?.transformRequest ");
|
|
1566
|
+
lines.push(" ? options.transformRequest(request) ");
|
|
1567
|
+
lines.push(" : request");
|
|
1568
|
+
lines.push(" ");
|
|
1569
|
+
lines.push(" // Simulate progress updates for demo");
|
|
1570
|
+
lines.push(" if (options?.onProgress) {");
|
|
1571
|
+
lines.push(" options.onProgress({");
|
|
1572
|
+
lines.push(" total: request.ids.length,");
|
|
1573
|
+
lines.push(" processed: 0,");
|
|
1574
|
+
lines.push(" succeeded: 0,");
|
|
1575
|
+
lines.push(" failed: 0,");
|
|
1576
|
+
lines.push(" percentage: 0,");
|
|
1577
|
+
lines.push(" status: 'processing'");
|
|
1578
|
+
lines.push(" })");
|
|
1579
|
+
lines.push(" }");
|
|
1580
|
+
lines.push(" ");
|
|
1581
|
+
lines.push(` const response = await apiClient.${this.camelCase(operationName)}(transformedRequest)`);
|
|
1582
|
+
lines.push(" ");
|
|
1583
|
+
lines.push(" // Transform response if needed");
|
|
1584
|
+
lines.push(" const transformedResponse = options?.transformResponse");
|
|
1585
|
+
lines.push(" ? options.transformResponse(response)");
|
|
1586
|
+
lines.push(" : response");
|
|
1587
|
+
lines.push(" ");
|
|
1588
|
+
lines.push(" // Final progress update");
|
|
1589
|
+
lines.push(" if (options?.onProgress && transformedResponse) {");
|
|
1590
|
+
lines.push(" options.onProgress({");
|
|
1591
|
+
lines.push(" total: transformedResponse.summary?.total || request.ids.length,");
|
|
1592
|
+
lines.push(" processed: transformedResponse.summary?.total || request.ids.length,");
|
|
1593
|
+
lines.push(" succeeded: transformedResponse.summary?.succeeded || 0,");
|
|
1594
|
+
lines.push(" failed: transformedResponse.summary?.failed || 0,");
|
|
1595
|
+
lines.push(" percentage: 100,");
|
|
1596
|
+
lines.push(" status: 'completed'");
|
|
1597
|
+
lines.push(" })");
|
|
1598
|
+
lines.push(" }");
|
|
1599
|
+
lines.push(" ");
|
|
1600
|
+
lines.push(" return transformedResponse");
|
|
1601
|
+
lines.push(" },");
|
|
1602
|
+
lines.push(this.generateBulkOptimisticUpdate(resource, bulkType));
|
|
1603
|
+
lines.push(this.generateBulkCacheInvalidation(resource));
|
|
1604
|
+
lines.push(" onSuccess: (data, variables, context) => {");
|
|
1605
|
+
lines.push(" // Handle partial success");
|
|
1606
|
+
lines.push(" if (data.failed.length > 0 && options?.onPartialSuccess) {");
|
|
1607
|
+
lines.push(" options.onPartialSuccess(data)");
|
|
1608
|
+
lines.push(" }");
|
|
1609
|
+
lines.push(" ");
|
|
1610
|
+
lines.push(" // Call original onSuccess if provided");
|
|
1611
|
+
lines.push(" options?.onSuccess?.(data, variables, context)");
|
|
1612
|
+
lines.push(" },");
|
|
1613
|
+
lines.push(" retry: (failureCount, error) => {");
|
|
1614
|
+
lines.push(" // Use custom retry config if provided");
|
|
1615
|
+
lines.push(" if (options?.retry?.maxAttempts !== undefined) {");
|
|
1616
|
+
lines.push(" return failureCount < options.retry.maxAttempts");
|
|
1617
|
+
lines.push(" }");
|
|
1618
|
+
lines.push(" // Default: retry up to 3 times for bulk operations");
|
|
1619
|
+
lines.push(" return failureCount < 3");
|
|
1620
|
+
lines.push(" },");
|
|
1621
|
+
lines.push(" retryDelay: (attemptIndex) => {");
|
|
1622
|
+
lines.push(" // Use custom retry delay if provided");
|
|
1623
|
+
lines.push(" if (options?.retry?.initialDelay) {");
|
|
1624
|
+
lines.push(" const delay = Math.min(");
|
|
1625
|
+
lines.push(" options.retry.initialDelay * Math.pow(options.retry.backoffMultiplier || 2, attemptIndex),");
|
|
1626
|
+
lines.push(" options.retry.maxDelay || 30000");
|
|
1627
|
+
lines.push(" )");
|
|
1628
|
+
lines.push(" return delay");
|
|
1629
|
+
lines.push(" }");
|
|
1630
|
+
lines.push(" // Default exponential backoff: 1s, 2s, 4s...");
|
|
1631
|
+
lines.push(" return Math.min(1000 * Math.pow(2, attemptIndex), 30000)");
|
|
1632
|
+
lines.push(" },");
|
|
1633
|
+
lines.push(" ...options");
|
|
1634
|
+
lines.push(" })");
|
|
1635
|
+
lines.push("}");
|
|
1636
|
+
return lines.join("\n");
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Generate optimistic updates for bulk operations
|
|
1640
|
+
*/
|
|
1641
|
+
generateBulkOptimisticUpdate(resource, bulkType) {
|
|
1642
|
+
switch (bulkType) {
|
|
1643
|
+
case "delete":
|
|
1644
|
+
return ` onMutate: async (request: BulkOperationRequest) => {
|
|
1645
|
+
// Cancel outgoing refetches
|
|
1646
|
+
await queryClient.cancelQueries({ queryKey: queryKeys.${resource}() })
|
|
1647
|
+
|
|
1648
|
+
// Snapshot previous value
|
|
1649
|
+
const previousData = queryClient.getQueryData(queryKeys.${resource}())
|
|
1650
|
+
|
|
1651
|
+
// Optimistically remove items
|
|
1652
|
+
queryClient.setQueryData(queryKeys.${resource}(), (old: any) => {
|
|
1653
|
+
if (Array.isArray(old)) {
|
|
1654
|
+
return old.filter((item: any) => !request.ids.includes(item.id))
|
|
1655
|
+
}
|
|
1656
|
+
return old
|
|
1657
|
+
})
|
|
1658
|
+
|
|
1659
|
+
return { previousData }
|
|
1660
|
+
},
|
|
1661
|
+
onError: (err: any, request: any, context: any) => {
|
|
1662
|
+
// Rollback on error
|
|
1663
|
+
if (context?.previousData !== undefined) {
|
|
1664
|
+
queryClient.setQueryData(queryKeys.${resource}(), context.previousData)
|
|
1665
|
+
}
|
|
1666
|
+
},`;
|
|
1667
|
+
case "update":
|
|
1668
|
+
return ` onMutate: async (request: BulkOperationRequest) => {
|
|
1669
|
+
await queryClient.cancelQueries({ queryKey: queryKeys.${resource}() })
|
|
1670
|
+
|
|
1671
|
+
const previousData = queryClient.getQueryData(queryKeys.${resource}())
|
|
1672
|
+
|
|
1673
|
+
// Optimistically update items
|
|
1674
|
+
queryClient.setQueryData(queryKeys.${resource}(), (old: any) => {
|
|
1675
|
+
if (Array.isArray(old)) {
|
|
1676
|
+
return old.map((item: any) =>
|
|
1677
|
+
request.ids.includes(item.id)
|
|
1678
|
+
? { ...item, ...request.data }
|
|
1679
|
+
: item
|
|
1680
|
+
)
|
|
1681
|
+
}
|
|
1682
|
+
return old
|
|
1683
|
+
})
|
|
1684
|
+
|
|
1685
|
+
return { previousData }
|
|
1686
|
+
},
|
|
1687
|
+
onError: (err: any, request: any, context: any) => {
|
|
1688
|
+
if (context?.previousData !== undefined) {
|
|
1689
|
+
queryClient.setQueryData(queryKeys.${resource}(), context.previousData)
|
|
1690
|
+
}
|
|
1691
|
+
},`;
|
|
1692
|
+
case "create":
|
|
1693
|
+
return ` onMutate: async (request: BulkOperationRequest) => {
|
|
1694
|
+
await queryClient.cancelQueries({ queryKey: queryKeys.${resource}() })
|
|
1695
|
+
|
|
1696
|
+
const previousData = queryClient.getQueryData(queryKeys.${resource}())
|
|
1697
|
+
|
|
1698
|
+
// Optimistically add items (if data provided)
|
|
1699
|
+
if (request.data && Array.isArray(request.data)) {
|
|
1700
|
+
queryClient.setQueryData(queryKeys.${resource}(), (old: any) => {
|
|
1701
|
+
if (Array.isArray(old)) {
|
|
1702
|
+
return [...old, ...request.data]
|
|
1703
|
+
}
|
|
1704
|
+
return old
|
|
1705
|
+
})
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
return { previousData }
|
|
1709
|
+
},
|
|
1710
|
+
onError: (err: any, request: any, context: any) => {
|
|
1711
|
+
if (context?.previousData !== undefined) {
|
|
1712
|
+
queryClient.setQueryData(queryKeys.${resource}(), context.previousData)
|
|
1713
|
+
}
|
|
1714
|
+
},`;
|
|
1715
|
+
default:
|
|
1716
|
+
return "";
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Generate cache invalidation for bulk operations
|
|
1721
|
+
*/
|
|
1722
|
+
generateBulkCacheInvalidation(resource) {
|
|
1723
|
+
return ` onSettled: () => {
|
|
1724
|
+
// Invalidate related queries after bulk operation
|
|
1725
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.${resource}() })
|
|
1726
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.all })
|
|
1727
|
+
},`;
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* Generate bulk mutation hook name
|
|
1731
|
+
*/
|
|
1732
|
+
generateBulkMutationHookName(endpoint, resourceName) {
|
|
1733
|
+
const requestBody = endpoint.requestBody;
|
|
1734
|
+
if (!isBulkOperation(endpoint.path, endpoint.method, requestBody)) {
|
|
1735
|
+
return null;
|
|
1736
|
+
}
|
|
1737
|
+
const bulkType = detectBulkOperationType2(endpoint.path, endpoint.method);
|
|
1738
|
+
if (bulkType && resourceName) {
|
|
1739
|
+
const bulkName = generateBulkOperationName(resourceName, bulkType);
|
|
1740
|
+
return `use${this.capitalize(bulkName)}`;
|
|
1741
|
+
}
|
|
1742
|
+
return null;
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Check if endpoint is a bulk operation
|
|
1746
|
+
*/
|
|
1747
|
+
isBulkEndpoint(endpoint) {
|
|
1748
|
+
const requestBody = endpoint.requestBody;
|
|
1749
|
+
return isBulkOperation(endpoint.path, endpoint.method, requestBody);
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Helper to convert to camelCase
|
|
1753
|
+
*/
|
|
1754
|
+
camelCase(str) {
|
|
1755
|
+
return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase()).replace(/^./, (char) => char.toLowerCase());
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Helper to capitalize string
|
|
1759
|
+
*/
|
|
1760
|
+
capitalize(str) {
|
|
1761
|
+
if (str.includes("_")) {
|
|
1762
|
+
return str.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
|
|
1763
|
+
}
|
|
1764
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1765
|
+
}
|
|
1766
|
+
};
|
|
1767
|
+
|
|
1768
|
+
// src/codegen/openapi/hook-generator.js
|
|
1769
|
+
var ReactHookGenerator = class {
|
|
1770
|
+
options;
|
|
1771
|
+
configManager;
|
|
1772
|
+
confidenceScorer;
|
|
1773
|
+
bulkHookGenerator;
|
|
1774
|
+
scoredNames = [];
|
|
1775
|
+
resourceConfigs = /* @__PURE__ */ new Map();
|
|
1776
|
+
constructor(options = {}) {
|
|
1777
|
+
this.options = {
|
|
1778
|
+
queryKeyPrefix: options.queryKeyPrefix || "api",
|
|
1779
|
+
includeInfiniteQueries: options.includeInfiniteQueries !== false,
|
|
1780
|
+
includeOptimisticUpdates: options.includeOptimisticUpdates !== false,
|
|
1781
|
+
includeMutationHelpers: options.includeMutationHelpers ?? true,
|
|
1782
|
+
authenticationRequired: options.authenticationRequired ?? true,
|
|
1783
|
+
errorHandling: options.errorHandling || "throw",
|
|
1784
|
+
configPath: options.configPath || "./hooks.config.json",
|
|
1785
|
+
enableConfidenceScoring: options.enableConfidenceScoring ?? true
|
|
1786
|
+
};
|
|
1787
|
+
this.configManager = new HookConfigManager(this.options.configPath);
|
|
1788
|
+
this.confidenceScorer = new ConfidenceScorer();
|
|
1789
|
+
this.bulkHookGenerator = new BulkHookGenerator();
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Detect resource operations from endpoints
|
|
1793
|
+
*/
|
|
1794
|
+
detectResourceOperations(endpoints) {
|
|
1795
|
+
const resourceMap = /* @__PURE__ */ new Map();
|
|
1796
|
+
endpoints.forEach((endpoint) => {
|
|
1797
|
+
const resourceName = this.extractResourceName(endpoint.path);
|
|
1798
|
+
if (!resourceName)
|
|
1799
|
+
return;
|
|
1800
|
+
const config = resourceMap.get(resourceName) || this.createDefaultConfig(resourceName);
|
|
1801
|
+
const requestBody = endpoint.requestBody;
|
|
1802
|
+
if (isBulkOperation(endpoint.path, endpoint.method, requestBody)) {
|
|
1803
|
+
const bulkType = detectBulkOperationType(endpoint.path, endpoint.method);
|
|
1804
|
+
switch (bulkType) {
|
|
1805
|
+
case "delete":
|
|
1806
|
+
config.operations.bulkDelete = {
|
|
1807
|
+
path: endpoint.path,
|
|
1808
|
+
method: endpoint.method
|
|
1809
|
+
};
|
|
1810
|
+
break;
|
|
1811
|
+
case "update":
|
|
1812
|
+
config.operations.bulkUpdate = {
|
|
1813
|
+
path: endpoint.path,
|
|
1814
|
+
method: endpoint.method
|
|
1815
|
+
};
|
|
1816
|
+
break;
|
|
1817
|
+
case "create":
|
|
1818
|
+
config.operations.bulkCreate = {
|
|
1819
|
+
path: endpoint.path,
|
|
1820
|
+
method: endpoint.method
|
|
1821
|
+
};
|
|
1822
|
+
break;
|
|
1823
|
+
case "mixed":
|
|
1824
|
+
default:
|
|
1825
|
+
if (!config.operations.customBulk) {
|
|
1826
|
+
config.operations.customBulk = [];
|
|
1827
|
+
}
|
|
1828
|
+
config.operations.customBulk.push({
|
|
1829
|
+
name: this.getOperationName(endpoint),
|
|
1830
|
+
path: endpoint.path,
|
|
1831
|
+
method: endpoint.method,
|
|
1832
|
+
operationType: bulkType || "mixed"
|
|
1833
|
+
});
|
|
1834
|
+
break;
|
|
1835
|
+
}
|
|
1836
|
+
} else {
|
|
1837
|
+
switch (endpoint.method.toLowerCase()) {
|
|
1838
|
+
case "get":
|
|
1839
|
+
if (endpoint.path.includes("{")) {
|
|
1840
|
+
} else {
|
|
1841
|
+
config.operations.list = { path: endpoint.path, method: endpoint.method };
|
|
1842
|
+
}
|
|
1843
|
+
break;
|
|
1844
|
+
case "post":
|
|
1845
|
+
config.operations.create = { path: endpoint.path, method: endpoint.method };
|
|
1846
|
+
break;
|
|
1847
|
+
case "put":
|
|
1848
|
+
case "patch":
|
|
1849
|
+
config.operations.update = { path: endpoint.path, method: endpoint.method };
|
|
1850
|
+
break;
|
|
1851
|
+
case "delete":
|
|
1852
|
+
config.operations.delete = { path: endpoint.path, method: endpoint.method };
|
|
1853
|
+
break;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
resourceMap.set(resourceName, config);
|
|
1857
|
+
});
|
|
1858
|
+
return resourceMap;
|
|
1859
|
+
}
|
|
1860
|
+
/**
|
|
1861
|
+
* Extract resource name from path
|
|
1862
|
+
*/
|
|
1863
|
+
extractResourceName(path) {
|
|
1864
|
+
const cleanPath = path.replace(/^\/api\/v\d+\//, "/");
|
|
1865
|
+
const segments = cleanPath.split("/").filter(Boolean);
|
|
1866
|
+
for (const segment of segments) {
|
|
1867
|
+
if (!segment.startsWith("{")) {
|
|
1868
|
+
return segment.replace(/-/g, "_");
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Create default API config for a resource
|
|
1875
|
+
*/
|
|
1876
|
+
createDefaultConfig(resourceName) {
|
|
1877
|
+
return {
|
|
1878
|
+
baseURL: "",
|
|
1879
|
+
resourceName,
|
|
1880
|
+
operations: {}
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
async generate(parsedAPI) {
|
|
1884
|
+
const endpoints = parsedAPI.endpoints;
|
|
1885
|
+
await this.configManager.load();
|
|
1886
|
+
this.resourceConfigs = this.detectResourceOperations(endpoints);
|
|
1887
|
+
this.scoredNames = [];
|
|
1888
|
+
const result = {
|
|
1889
|
+
queries: this.generateQueryHooks(endpoints),
|
|
1890
|
+
mutations: this.generateMutationHooks(endpoints),
|
|
1891
|
+
keys: this.generateQueryKeys(endpoints),
|
|
1892
|
+
types: this.generateHookTypes(endpoints),
|
|
1893
|
+
index: this.generateIndexFile(),
|
|
1894
|
+
report: void 0
|
|
1895
|
+
};
|
|
1896
|
+
if (this.options.enableConfidenceScoring && this.scoredNames.length > 0) {
|
|
1897
|
+
console.log(`Generated ${this.scoredNames.length} scored hook names`);
|
|
1898
|
+
result.report = this.confidenceScorer.generateReport(this.scoredNames);
|
|
1899
|
+
const stats = {
|
|
1900
|
+
totalGenerated: this.scoredNames.length,
|
|
1901
|
+
highConfidence: this.scoredNames.filter((s) => s.confidence === "high").length,
|
|
1902
|
+
mediumConfidence: this.scoredNames.filter((s) => s.confidence === "medium").length,
|
|
1903
|
+
lowConfidence: this.scoredNames.filter((s) => s.confidence === "low").length,
|
|
1904
|
+
humanReviewed: this.configManager.getReviewedNames().length
|
|
1905
|
+
};
|
|
1906
|
+
this.configManager.updateStatistics(stats);
|
|
1907
|
+
await this.configManager.save();
|
|
1908
|
+
}
|
|
1909
|
+
return result;
|
|
1910
|
+
}
|
|
1911
|
+
generateQueryHooks(endpoints) {
|
|
1912
|
+
const hooks = [];
|
|
1913
|
+
hooks.push(this.generateFileHeader("Query Hooks"));
|
|
1914
|
+
hooks.push("");
|
|
1915
|
+
hooks.push("import { useQuery, useInfiniteQuery, type UseQueryOptions, type UseInfiniteQueryOptions } from '@tanstack/react-query'");
|
|
1916
|
+
hooks.push("import { apiClient } from '../client'");
|
|
1917
|
+
hooks.push("import { queryKeys } from './keys'");
|
|
1918
|
+
hooks.push("import * as Types from './types'");
|
|
1919
|
+
hooks.push("");
|
|
1920
|
+
const queryEndpoints = endpoints.filter((endpoint) => endpoint.method === "get");
|
|
1921
|
+
const generatedHooks = /* @__PURE__ */ new Map();
|
|
1922
|
+
for (const endpoint of queryEndpoints) {
|
|
1923
|
+
let hookName = this.generateQueryHookName(endpoint);
|
|
1924
|
+
if (generatedHooks.has(hookName)) {
|
|
1925
|
+
const existingEndpoint = generatedHooks.get(hookName);
|
|
1926
|
+
const existingIsSimpler = existingEndpoint.path.split("/").filter((s) => !s.startsWith("{")).length < endpoint.path.split("/").filter((s) => !s.startsWith("{")).length;
|
|
1927
|
+
if (existingIsSimpler) {
|
|
1928
|
+
const suffix = endpoint.path.includes("/{subcategory_id}") ? "Single" : "List";
|
|
1929
|
+
hookName = `${hookName}${suffix}`;
|
|
1930
|
+
} else {
|
|
1931
|
+
const existingSuffix = existingEndpoint.path.includes("/{subcategory_id}") ? "Single" : "List";
|
|
1932
|
+
const existingHookName = hookName;
|
|
1933
|
+
const newExistingHookName = `${existingHookName}${existingSuffix}`;
|
|
1934
|
+
for (let i = hooks.length - 1; i >= 0; i--) {
|
|
1935
|
+
if (hooks[i].includes(`function ${existingHookName}(`)) {
|
|
1936
|
+
hooks[i] = hooks[i].replace(`function ${existingHookName}(`, `function ${newExistingHookName}(`);
|
|
1937
|
+
if (i > 0 && hooks[i - 1].includes(`${existingHookName}`)) {
|
|
1938
|
+
hooks[i - 1] = hooks[i - 1].replace(existingHookName, newExistingHookName);
|
|
1939
|
+
}
|
|
1940
|
+
break;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
generatedHooks.delete(existingHookName);
|
|
1944
|
+
generatedHooks.set(newExistingHookName, existingEndpoint);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
generatedHooks.set(hookName, endpoint);
|
|
1948
|
+
const hook = this.generateQueryHook(endpoint, hookName);
|
|
1949
|
+
hooks.push(hook);
|
|
1950
|
+
hooks.push("");
|
|
1951
|
+
if (this.options.includeInfiniteQueries && this.isListEndpoint(endpoint)) {
|
|
1952
|
+
const infiniteHook = this.generateInfiniteQueryHook(endpoint, hookName);
|
|
1953
|
+
hooks.push(infiniteHook);
|
|
1954
|
+
hooks.push("");
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
return hooks.join("\n");
|
|
1958
|
+
}
|
|
1959
|
+
generateQueryHook(endpoint, hookName) {
|
|
1960
|
+
const operationName = this.getOperationName(endpoint);
|
|
1961
|
+
const hasParams = this.hasRequiredParams(endpoint);
|
|
1962
|
+
const lines = [];
|
|
1963
|
+
lines.push("/**");
|
|
1964
|
+
if (endpoint.summary) {
|
|
1965
|
+
lines.push(` * ${endpoint.summary}`);
|
|
1966
|
+
}
|
|
1967
|
+
if (endpoint.description) {
|
|
1968
|
+
lines.push(` * ${endpoint.description}`);
|
|
1969
|
+
}
|
|
1970
|
+
lines.push(` * @param ${hasParams ? "params" : "options"} ${hasParams ? "Request parameters" : "Query options"}`);
|
|
1971
|
+
lines.push(" */");
|
|
1972
|
+
const paramType = hasParams ? this.generateParamType(endpoint) : "";
|
|
1973
|
+
const params = hasParams ? `params: ${paramType}, options?: UseQueryOptions<any, any, any>` : "options?: UseQueryOptions<any, any, any>";
|
|
1974
|
+
lines.push(`export function ${hookName}(${params}) {`);
|
|
1975
|
+
const queryKeyName = hookName.replace(/^use/, "");
|
|
1976
|
+
const queryKeyNameCamelCase = queryKeyName.charAt(0).toLowerCase() + queryKeyName.slice(1);
|
|
1977
|
+
const queryKeyCall = hasParams ? `queryKeys.${queryKeyNameCamelCase}(params)` : `queryKeys.${queryKeyNameCamelCase}()`;
|
|
1978
|
+
const apiCall = hasParams ? `() => apiClient.${this.camelCase(operationName)}(params)` : `() => apiClient.${this.camelCase(operationName)}()`;
|
|
1979
|
+
lines.push(" return useQuery({");
|
|
1980
|
+
lines.push(` queryKey: ${queryKeyCall},`);
|
|
1981
|
+
lines.push(` queryFn: ${apiCall},`);
|
|
1982
|
+
lines.push(" ...options");
|
|
1983
|
+
lines.push(" })");
|
|
1984
|
+
lines.push("}");
|
|
1985
|
+
return lines.join("\n");
|
|
1986
|
+
}
|
|
1987
|
+
generateInfiniteQueryHook(endpoint, baseHookName) {
|
|
1988
|
+
const hookName = baseHookName.replace("use", "useInfinite");
|
|
1989
|
+
const operationName = this.getOperationName(endpoint);
|
|
1990
|
+
const lines = [];
|
|
1991
|
+
lines.push("/**");
|
|
1992
|
+
lines.push(` * Infinite query version of ${baseHookName}`);
|
|
1993
|
+
lines.push(" */");
|
|
1994
|
+
const paramType = this.generateParamType(endpoint);
|
|
1995
|
+
const queryKeyName = baseHookName.replace(/^use/, "");
|
|
1996
|
+
const queryKeyNameCamelCase = queryKeyName.charAt(0).toLowerCase() + queryKeyName.slice(1);
|
|
1997
|
+
lines.push(`export function ${hookName}(params: ${paramType}, options?: UseInfiniteQueryOptions<any, any, any, any, any>) {`);
|
|
1998
|
+
lines.push(" return useInfiniteQuery({");
|
|
1999
|
+
lines.push(` queryKey: [...queryKeys.${queryKeyNameCamelCase}(), params],`);
|
|
2000
|
+
lines.push(` queryFn: ({ pageParam = 1 }) => apiClient.${this.camelCase(operationName)}({ ...params, page: pageParam }),`);
|
|
2001
|
+
lines.push(" getNextPageParam: (lastPage, allPages) => {");
|
|
2002
|
+
lines.push(" // Implement pagination logic based on your API response structure");
|
|
2003
|
+
lines.push(" return lastPage?.hasNextPage ? allPages.length + 1 : undefined");
|
|
2004
|
+
lines.push(" },");
|
|
2005
|
+
lines.push(" initialPageParam: 1,");
|
|
2006
|
+
lines.push(" ...options");
|
|
2007
|
+
lines.push(" })");
|
|
2008
|
+
lines.push("}");
|
|
2009
|
+
return lines.join("\n");
|
|
2010
|
+
}
|
|
2011
|
+
generateMutationHooks(endpoints) {
|
|
2012
|
+
const hooks = [];
|
|
2013
|
+
hooks.push(this.generateFileHeader("Mutation Hooks"));
|
|
2014
|
+
hooks.push("");
|
|
2015
|
+
hooks.push("import { useMutation, useQueryClient, type UseMutationOptions } from '@tanstack/react-query'");
|
|
2016
|
+
hooks.push("import { apiClient } from '../client'");
|
|
2017
|
+
hooks.push("import { queryKeys } from './keys'");
|
|
2018
|
+
hooks.push("import * as Types from './types'");
|
|
2019
|
+
hooks.push("import type { BulkOperationRequest, BulkOperationResponse, BulkOperationProgress, BulkMutationOptions } from '../bulk-types'");
|
|
2020
|
+
hooks.push("");
|
|
2021
|
+
const mutationEndpoints = endpoints.filter((endpoint) => endpoint.method !== "get");
|
|
2022
|
+
const generatedHooks = /* @__PURE__ */ new Map();
|
|
2023
|
+
for (const endpoint of mutationEndpoints) {
|
|
2024
|
+
const requestBody = endpoint.requestBody;
|
|
2025
|
+
const isBulk = isBulkOperation(endpoint.path, endpoint.method, requestBody);
|
|
2026
|
+
let hookName = this.generateMutationHookName(endpoint);
|
|
2027
|
+
if (generatedHooks.has(hookName)) {
|
|
2028
|
+
const existingEndpoint = generatedHooks.get(hookName);
|
|
2029
|
+
const methodSuffix = endpoint.method.charAt(0).toUpperCase() + endpoint.method.slice(1);
|
|
2030
|
+
hookName = `${hookName}${methodSuffix}`;
|
|
2031
|
+
}
|
|
2032
|
+
generatedHooks.set(hookName, endpoint);
|
|
2033
|
+
const hook = isBulk ? this.bulkHookGenerator.generateBulkMutationHook(endpoint, hookName, this.getOperationName(endpoint)) : this.generateMutationHook(endpoint, hookName);
|
|
2034
|
+
hooks.push(hook);
|
|
2035
|
+
hooks.push("");
|
|
2036
|
+
}
|
|
2037
|
+
return hooks.join("\n");
|
|
2038
|
+
}
|
|
2039
|
+
generateMutationHook(endpoint, hookName) {
|
|
2040
|
+
const operationName = this.getOperationName(endpoint);
|
|
2041
|
+
const lines = [];
|
|
2042
|
+
lines.push("/**");
|
|
2043
|
+
if (endpoint.summary) {
|
|
2044
|
+
lines.push(` * ${endpoint.summary}`);
|
|
2045
|
+
}
|
|
2046
|
+
if (endpoint.description) {
|
|
2047
|
+
lines.push(` * ${endpoint.description}`);
|
|
2048
|
+
}
|
|
2049
|
+
lines.push(" */");
|
|
2050
|
+
const mutationType = this.generateMutationType(endpoint);
|
|
2051
|
+
lines.push(`export function ${hookName}(options?: UseMutationOptions<any, any, ${mutationType}>) {`);
|
|
2052
|
+
lines.push(" const queryClient = useQueryClient()");
|
|
2053
|
+
lines.push("");
|
|
2054
|
+
lines.push(" return useMutation({");
|
|
2055
|
+
const hasParams = this.hasRequiredParams(endpoint);
|
|
2056
|
+
if (hasParams) {
|
|
2057
|
+
lines.push(` mutationFn: ({ pathParams, ...data }) => apiClient.${this.camelCase(operationName)}(pathParams, data),`);
|
|
2058
|
+
} else {
|
|
2059
|
+
lines.push(` mutationFn: (data) => apiClient.${this.camelCase(operationName)}(data),`);
|
|
2060
|
+
}
|
|
2061
|
+
if (this.options.includeOptimisticUpdates) {
|
|
2062
|
+
lines.push(this.generateOptimisticUpdate(endpoint));
|
|
2063
|
+
}
|
|
2064
|
+
lines.push(this.generateCacheInvalidation(endpoint));
|
|
2065
|
+
lines.push(" ...options");
|
|
2066
|
+
lines.push(" })");
|
|
2067
|
+
lines.push("}");
|
|
2068
|
+
return lines.join("\n");
|
|
2069
|
+
}
|
|
2070
|
+
generateOptimisticUpdate(endpoint) {
|
|
2071
|
+
const method = endpoint.method.toLowerCase();
|
|
2072
|
+
const resource = endpoint.tags?.[0] || "default";
|
|
2073
|
+
const hasPathParams = endpoint.parameters.some((p) => p.in === "path");
|
|
2074
|
+
switch (method) {
|
|
2075
|
+
case "post":
|
|
2076
|
+
return ` onMutate: async (data: any) => {
|
|
2077
|
+
// Cancel outgoing refetches
|
|
2078
|
+
await queryClient.cancelQueries({ queryKey: queryKeys.${resource}() })
|
|
2079
|
+
|
|
2080
|
+
// Snapshot previous value
|
|
2081
|
+
const previousData = queryClient.getQueryData(queryKeys.${resource}())
|
|
2082
|
+
|
|
2083
|
+
// Optimistically update cache
|
|
2084
|
+
queryClient.setQueryData(queryKeys.${resource}(), (old: any) => {
|
|
2085
|
+
if (Array.isArray(old)) {
|
|
2086
|
+
return [...old, data]
|
|
2087
|
+
}
|
|
2088
|
+
return old
|
|
2089
|
+
})
|
|
2090
|
+
|
|
2091
|
+
return { previousData }
|
|
2092
|
+
},
|
|
2093
|
+
onError: (err: any, data: any, context: any) => {
|
|
2094
|
+
// Rollback on error
|
|
2095
|
+
if (context?.previousData !== undefined) {
|
|
2096
|
+
queryClient.setQueryData(queryKeys.${resource}(), context.previousData)
|
|
2097
|
+
}
|
|
2098
|
+
},`;
|
|
2099
|
+
case "put":
|
|
2100
|
+
case "patch":
|
|
2101
|
+
if (hasPathParams) {
|
|
2102
|
+
return ` onMutate: async (data: any) => {
|
|
2103
|
+
await queryClient.cancelQueries({ queryKey: queryKeys.${resource}() })
|
|
2104
|
+
|
|
2105
|
+
const previousData = queryClient.getQueryData(queryKeys.${resource}())
|
|
2106
|
+
|
|
2107
|
+
// Update specific item in cache
|
|
2108
|
+
queryClient.setQueryData(queryKeys.${resource}(), (old: any) => {
|
|
2109
|
+
if (Array.isArray(old)) {
|
|
2110
|
+
return old.map((item: any) =>
|
|
2111
|
+
item.id === data.pathParams?.id || item.id === data.pathParams?.${resource.slice(0, -1)}_id
|
|
2112
|
+
? { ...item, ...data }
|
|
2113
|
+
: item
|
|
2114
|
+
)
|
|
2115
|
+
}
|
|
2116
|
+
return old
|
|
2117
|
+
})
|
|
2118
|
+
|
|
2119
|
+
return { previousData }
|
|
2120
|
+
},
|
|
2121
|
+
onError: (err: any, data: any, context: any) => {
|
|
2122
|
+
if (context?.previousData !== undefined) {
|
|
2123
|
+
queryClient.setQueryData(queryKeys.${resource}(), context.previousData)
|
|
2124
|
+
}
|
|
2125
|
+
},`;
|
|
2126
|
+
}
|
|
2127
|
+
return ` onMutate: async (data: any) => {
|
|
2128
|
+
await queryClient.cancelQueries({ queryKey: queryKeys.${resource}() })
|
|
2129
|
+
|
|
2130
|
+
const previousData = queryClient.getQueryData(queryKeys.${resource}())
|
|
2131
|
+
|
|
2132
|
+
// Update cache with new data
|
|
2133
|
+
queryClient.setQueryData(queryKeys.${resource}(), data)
|
|
2134
|
+
|
|
2135
|
+
return { previousData }
|
|
2136
|
+
},
|
|
2137
|
+
onError: (err: any, data: any, context: any) => {
|
|
2138
|
+
if (context?.previousData !== undefined) {
|
|
2139
|
+
queryClient.setQueryData(queryKeys.${resource}(), context.previousData)
|
|
2140
|
+
}
|
|
2141
|
+
},`;
|
|
2142
|
+
case "delete":
|
|
2143
|
+
return ` onMutate: async (data: any) => {
|
|
2144
|
+
await queryClient.cancelQueries({ queryKey: queryKeys.${resource}() })
|
|
2145
|
+
|
|
2146
|
+
const previousData = queryClient.getQueryData(queryKeys.${resource}())
|
|
2147
|
+
|
|
2148
|
+
// Remove item from cache
|
|
2149
|
+
queryClient.setQueryData(queryKeys.${resource}(), (old: any) => {
|
|
2150
|
+
if (Array.isArray(old)) {
|
|
2151
|
+
const idToDelete = data.pathParams?.id || data.pathParams?.${resource.slice(0, -1)}_id || data
|
|
2152
|
+
return old.filter((item: any) => item.id !== idToDelete)
|
|
2153
|
+
}
|
|
2154
|
+
return old
|
|
2155
|
+
})
|
|
2156
|
+
|
|
2157
|
+
return { previousData }
|
|
2158
|
+
},
|
|
2159
|
+
onError: (err: any, data: any, context: any) => {
|
|
2160
|
+
if (context?.previousData !== undefined) {
|
|
2161
|
+
queryClient.setQueryData(queryKeys.${resource}(), context.previousData)
|
|
2162
|
+
}
|
|
2163
|
+
},`;
|
|
2164
|
+
default:
|
|
2165
|
+
return "";
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
generateCacheInvalidation(endpoint) {
|
|
2169
|
+
const relatedTags = this.getRelatedQueryTags(endpoint);
|
|
2170
|
+
return ` onSettled: () => {
|
|
2171
|
+
// Invalidate related queries
|
|
2172
|
+
${relatedTags.map((tag) => `queryClient.invalidateQueries({ queryKey: queryKeys.${tag}() })`).join("\n ")}
|
|
2173
|
+
},`;
|
|
2174
|
+
}
|
|
2175
|
+
generateQueryKeys(endpoints) {
|
|
2176
|
+
const keys = [];
|
|
2177
|
+
keys.push(this.generateFileHeader("Query Keys"));
|
|
2178
|
+
keys.push("");
|
|
2179
|
+
keys.push("/**");
|
|
2180
|
+
keys.push(" * Centralized query key factory");
|
|
2181
|
+
keys.push(" * Ensures consistent cache key generation across the application");
|
|
2182
|
+
keys.push(" */");
|
|
2183
|
+
keys.push("");
|
|
2184
|
+
keys.push("export const queryKeys = {");
|
|
2185
|
+
keys.push(` all: ['${this.options.queryKeyPrefix}'] as const,`);
|
|
2186
|
+
keys.push("");
|
|
2187
|
+
const groupedEndpoints = this.groupEndpointsByResource(endpoints);
|
|
2188
|
+
for (const [resource, resourceEndpoints] of Object.entries(groupedEndpoints)) {
|
|
2189
|
+
keys.push(` // ${resource} keys`);
|
|
2190
|
+
keys.push(` ${resource}: () => [...queryKeys.all, '${resource}'] as const,`);
|
|
2191
|
+
const generatedKeys = /* @__PURE__ */ new Map();
|
|
2192
|
+
for (const endpoint of resourceEndpoints) {
|
|
2193
|
+
if (endpoint.method !== "get")
|
|
2194
|
+
continue;
|
|
2195
|
+
const operationName = this.getOperationName(endpoint);
|
|
2196
|
+
let keyName = this.camelCase(operationName.replace(/^get/, ""));
|
|
2197
|
+
if (generatedKeys.has(keyName)) {
|
|
2198
|
+
const existingEndpoint = generatedKeys.get(keyName);
|
|
2199
|
+
const existingIsSimpler = existingEndpoint.path.split("/").filter((s) => !s.startsWith("{")).length < endpoint.path.split("/").filter((s) => !s.startsWith("{")).length;
|
|
2200
|
+
if (existingIsSimpler) {
|
|
2201
|
+
const suffix = endpoint.path.includes("/{subcategory_id}") ? "Single" : "List";
|
|
2202
|
+
keyName = `${keyName}${suffix}`;
|
|
2203
|
+
} else {
|
|
2204
|
+
const existingSuffix = existingEndpoint.path.includes("/{subcategory_id}") ? "Single" : "List";
|
|
2205
|
+
const existingKeyName = keyName;
|
|
2206
|
+
const newExistingKeyName = `${existingKeyName}${existingSuffix}`;
|
|
2207
|
+
for (let i = keys.length - 1; i >= 0; i--) {
|
|
2208
|
+
if (keys[i].includes(`${existingKeyName}:`)) {
|
|
2209
|
+
keys[i] = keys[i].replace(`${existingKeyName}:`, `${newExistingKeyName}:`).replace(`'${existingKeyName}'`, `'${newExistingKeyName}'`);
|
|
2210
|
+
break;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
generatedKeys.delete(existingKeyName);
|
|
2214
|
+
generatedKeys.set(newExistingKeyName, existingEndpoint);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
generatedKeys.set(keyName, endpoint);
|
|
2218
|
+
if (this.hasRequiredParams(endpoint)) {
|
|
2219
|
+
keys.push(` ${keyName}: (params: any) => [...queryKeys.${resource}(), '${keyName}', params] as const,`);
|
|
2220
|
+
} else {
|
|
2221
|
+
keys.push(` ${keyName}: () => [...queryKeys.${resource}(), '${keyName}'] as const,`);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
keys.push("");
|
|
2225
|
+
}
|
|
2226
|
+
keys.push("}");
|
|
2227
|
+
return keys.join("\n");
|
|
2228
|
+
}
|
|
2229
|
+
generateHookTypes(endpoints) {
|
|
2230
|
+
const types = [];
|
|
2231
|
+
types.push(this.generateFileHeader("Hook Types"));
|
|
2232
|
+
types.push("");
|
|
2233
|
+
types.push("// Re-export bulk operation types for convenience");
|
|
2234
|
+
types.push("export type { BulkOperationRequest, BulkOperationResponse, BulkOperationProgress, BulkMutationOptions } from '../bulk-types'");
|
|
2235
|
+
types.push("");
|
|
2236
|
+
const generatedTypes = /* @__PURE__ */ new Set();
|
|
2237
|
+
for (const endpoint of endpoints) {
|
|
2238
|
+
const operationName = this.getOperationName(endpoint);
|
|
2239
|
+
if (this.hasRequiredParams(endpoint)) {
|
|
2240
|
+
const typeName = `${this.capitalize(operationName)}Params`;
|
|
2241
|
+
if (!generatedTypes.has(typeName)) {
|
|
2242
|
+
generatedTypes.add(typeName);
|
|
2243
|
+
const paramType = this.generateParamType(endpoint);
|
|
2244
|
+
types.push(`export interface ${typeName} ${paramType}`);
|
|
2245
|
+
types.push("");
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
if (endpoint.method !== "get") {
|
|
2249
|
+
const typeName = `${this.capitalize(operationName)}Data`;
|
|
2250
|
+
if (!generatedTypes.has(typeName)) {
|
|
2251
|
+
generatedTypes.add(typeName);
|
|
2252
|
+
const mutationType = this.generateMutationType(endpoint);
|
|
2253
|
+
types.push(`export interface ${typeName} ${mutationType}`);
|
|
2254
|
+
types.push("");
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
return types.join("\n");
|
|
2259
|
+
}
|
|
2260
|
+
generateIndexFile() {
|
|
2261
|
+
const exports2 = [];
|
|
2262
|
+
exports2.push(this.generateFileHeader("Generated React Hooks"));
|
|
2263
|
+
exports2.push("");
|
|
2264
|
+
exports2.push("// Query hooks");
|
|
2265
|
+
exports2.push("export * from './queries'");
|
|
2266
|
+
exports2.push("");
|
|
2267
|
+
exports2.push("// Mutation hooks");
|
|
2268
|
+
exports2.push("export * from './mutations'");
|
|
2269
|
+
exports2.push("");
|
|
2270
|
+
exports2.push("// Query keys");
|
|
2271
|
+
exports2.push("export { queryKeys } from './keys'");
|
|
2272
|
+
exports2.push("");
|
|
2273
|
+
exports2.push("// Hook types");
|
|
2274
|
+
exports2.push("export * from './types'");
|
|
2275
|
+
return exports2.join("\n");
|
|
2276
|
+
}
|
|
2277
|
+
generateFileHeader(title) {
|
|
2278
|
+
return `/**
|
|
2279
|
+
* ${title}
|
|
2280
|
+
*
|
|
2281
|
+
* Auto-generated React hooks from OpenAPI specification
|
|
2282
|
+
* Do not edit manually - regenerate using the hook generator
|
|
2283
|
+
*/`;
|
|
2284
|
+
}
|
|
2285
|
+
generateQueryHookName(endpoint) {
|
|
2286
|
+
const operationName = this.getOperationName(endpoint);
|
|
2287
|
+
return `use${this.capitalize(operationName)}`;
|
|
2288
|
+
}
|
|
2289
|
+
generateMutationHookName(endpoint) {
|
|
2290
|
+
const operationName = this.getOperationName(endpoint);
|
|
2291
|
+
const resourceName = this.extractResourceName(endpoint.path);
|
|
2292
|
+
if (resourceName) {
|
|
2293
|
+
const bulkHookName = this.bulkHookGenerator.generateBulkMutationHookName(endpoint, resourceName);
|
|
2294
|
+
if (bulkHookName) {
|
|
2295
|
+
return bulkHookName;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
return `use${this.capitalize(operationName)}`;
|
|
2299
|
+
}
|
|
2300
|
+
getOperationName(endpoint) {
|
|
2301
|
+
if (endpoint.operationId) {
|
|
2302
|
+
const override = this.configManager.getOverride(endpoint.operationId);
|
|
2303
|
+
if (override) {
|
|
2304
|
+
return override;
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
let operationName;
|
|
2308
|
+
if (endpoint.operationId) {
|
|
2309
|
+
operationName = this.cleanOperationId(endpoint.operationId, endpoint);
|
|
2310
|
+
} else {
|
|
2311
|
+
operationName = this.generateCleanName(endpoint);
|
|
2312
|
+
}
|
|
2313
|
+
const patterns = this.configManager.getPatterns();
|
|
2314
|
+
if (patterns.deduplicateSegments) {
|
|
2315
|
+
operationName = this.removeDuplicateSegments(operationName);
|
|
2316
|
+
}
|
|
2317
|
+
if (this.options.enableConfidenceScoring && endpoint.operationId) {
|
|
2318
|
+
const hookName = endpoint.method === "get" ? `use${this.capitalize(operationName)}` : `use${this.capitalize(this.getMethodPrefix(endpoint.method))}${this.capitalize(operationName)}`;
|
|
2319
|
+
const scored = this.confidenceScorer.scoreHookName(hookName, endpoint.operationId, endpoint.path, endpoint.method);
|
|
2320
|
+
if (scored.confidence === "low") {
|
|
2321
|
+
console.log(`Low confidence hook: ${hookName} (score: ${scored.score})`);
|
|
2322
|
+
}
|
|
2323
|
+
this.scoredNames.push(scored);
|
|
2324
|
+
if (scored.confidence === "low" && scored.suggestions.length > 0) {
|
|
2325
|
+
const suggestion = scored.suggestions[0];
|
|
2326
|
+
const prefix = `use${this.capitalize(this.getMethodPrefix(endpoint.method))}`;
|
|
2327
|
+
if (suggestion.startsWith(prefix)) {
|
|
2328
|
+
operationName = this.camelCase(suggestion.substring(prefix.length));
|
|
2329
|
+
} else if (suggestion.startsWith("use")) {
|
|
2330
|
+
operationName = this.camelCase(suggestion.substring(3));
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
return operationName;
|
|
2335
|
+
}
|
|
2336
|
+
cleanOperationId(operationId, endpoint) {
|
|
2337
|
+
const parsed = this.parseOperationId(operationId, endpoint);
|
|
2338
|
+
return this.generateNameFromParsed(parsed, endpoint);
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Parse operation ID into structured components
|
|
2342
|
+
*/
|
|
2343
|
+
parseOperationId(operationId, endpoint) {
|
|
2344
|
+
const cleaned = operationId.replace(/_api_v\d+_/g, "_").replace(/_(get|post|put|patch|delete)$/i, "");
|
|
2345
|
+
const parts = cleaned.split("_").filter(Boolean);
|
|
2346
|
+
const prefix = this.extractPrefix(parts, { ...endpoint, operationId });
|
|
2347
|
+
const resources = this.extractResourcesFromPath(endpoint.path);
|
|
2348
|
+
const segments = endpoint.path.split("/");
|
|
2349
|
+
const lastSegment = segments[segments.length - 1];
|
|
2350
|
+
const isCustomAction = !lastSegment.startsWith("{") && segments[segments.length - 2]?.startsWith("{");
|
|
2351
|
+
return {
|
|
2352
|
+
action: prefix?.action || "",
|
|
2353
|
+
resource: resources.primary || "",
|
|
2354
|
+
subResource: resources.sub?.[0] || "",
|
|
2355
|
+
method: endpoint.method,
|
|
2356
|
+
isCustomAction,
|
|
2357
|
+
customActionName: isCustomAction ? lastSegment.replace(/-/g, "_") : void 0,
|
|
2358
|
+
originalId: operationId,
|
|
2359
|
+
prefix: prefix?.fullPrefix || "",
|
|
2360
|
+
resources
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Extract meaningful prefix from operation ID parts
|
|
2365
|
+
*/
|
|
2366
|
+
extractPrefix(parts, endpoint) {
|
|
2367
|
+
if (parts.length === 0)
|
|
2368
|
+
return null;
|
|
2369
|
+
const originalId = endpoint.operationId || "";
|
|
2370
|
+
const knownActions = [
|
|
2371
|
+
"login",
|
|
2372
|
+
"logout",
|
|
2373
|
+
"register",
|
|
2374
|
+
"signup",
|
|
2375
|
+
"signin",
|
|
2376
|
+
"assign",
|
|
2377
|
+
"reassign",
|
|
2378
|
+
"create",
|
|
2379
|
+
"update",
|
|
2380
|
+
"delete",
|
|
2381
|
+
"get",
|
|
2382
|
+
"list",
|
|
2383
|
+
"health",
|
|
2384
|
+
"status",
|
|
2385
|
+
"verify",
|
|
2386
|
+
"validate"
|
|
2387
|
+
];
|
|
2388
|
+
const firstPart = parts[0].toLowerCase();
|
|
2389
|
+
if (knownActions.includes(firstPart)) {
|
|
2390
|
+
if (firstPart === "get" && originalId.startsWith("get_")) {
|
|
2391
|
+
const match = originalId.match(/^get_(.+?)_api_v\d+/);
|
|
2392
|
+
if (match) {
|
|
2393
|
+
const compoundName = match[1];
|
|
2394
|
+
if (compoundName === "health_status" || compoundName === "current_user_info") {
|
|
2395
|
+
return {
|
|
2396
|
+
action: firstPart,
|
|
2397
|
+
fullPrefix: `${firstPart}_${compoundName}`
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
if (parts.length > 1 && ["assign", "reassign"].includes(firstPart)) {
|
|
2403
|
+
if (firstPart === "reassign" && parts[1] === "transaction" && parts[2] === "budget") {
|
|
2404
|
+
return {
|
|
2405
|
+
action: "reassign",
|
|
2406
|
+
fullPrefix: "reassign_budget"
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
return {
|
|
2410
|
+
action: firstPart,
|
|
2411
|
+
fullPrefix: `${parts[0]}_${parts[1]}`
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
return {
|
|
2415
|
+
action: firstPart,
|
|
2416
|
+
fullPrefix: parts[0]
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
return null;
|
|
2420
|
+
}
|
|
2421
|
+
/**
|
|
2422
|
+
* Extract resources from API path
|
|
2423
|
+
*/
|
|
2424
|
+
extractResourcesFromPath(path) {
|
|
2425
|
+
const cleanPath = path.replace(/^\/api\/v\d+\//, "/");
|
|
2426
|
+
const segments = cleanPath.split("/").filter(Boolean);
|
|
2427
|
+
const resources = [];
|
|
2428
|
+
let lastHasParam = false;
|
|
2429
|
+
for (let i = 0; i < segments.length; i++) {
|
|
2430
|
+
if (!segments[i].startsWith("{")) {
|
|
2431
|
+
resources.push(segments[i].replace(/-/g, "_"));
|
|
2432
|
+
} else if (i === segments.length - 1) {
|
|
2433
|
+
lastHasParam = true;
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
return {
|
|
2437
|
+
primary: resources[0] || "",
|
|
2438
|
+
sub: resources.slice(1),
|
|
2439
|
+
lastHasParam
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
/**
|
|
2443
|
+
* Generate clean hook name from parsed components
|
|
2444
|
+
*/
|
|
2445
|
+
generateNameFromParsed(parsed, endpoint) {
|
|
2446
|
+
if (parsed.prefix === "login")
|
|
2447
|
+
return "login";
|
|
2448
|
+
if (parsed.prefix === "logout")
|
|
2449
|
+
return "logout";
|
|
2450
|
+
if (parsed.prefix && parsed.prefix.startsWith("get_") && parsed.prefix.includes("_")) {
|
|
2451
|
+
const withoutGet = parsed.prefix.substring(4);
|
|
2452
|
+
if (withoutGet === "health_status" || withoutGet === "current_user_info") {
|
|
2453
|
+
return withoutGet;
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
if (parsed.prefix && this.isMeaningfulPrefix(parsed.prefix, parsed.resources)) {
|
|
2457
|
+
return this.formatWithPrefix(parsed);
|
|
2458
|
+
}
|
|
2459
|
+
return this.formatStandardOperation(parsed, endpoint);
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Check if prefix adds meaningful context
|
|
2463
|
+
*/
|
|
2464
|
+
isMeaningfulPrefix(prefix, resources) {
|
|
2465
|
+
const action = prefix.split("_")[0];
|
|
2466
|
+
if (prefix === resources.primary)
|
|
2467
|
+
return false;
|
|
2468
|
+
if (prefix === `get_${resources.primary}`)
|
|
2469
|
+
return false;
|
|
2470
|
+
if (prefix === `list_${resources.primary}`)
|
|
2471
|
+
return false;
|
|
2472
|
+
return ["assign", "reassign", "verify", "validate", "sync", "refresh"].includes(action);
|
|
2473
|
+
}
|
|
2474
|
+
/**
|
|
2475
|
+
* Format name with meaningful prefix
|
|
2476
|
+
*/
|
|
2477
|
+
formatWithPrefix(parsed) {
|
|
2478
|
+
const { prefix, resources, originalId, isCustomAction, customActionName } = parsed;
|
|
2479
|
+
const prefixParts = prefix.split("_");
|
|
2480
|
+
const action = prefixParts[0];
|
|
2481
|
+
const subject = prefixParts.slice(1).join("_");
|
|
2482
|
+
if (isCustomAction && customActionName) {
|
|
2483
|
+
if (action === "reassign" && subject === "budget") {
|
|
2484
|
+
return `reassign_${this.singularize(resources.primary)}_budget`;
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
if (subject && resources.sub.length > 0) {
|
|
2488
|
+
const subResource = resources.sub[0];
|
|
2489
|
+
if (subject === subResource || subject === this.pluralize(subResource)) {
|
|
2490
|
+
return `${action}_${this.singularize(resources.primary)}_${subResource}`;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
if (resources.sub.length > 0 && !isCustomAction) {
|
|
2494
|
+
return `${action}_${this.singularize(resources.primary)}_${resources.sub[0]}`;
|
|
2495
|
+
}
|
|
2496
|
+
return `${action}_${this.singularize(resources.primary)}`;
|
|
2497
|
+
}
|
|
2498
|
+
/**
|
|
2499
|
+
* Format standard REST operation
|
|
2500
|
+
*/
|
|
2501
|
+
formatStandardOperation(parsed, endpoint) {
|
|
2502
|
+
const { resources, method } = parsed;
|
|
2503
|
+
const hasParams = endpoint.path.includes("{");
|
|
2504
|
+
const isListOperation = method === "get" && !hasParams;
|
|
2505
|
+
if (resources.sub.length > 0) {
|
|
2506
|
+
const primary = this.singularize(resources.primary);
|
|
2507
|
+
const sub = resources.sub[0];
|
|
2508
|
+
switch (method) {
|
|
2509
|
+
case "get":
|
|
2510
|
+
return isListOperation ? `${primary}_${this.pluralize(sub)}` : `${primary}_${sub}`;
|
|
2511
|
+
case "post":
|
|
2512
|
+
return `create_${primary}_${this.singularize(sub)}`;
|
|
2513
|
+
case "put":
|
|
2514
|
+
case "patch":
|
|
2515
|
+
return `set_${primary}_${sub}`;
|
|
2516
|
+
case "delete":
|
|
2517
|
+
return `remove_${primary}_${sub}`;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
switch (method) {
|
|
2521
|
+
case "get":
|
|
2522
|
+
return isListOperation ? this.pluralize(resources.primary) : this.singularize(resources.primary);
|
|
2523
|
+
case "post":
|
|
2524
|
+
return `create_${this.singularize(resources.primary)}`;
|
|
2525
|
+
case "put":
|
|
2526
|
+
case "patch":
|
|
2527
|
+
return `update_${this.singularize(resources.primary)}`;
|
|
2528
|
+
case "delete":
|
|
2529
|
+
return `delete_${this.singularize(resources.primary)}`;
|
|
2530
|
+
default:
|
|
2531
|
+
return this.singularize(resources.primary);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
generateCleanName(endpoint) {
|
|
2535
|
+
const pathParts = endpoint.path.split("/").filter((part) => part && !part.startsWith("{"));
|
|
2536
|
+
if (pathParts.length === 0)
|
|
2537
|
+
return endpoint.method;
|
|
2538
|
+
const resource = pathParts[pathParts.length - 1];
|
|
2539
|
+
const cleanResource = resource.replace(/-/g, "_");
|
|
2540
|
+
const segments = endpoint.path.split("/");
|
|
2541
|
+
const lastSegment = segments[segments.length - 1];
|
|
2542
|
+
const hasCustomAction = !lastSegment.startsWith("{") && segments[segments.length - 2]?.startsWith("{");
|
|
2543
|
+
if (hasCustomAction) {
|
|
2544
|
+
const mainResource = pathParts[pathParts.length - 2] || pathParts[pathParts.length - 1];
|
|
2545
|
+
const action = lastSegment.replace(/-/g, "_");
|
|
2546
|
+
return `${mainResource}_${action}`;
|
|
2547
|
+
}
|
|
2548
|
+
if (pathParts.length > 2 && segments.some((s) => s.startsWith("{"))) {
|
|
2549
|
+
return cleanResource;
|
|
2550
|
+
}
|
|
2551
|
+
switch (endpoint.method) {
|
|
2552
|
+
case "get":
|
|
2553
|
+
const isList = !endpoint.path.includes("{");
|
|
2554
|
+
if (isList && !cleanResource.endsWith("s") && !cleanResource.endsWith("ies")) {
|
|
2555
|
+
return this.pluralize(cleanResource);
|
|
2556
|
+
}
|
|
2557
|
+
return cleanResource;
|
|
2558
|
+
case "post":
|
|
2559
|
+
return `create_${cleanResource}`;
|
|
2560
|
+
case "put":
|
|
2561
|
+
case "patch":
|
|
2562
|
+
return `update_${cleanResource}`;
|
|
2563
|
+
case "delete":
|
|
2564
|
+
return `delete_${cleanResource}`;
|
|
2565
|
+
default:
|
|
2566
|
+
return `${endpoint.method}_${cleanResource}`;
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
pluralize(word) {
|
|
2570
|
+
if (word.endsWith("s") && !word.endsWith("ss") && !word.endsWith("us") && !word.endsWith("is")) {
|
|
2571
|
+
return word;
|
|
2572
|
+
}
|
|
2573
|
+
if (word.endsWith("y") && !["ay", "ey", "iy", "oy", "uy"].includes(word.slice(-2))) {
|
|
2574
|
+
return word.slice(0, -1) + "ies";
|
|
2575
|
+
}
|
|
2576
|
+
if (word.endsWith("ss") || word.endsWith("x") || word.endsWith("ch") || word.endsWith("sh")) {
|
|
2577
|
+
return word + "es";
|
|
2578
|
+
}
|
|
2579
|
+
if (word.endsWith("us")) {
|
|
2580
|
+
return word.slice(0, -2) + "i";
|
|
2581
|
+
}
|
|
2582
|
+
if (word.endsWith("is")) {
|
|
2583
|
+
return word.slice(0, -2) + "es";
|
|
2584
|
+
}
|
|
2585
|
+
return word + "s";
|
|
2586
|
+
}
|
|
2587
|
+
singularize(word) {
|
|
2588
|
+
if (word.endsWith("ies")) {
|
|
2589
|
+
return word.slice(0, -3) + "y";
|
|
2590
|
+
}
|
|
2591
|
+
if (word.endsWith("ses") || word.endsWith("xes") || word.endsWith("ches") || word.endsWith("shes")) {
|
|
2592
|
+
return word.slice(0, -2);
|
|
2593
|
+
}
|
|
2594
|
+
if (word.endsWith("s") && !word.endsWith("ss")) {
|
|
2595
|
+
return word.slice(0, -1);
|
|
2596
|
+
}
|
|
2597
|
+
return word;
|
|
2598
|
+
}
|
|
2599
|
+
hasRequiredParams(endpoint) {
|
|
2600
|
+
return endpoint.parameters.some((p) => p.required) || endpoint.parameters.some((p) => p.in === "path");
|
|
2601
|
+
}
|
|
2602
|
+
generateParamType(endpoint) {
|
|
2603
|
+
const pathParams = endpoint.parameters.filter((p) => p.in === "path");
|
|
2604
|
+
const queryParams = endpoint.parameters.filter((p) => p.in === "query");
|
|
2605
|
+
const properties = [];
|
|
2606
|
+
pathParams.forEach((param) => {
|
|
2607
|
+
properties.push(`${param.name}: ${this.getParameterType(param)}`);
|
|
2608
|
+
});
|
|
2609
|
+
queryParams.forEach((param) => {
|
|
2610
|
+
const optional = param.required ? "" : "?";
|
|
2611
|
+
properties.push(`${param.name}${optional}: ${this.getParameterType(param)}`);
|
|
2612
|
+
});
|
|
2613
|
+
return `{ ${properties.join("; ")} }`;
|
|
2614
|
+
}
|
|
2615
|
+
generateMutationType(endpoint) {
|
|
2616
|
+
const hasPathParams = endpoint.parameters.some((p) => p.in === "path");
|
|
2617
|
+
if (hasPathParams) {
|
|
2618
|
+
const pathType = this.generateParamType(endpoint);
|
|
2619
|
+
return `{ pathParams: ${pathType}; [key: string]: any }`;
|
|
2620
|
+
}
|
|
2621
|
+
return "{ [key: string]: any }";
|
|
2622
|
+
}
|
|
2623
|
+
getParameterType(param) {
|
|
2624
|
+
switch (param.schema.type) {
|
|
2625
|
+
case "string":
|
|
2626
|
+
return "string";
|
|
2627
|
+
case "number":
|
|
2628
|
+
case "integer":
|
|
2629
|
+
return "number";
|
|
2630
|
+
case "boolean":
|
|
2631
|
+
return "boolean";
|
|
2632
|
+
default:
|
|
2633
|
+
return "any";
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
isListEndpoint(endpoint) {
|
|
2637
|
+
if (endpoint.method.toLowerCase() !== "get")
|
|
2638
|
+
return false;
|
|
2639
|
+
if (endpoint.path.includes("{"))
|
|
2640
|
+
return false;
|
|
2641
|
+
if (endpoint.responses?.["200"]?.schema?.type === "array")
|
|
2642
|
+
return true;
|
|
2643
|
+
const description = `${endpoint.summary || ""} ${endpoint.description || ""}`.toLowerCase();
|
|
2644
|
+
return description.includes("list") || description.includes("get all") || description.includes("fetch all");
|
|
2645
|
+
}
|
|
2646
|
+
groupEndpointsByResource(endpoints) {
|
|
2647
|
+
const groups = {};
|
|
2648
|
+
for (const endpoint of endpoints) {
|
|
2649
|
+
const resource = endpoint.tags?.[0] || "default";
|
|
2650
|
+
if (!groups[resource]) {
|
|
2651
|
+
groups[resource] = [];
|
|
2652
|
+
}
|
|
2653
|
+
groups[resource].push(endpoint);
|
|
2654
|
+
}
|
|
2655
|
+
return groups;
|
|
2656
|
+
}
|
|
2657
|
+
getRelatedQueryTags(endpoint) {
|
|
2658
|
+
const resource = endpoint.tags?.[0] || "default";
|
|
2659
|
+
return [resource, "all"];
|
|
2660
|
+
}
|
|
2661
|
+
camelCase(str) {
|
|
2662
|
+
return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase()).replace(/^./, (char) => char.toLowerCase());
|
|
2663
|
+
}
|
|
2664
|
+
capitalize(str) {
|
|
2665
|
+
return str.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
|
|
2666
|
+
}
|
|
2667
|
+
getMethodPrefix(method) {
|
|
2668
|
+
const prefixes = {
|
|
2669
|
+
post: "create",
|
|
2670
|
+
put: "update",
|
|
2671
|
+
patch: "update",
|
|
2672
|
+
delete: "delete"
|
|
2673
|
+
};
|
|
2674
|
+
return prefixes[method.toLowerCase()] || method.toLowerCase();
|
|
2675
|
+
}
|
|
2676
|
+
removeDuplicateSegments(operationName) {
|
|
2677
|
+
const parts = operationName.split("_");
|
|
2678
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2679
|
+
const result = [];
|
|
2680
|
+
for (const part of parts) {
|
|
2681
|
+
const lower = part.toLowerCase();
|
|
2682
|
+
if (!seen.has(lower)) {
|
|
2683
|
+
seen.add(lower);
|
|
2684
|
+
result.push(part);
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
return result.join("_");
|
|
2688
|
+
}
|
|
2689
|
+
};
|
|
2690
|
+
async function generateHooks(parsedAPI, options) {
|
|
2691
|
+
const generator = new ReactHookGenerator(options);
|
|
2692
|
+
return generator.generate(parsedAPI);
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
// cli/commands/generate-hooks.ts
|
|
2696
|
+
async function generateHooksCommand(source, options) {
|
|
2697
|
+
try {
|
|
2698
|
+
console.log("\u{1F680} Pattern-Stack Hook Generator");
|
|
2699
|
+
console.log("================================");
|
|
2700
|
+
console.log();
|
|
2701
|
+
console.log("\u{1F4E5} Loading OpenAPI specification...");
|
|
2702
|
+
const spec = await loadOpenAPISpec(source);
|
|
2703
|
+
console.log(`\u2705 Loaded: ${spec.info.title} v${spec.info.version}`);
|
|
2704
|
+
console.log("\u{1F50D} Parsing specification...");
|
|
2705
|
+
const parsed = await parseOpenAPI(spec);
|
|
2706
|
+
console.log(`\u2705 Found ${parsed.endpoints.length} endpoints, ${parsed.schemas.length} schemas`);
|
|
2707
|
+
console.log("\u{1F3D7}\uFE0F Generating TypeScript types...");
|
|
2708
|
+
const types = generateTypes(parsed, {
|
|
2709
|
+
prefix: options.prefix,
|
|
2710
|
+
includeJSDoc: true,
|
|
2711
|
+
includeExamples: true
|
|
2712
|
+
});
|
|
2713
|
+
console.log("\u{1F527} Generating API client...");
|
|
2714
|
+
const client = generateAPIClient(parsed, {
|
|
2715
|
+
clientType: options.client,
|
|
2716
|
+
includeAuth: true,
|
|
2717
|
+
authType: options.auth,
|
|
2718
|
+
includeInterceptors: true
|
|
2719
|
+
});
|
|
2720
|
+
console.log("\u269B\uFE0F Generating React hooks...");
|
|
2721
|
+
const hooks = await generateHooks(parsed, {
|
|
2722
|
+
includeInfiniteQueries: true,
|
|
2723
|
+
includeOptimisticUpdates: true,
|
|
2724
|
+
includeMutationHelpers: true,
|
|
2725
|
+
enableConfidenceScoring: true
|
|
2726
|
+
});
|
|
2727
|
+
if (options.dryRun) {
|
|
2728
|
+
console.log("\n\u{1F4CB} Dry run - files that would be generated:");
|
|
2729
|
+
console.log("Types:");
|
|
2730
|
+
console.log(" - schemas.ts");
|
|
2731
|
+
console.log(" - endpoints.ts");
|
|
2732
|
+
console.log(" - parameters.ts");
|
|
2733
|
+
console.log(" - responses.ts");
|
|
2734
|
+
console.log();
|
|
2735
|
+
console.log("API Client:");
|
|
2736
|
+
console.log(" - client.ts");
|
|
2737
|
+
console.log(" - methods.ts");
|
|
2738
|
+
console.log(" - types.ts");
|
|
2739
|
+
console.log(" - config.ts");
|
|
2740
|
+
console.log();
|
|
2741
|
+
console.log("React Hooks:");
|
|
2742
|
+
console.log(" - queries.ts");
|
|
2743
|
+
console.log(" - mutations.ts");
|
|
2744
|
+
console.log(" - keys.ts");
|
|
2745
|
+
console.log(" - types.ts");
|
|
2746
|
+
console.log();
|
|
2747
|
+
console.log("Generated code preview:");
|
|
2748
|
+
console.log("======================");
|
|
2749
|
+
console.log();
|
|
2750
|
+
console.log("Types (first 20 lines):");
|
|
2751
|
+
console.log(types.schemas.split("\n").slice(0, 20).join("\n"));
|
|
2752
|
+
console.log("\n...");
|
|
2753
|
+
return;
|
|
2754
|
+
}
|
|
2755
|
+
console.log(`\u{1F4C1} Creating output directory: ${options.output}`);
|
|
2756
|
+
await ensureDir(options.output);
|
|
2757
|
+
await ensureDir((0, import_path3.join)(options.output, "types"));
|
|
2758
|
+
await ensureDir((0, import_path3.join)(options.output, "client"));
|
|
2759
|
+
await ensureDir((0, import_path3.join)(options.output, "hooks"));
|
|
2760
|
+
console.log("\u{1F4BE} Writing type definitions...");
|
|
2761
|
+
await writeFile((0, import_path3.join)(options.output, "types", "schemas.ts"), types.schemas);
|
|
2762
|
+
await writeFile((0, import_path3.join)(options.output, "types", "endpoints.ts"), types.endpoints);
|
|
2763
|
+
await writeFile((0, import_path3.join)(options.output, "types", "parameters.ts"), types.parameters);
|
|
2764
|
+
await writeFile((0, import_path3.join)(options.output, "types", "responses.ts"), types.responses);
|
|
2765
|
+
await writeFile((0, import_path3.join)(options.output, "types", "index.ts"), types.index);
|
|
2766
|
+
console.log("\u{1F4BE} Writing API client...");
|
|
2767
|
+
await writeFile((0, import_path3.join)(options.output, "client", "client.ts"), client.client);
|
|
2768
|
+
await writeFile((0, import_path3.join)(options.output, "client", "methods.ts"), client.methods);
|
|
2769
|
+
await writeFile((0, import_path3.join)(options.output, "client", "types.ts"), client.types);
|
|
2770
|
+
await writeFile((0, import_path3.join)(options.output, "client", "config.ts"), client.config);
|
|
2771
|
+
await writeFile((0, import_path3.join)(options.output, "client", "index.ts"), client.index);
|
|
2772
|
+
console.log("\u{1F4BE} Writing React hooks...");
|
|
2773
|
+
await writeFile((0, import_path3.join)(options.output, "hooks", "queries.ts"), hooks.queries);
|
|
2774
|
+
await writeFile((0, import_path3.join)(options.output, "hooks", "mutations.ts"), hooks.mutations);
|
|
2775
|
+
await writeFile((0, import_path3.join)(options.output, "hooks", "keys.ts"), hooks.keys);
|
|
2776
|
+
await writeFile((0, import_path3.join)(options.output, "hooks", "types.ts"), hooks.types);
|
|
2777
|
+
await writeFile((0, import_path3.join)(options.output, "hooks", "index.ts"), hooks.index);
|
|
2778
|
+
if (hooks.report) {
|
|
2779
|
+
await writeFile((0, import_path3.join)(options.output, "hooks", "confidence-report.md"), hooks.report);
|
|
2780
|
+
console.log("\u{1F4CA} Generated confidence report");
|
|
2781
|
+
const lowConfidenceCount = (hooks.report.match(/## Low Confidence Names/g) || []).length;
|
|
2782
|
+
if (lowConfidenceCount > 0) {
|
|
2783
|
+
console.log("\n\u26A0\uFE0F Some hook names have low confidence and may need review.");
|
|
2784
|
+
console.log("Run `npm run review:hooks` to interactively review and improve them.");
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
const mainIndex = `/**
|
|
2788
|
+
* Generated API Integration
|
|
2789
|
+
*
|
|
2790
|
+
* Auto-generated from OpenAPI specification: ${spec.info.title}
|
|
2791
|
+
* Version: ${spec.info.version}
|
|
2792
|
+
* Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
2793
|
+
*/
|
|
2794
|
+
|
|
2795
|
+
export * from './types'
|
|
2796
|
+
export * from './client'
|
|
2797
|
+
export * from './hooks'
|
|
2798
|
+
|
|
2799
|
+
// Quick start exports
|
|
2800
|
+
export { createAPIClient } from './client'
|
|
2801
|
+
export { queryKeys } from './hooks'`;
|
|
2802
|
+
await writeFile((0, import_path3.join)(options.output, "index.ts"), mainIndex);
|
|
2803
|
+
const usageExample = generateUsageExample(spec.info.title, options);
|
|
2804
|
+
await writeFile((0, import_path3.join)(options.output, "example.ts"), usageExample);
|
|
2805
|
+
console.log();
|
|
2806
|
+
console.log("\u2705 Generation complete!");
|
|
2807
|
+
console.log(`\u{1F4E6} Generated files in: ${options.output}`);
|
|
2808
|
+
console.log();
|
|
2809
|
+
console.log("\u{1F680} Quick start:");
|
|
2810
|
+
console.log(`import { createAPIClient, useGetUsers } from './${options.output}'`);
|
|
2811
|
+
console.log();
|
|
2812
|
+
console.log("\u{1F4A1} See example.ts for detailed usage instructions");
|
|
2813
|
+
} catch (error) {
|
|
2814
|
+
console.error("\u274C Generation failed:");
|
|
2815
|
+
console.error(error instanceof Error ? error.message : error);
|
|
2816
|
+
process.exit(1);
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
async function ensureDir(dir) {
|
|
2820
|
+
try {
|
|
2821
|
+
await import_fs3.promises.mkdir(dir, { recursive: true });
|
|
2822
|
+
} catch (error) {
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
async function writeFile(path, content) {
|
|
2826
|
+
await ensureDir((0, import_path3.dirname)(path));
|
|
2827
|
+
await import_fs3.promises.writeFile(path, content, "utf8");
|
|
2828
|
+
}
|
|
2829
|
+
function generateUsageExample(apiTitle, options) {
|
|
2830
|
+
return `/**
|
|
2831
|
+
* ${apiTitle} - Usage Example
|
|
2832
|
+
*
|
|
2833
|
+
* This file demonstrates how to use the generated API integration
|
|
2834
|
+
*/
|
|
2835
|
+
|
|
2836
|
+
import React from 'react'
|
|
2837
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
2838
|
+
import { createAPIClient, useGetUsers, useCreateUser } from './'
|
|
2839
|
+
|
|
2840
|
+
// 1. Set up the API client
|
|
2841
|
+
const apiClient = createAPIClient({
|
|
2842
|
+
baseUrl: 'https://api.example.com/v1',
|
|
2843
|
+
${options.auth === "bearer" ? `getAuthToken: () => localStorage.getItem('authToken'),` : ""}
|
|
2844
|
+
${options.auth === "apiKey" ? `getApiKey: () => process.env.REACT_APP_API_KEY,
|
|
2845
|
+
apiKeyHeader: 'X-API-Key',` : ""}
|
|
2846
|
+
onError: (error) => {
|
|
2847
|
+
console.error('API Error:', error)
|
|
2848
|
+
// Handle global errors (show toast, redirect to login, etc.)
|
|
2849
|
+
}
|
|
2850
|
+
})
|
|
2851
|
+
|
|
2852
|
+
// 2. Set up React Query
|
|
2853
|
+
const queryClient = new QueryClient({
|
|
2854
|
+
defaultOptions: {
|
|
2855
|
+
queries: {
|
|
2856
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
2857
|
+
cacheTime: 10 * 60 * 1000, // 10 minutes
|
|
2858
|
+
},
|
|
2859
|
+
},
|
|
2860
|
+
})
|
|
2861
|
+
|
|
2862
|
+
// 3. Wrap your app with providers
|
|
2863
|
+
function App() {
|
|
2864
|
+
return (
|
|
2865
|
+
<QueryClientProvider client={queryClient}>
|
|
2866
|
+
<UserList />
|
|
2867
|
+
</QueryClientProvider>
|
|
2868
|
+
)
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
// 4. Use generated hooks in components
|
|
2872
|
+
function UserList() {
|
|
2873
|
+
// Query hook with automatic loading, error, and data states
|
|
2874
|
+
const { data: users, isLoading, error } = useGetUsers({
|
|
2875
|
+
limit: 10
|
|
2876
|
+
})
|
|
2877
|
+
|
|
2878
|
+
// Mutation hook with optimistic updates
|
|
2879
|
+
const createUserMutation = useCreateUser({
|
|
2880
|
+
onSuccess: () => {
|
|
2881
|
+
console.log('User created successfully!')
|
|
2882
|
+
},
|
|
2883
|
+
onError: (error) => {
|
|
2884
|
+
console.error('Failed to create user:', error)
|
|
2885
|
+
}
|
|
2886
|
+
})
|
|
2887
|
+
|
|
2888
|
+
const handleCreateUser = () => {
|
|
2889
|
+
createUserMutation.mutate({
|
|
2890
|
+
email: 'new@example.com',
|
|
2891
|
+
name: 'New User'
|
|
2892
|
+
})
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
if (isLoading) return <div>Loading users...</div>
|
|
2896
|
+
if (error) return <div>Error: {error.message}</div>
|
|
2897
|
+
|
|
2898
|
+
return (
|
|
2899
|
+
<div>
|
|
2900
|
+
<h2>Users</h2>
|
|
2901
|
+
<button
|
|
2902
|
+
onClick={handleCreateUser}
|
|
2903
|
+
disabled={createUserMutation.isLoading}
|
|
2904
|
+
>
|
|
2905
|
+
{createUserMutation.isLoading ? 'Creating...' : 'Create User'}
|
|
2906
|
+
</button>
|
|
2907
|
+
|
|
2908
|
+
<ul>
|
|
2909
|
+
{users?.map(user => (
|
|
2910
|
+
<li key={user.id}>
|
|
2911
|
+
{user.name} ({user.email})
|
|
2912
|
+
</li>
|
|
2913
|
+
))}
|
|
2914
|
+
</ul>
|
|
2915
|
+
</div>
|
|
2916
|
+
)
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
// 5. Advanced usage with infinite queries
|
|
2920
|
+
function InfiniteUserList() {
|
|
2921
|
+
const {
|
|
2922
|
+
data,
|
|
2923
|
+
fetchNextPage,
|
|
2924
|
+
hasNextPage,
|
|
2925
|
+
isFetchingNextPage,
|
|
2926
|
+
} = useInfiniteGetUsers({
|
|
2927
|
+
limit: 20
|
|
2928
|
+
})
|
|
2929
|
+
|
|
2930
|
+
return (
|
|
2931
|
+
<div>
|
|
2932
|
+
{data?.pages.map((page, i) => (
|
|
2933
|
+
<React.Fragment key={i}>
|
|
2934
|
+
{page.map(user => (
|
|
2935
|
+
<div key={user.id}>{user.name}</div>
|
|
2936
|
+
))}
|
|
2937
|
+
</React.Fragment>
|
|
2938
|
+
))}
|
|
2939
|
+
|
|
2940
|
+
<button
|
|
2941
|
+
onClick={() => fetchNextPage()}
|
|
2942
|
+
disabled={!hasNextPage || isFetchingNextPage}
|
|
2943
|
+
>
|
|
2944
|
+
{isFetchingNextPage
|
|
2945
|
+
? 'Loading more...'
|
|
2946
|
+
: hasNextPage
|
|
2947
|
+
? 'Load More'
|
|
2948
|
+
: 'Nothing more to load'}
|
|
2949
|
+
</button>
|
|
2950
|
+
</div>
|
|
2951
|
+
)
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
export default App`;
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
// cli/commands/scaffold.ts
|
|
2958
|
+
var import_fs4 = require("fs");
|
|
2959
|
+
var import_path4 = require("path");
|
|
2960
|
+
async function scaffoldCommand(type, name, options) {
|
|
2961
|
+
console.log(`\u{1F3D7}\uFE0F Scaffolding ${type}: ${name}`);
|
|
2962
|
+
switch (type) {
|
|
2963
|
+
case "component":
|
|
2964
|
+
await scaffoldComponent(name, options);
|
|
2965
|
+
break;
|
|
2966
|
+
case "feature":
|
|
2967
|
+
await scaffoldFeature(name, options);
|
|
2968
|
+
break;
|
|
2969
|
+
case "template":
|
|
2970
|
+
await scaffoldTemplate(name, options);
|
|
2971
|
+
break;
|
|
2972
|
+
default:
|
|
2973
|
+
console.error(`\u274C Unknown scaffold type: ${type}`);
|
|
2974
|
+
console.log("Available types: component, feature, template");
|
|
2975
|
+
process.exit(1);
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
async function scaffoldComponent(name, options) {
|
|
2979
|
+
const outputDir = options.output || `./src/atoms/composed/${name}`;
|
|
2980
|
+
const componentContent = `/**
|
|
2981
|
+
* ${name} Component
|
|
2982
|
+
*
|
|
2983
|
+
* Generated by Pattern-Stack CLI
|
|
2984
|
+
*/
|
|
2985
|
+
|
|
2986
|
+
import React from 'react'
|
|
2987
|
+
import { cn } from '../../utils/utils'
|
|
2988
|
+
|
|
2989
|
+
export interface ${name}Props {
|
|
2990
|
+
className?: string
|
|
2991
|
+
children?: React.ReactNode
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
export function ${name}({ className, children, ...props }: ${name}Props) {
|
|
2995
|
+
return (
|
|
2996
|
+
<div className={cn("", className)} {...props}>
|
|
2997
|
+
{children}
|
|
2998
|
+
</div>
|
|
2999
|
+
)
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
${name}.displayName = "${name}"`;
|
|
3003
|
+
const indexContent = `export { ${name} } from './${name}'
|
|
3004
|
+
export type { ${name}Props } from './${name}'`;
|
|
3005
|
+
const testContent = `/**
|
|
3006
|
+
* ${name} Component Tests
|
|
3007
|
+
*/
|
|
3008
|
+
|
|
3009
|
+
import { render, screen } from '@testing-library/react'
|
|
3010
|
+
import { ${name} } from './${name}'
|
|
3011
|
+
|
|
3012
|
+
describe('${name}', () => {
|
|
3013
|
+
it('renders children', () => {
|
|
3014
|
+
render(<${name}>Test content</${name}>)
|
|
3015
|
+
expect(screen.getByText('Test content')).toBeInTheDocument()
|
|
3016
|
+
})
|
|
3017
|
+
|
|
3018
|
+
it('applies custom className', () => {
|
|
3019
|
+
const { container } = render(<${name} className="custom-class" />)
|
|
3020
|
+
expect(container.firstChild).toHaveClass('custom-class')
|
|
3021
|
+
})
|
|
3022
|
+
})`;
|
|
3023
|
+
await ensureDir2(outputDir);
|
|
3024
|
+
await writeFile2((0, import_path4.join)(outputDir, `${name}.tsx`), componentContent);
|
|
3025
|
+
await writeFile2((0, import_path4.join)(outputDir, "index.ts"), indexContent);
|
|
3026
|
+
await writeFile2((0, import_path4.join)(outputDir, `${name}.test.tsx`), testContent);
|
|
3027
|
+
console.log(`\u2705 Component scaffolded: ${outputDir}`);
|
|
3028
|
+
}
|
|
3029
|
+
async function scaffoldFeature(name, options) {
|
|
3030
|
+
const outputDir = options.output || `./src/features/${name.toLowerCase()}`;
|
|
3031
|
+
const hookContent = `/**
|
|
3032
|
+
* ${name} Hooks
|
|
3033
|
+
*
|
|
3034
|
+
* Generated by Pattern-Stack CLI
|
|
3035
|
+
*/
|
|
3036
|
+
|
|
3037
|
+
import { useState, useEffect } from 'react'
|
|
3038
|
+
|
|
3039
|
+
export function use${name}() {
|
|
3040
|
+
const [data, setData] = useState(null)
|
|
3041
|
+
const [loading, setLoading] = useState(false)
|
|
3042
|
+
const [error, setError] = useState<Error | null>(null)
|
|
3043
|
+
|
|
3044
|
+
// Add your logic here
|
|
3045
|
+
|
|
3046
|
+
return {
|
|
3047
|
+
data,
|
|
3048
|
+
loading,
|
|
3049
|
+
error,
|
|
3050
|
+
refetch: () => {
|
|
3051
|
+
// Implement refetch logic
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
}`;
|
|
3055
|
+
const componentContent = `/**
|
|
3056
|
+
* ${name} Component
|
|
3057
|
+
*
|
|
3058
|
+
* Generated by Pattern-Stack CLI
|
|
3059
|
+
*/
|
|
3060
|
+
|
|
3061
|
+
import React from 'react'
|
|
3062
|
+
import { use${name} } from '../hooks/use${name}'
|
|
3063
|
+
|
|
3064
|
+
export interface ${name}ComponentProps {
|
|
3065
|
+
className?: string
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
export function ${name}Component({ className }: ${name}ComponentProps) {
|
|
3069
|
+
const { data, loading, error } = use${name}()
|
|
3070
|
+
|
|
3071
|
+
if (loading) return <div>Loading...</div>
|
|
3072
|
+
if (error) return <div>Error: {error.message}</div>
|
|
3073
|
+
|
|
3074
|
+
return (
|
|
3075
|
+
<div className={className}>
|
|
3076
|
+
<h2>${name}</h2>
|
|
3077
|
+
{/* Add your UI here */}
|
|
3078
|
+
</div>
|
|
3079
|
+
)
|
|
3080
|
+
}`;
|
|
3081
|
+
const indexContent = `export { use${name} } from './hooks/use${name}'
|
|
3082
|
+
export { ${name}Component } from './components/${name}Component'
|
|
3083
|
+
export type { ${name}ComponentProps } from './components/${name}Component'`;
|
|
3084
|
+
await ensureDir2((0, import_path4.join)(outputDir, "hooks"));
|
|
3085
|
+
await ensureDir2((0, import_path4.join)(outputDir, "components"));
|
|
3086
|
+
await writeFile2((0, import_path4.join)(outputDir, "hooks", `use${name}.ts`), hookContent);
|
|
3087
|
+
await writeFile2((0, import_path4.join)(outputDir, "hooks", "index.ts"), `export { use${name} } from './use${name}'`);
|
|
3088
|
+
await writeFile2((0, import_path4.join)(outputDir, "components", `${name}Component.tsx`), componentContent);
|
|
3089
|
+
await writeFile2((0, import_path4.join)(outputDir, "components", "index.ts"), `export { ${name}Component } from './${name}Component'`);
|
|
3090
|
+
await writeFile2((0, import_path4.join)(outputDir, "index.ts"), indexContent);
|
|
3091
|
+
console.log(`\u2705 Feature scaffolded: ${outputDir}`);
|
|
3092
|
+
}
|
|
3093
|
+
async function scaffoldTemplate(name, options) {
|
|
3094
|
+
const outputDir = options.output || `./src/templates/${name.toLowerCase()}`;
|
|
3095
|
+
const templateContent = `/**
|
|
3096
|
+
* ${name} Template
|
|
3097
|
+
*
|
|
3098
|
+
* Generated by Pattern-Stack CLI
|
|
3099
|
+
*/
|
|
3100
|
+
|
|
3101
|
+
import React from 'react'
|
|
3102
|
+
import { PageTemplate } from '../PageTemplate'
|
|
3103
|
+
|
|
3104
|
+
export interface ${name}TemplateProps {
|
|
3105
|
+
title?: string
|
|
3106
|
+
children?: React.ReactNode
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
export function ${name}Template({
|
|
3110
|
+
title = "${name}",
|
|
3111
|
+
children
|
|
3112
|
+
}: ${name}TemplateProps) {
|
|
3113
|
+
return (
|
|
3114
|
+
<PageTemplate>
|
|
3115
|
+
<div className="space-y-6">
|
|
3116
|
+
<header>
|
|
3117
|
+
<h1 className="text-3xl font-bold">{title}</h1>
|
|
3118
|
+
</header>
|
|
3119
|
+
|
|
3120
|
+
<main>
|
|
3121
|
+
{children}
|
|
3122
|
+
</main>
|
|
3123
|
+
</div>
|
|
3124
|
+
</PageTemplate>
|
|
3125
|
+
)
|
|
3126
|
+
}`;
|
|
3127
|
+
const indexContent = `export { ${name}Template } from './${name}Template'
|
|
3128
|
+
export type { ${name}TemplateProps } from './${name}Template'`;
|
|
3129
|
+
await ensureDir2(outputDir);
|
|
3130
|
+
await writeFile2((0, import_path4.join)(outputDir, `${name}Template.tsx`), templateContent);
|
|
3131
|
+
await writeFile2((0, import_path4.join)(outputDir, "index.ts"), indexContent);
|
|
3132
|
+
console.log(`\u2705 Template scaffolded: ${outputDir}`);
|
|
3133
|
+
}
|
|
3134
|
+
async function ensureDir2(dir) {
|
|
3135
|
+
try {
|
|
3136
|
+
await import_fs4.promises.mkdir(dir, { recursive: true });
|
|
3137
|
+
} catch (error) {
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
async function writeFile2(path, content) {
|
|
3141
|
+
await ensureDir2((0, import_path4.dirname)(path));
|
|
3142
|
+
await import_fs4.promises.writeFile(path, content, "utf8");
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
// cli/commands/init.ts
|
|
3146
|
+
async function initCommand(template = "basic", options) {
|
|
3147
|
+
console.log(`\u{1F680} Initializing Pattern-Stack project with ${template} template`);
|
|
3148
|
+
if (options.name) {
|
|
3149
|
+
console.log(`\u{1F4E6} Project name: ${options.name}`);
|
|
3150
|
+
}
|
|
3151
|
+
if (options.dir) {
|
|
3152
|
+
console.log(`\u{1F4C1} Target directory: ${options.dir}`);
|
|
3153
|
+
}
|
|
3154
|
+
console.log("\n\u26A0\uFE0F Template initialization not yet implemented");
|
|
3155
|
+
console.log("This feature will be available in a future release.");
|
|
3156
|
+
console.log("\nFor now, you can:");
|
|
3157
|
+
console.log("1. Clone the Pattern-Stack repository");
|
|
3158
|
+
console.log("2. Use the generate hooks command to create API integrations");
|
|
3159
|
+
console.log("3. Use the scaffold command to create components");
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
// cli/index.ts
|
|
3163
|
+
var program = new import_commander.Command();
|
|
3164
|
+
program.name("pattern-stack").description("Pattern-Stack CLI for code generation and scaffolding").version("1.0.0");
|
|
3165
|
+
var generateCommand = program.command("generate").alias("g").description("Generate code from specifications");
|
|
3166
|
+
generateCommand.command("hooks").description("Generate React hooks from OpenAPI specification").argument("<source>", "OpenAPI specification (URL or file path)").option("-o, --output <dir>", "Output directory", "./src/generated").option("--prefix <prefix>", "Type name prefix", "").option("--client <type>", "API client type", "axios").option("--auth <type>", "Authentication type", "bearer").option("--dry-run", "Show what would be generated without writing files").action(generateHooksCommand);
|
|
3167
|
+
program.command("scaffold").alias("s").description("Scaffold components and structures").argument("<type>", "Type to scaffold (component, feature, template)").argument("<name>", "Name of the item to scaffold").option("-t, --template <template>", "Template to use").option("-o, --output <dir>", "Output directory").action(scaffoldCommand);
|
|
3168
|
+
program.command("init").description("Initialize a new Pattern-Stack project").argument("[template]", "Template to use (financial, ecommerce, analytics)").option("-n, --name <name>", "Project name").option("-d, --dir <directory>", "Target directory").option("--git", "Initialize git repository").option("--install", "Install dependencies").action(initCommand);
|
|
3169
|
+
program.command("config").description("Manage Pattern-Stack configuration").option("--show", "Show current configuration").option("--set <key=value>", "Set configuration value").action((options) => {
|
|
3170
|
+
if (options.show) {
|
|
3171
|
+
console.log("Current configuration:");
|
|
3172
|
+
}
|
|
3173
|
+
if (options.set) {
|
|
3174
|
+
const [key, value] = options.set.split("=");
|
|
3175
|
+
console.log(`Setting ${key} = ${value}`);
|
|
3176
|
+
}
|
|
3177
|
+
});
|
|
3178
|
+
program.command("help").description("Display help for commands").argument("[command]", "Command to get help for").action((command) => {
|
|
3179
|
+
if (command) {
|
|
3180
|
+
program.help();
|
|
3181
|
+
} else {
|
|
3182
|
+
console.log(`
|
|
3183
|
+
Pattern-Stack CLI
|
|
3184
|
+
|
|
3185
|
+
Available commands:
|
|
3186
|
+
generate hooks <source> Generate React hooks from OpenAPI spec
|
|
3187
|
+
scaffold <type> <name> Scaffold components and structures
|
|
3188
|
+
init [template] Initialize new project from template
|
|
3189
|
+
config Manage configuration
|
|
3190
|
+
|
|
3191
|
+
Examples:
|
|
3192
|
+
pattern-stack generate hooks ./api/openapi.json
|
|
3193
|
+
pattern-stack scaffold component UserCard
|
|
3194
|
+
pattern-stack init financial --name my-finance-app
|
|
3195
|
+
|
|
3196
|
+
For more information on a command:
|
|
3197
|
+
pattern-stack <command> --help
|
|
3198
|
+
`);
|
|
3199
|
+
}
|
|
3200
|
+
});
|
|
3201
|
+
program.on("command:*", () => {
|
|
3202
|
+
console.error("Invalid command: %s\nSee --help for a list of available commands.", program.args.join(" "));
|
|
3203
|
+
process.exit(1);
|
|
3204
|
+
});
|
|
3205
|
+
if (process.argv.length < 3) {
|
|
3206
|
+
program.help();
|
|
3207
|
+
} else {
|
|
3208
|
+
program.parse();
|
|
3209
|
+
}
|
|
3210
|
+
var index_default = program;
|