robodev 0.11.0 → 0.13.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/package.json +1 -1
- package/src/collect-files.test.ts +19 -0
- package/src/collect-files.ts +1 -0
- package/src/config.test.ts +26 -0
- package/src/config.ts +34 -5
- package/src/emit-starter.test.ts +68 -0
- package/src/emit-starter.ts +47 -13
- package/src/index.ts +45 -4
- package/templates/auth-chat/.config/local.spa.template.yml +27 -0
- package/templates/auth-chat/.oxlint-base.json +29 -0
- package/templates/auth-chat/.oxlintrc.json +78 -0
- package/templates/auth-chat/.rulesync/rules/frontend-api-boundary.md +36 -0
- package/templates/auth-chat/.rulesync/rules/frontend-component-structure.md +38 -0
- package/templates/auth-chat/.rulesync/rules/frontend-forms.md +32 -0
- package/templates/auth-chat/.rulesync/rules/frontend-notifications.md +24 -0
- package/templates/auth-chat/.rulesync/rules/frontend-povio-components.md +42 -0
- package/templates/auth-chat/.rulesync/rules/frontend-povio-ui.md +45 -0
- package/templates/auth-chat/.rulesync/rules/frontend-query-autocomplete.md +29 -0
- package/templates/auth-chat/.rulesync/rules/frontend-tables-and-lists.md +34 -0
- package/templates/auth-chat/.rulesync/rules/frontend-translations.md +26 -0
- package/templates/auth-chat/.rulesync/rules/project-overview.md +32 -0
- package/templates/auth-chat/.rulesync/rules/robodev-api.md +30 -0
- package/templates/auth-chat/.rulesync/rules/robodev-auth.md +40 -0
- package/templates/auth-chat/.rulesync/rules/robodev-database.md +22 -0
- package/templates/auth-chat/.rulesync/rules/role-based-app-structure.md +36 -0
- package/templates/auth-chat/.rulesync/skills/media-feature/SKILL.md +21 -0
- package/templates/auth-chat/.rulesync/skills/povio-ui-styling/SKILL.md +208 -0
- package/templates/auth-chat/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
- package/templates/auth-chat/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
- package/templates/auth-chat/.rulesync/skills/robodev-table/SKILL.md +12 -0
- package/templates/auth-chat/README.md +26 -7
- package/templates/auth-chat/api/invite.ts +43 -0
- package/templates/auth-chat/apps/fe/index.html +24 -0
- package/templates/auth-chat/apps/fe/openapi-codegen.config.ts +37 -0
- package/templates/auth-chat/apps/fe/package.json +82 -0
- package/templates/auth-chat/apps/fe/public/apple-touch-icon.png +0 -0
- package/templates/auth-chat/apps/fe/public/favicon-96x96.png +0 -0
- package/templates/auth-chat/apps/fe/public/favicon.ico +0 -0
- package/templates/auth-chat/apps/fe/public/favicon.svg +3 -0
- package/templates/auth-chat/apps/fe/public/site.webmanifest +21 -0
- package/templates/auth-chat/apps/fe/public/web-app-manifest-192x192.png +0 -0
- package/templates/auth-chat/apps/fe/public/web-app-manifest-512x512.png +0 -0
- package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Bold.otf +0 -0
- package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Medium.otf +0 -0
- package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Regular.otf +0 -0
- package/templates/auth-chat/apps/fe/src/assets/fonts/GeneralSans-Semibold.otf +0 -0
- package/templates/auth-chat/apps/fe/src/assets/locales/en/translation.json +202 -0
- package/templates/auth-chat/apps/fe/src/assets/locales/sl/translation.json +202 -0
- package/templates/auth-chat/apps/fe/src/clients/app-rest-client.ts +15 -0
- package/templates/auth-chat/apps/fe/src/clients/auth-token-store.ts +101 -0
- package/templates/auth-chat/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
- package/templates/auth-chat/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
- package/templates/auth-chat/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +37 -0
- package/templates/auth-chat/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
- package/templates/auth-chat/apps/fe/src/components/404.tsx +18 -0
- package/templates/auth-chat/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
- package/templates/auth-chat/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
- package/templates/auth-chat/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
- package/templates/auth-chat/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
- package/templates/auth-chat/apps/fe/src/components/features/chat/ChatPage.tsx +105 -0
- package/templates/auth-chat/apps/fe/src/components/features/chat/InviteForm.tsx +84 -0
- package/templates/auth-chat/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
- package/templates/auth-chat/apps/fe/src/components/googleAnalytics/GoogleAnalytics.tsx +35 -0
- package/templates/auth-chat/apps/fe/src/components/layout/AppLayout.tsx +19 -0
- package/templates/auth-chat/apps/fe/src/components/layout/app-header/AppHeader.tsx +91 -0
- package/templates/auth-chat/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +51 -0
- package/templates/auth-chat/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
- package/templates/auth-chat/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
- package/templates/auth-chat/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
- package/templates/auth-chat/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
- package/templates/auth-chat/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
- package/templates/auth-chat/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
- package/templates/auth-chat/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
- package/templates/auth-chat/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
- package/templates/auth-chat/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
- package/templates/auth-chat/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
- package/templates/auth-chat/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
- package/templates/auth-chat/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
- package/templates/auth-chat/apps/fe/src/components/shared/ui/Card.tsx +60 -0
- package/templates/auth-chat/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
- package/templates/auth-chat/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
- package/templates/auth-chat/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
- package/templates/auth-chat/apps/fe/src/config/app.config.ts +35 -0
- package/templates/auth-chat/apps/fe/src/config/i18n.ts +43 -0
- package/templates/auth-chat/apps/fe/src/config/inits/a11y.ts +14 -0
- package/templates/auth-chat/apps/fe/src/config/inits/logger.ts +7 -0
- package/templates/auth-chat/apps/fe/src/config/inits/sentry.ts +21 -0
- package/templates/auth-chat/apps/fe/src/config/jwt.config.ts +2 -0
- package/templates/auth-chat/apps/fe/src/config/query.config.ts +20 -0
- package/templates/auth-chat/apps/fe/src/hooks/useAuth.ts +5 -0
- package/templates/auth-chat/apps/fe/src/main.tsx +52 -0
- package/templates/auth-chat/apps/fe/src/pages/(guest)/login.tsx +23 -0
- package/templates/auth-chat/apps/fe/src/pages/(guest)/register.tsx +23 -0
- package/templates/auth-chat/apps/fe/src/pages/(guest)/route.tsx +18 -0
- package/templates/auth-chat/apps/fe/src/pages/(private)/index.tsx +23 -0
- package/templates/auth-chat/apps/fe/src/pages/(private)/profile.tsx +23 -0
- package/templates/auth-chat/apps/fe/src/pages/(private)/route.tsx +18 -0
- package/templates/auth-chat/apps/fe/src/pages/(public)/auth.tsx +37 -0
- package/templates/auth-chat/apps/fe/src/pages/(public)/route.tsx +9 -0
- package/templates/auth-chat/apps/fe/src/pages/__root.tsx +164 -0
- package/templates/auth-chat/apps/fe/src/providers/AppErrorBoundary.tsx +10 -0
- package/templates/auth-chat/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
- package/templates/auth-chat/apps/fe/src/providers/index.tsx +44 -0
- package/templates/auth-chat/apps/fe/src/providers/jwt.provider.tsx +95 -0
- package/templates/auth-chat/apps/fe/src/routeTree.gen.ts +225 -0
- package/templates/auth-chat/apps/fe/src/styles/base.css +103 -0
- package/templates/auth-chat/apps/fe/src/styles/fonts/fonts.tsx +10 -0
- package/templates/auth-chat/apps/fe/src/styles/fonts/general-sans.css +31 -0
- package/templates/auth-chat/apps/fe/src/styles/globals.css +28 -0
- package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
- package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
- package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
- package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
- package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/modal.override.ts +57 -0
- package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
- package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
- package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
- package/templates/auth-chat/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
- package/templates/auth-chat/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
- package/templates/auth-chat/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
- package/templates/auth-chat/apps/fe/src/styles/theme.css +2177 -0
- package/templates/auth-chat/apps/fe/src/types/i18next.d.ts +12 -0
- package/templates/auth-chat/apps/fe/src/types/table.d.ts +17 -0
- package/templates/auth-chat/apps/fe/src/types/ui.d.ts +26 -0
- package/templates/auth-chat/apps/fe/src/types/vite-env.d.ts +20 -0
- package/templates/auth-chat/apps/fe/src/utils/date.utils.ts +17 -0
- package/templates/auth-chat/apps/fe/src/utils/number.utils.ts +12 -0
- package/templates/auth-chat/apps/fe/src/utils/string.utils.ts +5 -0
- package/templates/auth-chat/apps/fe/src/vite-env.d.ts +1 -0
- package/templates/auth-chat/apps/fe/tsconfig.app.json +32 -0
- package/templates/auth-chat/apps/fe/tsconfig.json +9 -0
- package/templates/auth-chat/apps/fe/tsconfig.node.json +31 -0
- package/templates/auth-chat/apps/fe/vite.config.ts +94 -0
- package/templates/auth-chat/openapi.json +1016 -0
- package/templates/auth-chat/oxfmt.config.js +23 -0
- package/templates/auth-chat/package.json +6 -13
- package/templates/auth-chat/rulesync.jsonc +18 -0
- package/templates/auth-chat/tsconfig.json +2 -4
- package/templates/backend/README.md +2 -2
- package/templates/backend/api/_lib.ts +359 -0
- package/templates/backend/api/aliens/labels.ts +19 -0
- package/templates/backend/api/files/presigned-url.ts +48 -0
- package/templates/backend/api/files/upload.ts +29 -0
- package/templates/backend/api/planets/[id]/like.ts +36 -0
- package/templates/backend/api/planets/[id].ts +56 -13
- package/templates/backend/api/planets/paginate.ts +36 -0
- package/templates/backend/api/planets.ts +52 -44
- package/templates/backend/database.ts +33 -13
- package/templates/backend/package.json +1 -1
- package/templates/catalog.json +3 -3
- package/templates/empty/package.json +1 -1
- package/templates/space/.config/local.spa.template.yml +27 -0
- package/templates/space/.oxlint-base.json +29 -0
- package/templates/space/.oxlintrc.json +78 -0
- package/templates/space/.rulesync/rules/frontend-api-boundary.md +36 -0
- package/templates/space/.rulesync/rules/frontend-component-structure.md +38 -0
- package/templates/space/.rulesync/rules/frontend-forms.md +32 -0
- package/templates/space/.rulesync/rules/frontend-notifications.md +24 -0
- package/templates/space/.rulesync/rules/frontend-povio-components.md +42 -0
- package/templates/space/.rulesync/rules/frontend-povio-ui.md +45 -0
- package/templates/space/.rulesync/rules/frontend-query-autocomplete.md +29 -0
- package/templates/space/.rulesync/rules/frontend-tables-and-lists.md +34 -0
- package/templates/space/.rulesync/rules/frontend-translations.md +26 -0
- package/templates/space/.rulesync/rules/project-overview.md +32 -0
- package/templates/space/.rulesync/rules/robodev-api.md +30 -0
- package/templates/space/.rulesync/rules/robodev-auth.md +40 -0
- package/templates/space/.rulesync/rules/robodev-database.md +22 -0
- package/templates/space/.rulesync/rules/role-based-app-structure.md +36 -0
- package/templates/space/.rulesync/skills/media-feature/SKILL.md +21 -0
- package/templates/space/.rulesync/skills/povio-ui-styling/SKILL.md +208 -0
- package/templates/space/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
- package/templates/space/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
- package/templates/space/.rulesync/skills/robodev-table/SKILL.md +12 -0
- package/templates/space/README.md +26 -7
- package/templates/space/api/_lib.ts +359 -0
- package/templates/space/api/aliens/labels.ts +19 -0
- package/templates/space/api/files/presigned-url.ts +48 -0
- package/templates/space/api/files/upload.ts +29 -0
- package/templates/space/api/planets/[id]/like.ts +36 -0
- package/templates/space/api/planets/[id].ts +56 -13
- package/templates/space/api/planets/paginate.ts +36 -0
- package/templates/space/api/planets.ts +52 -44
- package/templates/space/apps/fe/index.html +24 -0
- package/templates/space/apps/fe/openapi-codegen.config.ts +37 -0
- package/templates/space/apps/fe/package.json +82 -0
- package/templates/space/apps/fe/public/apple-touch-icon.png +0 -0
- package/templates/space/apps/fe/public/favicon-96x96.png +0 -0
- package/templates/space/apps/fe/public/favicon.ico +0 -0
- package/templates/space/apps/fe/public/favicon.svg +3 -0
- package/templates/space/apps/fe/public/site.webmanifest +21 -0
- package/templates/space/apps/fe/public/web-app-manifest-192x192.png +0 -0
- package/templates/space/apps/fe/public/web-app-manifest-512x512.png +0 -0
- package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Bold.otf +0 -0
- package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Medium.otf +0 -0
- package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Regular.otf +0 -0
- package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Semibold.otf +0 -0
- package/templates/space/apps/fe/src/assets/locales/en/translation.json +301 -0
- package/templates/space/apps/fe/src/assets/locales/sl/translation.json +301 -0
- package/templates/space/apps/fe/src/clients/app-rest-client.ts +15 -0
- package/templates/space/apps/fe/src/clients/auth-token-store.ts +101 -0
- package/templates/space/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
- package/templates/space/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
- package/templates/space/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +37 -0
- package/templates/space/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
- package/templates/space/apps/fe/src/components/404.tsx +18 -0
- package/templates/space/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
- package/templates/space/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
- package/templates/space/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
- package/templates/space/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
- package/templates/space/apps/fe/src/components/features/home/HomePage.tsx +21 -0
- package/templates/space/apps/fe/src/components/features/planets/details/PlanetDetailsPage.tsx +139 -0
- package/templates/space/apps/fe/src/components/features/planets/details/PlanetEditPage.tsx +266 -0
- package/templates/space/apps/fe/src/components/features/planets/feed/PlanetFeedCard.tsx +131 -0
- package/templates/space/apps/fe/src/components/features/planets/feed/PlanetsFeedPage.tsx +119 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetCard.tsx +89 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetCreateModal.tsx +179 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsFilters.css +3 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsFilters.tsx +73 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsGridPage.tsx +51 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsInfiniteTablePage.tsx +79 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsPage.tsx +65 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsTablePage.tsx +72 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsTableSummary.tsx +29 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsViewTabs.tsx +79 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTable.tsx +30 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTableActions.tsx +64 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTableInfinite.tsx +36 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/planetsTableColumns.tsx +72 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/usePlanetTableFilters.tsx +102 -0
- package/templates/space/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
- package/templates/space/apps/fe/src/components/googleAnalytics/GoogleAnalytics.tsx +35 -0
- package/templates/space/apps/fe/src/components/layout/AppLayout.tsx +19 -0
- package/templates/space/apps/fe/src/components/layout/app-header/AppHeader.tsx +93 -0
- package/templates/space/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +53 -0
- package/templates/space/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
- package/templates/space/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
- package/templates/space/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
- package/templates/space/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
- package/templates/space/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
- package/templates/space/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
- package/templates/space/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
- package/templates/space/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
- package/templates/space/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
- package/templates/space/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
- package/templates/space/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
- package/templates/space/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
- package/templates/space/apps/fe/src/components/shared/ui/Card.tsx +60 -0
- package/templates/space/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
- package/templates/space/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
- package/templates/space/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
- package/templates/space/apps/fe/src/config/app.config.ts +35 -0
- package/templates/space/apps/fe/src/config/i18n.ts +43 -0
- package/templates/space/apps/fe/src/config/inits/a11y.ts +14 -0
- package/templates/space/apps/fe/src/config/inits/logger.ts +7 -0
- package/templates/space/apps/fe/src/config/inits/sentry.ts +21 -0
- package/templates/space/apps/fe/src/config/jwt.config.ts +2 -0
- package/templates/space/apps/fe/src/config/query.config.ts +20 -0
- package/templates/space/apps/fe/src/hooks/useAuth.ts +5 -0
- package/templates/space/apps/fe/src/main.tsx +52 -0
- package/templates/space/apps/fe/src/pages/(guest)/login.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(guest)/register.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(guest)/route.tsx +18 -0
- package/templates/space/apps/fe/src/pages/(private)/index.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(private)/planets/$id/edit.tsx +53 -0
- package/templates/space/apps/fe/src/pages/(private)/planets/$id/index.tsx +41 -0
- package/templates/space/apps/fe/src/pages/(private)/planets/index.tsx +24 -0
- package/templates/space/apps/fe/src/pages/(private)/planets-feed.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(private)/profile.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(private)/route.tsx +18 -0
- package/templates/space/apps/fe/src/pages/(public)/auth.tsx +37 -0
- package/templates/space/apps/fe/src/pages/(public)/route.tsx +9 -0
- package/templates/space/apps/fe/src/pages/__root.tsx +164 -0
- package/templates/space/apps/fe/src/providers/AppErrorBoundary.tsx +10 -0
- package/templates/space/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
- package/templates/space/apps/fe/src/providers/index.tsx +44 -0
- package/templates/space/apps/fe/src/providers/jwt.provider.tsx +95 -0
- package/templates/space/apps/fe/src/routeTree.gen.ts +309 -0
- package/templates/space/apps/fe/src/styles/base.css +103 -0
- package/templates/space/apps/fe/src/styles/fonts/fonts.tsx +10 -0
- package/templates/space/apps/fe/src/styles/fonts/general-sans.css +31 -0
- package/templates/space/apps/fe/src/styles/globals.css +28 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/modal.override.ts +57 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
- package/templates/space/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
- package/templates/space/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
- package/templates/space/apps/fe/src/styles/theme.css +2177 -0
- package/templates/space/apps/fe/src/types/i18next.d.ts +12 -0
- package/templates/space/apps/fe/src/types/table.d.ts +17 -0
- package/templates/space/apps/fe/src/types/ui.d.ts +26 -0
- package/templates/space/apps/fe/src/types/vite-env.d.ts +20 -0
- package/templates/space/apps/fe/src/utils/date.utils.ts +17 -0
- package/templates/space/apps/fe/src/utils/image-fallback.ts +9 -0
- package/templates/space/apps/fe/src/utils/media-upload.ts +63 -0
- package/templates/space/apps/fe/src/utils/number.utils.ts +12 -0
- package/templates/space/apps/fe/src/utils/string.utils.ts +5 -0
- package/templates/space/apps/fe/src/vite-env.d.ts +1 -0
- package/templates/space/apps/fe/tsconfig.app.json +32 -0
- package/templates/space/apps/fe/tsconfig.json +9 -0
- package/templates/space/apps/fe/tsconfig.node.json +31 -0
- package/templates/space/apps/fe/vite.config.ts +94 -0
- package/templates/space/database.ts +33 -13
- package/templates/space/openapi.json +1819 -0
- package/templates/space/oxfmt.config.js +23 -0
- package/templates/space/package.json +6 -13
- package/templates/space/rulesync.jsonc +18 -0
- package/templates/space/tsconfig.json +2 -4
- package/templates/auth-chat/index.html +0 -201
- package/templates/auth-chat/src/main.tsx +0 -282
- package/templates/auth-chat/vite.config.ts +0 -7
- package/templates/backend/api/health.ts +0 -7
- package/templates/backend/api/launch.ts +0 -29
- package/templates/backend/api/me.ts +0 -14
- package/templates/backend/api/motto.ts +0 -9
- package/templates/backend/api/rockets.ts +0 -43
- package/templates/space/api/health.ts +0 -7
- package/templates/space/api/launch.ts +0 -29
- package/templates/space/api/me.ts +0 -14
- package/templates/space/api/motto.ts +0 -9
- package/templates/space/api/rockets.ts +0 -43
- package/templates/space/index.html +0 -138
- package/templates/space/src/main.tsx +0 -229
- package/templates/space/vite.config.ts +0 -7
package/package.json
CHANGED
|
@@ -45,3 +45,22 @@ test("collectFiles includes hosted sources and skips denied paths", async () =>
|
|
|
45
45
|
await rm(root, { recursive: true, force: true });
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
|
+
|
|
49
|
+
test("collectFiles skips nested apps/fe sources", async () => {
|
|
50
|
+
const root = await mkdtemp(join(tmpdir(), "rd-cli-"));
|
|
51
|
+
try {
|
|
52
|
+
await writeFile(join(root, "database.ts"), "export default {}");
|
|
53
|
+
await mkdir(join(root, "apps/fe/src"), { recursive: true });
|
|
54
|
+
await writeFile(join(root, "apps/fe/src/main.tsx"), "export {}");
|
|
55
|
+
await writeFile(join(root, "apps/fe/index.html"), "<html></html>");
|
|
56
|
+
await writeFile(join(root, "apps/fe/package.json"), "{}");
|
|
57
|
+
const files = await collectFiles(root);
|
|
58
|
+
const paths = new Set(files.map((file) => file.path));
|
|
59
|
+
assert.ok(paths.has("database.ts"));
|
|
60
|
+
assert.ok(!paths.has("apps/fe/src/main.tsx"));
|
|
61
|
+
assert.ok(!paths.has("apps/fe/index.html"));
|
|
62
|
+
assert.ok(!paths.has("apps/fe/package.json"));
|
|
63
|
+
} finally {
|
|
64
|
+
await rm(root, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
});
|
package/src/collect-files.ts
CHANGED
|
@@ -86,6 +86,7 @@ function isUnsafe(path: string): boolean {
|
|
|
86
86
|
function isDenied(path: string): boolean {
|
|
87
87
|
const top = path.split("/")[0];
|
|
88
88
|
if (SKIP_DIRS.has(top)) return true;
|
|
89
|
+
if (path === "apps" || path.startsWith("apps/")) return true;
|
|
89
90
|
if (path === ".env" || path.startsWith(".env.")) return true;
|
|
90
91
|
const name = basename(path);
|
|
91
92
|
if (name.startsWith(".") && name !== ".gitignore") return true;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { writeViteApiUrl } from "./config.js";
|
|
7
|
+
|
|
8
|
+
test("writeViteApiUrl sets Vite and Povio public API URLs", async () => {
|
|
9
|
+
const root = await mkdtemp(join(tmpdir(), "rd-env-"));
|
|
10
|
+
try {
|
|
11
|
+
await mkdir(join(root, ".config"), { recursive: true });
|
|
12
|
+
await writeFile(
|
|
13
|
+
join(root, ".config/local.spa.template.yml"),
|
|
14
|
+
`APP_PUBLIC_API_URL: &APP_PUBLIC_API_URL "http://localhost:4000"\nVITE_PUBLIC_API_URL: *APP_PUBLIC_API_URL\n`,
|
|
15
|
+
);
|
|
16
|
+
await writeViteApiUrl("https://prj.example.test", root);
|
|
17
|
+
const env = await readFile(join(root, ".env"), "utf8");
|
|
18
|
+
assert.match(env, /^VITE_API_URL=https:\/\/prj\.example\.test$/m);
|
|
19
|
+
assert.match(env, /^VITE_PUBLIC_API_URL=https:\/\/prj\.example\.test$/m);
|
|
20
|
+
assert.match(env, /^APP_PUBLIC_API_URL=https:\/\/prj\.example\.test$/m);
|
|
21
|
+
const spa = await readFile(join(root, ".config/local.spa.yml"), "utf8");
|
|
22
|
+
assert.match(spa, /APP_PUBLIC_API_URL: &APP_PUBLIC_API_URL "https:\/\/prj\.example\.test"/);
|
|
23
|
+
} finally {
|
|
24
|
+
await rm(root, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
});
|
package/src/config.ts
CHANGED
|
@@ -65,7 +65,17 @@ export async function writeLink(link: ProjectLink, cwd = process.cwd()): Promise
|
|
|
65
65
|
await writeFile(linkPath(cwd), `${JSON.stringify(link, null, 2)}\n`);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
const PROJECT_API_ENV_KEYS = ["VITE_API_URL", "VITE_PUBLIC_API_URL", "APP_PUBLIC_API_URL"] as const;
|
|
69
|
+
|
|
70
|
+
function upsertEnvKey(content: string, key: string, value: string): string {
|
|
71
|
+
const line = `${key}=${value}`;
|
|
72
|
+
const pattern = new RegExp(`^${key}=.*$`, "m");
|
|
73
|
+
if (pattern.test(content)) return content.replace(pattern, line);
|
|
74
|
+
const trimmed = content.replace(/\s*$/, "");
|
|
75
|
+
return `${trimmed}${trimmed ? "\n" : ""}${line}\n`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Sets Vite / Povio API URLs so starter UIs talk to the deployed project host. */
|
|
69
79
|
export async function writeViteApiUrl(projectApiUrl: string, cwd = process.cwd()): Promise<void> {
|
|
70
80
|
const envPath = join(cwd, ".env");
|
|
71
81
|
let content = "";
|
|
@@ -74,10 +84,29 @@ export async function writeViteApiUrl(projectApiUrl: string, cwd = process.cwd()
|
|
|
74
84
|
} catch {
|
|
75
85
|
content = "";
|
|
76
86
|
}
|
|
77
|
-
|
|
78
|
-
content = content
|
|
79
|
-
} else {
|
|
80
|
-
content = `${content.replace(/\s*$/, "")}${content ? "\n" : ""}VITE_API_URL=${projectApiUrl}\n`;
|
|
87
|
+
for (const key of PROJECT_API_ENV_KEYS) {
|
|
88
|
+
content = upsertEnvKey(content, key, projectApiUrl);
|
|
81
89
|
}
|
|
82
90
|
await writeFile(envPath, content.endsWith("\n") ? content : `${content}\n`);
|
|
91
|
+
await rewriteLocalSpaConfig(projectApiUrl, cwd);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function rewriteLocalSpaConfig(projectApiUrl: string, cwd: string): Promise<void> {
|
|
95
|
+
const configDir = join(cwd, ".config");
|
|
96
|
+
const templatePath = join(configDir, "local.spa.template.yml");
|
|
97
|
+
const destPath = join(configDir, "local.spa.yml");
|
|
98
|
+
let source: string;
|
|
99
|
+
try {
|
|
100
|
+
source = await readFile(templatePath, "utf8");
|
|
101
|
+
} catch {
|
|
102
|
+
try {
|
|
103
|
+
source = await readFile(destPath, "utf8");
|
|
104
|
+
} catch {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const quoted = JSON.stringify(projectApiUrl);
|
|
109
|
+
const next = source.replace(/^(APP_PUBLIC_API_URL:\s*&APP_PUBLIC_API_URL\s+).+$/m, `$1${quoted}`);
|
|
110
|
+
await mkdir(configDir, { recursive: true });
|
|
111
|
+
await writeFile(destPath, next.endsWith("\n") ? next : `${next}\n`);
|
|
83
112
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { collectFiles } from "./collect-files.js";
|
|
7
|
+
import { cliRoot, emitStarter } from "./emit-starter.js";
|
|
8
|
+
|
|
9
|
+
const versions = { robodev: "0.12.0", sdk: "0.12.0", client: "0.12.0" };
|
|
10
|
+
|
|
11
|
+
test("emitStarter rewrites root package.json name only", async () => {
|
|
12
|
+
const dest = await mkdtemp(join(tmpdir(), "rd-emit-"));
|
|
13
|
+
try {
|
|
14
|
+
await emitStarter(join(cliRoot(), "..", "starters", "space"), dest, {
|
|
15
|
+
name: "Orbit",
|
|
16
|
+
packageName: "orbit-app",
|
|
17
|
+
title: "Space",
|
|
18
|
+
versions,
|
|
19
|
+
});
|
|
20
|
+
const rootPkg = JSON.parse(await readFile(join(dest, "package.json"), "utf8")) as {
|
|
21
|
+
name: string;
|
|
22
|
+
};
|
|
23
|
+
const fePkg = JSON.parse(await readFile(join(dest, "apps/fe/package.json"), "utf8")) as {
|
|
24
|
+
name: string;
|
|
25
|
+
dependencies?: Record<string, string>;
|
|
26
|
+
devDependencies?: Record<string, string>;
|
|
27
|
+
};
|
|
28
|
+
assert.equal(rootPkg.name, "orbit-app");
|
|
29
|
+
assert.equal(fePkg.name, "fe");
|
|
30
|
+
assert.equal(fePkg.dependencies?.robodev, undefined);
|
|
31
|
+
assert.equal(fePkg.devDependencies?.robodev, undefined);
|
|
32
|
+
const overview = await readFile(join(dest, ".rulesync/rules/project-overview.md"), "utf8");
|
|
33
|
+
assert.match(overview, /^# Space starter AI rules/m);
|
|
34
|
+
const readme = await readFile(join(dest, "README.md"), "utf8");
|
|
35
|
+
assert.match(readme, /^# Orbit/m);
|
|
36
|
+
} finally {
|
|
37
|
+
await rm(dest, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("collectFiles for auth-chat starter is Tiny APIs without apps/fe", async () => {
|
|
42
|
+
const files = await collectFiles(join(cliRoot(), "..", "starters", "auth-chat"));
|
|
43
|
+
const paths = new Set(files.map((file) => file.path));
|
|
44
|
+
assert.ok(paths.has("database.ts"));
|
|
45
|
+
assert.ok(paths.has("api/messages.ts"));
|
|
46
|
+
assert.ok(paths.has("api/invite.ts"));
|
|
47
|
+
assert.ok(paths.has("api/health.ts"));
|
|
48
|
+
assert.ok(paths.has("api/me.ts"));
|
|
49
|
+
assert.ok(!paths.has("index.html"));
|
|
50
|
+
assert.ok(!paths.has("src/main.tsx"));
|
|
51
|
+
assert.ok([...paths].every((path) => !path.startsWith("apps/")));
|
|
52
|
+
assert.ok(files.length < 200);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("collectFiles for space starter is Tiny APIs without apps/fe", async () => {
|
|
56
|
+
const files = await collectFiles(join(cliRoot(), "..", "starters", "space"));
|
|
57
|
+
const paths = new Set(files.map((file) => file.path));
|
|
58
|
+
assert.ok(paths.has("database.ts"));
|
|
59
|
+
assert.ok(paths.has("api/planets.ts"));
|
|
60
|
+
assert.ok(paths.has("api/planets/paginate.ts"));
|
|
61
|
+
assert.ok(paths.has("api/aliens/labels.ts"));
|
|
62
|
+
assert.ok(paths.has("api/files/presigned-url.ts"));
|
|
63
|
+
assert.ok(paths.has("api/files/upload.ts"));
|
|
64
|
+
assert.ok(!paths.has("index.html"));
|
|
65
|
+
assert.ok(!paths.has("src/main.tsx"));
|
|
66
|
+
assert.ok([...paths].every((path) => !path.startsWith("apps/")));
|
|
67
|
+
assert.ok(files.length < 200);
|
|
68
|
+
});
|
package/src/emit-starter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
1
|
+
import { copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, extname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
|
|
5
5
|
export type Starter = {
|
|
@@ -23,6 +23,20 @@ export type EmitContext = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
const SKIP_DIRS = new Set(["node_modules", ".robodev", ".git"]);
|
|
26
|
+
const BINARY_COPY_EXTENSIONS = new Set([
|
|
27
|
+
".png",
|
|
28
|
+
".jpg",
|
|
29
|
+
".jpeg",
|
|
30
|
+
".gif",
|
|
31
|
+
".webp",
|
|
32
|
+
".ico",
|
|
33
|
+
".bmp",
|
|
34
|
+
".woff",
|
|
35
|
+
".woff2",
|
|
36
|
+
".ttf",
|
|
37
|
+
".eot",
|
|
38
|
+
".otf",
|
|
39
|
+
]);
|
|
26
40
|
|
|
27
41
|
type DepMap = Record<string, string>;
|
|
28
42
|
|
|
@@ -103,22 +117,35 @@ export async function loadPackageVersions(): Promise<PackageVersions> {
|
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
/** Copies a starter tree and applies package + display rewrites used by create and prepack. */
|
|
106
|
-
export async function emitStarter(
|
|
120
|
+
export async function emitStarter(
|
|
121
|
+
src: string,
|
|
122
|
+
dest: string,
|
|
123
|
+
ctx: EmitContext,
|
|
124
|
+
isRoot = true,
|
|
125
|
+
): Promise<void> {
|
|
107
126
|
await mkdir(dest, { recursive: true });
|
|
108
127
|
const entries = await readdir(src, { withFileTypes: true });
|
|
109
128
|
for (const entry of entries) {
|
|
110
129
|
if (entry.isDirectory()) {
|
|
111
130
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
112
|
-
await emitStarter(join(src, entry.name), join(dest, entry.name), ctx);
|
|
131
|
+
await emitStarter(join(src, entry.name), join(dest, entry.name), ctx, false);
|
|
113
132
|
continue;
|
|
114
133
|
}
|
|
115
134
|
if (!entry.isFile() || entry.name === ".env") continue;
|
|
116
|
-
const
|
|
135
|
+
const srcPath = join(src, entry.name);
|
|
136
|
+
const destPath = join(dest, entry.name);
|
|
137
|
+
if (BINARY_COPY_EXTENSIONS.has(extname(entry.name).toLowerCase())) {
|
|
138
|
+
await copyFile(srcPath, destPath);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const content = await readFile(srcPath, "utf8");
|
|
117
142
|
const next =
|
|
118
143
|
entry.name === "package.json"
|
|
119
|
-
? rewritePackageJson(content, ctx)
|
|
120
|
-
:
|
|
121
|
-
|
|
144
|
+
? rewritePackageJson(content, ctx, isRoot)
|
|
145
|
+
: shouldRewriteDisplay(entry.name, isRoot)
|
|
146
|
+
? rewriteDisplay(content, ctx)
|
|
147
|
+
: content;
|
|
148
|
+
await writeFile(destPath, next);
|
|
122
149
|
}
|
|
123
150
|
}
|
|
124
151
|
|
|
@@ -154,9 +181,14 @@ async function readPackageVersion(path: string, fallback?: string): Promise<stri
|
|
|
154
181
|
throw new Error(`Missing version in ${path}`);
|
|
155
182
|
}
|
|
156
183
|
|
|
157
|
-
function
|
|
184
|
+
function shouldRewriteDisplay(fileName: string, isRoot: boolean): boolean {
|
|
185
|
+
if (!isRoot) return false;
|
|
186
|
+
return fileName === "README.md" || fileName.endsWith(".html");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function rewritePackageJson(raw: string, ctx: EmitContext, isRoot: boolean): string {
|
|
158
190
|
const pkg = JSON.parse(raw) as PackageJson;
|
|
159
|
-
pkg.name = ctx.packageName;
|
|
191
|
+
if (isRoot) pkg.name = ctx.packageName;
|
|
160
192
|
|
|
161
193
|
const pins: Record<string, string> = {
|
|
162
194
|
robodev: `^${ctx.versions.robodev}`,
|
|
@@ -172,9 +204,11 @@ function rewritePackageJson(raw: string, ctx: EmitContext): string {
|
|
|
172
204
|
}
|
|
173
205
|
|
|
174
206
|
const moved = pkg.dependencies?.robodev;
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
207
|
+
if (isRoot) {
|
|
208
|
+
if (pkg.dependencies) delete pkg.dependencies.robodev;
|
|
209
|
+
pkg.devDependencies = pkg.devDependencies ?? {};
|
|
210
|
+
pkg.devDependencies.robodev = pkg.devDependencies.robodev ?? moved ?? pins.robodev;
|
|
211
|
+
}
|
|
178
212
|
|
|
179
213
|
return `${JSON.stringify(pkg, null, 2)}\n`;
|
|
180
214
|
}
|
package/src/index.ts
CHANGED
|
@@ -397,6 +397,36 @@ async function npmInstall(root: string): Promise<void> {
|
|
|
397
397
|
});
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
+
async function bunAvailable(): Promise<boolean> {
|
|
401
|
+
try {
|
|
402
|
+
await execFileAsync("bun", ["--version"]);
|
|
403
|
+
return true;
|
|
404
|
+
} catch {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async function hasAppsFe(root: string): Promise<boolean> {
|
|
410
|
+
try {
|
|
411
|
+
return (await stat(join(root, "apps/fe"))).isDirectory();
|
|
412
|
+
} catch {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function printFeNextSteps(displayPath: string): void {
|
|
418
|
+
if (displayPath !== ".") {
|
|
419
|
+
console.log(` cd ${displayPath}`);
|
|
420
|
+
}
|
|
421
|
+
console.log(" cd apps/fe");
|
|
422
|
+
console.log(" bun install");
|
|
423
|
+
console.log(" bun openapi:gen");
|
|
424
|
+
console.log(" bun dev");
|
|
425
|
+
console.log(
|
|
426
|
+
" Open the Vite URL (default http://localhost:3000). The project App URL is APIs and Swagger, not this UI.",
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
400
430
|
/** Creates a Starbase project, copies the chosen starter, links, deploys, and installs deps. */
|
|
401
431
|
async function runCreate(args: string[]): Promise<void> {
|
|
402
432
|
const parsed = parseCreateArgs(args);
|
|
@@ -442,6 +472,9 @@ async function runCreate(args: string[]): Promise<void> {
|
|
|
442
472
|
"npm install failed. Copy the API URL printed above into Lovable, Bolt, or v0 as VITE_API_URL.",
|
|
443
473
|
);
|
|
444
474
|
console.log("https://robodev.povio.dev/docs/frontend");
|
|
475
|
+
} else if (await hasAppsFe(root)) {
|
|
476
|
+
console.log("Run `npm install` in the project folder.");
|
|
477
|
+
console.log("bun is required for apps/fe. APIs are already deployed.");
|
|
445
478
|
} else {
|
|
446
479
|
console.log("Run `npm install` in the project folder, then `npm run dev`.");
|
|
447
480
|
}
|
|
@@ -451,14 +484,22 @@ async function runCreate(args: string[]): Promise<void> {
|
|
|
451
484
|
const displayPath = relative(process.cwd(), root) || ".";
|
|
452
485
|
console.log("");
|
|
453
486
|
console.log("Next:");
|
|
454
|
-
if (
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
487
|
+
if (await hasAppsFe(root)) {
|
|
488
|
+
if (!(await bunAvailable())) {
|
|
489
|
+
console.log("bun is required for apps/fe. APIs are already deployed.");
|
|
490
|
+
}
|
|
491
|
+
printFeNextSteps(displayPath);
|
|
492
|
+
} else if (starter.kind === "backend") {
|
|
493
|
+
if (displayPath !== ".") {
|
|
494
|
+
console.log(` cd ${displayPath}`);
|
|
495
|
+
}
|
|
458
496
|
console.log(" Copy the API URL printed above into Lovable, Bolt, or v0 as VITE_API_URL.");
|
|
459
497
|
console.log(" Do not run robodev deploy or npm run build inside those tools.");
|
|
460
498
|
console.log(" https://robodev.povio.dev/docs/frontend");
|
|
461
499
|
} else {
|
|
500
|
+
if (displayPath !== ".") {
|
|
501
|
+
console.log(` cd ${displayPath}`);
|
|
502
|
+
}
|
|
462
503
|
console.log(" npm run dev");
|
|
463
504
|
}
|
|
464
505
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
STAGE: "local"
|
|
2
|
+
OUTPUT: "export"
|
|
3
|
+
|
|
4
|
+
ANALYZE: "false"
|
|
5
|
+
NEXT_TELEMETRY_DISABLED: "1"
|
|
6
|
+
|
|
7
|
+
VITE_PUBLIC_RELEASE: ${env:RELEASE}
|
|
8
|
+
VITE_PUBLIC_STAGE: ${func:stage}
|
|
9
|
+
|
|
10
|
+
APP_PUBLIC_API_URL: &APP_PUBLIC_API_URL "http://localhost:4000"
|
|
11
|
+
APP_PUBLIC_API_MODE: &APP_PUBLIC_API_MODE "real"
|
|
12
|
+
VITE_PUBLIC_API_URL: *APP_PUBLIC_API_URL
|
|
13
|
+
VITE_PUBLIC_API_MODE: *APP_PUBLIC_API_MODE
|
|
14
|
+
|
|
15
|
+
VITE_PUBLIC_LOG_LEVEL: "trace"
|
|
16
|
+
|
|
17
|
+
VITE_PUBLIC_SENTRY_DSN: ""
|
|
18
|
+
VITE_PUBLIC_SENTRY_ENVIRONMENT: ${func:stage}
|
|
19
|
+
VITE_PUBLIC_SENTRY_TRACES_SAMPLE_RATE: "0"
|
|
20
|
+
VITE_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE: "0"
|
|
21
|
+
VITE_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE: "0"
|
|
22
|
+
|
|
23
|
+
VITE_PUBLIC_AUTH_CUSTOM_JWT_ENABLE_MAGIC_LINK: "true"
|
|
24
|
+
|
|
25
|
+
VITE_PUBLIC_GOOGLE_ANALYTICS_MEASUREMENT_ID: ""
|
|
26
|
+
|
|
27
|
+
VITE_DEV_PORT: "3000"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "apps/fe/node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"plugins": ["eslint", "typescript", "unicorn", "import", "oxc", "promise", "vitest"],
|
|
4
|
+
"categories": {
|
|
5
|
+
"correctness": "error",
|
|
6
|
+
"perf": "error",
|
|
7
|
+
"pedantic": "off",
|
|
8
|
+
"style": "off",
|
|
9
|
+
"restriction": "off",
|
|
10
|
+
"suspicious": "off",
|
|
11
|
+
"nursery": "off"
|
|
12
|
+
},
|
|
13
|
+
"env": {
|
|
14
|
+
"builtin": true,
|
|
15
|
+
"node": true,
|
|
16
|
+
"shared-node-browser": true
|
|
17
|
+
},
|
|
18
|
+
"rules": {
|
|
19
|
+
"eslint/eqeqeq": [
|
|
20
|
+
"error",
|
|
21
|
+
"always",
|
|
22
|
+
{
|
|
23
|
+
"null": "ignore"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"typescript/no-floating-promises": "off",
|
|
27
|
+
"eslint/capitalized-comments": "off"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "apps/fe/node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"extends": [".oxlint-base.json"],
|
|
4
|
+
"plugins": [
|
|
5
|
+
"eslint",
|
|
6
|
+
"typescript",
|
|
7
|
+
"unicorn",
|
|
8
|
+
"import",
|
|
9
|
+
"vitest",
|
|
10
|
+
"react",
|
|
11
|
+
"react-perf",
|
|
12
|
+
"jsx-a11y"
|
|
13
|
+
],
|
|
14
|
+
"categories": {
|
|
15
|
+
"style": "error"
|
|
16
|
+
},
|
|
17
|
+
"ignorePatterns": [
|
|
18
|
+
"openapi-codegen.config.ts",
|
|
19
|
+
"postcss.config.js",
|
|
20
|
+
"out",
|
|
21
|
+
"dist",
|
|
22
|
+
"public",
|
|
23
|
+
"scripts",
|
|
24
|
+
".vscode",
|
|
25
|
+
".turbo",
|
|
26
|
+
"node_modules",
|
|
27
|
+
"apps/fe/src/routeTree.gen.ts",
|
|
28
|
+
"apps/fe/src/openapi",
|
|
29
|
+
"apps/fe/public"
|
|
30
|
+
],
|
|
31
|
+
"overrides": [
|
|
32
|
+
{
|
|
33
|
+
"files": ["vite-plugin-*.ts", "apps/fe/vite-plugin-*.ts"],
|
|
34
|
+
"rules": {
|
|
35
|
+
"import/no-nodejs-modules": "off"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"rules": {
|
|
40
|
+
"eslint/no-implicit-coercion": "off",
|
|
41
|
+
"no-map-spread": "off",
|
|
42
|
+
"eslint/sort-keys": "off",
|
|
43
|
+
"eslint/sort-imports": "off",
|
|
44
|
+
"eslint/no-ternary": "off",
|
|
45
|
+
"eslint/id-length": "off",
|
|
46
|
+
"eslint/no-magic-numbers": "off",
|
|
47
|
+
"eslint/arrow-body-style": "off",
|
|
48
|
+
"eslint/max-params": "off",
|
|
49
|
+
"eslint/func-style": "off",
|
|
50
|
+
"eslint/new-cap": "off",
|
|
51
|
+
"eslint/no-continue": "off",
|
|
52
|
+
"eslint/max-statements": "off",
|
|
53
|
+
"eslint/no-duplicate-imports": [
|
|
54
|
+
"error",
|
|
55
|
+
{
|
|
56
|
+
"allowSeparateTypeImports": true
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"react/exhaustive-deps": "off",
|
|
60
|
+
"react/jsx-props-no-spreading": "off",
|
|
61
|
+
"react/jsx-max-depth": "off",
|
|
62
|
+
"react-perf/jsx-no-new-object-as-prop": "off",
|
|
63
|
+
"react-perf/jsx-no-new-function-as-prop": "off",
|
|
64
|
+
"react-perf/jsx-no-new-array-as-prop": "off",
|
|
65
|
+
"react-perf/jsx-no-jsx-as-prop": "off",
|
|
66
|
+
"typescript/no-empty-interface": "off",
|
|
67
|
+
"import/no-named-export": "off",
|
|
68
|
+
"import/group-exports": "off",
|
|
69
|
+
"import/prefer-default-export": "off",
|
|
70
|
+
"import/exports-last": "off",
|
|
71
|
+
"import/consistent-type-specifier-style": "off",
|
|
72
|
+
"jest/require-hook": "off",
|
|
73
|
+
"unicorn/filename-case": "off",
|
|
74
|
+
"unicorn/prefer-global-this": "off",
|
|
75
|
+
"unicorn/no-await-expression-member": "off",
|
|
76
|
+
"unicorn/no-null": "off"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
root: false
|
|
3
|
+
targets: ["claudecode", "codexcli", "cursor"]
|
|
4
|
+
description: "Frontend must consume generated OpenAPI queries and models, not backend internals."
|
|
5
|
+
globs: ["apps/fe/src/**/*.{ts,tsx}"]
|
|
6
|
+
cursor:
|
|
7
|
+
alwaysApply: false
|
|
8
|
+
description: "Apply when editing frontend components, hooks, providers, pages, or clients that call the API."
|
|
9
|
+
globs: ["apps/fe/src/**/*.{ts,tsx}"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Frontend API Boundary
|
|
13
|
+
|
|
14
|
+
Frontend application code must consume the API through generated files in `apps/fe/src/openapi`.
|
|
15
|
+
|
|
16
|
+
Use generated modules such as:
|
|
17
|
+
|
|
18
|
+
- `@/openapi/<domain>/<domain>.queries`
|
|
19
|
+
- `@/openapi/<domain>/<domain>.models`
|
|
20
|
+
- `@/openapi/queryModules`
|
|
21
|
+
|
|
22
|
+
For generated mutations, pass invalidation through the mutation options instead of importing `queryClient` or calling `invalidateQueries` with generated query keys. The current module is invalidated by default through `invalidateCurrentModule: true`; invalidate dependent modules explicitly with `invalidateModules: [QueryModule.someModule]`.
|
|
23
|
+
|
|
24
|
+
After mutations, derive follow-up UI state from canonical API response or query state instead of leaving action controls unchanged. Disable duplicate submissions when the API state already represents a pending or completed action, and surface mutation errors through the default generated-client error handler or a feature-specific message.
|
|
25
|
+
|
|
26
|
+
Do not import `@robodev-ai/sdk`, `database.ts`, Drizzle tables, or API helper modules into frontend features.
|
|
27
|
+
|
|
28
|
+
`apps/fe/src/openapi` is generated by `@povio/openapi-codegen-cli` from root `openapi.json` (or a live project `/openapi.json` when `OPENAPI_LIVE=true`). Do not hand-edit generated OpenAPI client files.
|
|
29
|
+
|
|
30
|
+
When API contracts change:
|
|
31
|
+
|
|
32
|
+
1. Update `api/` handlers and `openapi.json` if the documented contract changed.
|
|
33
|
+
2. Run `bun openapi:gen` from `apps/fe`.
|
|
34
|
+
3. Update frontend code to use the regenerated queries and models.
|
|
35
|
+
|
|
36
|
+
Point `VITE_PUBLIC_API_URL` at the Starbase project host origin only. Do not append `/api`.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
root: false
|
|
3
|
+
targets: ["claudecode", "codexcli", "cursor"]
|
|
4
|
+
description: "Frontend component extraction and feature subcomponent structure conventions."
|
|
5
|
+
globs: ["apps/fe/src/**/*.{ts,tsx}"]
|
|
6
|
+
cursor:
|
|
7
|
+
alwaysApply: false
|
|
8
|
+
description: "Apply when organizing frontend feature components, page sections, mapped items, or reusable UI."
|
|
9
|
+
globs: ["apps/fe/src/**/*.{ts,tsx}"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Frontend Component Structure
|
|
13
|
+
|
|
14
|
+
Extract repeatable UI into subcomponents. Every named React component should live in its own file, even when it is currently used only once. Keep the component file near the feature, layout, page, or shared UI area that owns it.
|
|
15
|
+
|
|
16
|
+
Extract large or complex sections of pages into named components so route and page components stay readable. Anything mapped from an array should usually be a component, such as cards, table row actions, repeated sections, or list items.
|
|
17
|
+
|
|
18
|
+
The planets feature is the reference example while the template is still a prototype:
|
|
19
|
+
|
|
20
|
+
- `PlanetCard` for mapped grid cards.
|
|
21
|
+
- `PlanetsFilters` for shared filter controls.
|
|
22
|
+
- `PlanetsTableActions` for repeated row actions.
|
|
23
|
+
- `PlanetsTable` and `PlanetsTableInfinite` for table wrappers.
|
|
24
|
+
- `PlanetDetailsPage` and `PlanetEditPage` for large route-owned views.
|
|
25
|
+
|
|
26
|
+
When implementing a real app, remove the planets and aliens links from app navigation such as `apps/fe/src/components/layout/AppHeader.tsx` so these examples are not directly accessible in the product UI, but keep the example implementation available as a reference unless the team intentionally deletes the demo layer.
|
|
27
|
+
|
|
28
|
+
Route files should own routing, document metadata, and route data boundaries. Feature components should own the page UI and interaction details.
|
|
29
|
+
|
|
30
|
+
Exception: route-local wrappers such as `PageComponent` or layout guard components may stay in route files when they only connect routing primitives to feature components.
|
|
31
|
+
|
|
32
|
+
## Shared Utilities
|
|
33
|
+
|
|
34
|
+
Keep generic formatting, parsing, calculation, and data-shaping helpers in `apps/fe/src/utils/*.utils.ts` files instead of defining them inside component files. Export helpers through namespaces such as `DateUtils.formatDate(...)` or `NumberUtils.formatInteger(...)` so call sites stay explicit and related helpers stay grouped.
|
|
35
|
+
|
|
36
|
+
Component files may keep UI-local glue helpers only when the logic is tightly coupled to that component's JSX, such as adapting one component's prop shape or handling a local event. If the helper describes dates, numbers, arrays, strings, IDs, or other domain-neutral data, move it into a shared utility file.
|
|
37
|
+
|
|
38
|
+
For apps with multiple user roles that require distinct UI or business workflows, also apply the role-based app structure rule.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
root: false
|
|
3
|
+
targets: ["claudecode", "codexcli", "cursor"]
|
|
4
|
+
description: "Frontend form patterns with Povio UI useForm, generated schemas, and formControl wiring."
|
|
5
|
+
globs: ["apps/fe/src/**/*.{ts,tsx}"]
|
|
6
|
+
cursor:
|
|
7
|
+
alwaysApply: false
|
|
8
|
+
description: "Apply when building or changing frontend forms, modals, edit pages, or input components."
|
|
9
|
+
globs: ["apps/fe/src/**/*.{ts,tsx}"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Frontend Forms
|
|
13
|
+
|
|
14
|
+
Use `useForm` and form-aware inputs from `@povio/ui/tanstack` with generated OpenAPI Zod schemas for API-backed forms. The planets feature is the reference example while the template is still a prototype:
|
|
15
|
+
|
|
16
|
+
- Create form: `apps/fe/src/components/features/planets/list/PlanetCreateModal.tsx`
|
|
17
|
+
- Edit form: `apps/fe/src/components/features/planets/details/PlanetEditPage.tsx`
|
|
18
|
+
|
|
19
|
+
Prefer this shape:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
const form = useForm({
|
|
23
|
+
zodSchema: PlanetsModels.PlanetsCreateInputSchema,
|
|
24
|
+
defaultValues: { name: "" },
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Pass form state into Povio UI inputs with `field={{ form, name: "fieldName" }}`. Use `form.handleSubmit`, `form.reset`, and `form.setFieldValue` for form actions, and `useFormValue(form, selector)` for reactive field reads.
|
|
29
|
+
|
|
30
|
+
Avoid local `useState`, custom `value`, and custom `onChange` plumbing for fields that belong to the form. Use TanStack Form's `form.Field` adapter only when a component cannot accept the Povio UI `field` binding.
|
|
31
|
+
|
|
32
|
+
Use generated OpenAPI queries and mutations for submit handlers. Keep toast feedback and navigation close to the feature interaction that owns them.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
root: false
|
|
3
|
+
targets: ["claudecode", "codexcli", "cursor"]
|
|
4
|
+
description: "Frontend notification inbox, preferences, push permission, service worker, and token registration boundaries."
|
|
5
|
+
globs: ["apps/fe/src/**/*.{ts,tsx}", "apps/fe/public/**/*.{js,ts}"]
|
|
6
|
+
cursor:
|
|
7
|
+
alwaysApply: false
|
|
8
|
+
description: "Apply when frontend work handles notifications, push permission, service workers, push tokens, unread counts, or notification preferences."
|
|
9
|
+
globs: ["apps/fe/src/**/*.{ts,tsx}", "apps/fe/public/**/*.{js,ts}"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Frontend Notifications
|
|
13
|
+
|
|
14
|
+
Use generated `@/openapi` queries, mutations, and models for notification history, preferences, and push-token registration. Do not import `@robodev-ai/sdk` or `database.ts` into frontend feature code.
|
|
15
|
+
|
|
16
|
+
Request push permission only after an explanatory UI and explicit user gesture. Never request it automatically during initial application load. Handle unsupported, default, denied, granted, registered, expired, and failed-registration states.
|
|
17
|
+
|
|
18
|
+
Register the service worker and provider subscription through a small browser integration, then persist the token/subscription through the generated authenticated user API. Clean up or detach the current device registration on logout when required by the security model.
|
|
19
|
+
|
|
20
|
+
Keep push payloads minimal and privacy-safe. Validate internal deep links before navigation. Never depend on push as the only record of an important event; provide a paginated in-app notification list and unread state.
|
|
21
|
+
|
|
22
|
+
Use generated mutation invalidation options for unread counts, mark-read actions, preferences, and token changes. Do not access the query client directly for generated API state.
|
|
23
|
+
|
|
24
|
+
Use `@povio/ui` for permission prompts, notification lists, menus/popovers, tags, forms, confirmations, and toasts. Use `povio-ui-styling` for styling-layer decisions.
|