rothzerg 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/dist/cli.js +949 -0
- package/package.json +27 -0
- package/readme.md +0 -0
- package/templates/_base-website/App.tsx +59 -0
- package/templates/_base-website/InitialDataContext.tsx +21 -0
- package/templates/_base-website/_gitignore +5 -0
- package/templates/_base-website/client.tsx +26 -0
- package/templates/_base-website/components/DeepLinkLayout.css +41 -0
- package/templates/_base-website/components/DeepLinkLayout.tsx +39 -0
- package/templates/_base-website/components/DownloadSection.css +60 -0
- package/templates/_base-website/components/DownloadSection.tsx +61 -0
- package/templates/_base-website/components/SiteFeaturesSection.tsx +68 -0
- package/templates/_base-website/components/SiteFooter.tsx +29 -0
- package/templates/_base-website/components/SiteHeroSection.tsx +30 -0
- package/templates/_base-website/components/SiteHighlightsSection.tsx +38 -0
- package/templates/_base-website/components/SiteLanguageSwitcher.tsx +9 -0
- package/templates/_base-website/components/SiteNavigation.tsx +24 -0
- package/templates/_base-website/config.ts +78 -0
- package/templates/_base-website/data/blog/authors.json +18 -0
- package/templates/_base-website/data/blog/categories.json +53 -0
- package/templates/_base-website/data/blog/posts.json +29 -0
- package/templates/_base-website/data/blog/tags.json +86 -0
- package/templates/_base-website/i18n/config.ts +60 -0
- package/templates/_base-website/i18n/sections/blog/_index.ts +41 -0
- package/templates/_base-website/i18n/sections/blog/ar.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/de.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/en.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/es.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/fr.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/hi.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/id.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/it.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/ja.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/ko.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/nl.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/pl.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/pt.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/ru.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/sv.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/th.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/tr.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/vi.ts +24 -0
- package/templates/_base-website/i18n/sections/blog/zh.ts +24 -0
- package/templates/_base-website/i18n/sections/common/_index.ts +41 -0
- package/templates/_base-website/i18n/sections/common/ar.ts +29 -0
- package/templates/_base-website/i18n/sections/common/de.ts +29 -0
- package/templates/_base-website/i18n/sections/common/en.ts +29 -0
- package/templates/_base-website/i18n/sections/common/es.ts +29 -0
- package/templates/_base-website/i18n/sections/common/fr.ts +29 -0
- package/templates/_base-website/i18n/sections/common/hi.ts +29 -0
- package/templates/_base-website/i18n/sections/common/id.ts +29 -0
- package/templates/_base-website/i18n/sections/common/it.ts +29 -0
- package/templates/_base-website/i18n/sections/common/ja.ts +29 -0
- package/templates/_base-website/i18n/sections/common/ko.ts +29 -0
- package/templates/_base-website/i18n/sections/common/nl.ts +29 -0
- package/templates/_base-website/i18n/sections/common/pl.ts +29 -0
- package/templates/_base-website/i18n/sections/common/pt.ts +29 -0
- package/templates/_base-website/i18n/sections/common/ru.ts +29 -0
- package/templates/_base-website/i18n/sections/common/sv.ts +29 -0
- package/templates/_base-website/i18n/sections/common/th.ts +29 -0
- package/templates/_base-website/i18n/sections/common/tr.ts +29 -0
- package/templates/_base-website/i18n/sections/common/vi.ts +29 -0
- package/templates/_base-website/i18n/sections/common/zh.ts +29 -0
- package/templates/_base-website/i18n/sections/deepLink/_index.ts +41 -0
- package/templates/_base-website/i18n/sections/deepLink/ar.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/de.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/en.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/es.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/fr.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/hi.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/id.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/it.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/ja.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/ko.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/nl.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/pl.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/pt.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/ru.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/sv.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/th.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/tr.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/vi.ts +14 -0
- package/templates/_base-website/i18n/sections/deepLink/zh.ts +14 -0
- package/templates/_base-website/i18n/sections/home/_index.ts +41 -0
- package/templates/_base-website/i18n/sections/home/ar.ts +85 -0
- package/templates/_base-website/i18n/sections/home/de.ts +85 -0
- package/templates/_base-website/i18n/sections/home/en.ts +85 -0
- package/templates/_base-website/i18n/sections/home/es.ts +85 -0
- package/templates/_base-website/i18n/sections/home/fr.ts +85 -0
- package/templates/_base-website/i18n/sections/home/hi.ts +85 -0
- package/templates/_base-website/i18n/sections/home/id.ts +85 -0
- package/templates/_base-website/i18n/sections/home/it.ts +85 -0
- package/templates/_base-website/i18n/sections/home/ja.ts +85 -0
- package/templates/_base-website/i18n/sections/home/ko.ts +85 -0
- package/templates/_base-website/i18n/sections/home/nl.ts +85 -0
- package/templates/_base-website/i18n/sections/home/pl.ts +85 -0
- package/templates/_base-website/i18n/sections/home/pt.ts +85 -0
- package/templates/_base-website/i18n/sections/home/ru.ts +85 -0
- package/templates/_base-website/i18n/sections/home/sv.ts +85 -0
- package/templates/_base-website/i18n/sections/home/th.ts +85 -0
- package/templates/_base-website/i18n/sections/home/tr.ts +85 -0
- package/templates/_base-website/i18n/sections/home/vi.ts +85 -0
- package/templates/_base-website/i18n/sections/home/zh.ts +85 -0
- package/templates/_base-website/i18n/sections/support/_index.ts +41 -0
- package/templates/_base-website/i18n/sections/support/ar.ts +37 -0
- package/templates/_base-website/i18n/sections/support/de.ts +37 -0
- package/templates/_base-website/i18n/sections/support/en.ts +37 -0
- package/templates/_base-website/i18n/sections/support/es.ts +37 -0
- package/templates/_base-website/i18n/sections/support/fr.ts +37 -0
- package/templates/_base-website/i18n/sections/support/hi.ts +37 -0
- package/templates/_base-website/i18n/sections/support/id.ts +37 -0
- package/templates/_base-website/i18n/sections/support/it.ts +37 -0
- package/templates/_base-website/i18n/sections/support/ja.ts +37 -0
- package/templates/_base-website/i18n/sections/support/ko.ts +37 -0
- package/templates/_base-website/i18n/sections/support/nl.ts +37 -0
- package/templates/_base-website/i18n/sections/support/pl.ts +37 -0
- package/templates/_base-website/i18n/sections/support/pt.ts +37 -0
- package/templates/_base-website/i18n/sections/support/ru.ts +37 -0
- package/templates/_base-website/i18n/sections/support/sv.ts +37 -0
- package/templates/_base-website/i18n/sections/support/th.ts +37 -0
- package/templates/_base-website/i18n/sections/support/tr.ts +37 -0
- package/templates/_base-website/i18n/sections/support/vi.ts +37 -0
- package/templates/_base-website/i18n/sections/support/zh.ts +37 -0
- package/templates/_base-website/i18n/translations.ts +25 -0
- package/templates/_base-website/index.ts +460 -0
- package/templates/_base-website/pages/404.tsx +35 -0
- package/templates/_base-website/pages/blog/author.tsx +97 -0
- package/templates/_base-website/pages/blog/category.tsx +89 -0
- package/templates/_base-website/pages/blog/index.tsx +81 -0
- package/templates/_base-website/pages/blog/post.tsx +110 -0
- package/templates/_base-website/pages/blog/tag.tsx +86 -0
- package/templates/_base-website/pages/custom-pages/example.tsx +54 -0
- package/templates/_base-website/pages/index.tsx +29 -0
- package/templates/_base-website/pages/privacy.tsx +45 -0
- package/templates/_base-website/pages/support.tsx +154 -0
- package/templates/_base-website/pages/terms.tsx +68 -0
- package/templates/_base-website/public/images/16.png +0 -0
- package/templates/_base-website/public/images/apple-touch-icon.png +0 -0
- package/templates/_base-website/public/images/favicon-32x32.png +0 -0
- package/templates/_base-website/public/images/favicon.png +0 -0
- package/templates/_base-website/public/images/logo_dark.svg +6 -0
- package/templates/_base-website/public/images/logo_light.svg +6 -0
- package/templates/_base-website/public/images/og-image.png +0 -0
- package/templates/_base-website/public/images/screenshots/ar_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/de_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/en_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/es_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/fr_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/hi_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/id_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/it_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/ja_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/ko_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/nl_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/pl_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/pt_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/ru_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/sv_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/th_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/tr_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/vi_dashboard.jpg +0 -0
- package/templates/_base-website/public/images/screenshots/zh_dashboard.jpg +0 -0
- package/templates/_base-website/rothzerg.template.json +63 -0
- package/templates/_base-website/styles/404.css +32 -0
- package/templates/_base-website/styles/_app.css +131 -0
- package/templates/_base-website/styles/_shared.css +194 -0
- package/templates/_base-website/styles/index.css +1 -0
- package/templates/_base-website/styles/privacy.css +1 -0
- package/templates/_base-website/styles/support.css +148 -0
- package/templates/_base-website/styles/terms.css +22 -0
- package/templates/mobile-app/_gitignore +46 -0
- package/templates/mobile-app/analysis_options.yaml +28 -0
- package/templates/mobile-app/android/app/build.gradle.kts +56 -0
- package/templates/mobile-app/android/app/google-services.json +29 -0
- package/templates/mobile-app/android/app/src/debug/AndroidManifest.xml +7 -0
- package/templates/mobile-app/android/app/src/main/AndroidManifest.xml +54 -0
- package/templates/mobile-app/android/app/src/main/kotlin/com/example/{{projectNameSnake}}/MainActivity.kt +5 -0
- package/templates/mobile-app/android/app/src/main/res/drawable/launch_background.xml +12 -0
- package/templates/mobile-app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
- package/templates/mobile-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/templates/mobile-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/templates/mobile-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/templates/mobile-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/templates/mobile-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/templates/mobile-app/android/app/src/main/res/values/styles.xml +18 -0
- package/templates/mobile-app/android/app/src/main/res/values-night/styles.xml +18 -0
- package/templates/mobile-app/android/app/src/profile/AndroidManifest.xml +7 -0
- package/templates/mobile-app/android/build.gradle.kts +24 -0
- package/templates/mobile-app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
- package/templates/mobile-app/android/gradle.properties +2 -0
- package/templates/mobile-app/android/settings.gradle.kts +31 -0
- package/templates/mobile-app/assets/icons/logo.png +0 -0
- package/templates/mobile-app/assets/icons/logo_dark.svg +5 -0
- package/templates/mobile-app/assets/icons/logo_light.svg +5 -0
- package/templates/mobile-app/assets/lottie/tick.json +1 -0
- package/templates/mobile-app/devtools_options.yaml +3 -0
- package/templates/mobile-app/ios/Flutter/AppFrameworkInfo.plist +26 -0
- package/templates/mobile-app/ios/Flutter/Debug.xcconfig +2 -0
- package/templates/mobile-app/ios/Flutter/Release.xcconfig +2 -0
- package/templates/mobile-app/ios/Podfile +46 -0
- package/templates/mobile-app/ios/Podfile.lock +1807 -0
- package/templates/mobile-app/ios/Runner/AppDelegate.swift +16 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
- package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
- package/templates/mobile-app/ios/Runner/Base.lproj/LaunchScreen.storyboard +37 -0
- package/templates/mobile-app/ios/Runner/Base.lproj/Main.storyboard +26 -0
- package/templates/mobile-app/ios/Runner/GoogleService-Info.plist +30 -0
- package/templates/mobile-app/ios/Runner/Info.plist +70 -0
- package/templates/mobile-app/ios/Runner/Runner-Bridging-Header.h +1 -0
- package/templates/mobile-app/ios/Runner.xcodeproj/project.pbxproj +772 -0
- package/templates/mobile-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/templates/mobile-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/templates/mobile-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- package/templates/mobile-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
- package/templates/mobile-app/ios/Runner.xcworkspace/contents.xcworkspacedata +10 -0
- package/templates/mobile-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/templates/mobile-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
- package/templates/mobile-app/ios/RunnerTests/RunnerTests.swift +12 -0
- package/templates/mobile-app/l10n.yaml +5 -0
- package/templates/mobile-app/lib/core/config/app_config.dart +76 -0
- package/templates/mobile-app/lib/core/constants/app_colors.dart +13 -0
- package/templates/mobile-app/lib/core/constants/app_constants.dart +123 -0
- package/templates/mobile-app/lib/core/constants/countries.dart +199 -0
- package/templates/mobile-app/lib/core/constants/keys.dart +46 -0
- package/templates/mobile-app/lib/core/constants/languages.dart +51 -0
- package/templates/mobile-app/lib/core/services/admin_service.dart +271 -0
- package/templates/mobile-app/lib/core/services/analytics_service.dart +296 -0
- package/templates/mobile-app/lib/core/services/auth_service.dart +185 -0
- package/templates/mobile-app/lib/core/services/device_service.dart +228 -0
- package/templates/mobile-app/lib/core/services/firebase_service.dart +490 -0
- package/templates/mobile-app/lib/core/services/in_app_messaging_service.dart +166 -0
- package/templates/mobile-app/lib/core/services/logging_service.dart +451 -0
- package/templates/mobile-app/lib/core/services/messaging_service.dart +460 -0
- package/templates/mobile-app/lib/core/services/remote_config_service.dart +167 -0
- package/templates/mobile-app/lib/core/services/revenue_cat_service.dart +364 -0
- package/templates/mobile-app/lib/core/services/storage_service.dart +313 -0
- package/templates/mobile-app/lib/core/theme/dark_theme.dart +295 -0
- package/templates/mobile-app/lib/core/theme/light_theme.dart +296 -0
- package/templates/mobile-app/lib/core/utils/formatters.dart +236 -0
- package/templates/mobile-app/lib/core/utils/image_picker_helper.dart +78 -0
- package/templates/mobile-app/lib/core/utils/validators.dart +292 -0
- package/templates/mobile-app/lib/firebase_options.dart +19 -0
- package/templates/mobile-app/lib/l10n/app_en.arb +1371 -0
- package/templates/mobile-app/lib/l10n/app_localizations.dart +2042 -0
- package/templates/mobile-app/lib/l10n/app_localizations_en.dart +1033 -0
- package/templates/mobile-app/lib/main.dart +152 -0
- package/templates/mobile-app/lib/models/device_model.dart +90 -0
- package/templates/mobile-app/lib/models/news_banner.dart +53 -0
- package/templates/mobile-app/lib/models/user_model.dart +120 -0
- package/templates/mobile-app/lib/providers/admin_provider.dart +67 -0
- package/templates/mobile-app/lib/providers/auth_provider.dart +328 -0
- package/templates/mobile-app/lib/providers/remote_config_provider.dart +81 -0
- package/templates/mobile-app/lib/providers/subscription_provider.dart +67 -0
- package/templates/mobile-app/lib/providers/theme_provider.dart +51 -0
- package/templates/mobile-app/lib/screens/admin/admin_panel_screen.dart +436 -0
- package/templates/mobile-app/lib/screens/admin/admin_panel_user_details_screen.dart +942 -0
- package/templates/mobile-app/lib/screens/admin/admin_panel_users_screen.dart +344 -0
- package/templates/mobile-app/lib/screens/auth/auth_home_screen.dart +266 -0
- package/templates/mobile-app/lib/screens/auth/forgot_password_screen.dart +214 -0
- package/templates/mobile-app/lib/screens/auth/login_screen.dart +312 -0
- package/templates/mobile-app/lib/screens/auth/profile_setup_screen.dart +157 -0
- package/templates/mobile-app/lib/screens/auth/register_screen.dart +288 -0
- package/templates/mobile-app/lib/screens/developer/developer_panel_screen.dart +436 -0
- package/templates/mobile-app/lib/screens/developer/remote_config_screen.dart +315 -0
- package/templates/mobile-app/lib/screens/developer/remote_config_test_screen.dart +461 -0
- package/templates/mobile-app/lib/screens/home/home_screen.dart +270 -0
- package/templates/mobile-app/lib/screens/legal/privacy_policy_screen.dart +63 -0
- package/templates/mobile-app/lib/screens/legal/terms_screen.dart +82 -0
- package/templates/mobile-app/lib/screens/main_navigation_screen.dart +85 -0
- package/templates/mobile-app/lib/screens/premium/subscription_screen.dart +516 -0
- package/templates/mobile-app/lib/screens/profile/edit_profile_screen.dart +246 -0
- package/templates/mobile-app/lib/screens/settings/about_app_screen.dart +200 -0
- package/templates/mobile-app/lib/screens/settings/cache_manager_screen.dart +400 -0
- package/templates/mobile-app/lib/screens/settings/change_password_screen.dart +269 -0
- package/templates/mobile-app/lib/screens/settings/delete_account_screen.dart +261 -0
- package/templates/mobile-app/lib/screens/settings/settings_screen.dart +400 -0
- package/templates/mobile-app/lib/screens/splash/splash_screen.dart +125 -0
- package/templates/mobile-app/lib/widgets/app_button.dart +255 -0
- package/templates/mobile-app/lib/widgets/app_drawer.dart +207 -0
- package/templates/mobile-app/lib/widgets/bottom_sheet_selector.dart +79 -0
- package/templates/mobile-app/lib/widgets/button_banner.dart +177 -0
- package/templates/mobile-app/lib/widgets/country_picker.dart +169 -0
- package/templates/mobile-app/lib/widgets/custom_app_bar.dart +30 -0
- package/templates/mobile-app/lib/widgets/email_verification_banner.dart +163 -0
- package/templates/mobile-app/lib/widgets/empty_screen.dart +42 -0
- package/templates/mobile-app/lib/widgets/language_picker.dart +145 -0
- package/templates/mobile-app/lib/widgets/news_banner_widget.dart +264 -0
- package/templates/mobile-app/lib/widgets/remote_config/remote_config_widgets.dart +294 -0
- package/templates/mobile-app/lib/widgets/section_header.dart +23 -0
- package/templates/mobile-app/lib/widgets/success_overlay.dart +117 -0
- package/templates/mobile-app/lib/widgets/top_notification.dart +263 -0
- package/templates/mobile-app/lib/widgets/user_avatar_picker.dart +58 -0
- package/templates/mobile-app/pubspec.yaml +97 -0
- package/templates/mobile-app/rothzerg.template.json +43 -0
- package/templates/web-app/_gitignore +4 -0
- package/templates/web-app/index.html +12 -0
- package/templates/web-app/package.json +23 -0
- package/templates/web-app/rothzerg.template.json +41 -0
- package/templates/web-app/src/App.css +6 -0
- package/templates/web-app/src/App.tsx +12 -0
- package/templates/web-app/src/index.css +14 -0
- package/templates/web-app/src/main.tsx +10 -0
- package/templates/web-app/tsconfig.app.json +20 -0
- package/templates/web-app/tsconfig.json +7 -0
- package/templates/web-app/tsconfig.node.json +18 -0
- package/templates/web-app/vite.config.ts +6 -0
- package/templates/website-blog/overrides/config.ts +76 -0
- package/templates/website-blog/overrides/pages/index.tsx +63 -0
- package/templates/website-blog/rothzerg.template.json +29 -0
- package/templates/website-business/overrides/App.tsx +49 -0
- package/templates/website-business/overrides/components/SiteHeroSection.tsx +33 -0
- package/templates/website-business/overrides/config.ts +76 -0
- package/templates/website-business/overrides/pages/about.tsx +50 -0
- package/templates/website-business/overrides/pages/contact.tsx +61 -0
- package/templates/website-business/overrides/pages/index.tsx +37 -0
- package/templates/website-business/overrides/pages/services.tsx +70 -0
- package/templates/website-business/rothzerg.template.json +18 -0
- package/templates/website-mobile/overrides/App.tsx +62 -0
- package/templates/website-mobile/overrides/components/SiteHeroSection.tsx +18 -0
- package/templates/website-mobile/overrides/config.ts +76 -0
- package/templates/website-mobile/overrides/pages/index.tsx +34 -0
- package/templates/website-mobile/rothzerg.template.json +64 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import path8 from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import fs7 from "fs";
|
|
7
|
+
import * as p4 from "@clack/prompts";
|
|
8
|
+
import pc3 from "picocolors";
|
|
9
|
+
|
|
10
|
+
// src/commands/create.ts
|
|
11
|
+
import path6 from "path";
|
|
12
|
+
import * as p2 from "@clack/prompts";
|
|
13
|
+
import pc from "picocolors";
|
|
14
|
+
|
|
15
|
+
// src/engine/discovery.ts
|
|
16
|
+
import fs2 from "fs";
|
|
17
|
+
import path2 from "path";
|
|
18
|
+
|
|
19
|
+
// src/utils.ts
|
|
20
|
+
import fs from "fs";
|
|
21
|
+
import path from "path";
|
|
22
|
+
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
23
|
+
".ts",
|
|
24
|
+
".tsx",
|
|
25
|
+
".js",
|
|
26
|
+
".jsx",
|
|
27
|
+
".json",
|
|
28
|
+
".html",
|
|
29
|
+
".css",
|
|
30
|
+
".scss",
|
|
31
|
+
".md",
|
|
32
|
+
".txt",
|
|
33
|
+
".yaml",
|
|
34
|
+
".yml",
|
|
35
|
+
".toml",
|
|
36
|
+
".xml",
|
|
37
|
+
".svg",
|
|
38
|
+
".dart",
|
|
39
|
+
".gradle",
|
|
40
|
+
".kt",
|
|
41
|
+
".swift",
|
|
42
|
+
".plist",
|
|
43
|
+
".sh",
|
|
44
|
+
".env",
|
|
45
|
+
".gitignore",
|
|
46
|
+
".npmrc",
|
|
47
|
+
".prettierrc",
|
|
48
|
+
".eslintrc",
|
|
49
|
+
".editorconfig"
|
|
50
|
+
]);
|
|
51
|
+
function isTextFile(filePath) {
|
|
52
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
53
|
+
const base = path.basename(filePath).toLowerCase();
|
|
54
|
+
const textBasenames = /* @__PURE__ */ new Set([
|
|
55
|
+
"makefile",
|
|
56
|
+
"dockerfile",
|
|
57
|
+
"procfile",
|
|
58
|
+
"rakefile",
|
|
59
|
+
".env",
|
|
60
|
+
".gitignore",
|
|
61
|
+
".npmrc",
|
|
62
|
+
".prettierrc"
|
|
63
|
+
]);
|
|
64
|
+
return TEXT_EXTENSIONS.has(ext) || textBasenames.has(base);
|
|
65
|
+
}
|
|
66
|
+
function applyFileRename(relativePath) {
|
|
67
|
+
const parts = relativePath.split(path.sep);
|
|
68
|
+
const renamed = parts.map((part) => {
|
|
69
|
+
if (part.startsWith("_") && !part.startsWith("__")) {
|
|
70
|
+
return "." + part.slice(1);
|
|
71
|
+
}
|
|
72
|
+
return part;
|
|
73
|
+
});
|
|
74
|
+
return renamed.join(path.sep);
|
|
75
|
+
}
|
|
76
|
+
function listFilesRecursive(dir) {
|
|
77
|
+
const results = [];
|
|
78
|
+
function walk(current, relative) {
|
|
79
|
+
const entries = fs.readdirSync(current, { withFileTypes: true });
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const entryRelative = relative ? path.join(relative, entry.name) : entry.name;
|
|
82
|
+
const entryAbsolute = path.join(current, entry.name);
|
|
83
|
+
if (entry.isDirectory()) {
|
|
84
|
+
walk(entryAbsolute, entryRelative);
|
|
85
|
+
} else {
|
|
86
|
+
results.push(entryRelative);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
walk(dir, "");
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
function readJson(filePath) {
|
|
94
|
+
try {
|
|
95
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
96
|
+
return JSON.parse(content);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
throw new Error(`Failed to read JSON at ${filePath}: ${err.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function writeJson(filePath, data) {
|
|
102
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
103
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
104
|
+
}
|
|
105
|
+
function exists(p5) {
|
|
106
|
+
return fs.existsSync(p5);
|
|
107
|
+
}
|
|
108
|
+
function ensureDir(dir) {
|
|
109
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
110
|
+
}
|
|
111
|
+
function matchesGlob(pattern, filePath) {
|
|
112
|
+
const normalizedFile = filePath.replace(/\\/g, "/");
|
|
113
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
114
|
+
const regexStr = normalizedPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, "__STAR__").replace(/\*\*/g, "__GLOBSTAR__").replace(/\*/g, "[^/]*").replace(/__GLOBSTAR__/g, ".*").replace(/__STAR__/g, "\\*");
|
|
115
|
+
const regex = new RegExp(`^${regexStr}$`);
|
|
116
|
+
return regex.test(normalizedFile);
|
|
117
|
+
}
|
|
118
|
+
function matchesAnyGlob(patterns, filePath) {
|
|
119
|
+
return patterns.some((p5) => matchesGlob(p5, filePath));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/engine/discovery.ts
|
|
123
|
+
var TEMPLATE_CONFIG_FILENAME = "rothzerg.template.json";
|
|
124
|
+
function discoverTemplates(templatesDir) {
|
|
125
|
+
if (!fs2.existsSync(templatesDir)) {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
const entries = fs2.readdirSync(templatesDir, { withFileTypes: true });
|
|
129
|
+
const templates = [];
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
if (!entry.isDirectory()) continue;
|
|
132
|
+
if (entry.name.startsWith("_")) continue;
|
|
133
|
+
const configPath = path2.join(templatesDir, entry.name, TEMPLATE_CONFIG_FILENAME);
|
|
134
|
+
if (!fs2.existsSync(configPath)) continue;
|
|
135
|
+
try {
|
|
136
|
+
const config = readJson(configPath);
|
|
137
|
+
templates.push(config);
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
templates.sort((a, b) => {
|
|
142
|
+
if (a.category !== b.category) return a.category.localeCompare(b.category);
|
|
143
|
+
const aOrder = a.order ?? 999;
|
|
144
|
+
const bOrder = b.order ?? 999;
|
|
145
|
+
if (aOrder !== bOrder) return aOrder - bOrder;
|
|
146
|
+
return a.displayName.localeCompare(b.displayName);
|
|
147
|
+
});
|
|
148
|
+
return templates;
|
|
149
|
+
}
|
|
150
|
+
function loadTemplateConfig(templatesDir, name) {
|
|
151
|
+
const configPath = path2.join(templatesDir, name, TEMPLATE_CONFIG_FILENAME);
|
|
152
|
+
if (!fs2.existsSync(configPath)) {
|
|
153
|
+
throw new Error(`Template "${name}" not found at ${configPath}`);
|
|
154
|
+
}
|
|
155
|
+
return readJson(configPath);
|
|
156
|
+
}
|
|
157
|
+
function getTemplateDir(templatesDir, name) {
|
|
158
|
+
return path2.join(templatesDir, name);
|
|
159
|
+
}
|
|
160
|
+
function groupByCategory(templates) {
|
|
161
|
+
const groups = /* @__PURE__ */ new Map();
|
|
162
|
+
for (const template of templates) {
|
|
163
|
+
const group = groups.get(template.category) ?? [];
|
|
164
|
+
group.push(template);
|
|
165
|
+
groups.set(template.category, group);
|
|
166
|
+
}
|
|
167
|
+
return groups;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/engine/inheritance.ts
|
|
171
|
+
import path3 from "path";
|
|
172
|
+
import fs3 from "fs";
|
|
173
|
+
function resolveTemplate(templatesDir, templateConfig) {
|
|
174
|
+
if (!templateConfig.extends) {
|
|
175
|
+
const dir = getTemplateDir(templatesDir, templateConfig.name);
|
|
176
|
+
const files2 = buildFileList(dir, dir);
|
|
177
|
+
return { config: templateConfig, files: files2, dir };
|
|
178
|
+
}
|
|
179
|
+
const baseConfig = loadTemplateConfig(templatesDir, templateConfig.extends);
|
|
180
|
+
const baseDir = getTemplateDir(templatesDir, templateConfig.extends);
|
|
181
|
+
const childDir = getTemplateDir(templatesDir, templateConfig.name);
|
|
182
|
+
const overridesDir = path3.join(childDir, "overrides");
|
|
183
|
+
const baseFiles = buildFileList(baseDir, baseDir);
|
|
184
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
185
|
+
for (const f of baseFiles) {
|
|
186
|
+
fileMap.set(f.relativePath, f);
|
|
187
|
+
}
|
|
188
|
+
if (fs3.existsSync(overridesDir)) {
|
|
189
|
+
const overrideFiles = buildFileList(overridesDir, overridesDir);
|
|
190
|
+
for (const f of overrideFiles) {
|
|
191
|
+
fileMap.set(f.relativePath, f);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const files = Array.from(fileMap.values());
|
|
195
|
+
const mergedConfig = mergeConfigs(baseConfig, templateConfig);
|
|
196
|
+
return { config: mergedConfig, files, dir: baseDir };
|
|
197
|
+
}
|
|
198
|
+
function buildFileList(dir, rootDir) {
|
|
199
|
+
const relativePaths = listFilesRecursive(dir);
|
|
200
|
+
return relativePaths.filter((rel) => {
|
|
201
|
+
const normalized = rel.replace(/\\/g, "/");
|
|
202
|
+
return normalized !== "rothzerg.template.json" && !normalized.startsWith("overrides/");
|
|
203
|
+
}).map((relativePath) => ({
|
|
204
|
+
src: path3.join(rootDir, relativePath),
|
|
205
|
+
relativePath
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
function mergeConfigs(base, child) {
|
|
209
|
+
return {
|
|
210
|
+
// Scalar fields: child wins
|
|
211
|
+
name: child.name,
|
|
212
|
+
displayName: child.displayName,
|
|
213
|
+
description: child.description,
|
|
214
|
+
icon: child.icon ?? base.icon,
|
|
215
|
+
category: child.category,
|
|
216
|
+
order: child.order ?? base.order,
|
|
217
|
+
extends: child.extends,
|
|
218
|
+
// Prompts: concatenated
|
|
219
|
+
prompts: [...base.prompts ?? [], ...child.prompts ?? []],
|
|
220
|
+
// Derived: merged, child overrides
|
|
221
|
+
derived: { ...base.derived ?? {}, ...child.derived ?? {} },
|
|
222
|
+
// Target: child wins, falls back to base
|
|
223
|
+
target: child.target ?? base.target,
|
|
224
|
+
// ConditionalFiles: merged, child overrides
|
|
225
|
+
conditionalFiles: {
|
|
226
|
+
...base.conditionalFiles ?? {},
|
|
227
|
+
...child.conditionalFiles ?? {}
|
|
228
|
+
},
|
|
229
|
+
// Hooks: concatenated
|
|
230
|
+
hooks: {
|
|
231
|
+
postCreate: [
|
|
232
|
+
...base.hooks?.postCreate ?? [],
|
|
233
|
+
...child.hooks?.postCreate ?? []
|
|
234
|
+
],
|
|
235
|
+
postUpgrade: [
|
|
236
|
+
...base.hooks?.postUpgrade ?? [],
|
|
237
|
+
...child.hooks?.postUpgrade ?? []
|
|
238
|
+
]
|
|
239
|
+
},
|
|
240
|
+
// Upgrade: child wins
|
|
241
|
+
upgrade: child.upgrade ?? base.upgrade,
|
|
242
|
+
// NextSteps: child wins
|
|
243
|
+
nextSteps: child.nextSteps ?? base.nextSteps
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/engine/prompts.ts
|
|
248
|
+
import * as p from "@clack/prompts";
|
|
249
|
+
|
|
250
|
+
// src/engine/placeholders.ts
|
|
251
|
+
var FILTERS = {
|
|
252
|
+
pascalCase: (v) => v.replace(/[-_.\s]+(.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (_, c) => c.toUpperCase()),
|
|
253
|
+
camelCase: (v) => v.replace(/[-_.\s]+(.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (_, c) => c.toLowerCase()),
|
|
254
|
+
snakeCase: (v) => v.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-.\s]+/g, "_").toLowerCase(),
|
|
255
|
+
kebabCase: (v) => v.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[_.\s]+/g, "-").toLowerCase(),
|
|
256
|
+
titleCase: (v) => {
|
|
257
|
+
const stripped = v.replace(/\.[a-z]{2,}$/i, "");
|
|
258
|
+
return stripped.charAt(0).toUpperCase() + stripped.slice(1).toLowerCase();
|
|
259
|
+
},
|
|
260
|
+
upperCase: (v) => v.toUpperCase(),
|
|
261
|
+
lowerCase: (v) => v.toLowerCase(),
|
|
262
|
+
stripTld: (v) => v.replace(/\.[a-z]{2,}$/i, "")
|
|
263
|
+
};
|
|
264
|
+
function applyFilters(value, filterNames) {
|
|
265
|
+
return filterNames.reduce((v, name) => {
|
|
266
|
+
const filter = FILTERS[name.trim()];
|
|
267
|
+
if (!filter) throw new Error(`Unknown placeholder filter: "${name.trim()}"`);
|
|
268
|
+
return filter(v);
|
|
269
|
+
}, value);
|
|
270
|
+
}
|
|
271
|
+
function resolveExpr(expr, vars, configVars) {
|
|
272
|
+
const parts = expr.split("|").map((p5) => p5.trim());
|
|
273
|
+
const [head, ...filters] = parts;
|
|
274
|
+
let value;
|
|
275
|
+
if (head.startsWith("config:")) {
|
|
276
|
+
const key = head.slice("config:".length);
|
|
277
|
+
value = configVars[key] ?? "";
|
|
278
|
+
} else if (head.startsWith("builtIn:")) {
|
|
279
|
+
const key = head.slice("builtIn:".length);
|
|
280
|
+
value = resolveBuiltIn(key);
|
|
281
|
+
} else {
|
|
282
|
+
const raw = vars[head];
|
|
283
|
+
if (raw === void 0) return `{{${expr}}}`;
|
|
284
|
+
value = String(raw);
|
|
285
|
+
}
|
|
286
|
+
return filters.length > 0 ? applyFilters(value, filters) : value;
|
|
287
|
+
}
|
|
288
|
+
function resolveBuiltIn(key) {
|
|
289
|
+
switch (key) {
|
|
290
|
+
case "year":
|
|
291
|
+
return String((/* @__PURE__ */ new Date()).getFullYear());
|
|
292
|
+
case "timestamp":
|
|
293
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
294
|
+
case "date":
|
|
295
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
296
|
+
default:
|
|
297
|
+
return "";
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
var PLACEHOLDER_RE = /\{\{([^}]+)\}\}/g;
|
|
301
|
+
function interpolate(template, vars, configVars = {}) {
|
|
302
|
+
return template.replace(PLACEHOLDER_RE, (match, expr) => {
|
|
303
|
+
try {
|
|
304
|
+
return resolveExpr(expr.trim(), vars, configVars);
|
|
305
|
+
} catch {
|
|
306
|
+
return match;
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function computeDerived(derived, vars, configVars = {}) {
|
|
311
|
+
const result = { ...vars };
|
|
312
|
+
for (const [key, template] of Object.entries(derived)) {
|
|
313
|
+
result[key] = interpolate(template, result, configVars);
|
|
314
|
+
}
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
function processFileContent(content, vars, configVars = {}) {
|
|
318
|
+
return interpolate(content, vars, configVars);
|
|
319
|
+
}
|
|
320
|
+
function evaluateCondition(condition, vars, configVars = {}) {
|
|
321
|
+
const trimmed = condition.trim();
|
|
322
|
+
if (trimmed.startsWith("!")) {
|
|
323
|
+
const inner = trimmed.slice(1).trim();
|
|
324
|
+
return !evaluateCondition(inner, vars, configVars);
|
|
325
|
+
}
|
|
326
|
+
const resolved = interpolate(trimmed, vars, configVars);
|
|
327
|
+
if (resolved === "true" || resolved === "1" || resolved === "yes") return true;
|
|
328
|
+
if (resolved === "false" || resolved === "0" || resolved === "no" || resolved === "") return false;
|
|
329
|
+
return resolved.length > 0;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/engine/prompts.ts
|
|
333
|
+
async function runPrompts(prompts, prefilled = {}, configVars = {}) {
|
|
334
|
+
const vars = { ...prefilled };
|
|
335
|
+
for (const prompt of prompts) {
|
|
336
|
+
if (vars[prompt.name] !== void 0) continue;
|
|
337
|
+
const answer = await runSinglePrompt(prompt, vars, configVars);
|
|
338
|
+
if (p.isCancel(answer)) {
|
|
339
|
+
p.cancel("Operation cancelled.");
|
|
340
|
+
process.exit(0);
|
|
341
|
+
}
|
|
342
|
+
vars[prompt.name] = answer;
|
|
343
|
+
}
|
|
344
|
+
return vars;
|
|
345
|
+
}
|
|
346
|
+
async function runSinglePrompt(prompt, currentVars, configVars) {
|
|
347
|
+
const message = prompt.message;
|
|
348
|
+
switch (prompt.type) {
|
|
349
|
+
case "text": {
|
|
350
|
+
const defaultValue = resolveDefault(prompt.default, currentVars, configVars);
|
|
351
|
+
return p.text({
|
|
352
|
+
message,
|
|
353
|
+
placeholder: prompt.placeholder,
|
|
354
|
+
defaultValue: typeof defaultValue === "string" ? defaultValue : void 0,
|
|
355
|
+
validate: prompt.validate ? (value) => {
|
|
356
|
+
const pattern = new RegExp(prompt.validate);
|
|
357
|
+
if (!pattern.test(value)) {
|
|
358
|
+
return prompt.validateMessage ?? `Must match pattern: ${prompt.validate}`;
|
|
359
|
+
}
|
|
360
|
+
return void 0;
|
|
361
|
+
} : void 0
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
case "confirm": {
|
|
365
|
+
const defaultValue = resolveDefault(prompt.default, currentVars, configVars);
|
|
366
|
+
return p.confirm({
|
|
367
|
+
message,
|
|
368
|
+
initialValue: typeof defaultValue === "boolean" ? defaultValue : prompt.default
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
case "select": {
|
|
372
|
+
const options = (prompt.options ?? []).map((opt) => ({
|
|
373
|
+
value: opt.value,
|
|
374
|
+
label: opt.label,
|
|
375
|
+
hint: opt.hint
|
|
376
|
+
}));
|
|
377
|
+
return p.select({ message, options });
|
|
378
|
+
}
|
|
379
|
+
case "multiselect": {
|
|
380
|
+
const options = (prompt.options ?? []).map((opt) => ({
|
|
381
|
+
value: opt.value,
|
|
382
|
+
label: opt.label,
|
|
383
|
+
hint: opt.hint
|
|
384
|
+
}));
|
|
385
|
+
return p.multiselect({
|
|
386
|
+
message,
|
|
387
|
+
options,
|
|
388
|
+
required: prompt.required ?? false
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
default:
|
|
392
|
+
throw new Error(`Unknown prompt type: "${prompt.type}"`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function resolveDefault(defaultValue, vars, configVars) {
|
|
396
|
+
if (defaultValue === void 0) return void 0;
|
|
397
|
+
if (typeof defaultValue === "boolean") return defaultValue;
|
|
398
|
+
if (Array.isArray(defaultValue)) return defaultValue;
|
|
399
|
+
return interpolate(defaultValue, vars, configVars);
|
|
400
|
+
}
|
|
401
|
+
function parseCliFlags(args) {
|
|
402
|
+
const vars = {};
|
|
403
|
+
for (let i = 0; i < args.length; i++) {
|
|
404
|
+
const arg = args[i];
|
|
405
|
+
if (!arg.startsWith("--")) continue;
|
|
406
|
+
const withoutDashes = arg.slice(2);
|
|
407
|
+
if (withoutDashes.includes("=")) {
|
|
408
|
+
const [key, ...valueParts] = withoutDashes.split("=");
|
|
409
|
+
vars[key] = coerceValue(valueParts.join("="));
|
|
410
|
+
} else {
|
|
411
|
+
const next = args[i + 1];
|
|
412
|
+
if (next && !next.startsWith("--")) {
|
|
413
|
+
vars[withoutDashes] = coerceValue(next);
|
|
414
|
+
i++;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return vars;
|
|
419
|
+
}
|
|
420
|
+
function coerceValue(value) {
|
|
421
|
+
if (value === "true") return true;
|
|
422
|
+
if (value === "false") return false;
|
|
423
|
+
return value;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/engine/conditions.ts
|
|
427
|
+
function filterConditionalFiles(files, conditionalFiles, vars, configVars = {}) {
|
|
428
|
+
if (!conditionalFiles || Object.keys(conditionalFiles).length === 0) {
|
|
429
|
+
return files;
|
|
430
|
+
}
|
|
431
|
+
return files.filter((file) => {
|
|
432
|
+
const normalizedPath = file.relativePath.replace(/\\/g, "/");
|
|
433
|
+
for (const [glob, condition] of Object.entries(conditionalFiles)) {
|
|
434
|
+
if (matchesGlob(glob, normalizedPath)) {
|
|
435
|
+
const include = evaluateCondition(condition, vars, configVars);
|
|
436
|
+
if (!include) return false;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return true;
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/engine/copy.ts
|
|
444
|
+
import fs4 from "fs";
|
|
445
|
+
import path4 from "path";
|
|
446
|
+
function copyFiles(files, targetDir, vars, configVars = {}) {
|
|
447
|
+
const copied = [];
|
|
448
|
+
const skipped = [];
|
|
449
|
+
ensureDir(targetDir);
|
|
450
|
+
for (const file of files) {
|
|
451
|
+
const renamedRelative = applyFileRename(file.relativePath);
|
|
452
|
+
const destPath = path4.join(targetDir, renamedRelative);
|
|
453
|
+
ensureDir(path4.dirname(destPath));
|
|
454
|
+
if (isTextFile(file.src)) {
|
|
455
|
+
try {
|
|
456
|
+
const content = fs4.readFileSync(file.src, "utf-8");
|
|
457
|
+
const processed = processFileContent(content, vars, configVars);
|
|
458
|
+
fs4.writeFileSync(destPath, processed, "utf-8");
|
|
459
|
+
copied.push(renamedRelative);
|
|
460
|
+
} catch {
|
|
461
|
+
fs4.copyFileSync(file.src, destPath);
|
|
462
|
+
copied.push(renamedRelative);
|
|
463
|
+
}
|
|
464
|
+
} else {
|
|
465
|
+
fs4.copyFileSync(file.src, destPath);
|
|
466
|
+
copied.push(renamedRelative);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return { copied, skipped };
|
|
470
|
+
}
|
|
471
|
+
function copyFileWithTransform(src, dest, vars, configVars = {}) {
|
|
472
|
+
ensureDir(path4.dirname(dest));
|
|
473
|
+
if (isTextFile(src)) {
|
|
474
|
+
const content = fs4.readFileSync(src, "utf-8");
|
|
475
|
+
const processed = processFileContent(content, vars, configVars);
|
|
476
|
+
fs4.writeFileSync(dest, processed, "utf-8");
|
|
477
|
+
} else {
|
|
478
|
+
fs4.copyFileSync(src, dest);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/engine/hooks.ts
|
|
483
|
+
import { spawnSync } from "child_process";
|
|
484
|
+
async function runHooks(hooks, context) {
|
|
485
|
+
const results = [];
|
|
486
|
+
for (const hook of hooks) {
|
|
487
|
+
if (hook.when) {
|
|
488
|
+
const shouldRun = evaluateCondition(hook.when, context.vars, context.configVars);
|
|
489
|
+
if (!shouldRun) continue;
|
|
490
|
+
}
|
|
491
|
+
const result = await runHook(hook, context);
|
|
492
|
+
results.push(result);
|
|
493
|
+
}
|
|
494
|
+
return results;
|
|
495
|
+
}
|
|
496
|
+
async function runHook(hook, context) {
|
|
497
|
+
const hookName = interpolate(hook.run, context.vars, context.configVars);
|
|
498
|
+
try {
|
|
499
|
+
if (hook.builtin) {
|
|
500
|
+
const builtin = context.builtins[hook.run];
|
|
501
|
+
if (!builtin) {
|
|
502
|
+
throw new Error(`Unknown built-in hook: "${hook.run}"`);
|
|
503
|
+
}
|
|
504
|
+
await builtin(context);
|
|
505
|
+
return { hook: hookName, success: true };
|
|
506
|
+
}
|
|
507
|
+
const cwd = hook.cwd ? interpolate(hook.cwd, context.vars, context.configVars) : context.projectDir;
|
|
508
|
+
const [cmd, ...args] = hookName.split(" ");
|
|
509
|
+
const result = spawnSync(cmd, args, {
|
|
510
|
+
cwd,
|
|
511
|
+
stdio: "inherit",
|
|
512
|
+
shell: true
|
|
513
|
+
});
|
|
514
|
+
if (result.status !== 0) {
|
|
515
|
+
throw new Error(`Command exited with code ${result.status ?? "unknown"}`);
|
|
516
|
+
}
|
|
517
|
+
return { hook: hookName, success: true };
|
|
518
|
+
} catch (err) {
|
|
519
|
+
return { hook: hookName, success: false, error: err.message };
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/hooks/register-website.ts
|
|
524
|
+
import fs5 from "fs";
|
|
525
|
+
import path5 from "path";
|
|
526
|
+
var registerWebsite = (context) => {
|
|
527
|
+
const { vars, configVars } = context;
|
|
528
|
+
const websitesRoot = configVars["websitesRoot"];
|
|
529
|
+
if (!websitesRoot) {
|
|
530
|
+
throw new Error(
|
|
531
|
+
'register-website hook requires "websitesRoot" in global config. Set it in ~/.rothzerg/config.json or via --config flag.'
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
const indexPath = path5.join(websitesRoot, "app", "index.ts");
|
|
535
|
+
if (!fs5.existsSync(indexPath)) {
|
|
536
|
+
throw new Error(`Could not find app/index.ts at: ${indexPath}`);
|
|
537
|
+
}
|
|
538
|
+
const domain = String(vars["domain"] ?? "");
|
|
539
|
+
const handlerName = String(vars["handlerName"] ?? `handleRequest`);
|
|
540
|
+
const localDomain = String(vars["localDomain"] ?? `${domain.replace(/\.[^.]+$/, "")}.localhost.com`);
|
|
541
|
+
let content = fs5.readFileSync(indexPath, "utf-8");
|
|
542
|
+
const importStatement = `import { handleRequest as ${handlerName} } from "./websites/${domain}";`;
|
|
543
|
+
if (!content.includes(importStatement)) {
|
|
544
|
+
const lastImportMatch = [...content.matchAll(/^import .+;?$/gm)];
|
|
545
|
+
if (lastImportMatch.length > 0) {
|
|
546
|
+
const lastImport = lastImportMatch[lastImportMatch.length - 1];
|
|
547
|
+
const insertAt = lastImport.index + lastImport[0].length;
|
|
548
|
+
content = content.slice(0, insertAt) + "\n" + importStatement + content.slice(insertAt);
|
|
549
|
+
} else {
|
|
550
|
+
content = importStatement + "\n" + content;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const websiteEntry = ` {
|
|
554
|
+
location: "${domain}",
|
|
555
|
+
domains: ["${localDomain}", "dev.${domain}", "www.${domain}", "${domain}"],
|
|
556
|
+
handleRequest: (hostname: string, pathname: string, req: Request) => ${handlerName}(hostname, pathname),
|
|
557
|
+
},`;
|
|
558
|
+
if (!content.includes(`location: "${domain}"`)) {
|
|
559
|
+
const arrayMatch = content.match(/const websites[^=]*=\s*\[/);
|
|
560
|
+
if (arrayMatch && arrayMatch.index !== void 0) {
|
|
561
|
+
const startIdx = arrayMatch.index + arrayMatch[0].length;
|
|
562
|
+
let depth = 1;
|
|
563
|
+
let i = startIdx;
|
|
564
|
+
while (i < content.length && depth > 0) {
|
|
565
|
+
if (content[i] === "[") depth++;
|
|
566
|
+
else if (content[i] === "]") depth--;
|
|
567
|
+
i++;
|
|
568
|
+
}
|
|
569
|
+
const closingIdx = i - 1;
|
|
570
|
+
content = content.slice(0, closingIdx) + "\n" + websiteEntry + "\n" + content.slice(closingIdx);
|
|
571
|
+
} else {
|
|
572
|
+
throw new Error(
|
|
573
|
+
"Could not find `const websites = [` array in app/index.ts. Please manually add the entry."
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
fs5.writeFileSync(indexPath, content, "utf-8");
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// src/commands/create.ts
|
|
581
|
+
async function createCommand(options) {
|
|
582
|
+
const { templatesDir, globalConfig, cliFlags, version } = options;
|
|
583
|
+
const configVars = globalConfig;
|
|
584
|
+
const allTemplates = discoverTemplates(templatesDir);
|
|
585
|
+
if (allTemplates.length === 0) {
|
|
586
|
+
p2.log.error("No templates found in " + templatesDir);
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
let templateConfig;
|
|
590
|
+
if (options.templateName) {
|
|
591
|
+
try {
|
|
592
|
+
templateConfig = loadTemplateConfig(templatesDir, options.templateName);
|
|
593
|
+
} catch {
|
|
594
|
+
p2.log.error(`Template "${options.templateName}" not found.`);
|
|
595
|
+
p2.log.info(`Available templates: ${allTemplates.map((t) => t.name).join(", ")}`);
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
templateConfig = await promptTemplateSelection(allTemplates);
|
|
600
|
+
}
|
|
601
|
+
const resolved = resolveTemplate(templatesDir, templateConfig);
|
|
602
|
+
const config = resolved.config;
|
|
603
|
+
const answers = await runPrompts(config.prompts, cliFlags, configVars);
|
|
604
|
+
const vars = computeDerived(config.derived ?? {}, answers, configVars);
|
|
605
|
+
const targetDir = resolveTargetDir(config, vars, configVars);
|
|
606
|
+
if (exists(targetDir)) {
|
|
607
|
+
const overwrite = await p2.confirm({
|
|
608
|
+
message: `Directory ${pc.yellow(targetDir)} already exists. Continue anyway?`,
|
|
609
|
+
initialValue: false
|
|
610
|
+
});
|
|
611
|
+
if (p2.isCancel(overwrite) || !overwrite) {
|
|
612
|
+
p2.cancel("Aborted.");
|
|
613
|
+
process.exit(0);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
const filteredFiles = filterConditionalFiles(
|
|
617
|
+
resolved.files,
|
|
618
|
+
config.conditionalFiles,
|
|
619
|
+
vars,
|
|
620
|
+
configVars
|
|
621
|
+
);
|
|
622
|
+
p2.log.step(`Creating ${pc.cyan(config.displayName)} \u2192 ${pc.dim(targetDir)}`);
|
|
623
|
+
const { copied } = copyFiles(filteredFiles, targetDir, vars, configVars);
|
|
624
|
+
p2.log.success(`Copied ${copied.length} files`);
|
|
625
|
+
const projectConfigPath = path6.join(targetDir, ".rothzerg.json");
|
|
626
|
+
writeJson(projectConfigPath, {
|
|
627
|
+
engine: "rothzerg",
|
|
628
|
+
template: config.name,
|
|
629
|
+
version,
|
|
630
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
631
|
+
variables: vars
|
|
632
|
+
});
|
|
633
|
+
if (config.hooks?.postCreate && config.hooks.postCreate.length > 0) {
|
|
634
|
+
const hookContext = {
|
|
635
|
+
vars,
|
|
636
|
+
configVars,
|
|
637
|
+
projectDir: targetDir,
|
|
638
|
+
builtins: {
|
|
639
|
+
"register-website": registerWebsite
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
const results = await runHooks(config.hooks.postCreate, hookContext);
|
|
643
|
+
for (const result of results) {
|
|
644
|
+
if (result.success) {
|
|
645
|
+
p2.log.success(result.hook);
|
|
646
|
+
} else {
|
|
647
|
+
p2.log.warn(`${result.hook} \u2014 ${result.error}`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
p2.outro(pc.green("Done! Your project is ready."));
|
|
652
|
+
if (config.nextSteps && config.nextSteps.length > 0) {
|
|
653
|
+
console.log("\n Next steps:");
|
|
654
|
+
for (const step of config.nextSteps) {
|
|
655
|
+
console.log(" " + pc.cyan(interpolate(step, vars, configVars)));
|
|
656
|
+
}
|
|
657
|
+
console.log();
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
async function promptTemplateSelection(templates) {
|
|
661
|
+
const groups = groupByCategory(templates);
|
|
662
|
+
const options = [];
|
|
663
|
+
for (const [category, items] of groups) {
|
|
664
|
+
const groupLabel = category.charAt(0).toUpperCase() + category.slice(1) + "s";
|
|
665
|
+
for (const tmpl of items) {
|
|
666
|
+
options.push({
|
|
667
|
+
value: tmpl.name,
|
|
668
|
+
label: `${tmpl.icon ?? "\u2022"} ${tmpl.displayName}`,
|
|
669
|
+
hint: tmpl.description,
|
|
670
|
+
group: groupLabel
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const selected = await p2.select({
|
|
675
|
+
message: "What would you like to create?",
|
|
676
|
+
options
|
|
677
|
+
});
|
|
678
|
+
if (p2.isCancel(selected)) {
|
|
679
|
+
p2.cancel("Operation cancelled.");
|
|
680
|
+
process.exit(0);
|
|
681
|
+
}
|
|
682
|
+
return templates.find((t) => t.name === selected);
|
|
683
|
+
}
|
|
684
|
+
function resolveTargetDir(config, vars, configVars) {
|
|
685
|
+
const { target } = config;
|
|
686
|
+
const dirName = interpolate(target.dirName, vars, configVars);
|
|
687
|
+
if (target.type === "subdirectory" && target.baseDir) {
|
|
688
|
+
const baseDir = interpolate(target.baseDir, vars, configVars);
|
|
689
|
+
return path6.resolve(baseDir, dirName);
|
|
690
|
+
}
|
|
691
|
+
return path6.resolve(process.cwd(), dirName);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/commands/upgrade.ts
|
|
695
|
+
import path7 from "path";
|
|
696
|
+
import fs6 from "fs";
|
|
697
|
+
import * as p3 from "@clack/prompts";
|
|
698
|
+
import pc2 from "picocolors";
|
|
699
|
+
async function upgradeCommand(options) {
|
|
700
|
+
const { projectDir, templatesDir, globalConfig, dryRun, version } = options;
|
|
701
|
+
const configVars = globalConfig;
|
|
702
|
+
const projectConfigPath = path7.join(projectDir, ".rothzerg.json");
|
|
703
|
+
if (!exists(projectConfigPath)) {
|
|
704
|
+
p3.log.error(
|
|
705
|
+
`No ${pc2.yellow(".rothzerg.json")} found in ${projectDir}.
|
|
706
|
+
Run this command from inside a project created with rothzerg.`
|
|
707
|
+
);
|
|
708
|
+
process.exit(1);
|
|
709
|
+
}
|
|
710
|
+
const projectConfig = readJson(projectConfigPath);
|
|
711
|
+
const storedVars = projectConfig.variables;
|
|
712
|
+
p3.intro(`Upgrading ${pc2.cyan(projectConfig.template)} (was v${projectConfig.version} \u2192 now v${version})`);
|
|
713
|
+
const templateConfig = loadTemplateConfig(templatesDir, projectConfig.template);
|
|
714
|
+
const resolved = resolveTemplate(templatesDir, templateConfig);
|
|
715
|
+
const config = resolved.config;
|
|
716
|
+
const vars = computeDerived(config.derived ?? {}, storedVars, configVars);
|
|
717
|
+
const filteredFiles = filterConditionalFiles(
|
|
718
|
+
resolved.files,
|
|
719
|
+
config.conditionalFiles,
|
|
720
|
+
vars,
|
|
721
|
+
configVars
|
|
722
|
+
);
|
|
723
|
+
const upgradeRules = config.upgrade;
|
|
724
|
+
const summary = {
|
|
725
|
+
merged: [],
|
|
726
|
+
overwritten: [],
|
|
727
|
+
added: [],
|
|
728
|
+
skipped: []
|
|
729
|
+
};
|
|
730
|
+
for (const file of filteredFiles) {
|
|
731
|
+
const rel = file.relativePath.replace(/\\/g, "/");
|
|
732
|
+
const destPath = path7.join(projectDir, rel);
|
|
733
|
+
const destExists = exists(destPath);
|
|
734
|
+
if (matchesAnyGlob(upgradeRules.ignore, rel)) {
|
|
735
|
+
summary.skipped.push(rel);
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
if (matchesAnyGlob(upgradeRules.merge, rel)) {
|
|
739
|
+
if (!dryRun) {
|
|
740
|
+
mergeFile(file.src, destPath, rel);
|
|
741
|
+
}
|
|
742
|
+
summary.merged.push(rel);
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (matchesAnyGlob(upgradeRules.overwrite, rel)) {
|
|
746
|
+
if (!dryRun) {
|
|
747
|
+
if (destExists) {
|
|
748
|
+
fs6.copyFileSync(destPath, destPath + ".rothzerg-backup");
|
|
749
|
+
}
|
|
750
|
+
copyFileWithTransform(file.src, destPath, vars, configVars);
|
|
751
|
+
}
|
|
752
|
+
summary.overwritten.push(rel);
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (!destExists) {
|
|
756
|
+
if (!dryRun) {
|
|
757
|
+
copyFileWithTransform(file.src, destPath, vars, configVars);
|
|
758
|
+
}
|
|
759
|
+
summary.added.push(rel);
|
|
760
|
+
} else {
|
|
761
|
+
summary.skipped.push(rel);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (dryRun) {
|
|
765
|
+
p3.log.info(pc2.yellow("Dry run \u2014 no changes were made."));
|
|
766
|
+
console.log();
|
|
767
|
+
}
|
|
768
|
+
if (summary.overwritten.length > 0) {
|
|
769
|
+
p3.log.success(`Overwritten (${summary.overwritten.length}):`);
|
|
770
|
+
for (const f of summary.overwritten) console.log(" " + pc2.cyan(f));
|
|
771
|
+
}
|
|
772
|
+
if (summary.merged.length > 0) {
|
|
773
|
+
p3.log.success(`Merged (${summary.merged.length}):`);
|
|
774
|
+
for (const f of summary.merged) console.log(" " + pc2.cyan(f));
|
|
775
|
+
}
|
|
776
|
+
if (summary.added.length > 0) {
|
|
777
|
+
p3.log.success(`Added (${summary.added.length}):`);
|
|
778
|
+
for (const f of summary.added) console.log(" " + pc2.green(f));
|
|
779
|
+
}
|
|
780
|
+
if (summary.skipped.length > 0) {
|
|
781
|
+
p3.log.info(`Skipped (${summary.skipped.length})`);
|
|
782
|
+
}
|
|
783
|
+
if (!dryRun) {
|
|
784
|
+
writeJson(projectConfigPath, {
|
|
785
|
+
...projectConfig,
|
|
786
|
+
version
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
if (!dryRun && config.hooks?.postUpgrade && config.hooks.postUpgrade.length > 0) {
|
|
790
|
+
const hookContext = {
|
|
791
|
+
vars,
|
|
792
|
+
configVars,
|
|
793
|
+
projectDir,
|
|
794
|
+
builtins: {
|
|
795
|
+
"register-website": registerWebsite
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
const results = await runHooks(config.hooks.postUpgrade, hookContext);
|
|
799
|
+
for (const result of results) {
|
|
800
|
+
if (result.success) {
|
|
801
|
+
p3.log.success(result.hook);
|
|
802
|
+
} else {
|
|
803
|
+
p3.log.warn(`${result.hook} \u2014 ${result.error}`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
p3.outro(pc2.green("Upgrade complete."));
|
|
808
|
+
}
|
|
809
|
+
function mergeFile(srcPath, destPath, rel) {
|
|
810
|
+
if (rel === "package.json" || rel.endsWith("/package.json")) {
|
|
811
|
+
mergePackageJson(srcPath, destPath);
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
if (rel === "pubspec.yaml" || rel.endsWith("/pubspec.yaml")) {
|
|
815
|
+
fs6.copyFileSync(srcPath, destPath);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
fs6.copyFileSync(srcPath, destPath);
|
|
819
|
+
}
|
|
820
|
+
function mergePackageJson(srcPath, destPath) {
|
|
821
|
+
if (!exists(destPath)) {
|
|
822
|
+
fs6.copyFileSync(srcPath, destPath);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const template = JSON.parse(fs6.readFileSync(srcPath, "utf-8"));
|
|
826
|
+
const existing = JSON.parse(fs6.readFileSync(destPath, "utf-8"));
|
|
827
|
+
const merged = {
|
|
828
|
+
...template,
|
|
829
|
+
// Preserve user fields
|
|
830
|
+
name: existing.name ?? template.name,
|
|
831
|
+
version: existing.version ?? template.version,
|
|
832
|
+
description: existing.description ?? template.description,
|
|
833
|
+
private: existing.private ?? template.private,
|
|
834
|
+
author: existing.author ?? template.author,
|
|
835
|
+
license: existing.license ?? template.license,
|
|
836
|
+
// Merge dependencies: template versions take precedence, user additions kept
|
|
837
|
+
dependencies: mergeDepMap(template.dependencies, existing.dependencies),
|
|
838
|
+
devDependencies: mergeDepMap(template.devDependencies, existing.devDependencies),
|
|
839
|
+
// Scripts: template wins, but user-added scripts are preserved
|
|
840
|
+
scripts: {
|
|
841
|
+
...existing.scripts ?? {},
|
|
842
|
+
...template.scripts ?? {}
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
fs6.writeFileSync(destPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
846
|
+
}
|
|
847
|
+
function mergeDepMap(templateDeps, existingDeps) {
|
|
848
|
+
const result = { ...existingDeps ?? {} };
|
|
849
|
+
for (const [pkg2, ver] of Object.entries(templateDeps ?? {})) {
|
|
850
|
+
result[pkg2] = ver;
|
|
851
|
+
}
|
|
852
|
+
return result;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// src/cli.ts
|
|
856
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
857
|
+
var __dirname = path8.dirname(__filename);
|
|
858
|
+
var TEMPLATES_DIR = path8.resolve(__dirname, "..", "templates");
|
|
859
|
+
var pkg = JSON.parse(
|
|
860
|
+
fs7.readFileSync(path8.resolve(__dirname, "..", "package.json"), "utf-8")
|
|
861
|
+
);
|
|
862
|
+
var VERSION = pkg.version;
|
|
863
|
+
function loadGlobalConfig() {
|
|
864
|
+
const configPaths = [
|
|
865
|
+
// Project-local config (takes priority)
|
|
866
|
+
path8.resolve(process.cwd(), ".rothzerg", "config.json"),
|
|
867
|
+
// User home config
|
|
868
|
+
path8.resolve(process.env["HOME"] ?? "~", ".rothzerg", "config.json")
|
|
869
|
+
];
|
|
870
|
+
for (const configPath of configPaths) {
|
|
871
|
+
if (fs7.existsSync(configPath)) {
|
|
872
|
+
try {
|
|
873
|
+
return JSON.parse(fs7.readFileSync(configPath, "utf-8"));
|
|
874
|
+
} catch {
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return {};
|
|
879
|
+
}
|
|
880
|
+
var HELP = `
|
|
881
|
+
${pc3.bold("rothzerg")} v${VERSION}
|
|
882
|
+
|
|
883
|
+
${pc3.dim("Plugin-style CLI engine for scaffolding projects from config-driven templates.")}
|
|
884
|
+
|
|
885
|
+
${pc3.bold("Usage:")}
|
|
886
|
+
${pc3.cyan("rothzerg create [template]")} Create a new project
|
|
887
|
+
${pc3.cyan("rothzerg upgrade")} Upgrade the current project
|
|
888
|
+
|
|
889
|
+
${pc3.bold("Options:")}
|
|
890
|
+
${pc3.cyan("--help, -h")} Show this help
|
|
891
|
+
${pc3.cyan("--version, -v")} Show version
|
|
892
|
+
${pc3.cyan("--dry-run")} (upgrade) Preview without applying changes
|
|
893
|
+
|
|
894
|
+
${pc3.bold("Examples:")}
|
|
895
|
+
${pc3.dim("bunx rothzerg create")}
|
|
896
|
+
${pc3.dim("bunx rothzerg create web-app --name my-app")}
|
|
897
|
+
${pc3.dim("bunx rothzerg create website-blog --domain myblog.com")}
|
|
898
|
+
${pc3.dim("bunx rothzerg upgrade")}
|
|
899
|
+
${pc3.dim("bunx rothzerg upgrade --dry-run")}
|
|
900
|
+
`;
|
|
901
|
+
async function main() {
|
|
902
|
+
const args = process.argv.slice(2);
|
|
903
|
+
if (args.includes("--help") || args.includes("-h") || args.length === 0) {
|
|
904
|
+
console.log(HELP);
|
|
905
|
+
process.exit(0);
|
|
906
|
+
}
|
|
907
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
908
|
+
console.log(VERSION);
|
|
909
|
+
process.exit(0);
|
|
910
|
+
}
|
|
911
|
+
const command = args[0];
|
|
912
|
+
const rest = args.slice(1);
|
|
913
|
+
p4.intro(`${pc3.bold("rothzerg")} ${pc3.dim(`v${VERSION}`)}`);
|
|
914
|
+
const globalConfig = loadGlobalConfig();
|
|
915
|
+
switch (command) {
|
|
916
|
+
case "create": {
|
|
917
|
+
const templateName = rest[0] && !rest[0].startsWith("--") ? rest[0] : void 0;
|
|
918
|
+
const flagArgs = templateName ? rest.slice(1) : rest;
|
|
919
|
+
const cliFlags = parseCliFlags(flagArgs);
|
|
920
|
+
await createCommand({
|
|
921
|
+
templateName,
|
|
922
|
+
cliFlags,
|
|
923
|
+
templatesDir: TEMPLATES_DIR,
|
|
924
|
+
globalConfig,
|
|
925
|
+
version: VERSION
|
|
926
|
+
});
|
|
927
|
+
break;
|
|
928
|
+
}
|
|
929
|
+
case "upgrade": {
|
|
930
|
+
const dryRun = rest.includes("--dry-run");
|
|
931
|
+
await upgradeCommand({
|
|
932
|
+
dryRun,
|
|
933
|
+
projectDir: process.cwd(),
|
|
934
|
+
templatesDir: TEMPLATES_DIR,
|
|
935
|
+
globalConfig,
|
|
936
|
+
version: VERSION
|
|
937
|
+
});
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
default:
|
|
941
|
+
p4.log.error(`Unknown command: "${command}"`);
|
|
942
|
+
console.log(HELP);
|
|
943
|
+
process.exit(1);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
main().catch((err) => {
|
|
947
|
+
console.error(pc3.red("Fatal error:"), err.message ?? err);
|
|
948
|
+
process.exit(1);
|
|
949
|
+
});
|