@podosoft/podokit 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/add.d.ts +2 -0
- package/dist/add.js +5 -1
- package/dist/assemble.d.ts +14 -0
- package/dist/assemble.js +60 -0
- package/dist/create.d.ts +2 -0
- package/dist/create.js +7 -0
- package/dist/eject.d.ts +11 -0
- package/dist/eject.js +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +122 -1
- package/dist/inspect.d.ts +43 -0
- package/dist/inspect.js +82 -0
- package/dist/lockfile.d.ts +80 -0
- package/dist/lockfile.js +193 -0
- package/dist/templates/base/dot-env.example +11 -6
- package/dist/templates/fullstack-nest-svelte/README.md +2 -2
- package/dist/templates/fullstack-nest-svelte/apps/api/package.json +21 -2
- package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +6 -3
- package/dist/templates/fullstack-nest-svelte/apps/api/src/common/app-exception.ts +4 -12
- package/dist/templates/fullstack-nest-svelte/apps/api/src/config/env.validation.ts +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/api/src/main.ts +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/api/tsconfig.json +14 -5
- package/dist/templates/fullstack-nest-svelte/apps/web/package.json +4 -3
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/api.ts +5 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-content.svelte +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-item.svelte +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/switch/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/switch/switch.svelte +29 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/api.ts +19 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +44 -5
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +8 -3
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/[...path]/+server.ts +12 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
- package/dist/templates/fullstack-nest-svelte/dot-env.example +10 -5
- package/dist/templates/fullstack-nest-svelte/dot-gitignore +5 -0
- package/dist/templates/fullstack-nest-svelte/infra/docker/docker-compose.yml +37 -0
- package/dist/templates/fullstack-nest-svelte/infra/docker/sms-sink.mjs +40 -0
- package/dist/templates/fullstack-nest-svelte/package.json +11 -3
- package/dist/templates/fullstack-nest-svelte/tests/README.md +15 -0
- package/dist/templates/fullstack-nest-svelte/tests/api/health.api.spec.ts +14 -0
- package/dist/templates/fullstack-nest-svelte/tests/package.json +15 -0
- package/dist/templates/fullstack-nest-svelte/tests/playwright.config.ts +25 -0
- package/dist/templates/fullstack-nest-svelte/tests/ui/smoke.ui.spec.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/app-sidebar.svelte +65 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.svelte +149 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/language-switch.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +44 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-pagination.svelte +39 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-toolbar.svelte +93 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/theme-toggle.svelte +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-content.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-trigger.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible.svelte +11 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/index.ts +13 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/index.ts +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-content.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-ellipsis.svelte +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-item.svelte +14 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-link.svelte +42 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next-button.svelte +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next.svelte +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-prev-button.svelte +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-previous.svelte +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination.svelte +28 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/index.ts +34 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-close.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-content.svelte +55 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-description.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-footer.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-header.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-overlay.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-portal.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-title.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/constants.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/context.svelte.ts +81 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/index.ts +75 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-content.svelte +24 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-footer.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-header.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-input.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-inset.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte +37 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte +24 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte +102 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte +39 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-provider.svelte +53 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-rail.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-separator.svelte +19 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar.svelte +108 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/user-avatar.svelte +12 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/hooks/is-mobile.svelte.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/index.ts +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +575 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/guards.ts +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.server.ts +30 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.svelte +50 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/accept-invitation/[id]/+page.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/+page.svelte +45 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.server.ts +5 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.svelte +685 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.server.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.svelte +123 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.server.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.svelte +264 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.server.ts +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.svelte +156 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.server.ts +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.svelte +322 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.server.ts +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.svelte +552 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/+layout.svelte +13 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/forgot-password/+page.svelte +61 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.server.ts +14 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +181 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/reset-password/+page.svelte +51 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/signup/+page.svelte +76 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/verify-email/+page.svelte +63 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.server.ts +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+page.svelte +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/oauth2/consent/+page.svelte +54 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +97 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +80 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +306 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/email.api.spec.ts +120 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +134 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/hydration.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/mailpit.ts +49 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/sms.ts +35 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/totp.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +26 -0
- package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +40 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +126 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +16 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +45 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +25 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/data-table.ui.spec.ts +32 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email-admin.ui.spec.ts +60 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +42 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +22 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +13 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/oidc.ui.spec.ts +57 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/organizations.ui.spec.ts +52 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/passkey.ui.spec.ts +36 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +22 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/settings.ui.spec.ts +34 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sidebar.ui.spec.ts +16 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/table-toolbar.ui.spec.ts +32 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users-crud.ui.spec.ts +145 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +51 -0
- package/dist/templates/modules/admin-dashboard/module.manifest.json +47 -0
- package/dist/templates/modules/api-key-auth/module.manifest.json +3 -3
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/README.md +59 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-enabled.ts +16 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-events.ts +34 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +74 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-log.entity.ts +29 -8
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.controller.ts +7 -2
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.decorator.ts +23 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.interceptor.ts +38 -21
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +2 -1
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +36 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/migrations/1720300000000-InitAuditLogs.ts +10 -4
- package/dist/templates/modules/audit-log/module.manifest.json +37 -8
- package/dist/templates/modules/auth/files/apps/api/src/account/account.controller.ts +85 -3
- package/dist/templates/modules/auth/files/apps/api/src/account/account.module.ts +2 -1
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.ts +73 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +199 -31
- package/dist/templates/modules/auth/files/apps/api/src/auth/db.ts +12 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/feature-gate.ts +56 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/org-permissions.ts +22 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/permissions.ts +23 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.controller.ts +33 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.entity.ts +26 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.module.ts +12 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.service.ts +118 -0
- package/dist/templates/modules/auth/files/apps/api/src/mail/mailer.ts +65 -0
- package/dist/templates/modules/auth/files/apps/api/src/migrations/1720400000000-InitAppSettings.ts +38 -0
- package/dist/templates/modules/auth/files/apps/api/src/migrations/1720500000000-InitAuthConfig.ts +27 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/app-setting.entity.ts +16 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.ts +25 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/settings.module.ts +11 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/settings.service.ts +57 -0
- package/dist/templates/modules/auth/files/apps/api/src/sms/sms.ts +23 -0
- package/dist/templates/modules/auth/module.manifest.json +54 -20
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +6 -3
- package/dist/templates/modules/bullmq/module.manifest.json +4 -4
- package/dist/templates/modules/file-upload/module.manifest.json +3 -3
- package/dist/templates/modules/job-progress/module.manifest.json +9 -9
- package/dist/templates/modules/logging/module.manifest.json +3 -3
- package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +5 -3
- package/dist/templates/modules/object-storage-s3/module.manifest.json +5 -5
- package/dist/templates/modules/rate-limit/module.manifest.json +2 -2
- package/dist/templates/modules/redis/module.manifest.json +4 -4
- package/dist/templates/modules/sse/module.manifest.json +4 -4
- package/dist/templates/todo/README.md +2 -2
- package/dist/templates/todo/apps/api/package.json +21 -2
- package/dist/templates/todo/apps/api/src/app.module.ts +6 -3
- package/dist/templates/todo/apps/api/src/common/app-exception.ts +4 -12
- package/dist/templates/todo/apps/api/src/config/env.validation.ts +1 -1
- package/dist/templates/todo/apps/api/src/main.ts +1 -1
- package/dist/templates/todo/apps/api/tsconfig.json +14 -5
- package/dist/templates/todo/apps/web/package.json +4 -3
- package/dist/templates/todo/apps/web/src/lib/api.ts +5 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/switch/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/switch/switch.svelte +29 -0
- package/dist/templates/todo/apps/web/src/lib/server/api.ts +19 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +44 -5
- package/dist/templates/todo/apps/web/src/routes/+page.svelte +19 -18
- package/dist/templates/todo/apps/web/src/routes/api/[...path]/+server.ts +12 -0
- package/dist/templates/todo/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
- package/dist/templates/todo/dot-env.example +10 -5
- package/dist/templates/todo/dot-gitignore +5 -0
- package/dist/templates/todo/infra/docker/docker-compose.yml +4 -0
- package/dist/templates/todo/package.json +11 -3
- package/dist/templates/todo/tests/README.md +15 -0
- package/dist/templates/todo/tests/api/health.api.spec.ts +14 -0
- package/dist/templates/todo/tests/package.json +15 -0
- package/dist/templates/todo/tests/playwright.config.ts +25 -0
- package/dist/templates/todo/tests/ui/smoke.ui.spec.ts +8 -0
- package/dist/update.d.ts +49 -0
- package/dist/update.js +158 -0
- package/package.json +2 -2
- package/dist/templates/fullstack-nest-svelte/apps/api/test/health.e2e-spec.ts +0 -23
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/health/+server.ts +0 -12
- package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +0 -23
- package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +0 -12
- package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +0 -24
- package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +0 -24
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
|
-
"module": "
|
|
3
|
+
"module": "nodenext",
|
|
4
4
|
"target": "ES2022",
|
|
5
|
-
"moduleResolution": "
|
|
5
|
+
"moduleResolution": "nodenext",
|
|
6
6
|
"outDir": "dist",
|
|
7
7
|
"rootDir": "src",
|
|
8
8
|
"declaration": false,
|
|
@@ -14,8 +14,17 @@
|
|
|
14
14
|
"noImplicitReturns": true,
|
|
15
15
|
"esModuleInterop": true,
|
|
16
16
|
"skipLibCheck": true,
|
|
17
|
-
"forceConsistentCasingInFileNames": true
|
|
17
|
+
"forceConsistentCasingInFileNames": true,
|
|
18
|
+
"types": [
|
|
19
|
+
"node"
|
|
20
|
+
]
|
|
18
21
|
},
|
|
19
|
-
"include": [
|
|
20
|
-
|
|
22
|
+
"include": [
|
|
23
|
+
"src/**/*.ts"
|
|
24
|
+
],
|
|
25
|
+
"exclude": [
|
|
26
|
+
"node_modules",
|
|
27
|
+
"dist",
|
|
28
|
+
"test"
|
|
29
|
+
]
|
|
21
30
|
}
|
|
@@ -4,15 +4,16 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"dev": "vite dev --port
|
|
7
|
+
"dev": "vite dev --port 5001",
|
|
8
8
|
"build": "vite build",
|
|
9
9
|
"preview": "vite preview",
|
|
10
10
|
"lint": "svelte-check --tsconfig ./tsconfig.json",
|
|
11
|
-
"test": "echo \"add
|
|
11
|
+
"test": "echo \"unit: add vitest; e2e: npm run test:e2e (see tests/)\" && exit 0"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@internationalized/date": "^3.12.2",
|
|
15
15
|
"@lucide/svelte": "^1.23.0",
|
|
16
|
+
"@podosoft/podokit-api-client": "^0.5.0",
|
|
16
17
|
"bits-ui": "^2.18.1",
|
|
17
18
|
"clsx": "^2.1.1",
|
|
18
19
|
"mode-watcher": "^1.1.0",
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
"svelte-check": "^4.0.0",
|
|
30
31
|
"tailwindcss": "^4.0.0",
|
|
31
32
|
"typesafe-i18n": "^5.26.2",
|
|
32
|
-
"typescript": "^
|
|
33
|
+
"typescript": "^6.0.3",
|
|
33
34
|
"vite": "^5.4.0",
|
|
34
35
|
"tw-animate-css": "^1.4.0"
|
|
35
36
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Switch as SwitchPrimitive } from "bits-ui";
|
|
3
|
+
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
ref = $bindable(null),
|
|
7
|
+
checked = $bindable(false),
|
|
8
|
+
class: className,
|
|
9
|
+
...restProps
|
|
10
|
+
}: WithoutChildrenOrChild<SwitchPrimitive.RootProps> = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<SwitchPrimitive.Root
|
|
14
|
+
bind:ref
|
|
15
|
+
data-slot="switch"
|
|
16
|
+
class={cn(
|
|
17
|
+
"peer focus-visible:border-ring focus-visible:ring-ring/50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
bind:checked
|
|
21
|
+
{...restProps}
|
|
22
|
+
>
|
|
23
|
+
<SwitchPrimitive.Thumb
|
|
24
|
+
data-slot="switch-thumb"
|
|
25
|
+
class={cn(
|
|
26
|
+
"bg-background pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
|
27
|
+
)}
|
|
28
|
+
/>
|
|
29
|
+
</SwitchPrimitive.Root>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RequestEvent } from "@sveltejs/kit";
|
|
2
|
+
import { createApiClient, type ApiClient } from "@podosoft/podokit-api-client";
|
|
3
|
+
import { backendBaseUrl } from "./backend-proxy";
|
|
4
|
+
|
|
5
|
+
// Server-side client for load functions and hooks: points at the internal
|
|
6
|
+
// backend URL and forwards the incoming request's cookies.
|
|
7
|
+
export function serverApiClient(event: RequestEvent): ApiClient {
|
|
8
|
+
const cookie = event.request.headers.get("cookie") ?? "";
|
|
9
|
+
const fetchWithCookies: typeof fetch = (input, init) => {
|
|
10
|
+
const headers = new Headers(init?.headers);
|
|
11
|
+
if (cookie) headers.set("cookie", cookie);
|
|
12
|
+
return event.fetch(input, { ...init, headers });
|
|
13
|
+
};
|
|
14
|
+
return createApiClient({
|
|
15
|
+
baseUrl: backendBaseUrl(),
|
|
16
|
+
apiBasePath: "",
|
|
17
|
+
fetch: fetchWithCookies,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -1,16 +1,55 @@
|
|
|
1
1
|
// Server-side proxy boundary. The browser never talks to the API directly:
|
|
2
|
-
//
|
|
3
|
-
|
|
2
|
+
// requests go through SvelteKit server routes, which forward an allowlist of
|
|
3
|
+
// headers to BACKEND_INTERNAL_URL and relay the response (including Set-Cookie).
|
|
4
|
+
const FORWARDED_HEADERS = ["authorization", "cookie", "content-type", "accept", "origin", "referer"];
|
|
5
|
+
const RELAYED_RESPONSE_HEADERS = ["content-type", "location", "cache-control"];
|
|
4
6
|
|
|
5
7
|
export function backendBaseUrl(): string {
|
|
6
|
-
return process.env.BACKEND_INTERNAL_URL ?? "http://localhost:
|
|
8
|
+
return process.env.BACKEND_INTERNAL_URL ?? "http://localhost:5002";
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
// Normalize the resolved client IP for the forwarded header. better-auth stores
|
|
12
|
+
// IPv4 as-is but anonymizes IPv6 (masks the host bits), so an IPv6 loopback would
|
|
13
|
+
// be recorded as "::" — map loopback to 127.0.0.1 for a clean local value, and
|
|
14
|
+
// unwrap IPv4-mapped addresses.
|
|
15
|
+
function normalizeClientIp(address: string | undefined): string | undefined {
|
|
16
|
+
if (!address) return undefined;
|
|
17
|
+
if (address === "::1" || address === "::") return "127.0.0.1";
|
|
18
|
+
if (address.startsWith("::ffff:")) return address.slice("::ffff:".length);
|
|
19
|
+
return address;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function proxyRequest(
|
|
23
|
+
request: Request,
|
|
24
|
+
targetUrl: string,
|
|
25
|
+
clientAddress?: string,
|
|
26
|
+
): Promise<Response> {
|
|
10
27
|
const headers = new Headers();
|
|
11
28
|
for (const name of FORWARDED_HEADERS) {
|
|
12
29
|
const value = request.headers.get(name);
|
|
13
30
|
if (value) headers.set(name, value);
|
|
14
31
|
}
|
|
15
|
-
|
|
32
|
+
// Forward the resolved client IP so the API (better-auth) can record it.
|
|
33
|
+
const forwardedIp = normalizeClientIp(clientAddress);
|
|
34
|
+
if (forwardedIp) headers.set("x-forwarded-for", forwardedIp);
|
|
35
|
+
|
|
36
|
+
const hasBody = request.method !== "GET" && request.method !== "HEAD";
|
|
37
|
+
const upstream = await fetch(targetUrl, {
|
|
38
|
+
method: request.method,
|
|
39
|
+
headers,
|
|
40
|
+
body: hasBody ? await request.text() : undefined,
|
|
41
|
+
redirect: "manual",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const responseHeaders = new Headers();
|
|
45
|
+
for (const name of RELAYED_RESPONSE_HEADERS) {
|
|
46
|
+
const value = upstream.headers.get(name);
|
|
47
|
+
if (value) responseHeaders.set(name, value);
|
|
48
|
+
}
|
|
49
|
+
// Relay Set-Cookie so auth session cookies reach the browser.
|
|
50
|
+
for (const cookie of upstream.headers.getSetCookie()) {
|
|
51
|
+
responseHeaders.append("set-cookie", cookie);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return new Response(upstream.body, { status: upstream.status, headers: responseHeaders });
|
|
16
55
|
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { Input } from "$lib/components/ui/input";
|
|
4
4
|
import { Checkbox } from "$lib/components/ui/checkbox";
|
|
5
5
|
import * as Card from "$lib/components/ui/card";
|
|
6
|
+
import { api } from "$lib/api";
|
|
7
|
+
import { ApiError } from "@podosoft/podokit-api-client";
|
|
6
8
|
|
|
7
9
|
type Todo = { id: string; title: string; completed: boolean };
|
|
8
10
|
|
|
@@ -10,40 +12,39 @@
|
|
|
10
12
|
let title = $state("");
|
|
11
13
|
let error = $state<string | null>(null);
|
|
12
14
|
|
|
15
|
+
function fail(err: unknown, action: string): void {
|
|
16
|
+
error = err instanceof ApiError ? `${action}: ${err.message}` : `${action} failed`;
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
async function load(): Promise<void> {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
try {
|
|
21
|
+
todos = await api.get<Todo[]>("/todos");
|
|
22
|
+
error = null;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
fail(err, "Load");
|
|
25
|
+
}
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
async function add(event: SubmitEvent): Promise<void> {
|
|
20
29
|
event.preventDefault();
|
|
21
30
|
const value = title.trim();
|
|
22
31
|
if (!value) return;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
headers: { "content-type": "application/json" },
|
|
26
|
-
body: JSON.stringify({ title: value }),
|
|
27
|
-
});
|
|
28
|
-
if (res.ok) {
|
|
32
|
+
try {
|
|
33
|
+
await api.post("/todos", { title: value });
|
|
29
34
|
title = "";
|
|
30
35
|
await load();
|
|
31
|
-
}
|
|
32
|
-
|
|
36
|
+
} catch (err) {
|
|
37
|
+
fail(err, "Add");
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
async function toggle(todo: Todo): Promise<void> {
|
|
37
|
-
await
|
|
38
|
-
method: "PATCH",
|
|
39
|
-
headers: { "content-type": "application/json" },
|
|
40
|
-
body: JSON.stringify({ completed: !todo.completed }),
|
|
41
|
-
});
|
|
42
|
+
await api.patch(`/todos/${todo.id}`, { completed: !todo.completed });
|
|
42
43
|
await load();
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
async function remove(todo: Todo): Promise<void> {
|
|
46
|
-
await
|
|
47
|
+
await api.del(`/todos/${todo.id}`);
|
|
47
48
|
await load();
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
<Card.Root>
|
|
62
63
|
<Card.Header>
|
|
63
64
|
<Card.Title>Todos</Card.Title>
|
|
64
|
-
<Card.Description>A NestJS + SvelteKit CRUD example.</Card.Description>
|
|
65
|
+
<Card.Description>A NestJS + SvelteKit CRUD example via the typed ApiClient.</Card.Description>
|
|
65
66
|
</Card.Header>
|
|
66
67
|
<Card.Content class="flex flex-col gap-4">
|
|
67
68
|
<form class="flex gap-2" onsubmit={add}>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
+
import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
|
|
3
|
+
|
|
4
|
+
// Proxy the app's REST API, stripping the /api prefix (backend routes are at root).
|
|
5
|
+
const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
|
|
6
|
+
proxyRequest(request, `${backendBaseUrl()}/${params.path}${url.search}`, getClientAddress());
|
|
7
|
+
|
|
8
|
+
export const GET = handler;
|
|
9
|
+
export const POST = handler;
|
|
10
|
+
export const PUT = handler;
|
|
11
|
+
export const PATCH = handler;
|
|
12
|
+
export const DELETE = handler;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RequestHandler } from "@sveltejs/kit";
|
|
2
|
+
import { backendBaseUrl, proxyRequest } from "$lib/server/backend-proxy";
|
|
3
|
+
|
|
4
|
+
// Proxy the better-auth handler, keeping the /api/auth prefix.
|
|
5
|
+
const handler: RequestHandler = ({ request, params, url, getClientAddress }) =>
|
|
6
|
+
proxyRequest(request, `${backendBaseUrl()}/api/auth/${params.all}${url.search}`, getClientAddress());
|
|
7
|
+
|
|
8
|
+
export const GET = handler;
|
|
9
|
+
export const POST = handler;
|
|
10
|
+
export const PUT = handler;
|
|
11
|
+
export const PATCH = handler;
|
|
12
|
+
export const DELETE = handler;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Copy to .env and adjust. Never commit real secrets.
|
|
2
|
+
|
|
3
|
+
# ── Required (runtime) ───────────────────────────────────────────────
|
|
2
4
|
NODE_ENV=development
|
|
3
|
-
PORT=
|
|
5
|
+
PORT=5002
|
|
4
6
|
|
|
5
7
|
POSTGRES_HOST=localhost
|
|
6
8
|
POSTGRES_PORT=5432
|
|
@@ -8,9 +10,12 @@ POSTGRES_USER=podokit
|
|
|
8
10
|
POSTGRES_PASSWORD=podokit
|
|
9
11
|
POSTGRES_DB=podokit
|
|
10
12
|
|
|
13
|
+
# Web -> API boundary (server-side only; not exposed to the browser)
|
|
14
|
+
BACKEND_INTERNAL_URL=http://localhost:5002
|
|
15
|
+
CORS_ORIGIN=http://localhost:5001
|
|
16
|
+
|
|
17
|
+
# ── Optional / development ───────────────────────────────────────────
|
|
18
|
+
# Redis — only needed by cache/queue modules. Start it with:
|
|
19
|
+
# docker compose --profile cache up
|
|
11
20
|
REDIS_HOST=localhost
|
|
12
21
|
REDIS_PORT=6379
|
|
13
|
-
|
|
14
|
-
# Web -> API boundary (server-side only; not exposed to the browser)
|
|
15
|
-
BACKEND_INTERNAL_URL=http://localhost:3000
|
|
16
|
-
CORS_ORIGIN=http://localhost:5173
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
services:
|
|
2
|
+
# --- Runtime (always started by `docker compose up`) ---
|
|
2
3
|
postgres:
|
|
3
4
|
image: postgres:16-alpine
|
|
4
5
|
environment:
|
|
@@ -15,8 +16,11 @@ services:
|
|
|
15
16
|
timeout: 5s
|
|
16
17
|
retries: 5
|
|
17
18
|
|
|
19
|
+
# --- Runtime, but only needed by cache/queue modules. Start with:
|
|
20
|
+
# docker compose --profile cache up
|
|
18
21
|
redis:
|
|
19
22
|
image: redis:7-alpine
|
|
23
|
+
profiles: ["cache"]
|
|
20
24
|
ports:
|
|
21
25
|
- "${REDIS_PORT:-6379}:6379"
|
|
22
26
|
healthcheck:
|
|
@@ -2,12 +2,20 @@
|
|
|
2
2
|
"name": "{{projectName}}",
|
|
3
3
|
"version": "0.0.0",
|
|
4
4
|
"private": true,
|
|
5
|
-
"engines": {
|
|
6
|
-
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=20"
|
|
7
|
+
},
|
|
8
|
+
"workspaces": [
|
|
9
|
+
"apps/*",
|
|
10
|
+
"tests"
|
|
11
|
+
],
|
|
7
12
|
"scripts": {
|
|
8
13
|
"dev": "npm run dev --workspaces --if-present",
|
|
9
14
|
"build": "npm run build --workspaces --if-present",
|
|
10
15
|
"lint": "npm run lint --workspaces --if-present",
|
|
11
|
-
"test": "npm run test --workspaces --if-present"
|
|
16
|
+
"test": "npm run test --workspaces --if-present",
|
|
17
|
+
"test:e2e": "npm run test:e2e -w tests",
|
|
18
|
+
"test:e2e:ui": "npm run test:e2e:ui -w tests",
|
|
19
|
+
"test:e2e:api": "npm run test:e2e:api -w tests"
|
|
12
20
|
}
|
|
13
21
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# tests
|
|
2
|
+
|
|
3
|
+
End-to-end tests for this app, run with [`@playwright/test`](https://playwright.dev):
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm run test:e2e # all specs (api + ui)
|
|
7
|
+
npm run test:e2e:api # backend endpoints (request-only)
|
|
8
|
+
npm run test:e2e:ui # pages (chromium)
|
|
9
|
+
npm run test:e2e:report # open the HTML report
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The suite runs against a live stack (web on `E2E_BASE_URL`, default
|
|
13
|
+
`http://localhost:5001`, proxying `/api/*` to the API). Start PostgreSQL, run the
|
|
14
|
+
auth migration, and start the API + web first. Files ending in `*.api.spec.ts` run
|
|
15
|
+
in the request-only `api` project; `*.ui.spec.ts` run in the chromium `ui` project.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
// Health endpoints are public and present in every generated app.
|
|
4
|
+
test("GET /health is public and ok @smoke", async ({ request }) => {
|
|
5
|
+
const res = await request.get("/api/health");
|
|
6
|
+
expect(res.ok()).toBeTruthy();
|
|
7
|
+
expect((await res.json()).status).toBe("ok");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("GET /health/ready reports the database", async ({ request }) => {
|
|
11
|
+
const res = await request.get("/api/health/ready");
|
|
12
|
+
expect(res.status()).toBe(200);
|
|
13
|
+
expect((await res.json()).status).toBe("ready");
|
|
14
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}-tests",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test:e2e": "playwright test",
|
|
7
|
+
"test:e2e:ui": "playwright test --project=ui",
|
|
8
|
+
"test:e2e:api": "playwright test --project=api",
|
|
9
|
+
"test:e2e:report": "playwright show-report"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@playwright/test": "^1.61.1",
|
|
13
|
+
"otpauth": "^9.5.1"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
// The suite runs against a live stack: web on E2E_BASE_URL (proxying /api/* to
|
|
4
|
+
// the API). Start it first (see docs/testing.md) — locally via the dev harness,
|
|
5
|
+
// in CI via scripts/e2e-ci.mjs. Two projects: `api` (request-only, backend e2e)
|
|
6
|
+
// and `ui` (chromium, pages). Files are routed by suffix (*.api/*.ui.spec.ts).
|
|
7
|
+
const baseURL = process.env.E2E_BASE_URL ?? "http://localhost:5001";
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
testDir: ".",
|
|
11
|
+
fullyParallel: true,
|
|
12
|
+
forbidOnly: !!process.env.CI,
|
|
13
|
+
retries: process.env.CI ? 2 : 0,
|
|
14
|
+
workers: process.env.CI ? 1 : undefined,
|
|
15
|
+
reporter: [["list"], ["html", { open: "never" }]],
|
|
16
|
+
use: {
|
|
17
|
+
baseURL,
|
|
18
|
+
trace: "on-first-retry",
|
|
19
|
+
screenshot: "only-on-failure",
|
|
20
|
+
},
|
|
21
|
+
projects: [
|
|
22
|
+
{ name: "api", testMatch: /.*\.api\.spec\.ts/ },
|
|
23
|
+
{ name: "ui", testMatch: /.*\.ui\.spec\.ts/, use: { ...devices["Desktop Chrome"] } },
|
|
24
|
+
],
|
|
25
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
// Module-agnostic: the app responds at "/" (a page or a redirect to it).
|
|
4
|
+
test("app is reachable @smoke", async ({ page }) => {
|
|
5
|
+
const res = await page.goto("/");
|
|
6
|
+
expect(res?.status()).toBeLessThan(400);
|
|
7
|
+
await expect(page.locator("body")).toBeVisible();
|
|
8
|
+
});
|
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type Tier } from "./lockfile";
|
|
2
|
+
/**
|
|
3
|
+
* `podo update` planner. For now it produces a dry-run plan only: it assembles
|
|
4
|
+
* the new-version tree from the installed CLI's templates and compares it to the
|
|
5
|
+
* working copy, classifying each file. Applying the plan (writing files, 3-way
|
|
6
|
+
* merging) is a later step (see ADR-0009).
|
|
7
|
+
*/
|
|
8
|
+
export type Action = "update" | "merge" | "conflict" | "add" | "remove" | "skip" | "up-to-date";
|
|
9
|
+
export interface FileChange {
|
|
10
|
+
path: string;
|
|
11
|
+
tier: Tier;
|
|
12
|
+
action: Action;
|
|
13
|
+
/** Why the action was chosen (for the report). */
|
|
14
|
+
note: string;
|
|
15
|
+
}
|
|
16
|
+
export interface UpdatePlan {
|
|
17
|
+
fromVersion: string;
|
|
18
|
+
toVersion: string;
|
|
19
|
+
template: string;
|
|
20
|
+
modules: string[];
|
|
21
|
+
changes: FileChange[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build the update plan. `templatesDir` is the installed CLI's template set (the
|
|
25
|
+
* new version); the lock records what PodoKit last wrote (to detect user edits).
|
|
26
|
+
*/
|
|
27
|
+
export declare function planUpdate(projectRoot: string, templatesDir: string): UpdatePlan;
|
|
28
|
+
export interface ApplyOptions {
|
|
29
|
+
/** Templates for the version the project is currently on, for 3-way merges. */
|
|
30
|
+
oldTemplatesDir?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ApplyResult {
|
|
33
|
+
written: string[];
|
|
34
|
+
removed: string[];
|
|
35
|
+
/** Files 3-way merged cleanly. */
|
|
36
|
+
merged: string[];
|
|
37
|
+
/** Files written with conflict markers for manual resolution. */
|
|
38
|
+
conflicts: string[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Apply an update to the working copy. Clean updates and additions are written,
|
|
42
|
+
* upstream removals deleted, and user-edited files 3-way merged against the old
|
|
43
|
+
* version (when `oldTemplatesDir` is given) or written with git-style conflict
|
|
44
|
+
* markers otherwise. Owned files are never touched. Refreshes the lockfile and
|
|
45
|
+
* stamps the new version.
|
|
46
|
+
*/
|
|
47
|
+
export declare function applyUpdate(projectRoot: string, templatesDir: string, options?: ApplyOptions): ApplyResult;
|
|
48
|
+
/** Count actions for a one-line summary. */
|
|
49
|
+
export declare function summarize(plan: UpdatePlan): Record<Action, number>;
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.planUpdate = planUpdate;
|
|
4
|
+
exports.applyUpdate = applyUpdate;
|
|
5
|
+
exports.summarize = summarize;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const podokit_template_engine_1 = require("@podosoft/podokit-template-engine");
|
|
9
|
+
const assemble_1 = require("./assemble");
|
|
10
|
+
const lockfile_1 = require("./lockfile");
|
|
11
|
+
const inspect_1 = require("./inspect");
|
|
12
|
+
function treeText(tree, path) {
|
|
13
|
+
const file = tree.get(path);
|
|
14
|
+
if (!file)
|
|
15
|
+
return null;
|
|
16
|
+
return typeof file.content === "string" ? file.content : file.content.toString("utf8");
|
|
17
|
+
}
|
|
18
|
+
function diskContent(projectRoot, path) {
|
|
19
|
+
const abs = (0, node_path_1.join)(projectRoot, path);
|
|
20
|
+
return (0, node_fs_1.existsSync)(abs) ? (0, node_fs_1.readFileSync)(abs) : null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build the update plan. `templatesDir` is the installed CLI's template set (the
|
|
24
|
+
* new version); the lock records what PodoKit last wrote (to detect user edits).
|
|
25
|
+
*/
|
|
26
|
+
function planUpdate(projectRoot, templatesDir) {
|
|
27
|
+
const manifest = (0, lockfile_1.readManifest)(projectRoot);
|
|
28
|
+
const lock = (0, lockfile_1.readFilesLock)(projectRoot);
|
|
29
|
+
if (!manifest || !lock)
|
|
30
|
+
throw new inspect_1.NotAProjectError();
|
|
31
|
+
const newTree = (0, assemble_1.assembleProject)({
|
|
32
|
+
templatesDir,
|
|
33
|
+
template: manifest.template,
|
|
34
|
+
answers: manifest.answers,
|
|
35
|
+
modules: manifest.modules.map((m) => m.name),
|
|
36
|
+
});
|
|
37
|
+
const changes = [];
|
|
38
|
+
const paths = new Set([...newTree.keys(), ...Object.keys(lock.files)]);
|
|
39
|
+
for (const path of [...paths].sort()) {
|
|
40
|
+
const locked = lock.files[path];
|
|
41
|
+
const newText = treeText(newTree, path);
|
|
42
|
+
const disk = diskContent(projectRoot, path);
|
|
43
|
+
// tier: prefer the lock; else classify the new file.
|
|
44
|
+
const tier = locked?.tier ??
|
|
45
|
+
(newText !== null ? (0, lockfile_1.classifyTier)(path, newText, manifest.ownedGlobs) : "managed");
|
|
46
|
+
if (tier === "owned") {
|
|
47
|
+
changes.push({ path, tier, action: "skip", note: "owned — never modified" });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// File removed upstream.
|
|
51
|
+
if (newText === null) {
|
|
52
|
+
if (disk === null)
|
|
53
|
+
continue; // already gone
|
|
54
|
+
const edited = locked && (0, podokit_template_engine_1.hashContent)(disk) !== locked.outHash;
|
|
55
|
+
changes.push({
|
|
56
|
+
path,
|
|
57
|
+
tier,
|
|
58
|
+
action: edited ? "conflict" : "remove",
|
|
59
|
+
note: edited ? "removed upstream but edited locally" : "removed upstream",
|
|
60
|
+
});
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const newHash = (0, podokit_template_engine_1.hashContent)(newText);
|
|
64
|
+
// New file added upstream.
|
|
65
|
+
if (disk === null) {
|
|
66
|
+
changes.push({ path, tier, action: "add", note: "new in this version" });
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const diskHash = (0, podokit_template_engine_1.hashContent)(disk);
|
|
70
|
+
if (diskHash === newHash) {
|
|
71
|
+
changes.push({ path, tier, action: "up-to-date", note: "already current" });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const edited = !locked || diskHash !== locked.outHash;
|
|
75
|
+
changes.push({
|
|
76
|
+
path,
|
|
77
|
+
tier,
|
|
78
|
+
action: edited ? "conflict" : "update",
|
|
79
|
+
note: edited ? "you edited this; update would need a merge" : "clean update",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
fromVersion: manifest.podokitVersion,
|
|
84
|
+
toVersion: (0, lockfile_1.podokitVersion)(),
|
|
85
|
+
template: manifest.template,
|
|
86
|
+
modules: manifest.modules.map((m) => m.name),
|
|
87
|
+
changes,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function writeFile(projectRoot, path, content) {
|
|
91
|
+
const abs = (0, node_path_1.join)(projectRoot, path);
|
|
92
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(abs), { recursive: true });
|
|
93
|
+
(0, node_fs_1.writeFileSync)(abs, content);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Apply an update to the working copy. Clean updates and additions are written,
|
|
97
|
+
* upstream removals deleted, and user-edited files 3-way merged against the old
|
|
98
|
+
* version (when `oldTemplatesDir` is given) or written with git-style conflict
|
|
99
|
+
* markers otherwise. Owned files are never touched. Refreshes the lockfile and
|
|
100
|
+
* stamps the new version.
|
|
101
|
+
*/
|
|
102
|
+
function applyUpdate(projectRoot, templatesDir, options = {}) {
|
|
103
|
+
const manifest = (0, lockfile_1.readManifest)(projectRoot);
|
|
104
|
+
if (!manifest)
|
|
105
|
+
throw new inspect_1.NotAProjectError();
|
|
106
|
+
const plan = planUpdate(projectRoot, templatesDir);
|
|
107
|
+
const modules = manifest.modules.map((m) => m.name);
|
|
108
|
+
const newTree = (0, assemble_1.assembleProject)({ templatesDir, template: manifest.template, answers: manifest.answers, modules });
|
|
109
|
+
const oldTree = options.oldTemplatesDir
|
|
110
|
+
? (0, assemble_1.assembleProject)({ templatesDir: options.oldTemplatesDir, template: manifest.template, answers: manifest.answers, modules })
|
|
111
|
+
: null;
|
|
112
|
+
const result = { written: [], removed: [], merged: [], conflicts: [] };
|
|
113
|
+
for (const change of plan.changes) {
|
|
114
|
+
const newText = treeText(newTree, change.path);
|
|
115
|
+
if (change.action === "update" || change.action === "add") {
|
|
116
|
+
writeFile(projectRoot, change.path, newText ?? "");
|
|
117
|
+
result.written.push(change.path);
|
|
118
|
+
}
|
|
119
|
+
else if (change.action === "remove") {
|
|
120
|
+
(0, node_fs_1.rmSync)((0, node_path_1.join)(projectRoot, change.path), { force: true });
|
|
121
|
+
result.removed.push(change.path);
|
|
122
|
+
}
|
|
123
|
+
else if (change.action === "conflict" && newText !== null) {
|
|
124
|
+
const disk = (0, node_fs_1.readFileSync)((0, node_path_1.join)(projectRoot, change.path), "utf8");
|
|
125
|
+
const base = oldTree ? treeText(oldTree, change.path) : null;
|
|
126
|
+
if (base === null) {
|
|
127
|
+
// No old version to merge against — never clobber the user's edits.
|
|
128
|
+
result.conflicts.push(change.path);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const merge = (0, podokit_template_engine_1.threeWayMerge)(base, disk, newText, { current: "current", next: "podokit" });
|
|
132
|
+
writeFile(projectRoot, change.path, merge.merged);
|
|
133
|
+
if (merge.conflicts > 0)
|
|
134
|
+
result.conflicts.push(change.path);
|
|
135
|
+
else
|
|
136
|
+
result.merged.push(change.path);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Refresh the lock to match what we just wrote and stamp the new version.
|
|
140
|
+
(0, lockfile_1.writeFilesLock)(projectRoot, (0, lockfile_1.computeFilesLock)(projectRoot, manifest.ownedGlobs));
|
|
141
|
+
(0, lockfile_1.writeManifest)(projectRoot, { ...manifest, podokitVersion: (0, lockfile_1.podokitVersion)() });
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
/** Count actions for a one-line summary. */
|
|
145
|
+
function summarize(plan) {
|
|
146
|
+
const counts = {
|
|
147
|
+
update: 0,
|
|
148
|
+
merge: 0,
|
|
149
|
+
conflict: 0,
|
|
150
|
+
add: 0,
|
|
151
|
+
remove: 0,
|
|
152
|
+
skip: 0,
|
|
153
|
+
"up-to-date": 0,
|
|
154
|
+
};
|
|
155
|
+
for (const change of plan.changes)
|
|
156
|
+
counts[change.action] += 1;
|
|
157
|
+
return counts;
|
|
158
|
+
}
|