@aurostack/stacks 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/cli/src/extract.mjs +165 -0
- package/cli/src/fsx.mjs +197 -0
- package/cli/src/generate.mjs +235 -0
- package/cli/src/hooks.mjs +55 -0
- package/cli/src/manifest.mjs +163 -0
- package/cli/src/strip.mjs +208 -0
- package/cli/src/tokens.mjs +102 -0
- package/cli/stack.mjs +672 -0
- package/package.json +37 -0
- package/templates/nest-api/files/.bin/db.ts +123 -0
- package/templates/nest-api/files/.bin/dc.ts +74 -0
- package/templates/nest-api/files/.bin/helpers.ts +49 -0
- package/templates/nest-api/files/.bin/secrets.ts +68 -0
- package/templates/nest-api/files/.bin/start.ts +47 -0
- package/templates/nest-api/files/.dockerignore +7 -0
- package/templates/nest-api/files/.env.example +91 -0
- package/templates/nest-api/files/.env.test.example +75 -0
- package/templates/nest-api/files/.forgejo/workflows/build-and-push.yml +41 -0
- package/templates/nest-api/files/.forgejo/workflows/test-e2e.yml +83 -0
- package/templates/nest-api/files/.forgejo/workflows/test.yml +53 -0
- package/templates/nest-api/files/.npmrc.example +1 -0
- package/templates/nest-api/files/.nvmrc +1 -0
- package/templates/nest-api/files/.prettierrc +8 -0
- package/templates/nest-api/files/.swcrc +15 -0
- package/templates/nest-api/files/.watchmanconfig +3 -0
- package/templates/nest-api/files/.yarnrc.yml +1 -0
- package/templates/nest-api/files/Dockerfile +40 -0
- package/templates/nest-api/files/__mocks__/activation-twofa.js +7 -0
- package/templates/nest-api/files/__mocks__/activation.js +7 -0
- package/templates/nest-api/files/__mocks__/forgot-password.js +7 -0
- package/templates/nest-api/files/__mocks__/main-layout.js +7 -0
- package/templates/nest-api/files/__mocks__/password-changed.js +7 -0
- package/templates/nest-api/files/__mocks__/reset-password.js +7 -0
- package/templates/nest-api/files/__mocks__/set-password.js +7 -0
- package/templates/nest-api/files/__mocks__/welcome.js +7 -0
- package/templates/nest-api/files/_gitignore +62 -0
- package/templates/nest-api/files/compose.test.yml +33 -0
- package/templates/nest-api/files/compose.yml +39 -0
- package/templates/nest-api/files/emails/activation-twofa.tsx +58 -0
- package/templates/nest-api/files/emails/activation.tsx +36 -0
- package/templates/nest-api/files/emails/forgot-password.tsx +29 -0
- package/templates/nest-api/files/emails/main-layout.tsx +134 -0
- package/templates/nest-api/files/emails/password-changed.tsx +24 -0
- package/templates/nest-api/files/emails/reset-password.tsx +24 -0
- package/templates/nest-api/files/emails/set-password.tsx +25 -0
- package/templates/nest-api/files/emails/welcome.tsx +39 -0
- package/templates/nest-api/files/eslint.config.mjs +28 -0
- package/templates/nest-api/files/global.d.ts +5 -0
- package/templates/nest-api/files/nest-cli.json +8 -0
- package/templates/nest-api/files/package.json +150 -0
- package/templates/nest-api/files/prisma/schema/auth.prisma +75 -0
- package/templates/nest-api/files/prisma/schema/base.prisma +17 -0
- package/templates/nest-api/files/prisma/schema/enum.prisma +17 -0
- package/templates/nest-api/files/prisma/schema/notification.prisma +14 -0
- package/templates/nest-api/files/prisma.config.ts +13 -0
- package/templates/nest-api/files/public/.gitkeep +0 -0
- package/templates/nest-api/files/seeders/index.ts +27 -0
- package/templates/nest-api/files/seeders/util.ts +88 -0
- package/templates/nest-api/files/src/__tests__/app.controller.spec.ts +29 -0
- package/templates/nest-api/files/src/app.controller.ts +40 -0
- package/templates/nest-api/files/src/app.module.ts +268 -0
- package/templates/nest-api/files/src/app.resolver.ts +32 -0
- package/templates/nest-api/files/src/app.setup.ts +178 -0
- package/templates/nest-api/files/src/common/__tests__/auth.service.spec.ts +31 -0
- package/templates/nest-api/files/src/common/__tests__/cache.service.spec.ts +214 -0
- package/templates/nest-api/files/src/common/__tests__/config.service.spec.ts +56 -0
- package/templates/nest-api/files/src/common/__tests__/generator.service.spec.ts +114 -0
- package/templates/nest-api/files/src/common/__tests__/health.controller.spec.ts +72 -0
- package/templates/nest-api/files/src/common/__tests__/helpers.spec.ts +124 -0
- package/templates/nest-api/files/src/common/__tests__/mail.service.spec.ts +75 -0
- package/templates/nest-api/files/src/common/__tests__/prisma.indicator.spec.ts +39 -0
- package/templates/nest-api/files/src/common/__tests__/prisma.service.spec.ts +15 -0
- package/templates/nest-api/files/src/common/__tests__/records.spec.ts +87 -0
- package/templates/nest-api/files/src/common/__tests__/schemas.spec.ts +142 -0
- package/templates/nest-api/files/src/common/__tests__/throttler-storage.service.spec.ts +80 -0
- package/templates/nest-api/files/src/common/common.module.ts +72 -0
- package/templates/nest-api/files/src/common/constants/index.ts +2 -0
- package/templates/nest-api/files/src/common/constants/options.ts +52 -0
- package/templates/nest-api/files/src/common/constants/pino.ts +24 -0
- package/templates/nest-api/files/src/common/controllers/health.controller.ts +54 -0
- package/templates/nest-api/files/src/common/controllers/index.ts +2 -0
- package/templates/nest-api/files/src/common/controllers/metrics.controller.ts +21 -0
- package/templates/nest-api/files/src/common/decorators/index.ts +2 -0
- package/templates/nest-api/files/src/common/decorators/misc.decorator.ts +49 -0
- package/templates/nest-api/files/src/common/decorators/multi-part.decorator.ts +6 -0
- package/templates/nest-api/files/src/common/dto/filters.dto.ts +25 -0
- package/templates/nest-api/files/src/common/dto/index.ts +1 -0
- package/templates/nest-api/files/src/common/entity/index.ts +2 -0
- package/templates/nest-api/files/src/common/entity/ok.entity.ts +11 -0
- package/templates/nest-api/files/src/common/entity/pagination.entity.ts +24 -0
- package/templates/nest-api/files/src/common/exceptions/index.ts +1 -0
- package/templates/nest-api/files/src/common/exceptions/prisma.exception.ts +57 -0
- package/templates/nest-api/files/src/common/guards/feature-flag.guard.ts +51 -0
- package/templates/nest-api/files/src/common/guards/index.ts +1 -0
- package/templates/nest-api/files/src/common/interceptors/index.ts +1 -0
- package/templates/nest-api/files/src/common/interceptors/metrics.interceptor.ts +37 -0
- package/templates/nest-api/files/src/common/misc/helpers.ts +26 -0
- package/templates/nest-api/files/src/common/misc/index.ts +3 -0
- package/templates/nest-api/files/src/common/misc/origin-builder.ts +56 -0
- package/templates/nest-api/files/src/common/misc/prisma.indicator.ts +24 -0
- package/templates/nest-api/files/src/common/modules/index.ts +3 -0
- package/templates/nest-api/files/src/common/modules/logger.module.ts +104 -0
- package/templates/nest-api/files/src/common/modules/metrics.module.ts +28 -0
- package/templates/nest-api/files/src/common/modules/queue.module.ts +23 -0
- package/templates/nest-api/files/src/common/processors/index.ts +1 -0
- package/templates/nest-api/files/src/common/processors/mail/index.ts +47 -0
- package/templates/nest-api/files/src/common/processors/mail/process.ts +225 -0
- package/templates/nest-api/files/src/common/schemas/fields.ts +76 -0
- package/templates/nest-api/files/src/common/schemas/index.ts +1 -0
- package/templates/nest-api/files/src/common/services/auth-rate-limit.storage.ts +79 -0
- package/templates/nest-api/files/src/common/services/auth.service.ts +16 -0
- package/templates/nest-api/files/src/common/services/cache.service.ts +118 -0
- package/templates/nest-api/files/src/common/services/config.service.ts +388 -0
- package/templates/nest-api/files/src/common/services/feature-flag.service.ts +117 -0
- package/templates/nest-api/files/src/common/services/generator.service.ts +54 -0
- package/templates/nest-api/files/src/common/services/index.ts +10 -0
- package/templates/nest-api/files/src/common/services/logger.service.ts +14 -0
- package/templates/nest-api/files/src/common/services/mail.service.ts +44 -0
- package/templates/nest-api/files/src/common/services/prisma.service.ts +106 -0
- package/templates/nest-api/files/src/common/services/throttler-storage.service.ts +112 -0
- package/templates/nest-api/files/src/common/types/email.ts +55 -0
- package/templates/nest-api/files/src/common/types/index.ts +3 -0
- package/templates/nest-api/files/src/common/types/misc.ts +20 -0
- package/templates/nest-api/files/src/common/types/prisma.ts +18 -0
- package/templates/nest-api/files/src/common/utils/enum.ts +39 -0
- package/templates/nest-api/files/src/common/utils/helpers.ts +139 -0
- package/templates/nest-api/files/src/common/utils/index.ts +5 -0
- package/templates/nest-api/files/src/common/utils/ip.ts +22 -0
- package/templates/nest-api/files/src/common/utils/records.ts +66 -0
- package/templates/nest-api/files/src/common/utils/redis.keys.ts +31 -0
- package/templates/nest-api/files/src/lib/__tests__/access.spec.ts +33 -0
- package/templates/nest-api/files/src/lib/access.ts +114 -0
- package/templates/nest-api/files/src/lib/auth.ts +76 -0
- package/templates/nest-api/files/src/lib/rate-limit.ts +20 -0
- package/templates/nest-api/files/src/main.ts +28 -0
- package/templates/nest-api/files/src/media/__tests__/media.service.spec.ts +60 -0
- package/templates/nest-api/files/src/media/__tests__/s3.service.spec.ts +159 -0
- package/templates/nest-api/files/src/media/entity/file.entity.ts +7 -0
- package/templates/nest-api/files/src/media/entity/index.ts +1 -0
- package/templates/nest-api/files/src/media/media.module.ts +20 -0
- package/templates/nest-api/files/src/media/processors/index.ts +44 -0
- package/templates/nest-api/files/src/media/services/index.ts +2 -0
- package/templates/nest-api/files/src/media/services/media.service.ts +29 -0
- package/templates/nest-api/files/src/media/services/s3.service.ts +168 -0
- package/templates/nest-api/files/src/notifications/controllers/index.ts +1 -0
- package/templates/nest-api/files/src/notifications/controllers/notifications.controller.ts +72 -0
- package/templates/nest-api/files/src/notifications/dto/index.ts +1 -0
- package/templates/nest-api/files/src/notifications/dto/notification.dto.ts +11 -0
- package/templates/nest-api/files/src/notifications/entity/index.ts +1 -0
- package/templates/nest-api/files/src/notifications/entity/notification.entity.ts +37 -0
- package/templates/nest-api/files/src/notifications/notifications.module.ts +11 -0
- package/templates/nest-api/files/src/notifications/services/index.ts +1 -0
- package/templates/nest-api/files/src/notifications/services/notifications.service.ts +98 -0
- package/templates/nest-api/files/src/realtime/__tests__/events.gateway.spec.ts +88 -0
- package/templates/nest-api/files/src/realtime/__tests__/rooms.spec.ts +29 -0
- package/templates/nest-api/files/src/realtime/__tests__/ws-permissions.guard.spec.ts +54 -0
- package/templates/nest-api/files/src/realtime/adapters/redis-io.adapter.ts +57 -0
- package/templates/nest-api/files/src/realtime/constants/index.ts +68 -0
- package/templates/nest-api/files/src/realtime/decorators/index.ts +1 -0
- package/templates/nest-api/files/src/realtime/decorators/ws-permissions.decorator.ts +14 -0
- package/templates/nest-api/files/src/realtime/gateways/events.gateway.ts +187 -0
- package/templates/nest-api/files/src/realtime/guards/index.ts +2 -0
- package/templates/nest-api/files/src/realtime/guards/ws-auth.guard.ts +20 -0
- package/templates/nest-api/files/src/realtime/guards/ws-permissions.guard.ts +35 -0
- package/templates/nest-api/files/src/realtime/misc/session-from-socket.ts +54 -0
- package/templates/nest-api/files/src/realtime/realtime.module.ts +26 -0
- package/templates/nest-api/files/src/realtime/services/presence.service.ts +56 -0
- package/templates/nest-api/files/src/realtime/services/realtime.service.ts +72 -0
- package/templates/nest-api/files/src/users/__tests__/current-user.controller.spec.ts +77 -0
- package/templates/nest-api/files/src/users/__tests__/current-user.service.spec.ts +159 -0
- package/templates/nest-api/files/src/users/__tests__/user.entity.spec.ts +175 -0
- package/templates/nest-api/files/src/users/__tests__/users.controller.spec.ts +89 -0
- package/templates/nest-api/files/src/users/__tests__/users.service.spec.ts +398 -0
- package/templates/nest-api/files/src/users/controllers/current-user.controller.ts +82 -0
- package/templates/nest-api/files/src/users/controllers/index.ts +2 -0
- package/templates/nest-api/files/src/users/controllers/users.controller.ts +40 -0
- package/templates/nest-api/files/src/users/decorators/index.ts +1 -0
- package/templates/nest-api/files/src/users/decorators/permissions.decorator.ts +24 -0
- package/templates/nest-api/files/src/users/dto/avatar.dto.ts +13 -0
- package/templates/nest-api/files/src/users/dto/filters.dto.ts +8 -0
- package/templates/nest-api/files/src/users/dto/index.ts +2 -0
- package/templates/nest-api/files/src/users/entity/index.ts +1 -0
- package/templates/nest-api/files/src/users/entity/user.entity.ts +53 -0
- package/templates/nest-api/files/src/users/services/current-user.service.ts +46 -0
- package/templates/nest-api/files/src/users/services/index.ts +2 -0
- package/templates/nest-api/files/src/users/services/users.service.ts +88 -0
- package/templates/nest-api/files/src/users/users.module.ts +10 -0
- package/templates/nest-api/files/test/app.spec.ts +102 -0
- package/templates/nest-api/files/test/auth.spec.ts +108 -0
- package/templates/nest-api/files/test/current-user.spec.ts +154 -0
- package/templates/nest-api/files/test/factory/app.ts +172 -0
- package/templates/nest-api/files/test/factory/constants.ts +22 -0
- package/templates/nest-api/files/test/factory/dto.ts +27 -0
- package/templates/nest-api/files/test/factory/entity.ts +70 -0
- package/templates/nest-api/files/test/factory/gen.ts +75 -0
- package/templates/nest-api/files/test/factory/util.ts +21 -0
- package/templates/nest-api/files/test/notifications.spec.ts +176 -0
- package/templates/nest-api/files/test/rate-limit.spec.ts +180 -0
- package/templates/nest-api/files/test/setup.ts +1 -0
- package/templates/nest-api/files/test/users.spec.ts +437 -0
- package/templates/nest-api/files/tsconfig.build.json +13 -0
- package/templates/nest-api/files/tsconfig.json +35 -0
- package/templates/nest-api/files/vitest.config.ts +53 -0
- package/templates/nest-api/template.json +452 -0
- package/templates/node-worker/files/.bin/db.ts +123 -0
- package/templates/node-worker/files/.bin/helpers.ts +49 -0
- package/templates/node-worker/files/.bin/secrets.ts +68 -0
- package/templates/node-worker/files/.dockerignore +5 -0
- package/templates/node-worker/files/.env.example +21 -0
- package/templates/node-worker/files/.forgejo/workflows/build-and-push.yml +41 -0
- package/templates/node-worker/files/.nvmrc +1 -0
- package/templates/node-worker/files/.prettierignore +4 -0
- package/templates/node-worker/files/.prettierrc +8 -0
- package/templates/node-worker/files/.yarnrc.yml +5 -0
- package/templates/node-worker/files/Dockerfile +63 -0
- package/templates/node-worker/files/Dockerfile.dev +59 -0
- package/templates/node-worker/files/_gitignore +48 -0
- package/templates/node-worker/files/ecosystem.config.cjs +10 -0
- package/templates/node-worker/files/eslint.config.js +37 -0
- package/templates/node-worker/files/package.json +67 -0
- package/templates/node-worker/files/prisma/_gitignore +1 -0
- package/templates/node-worker/files/prisma/schema/base.prisma +36 -0
- package/templates/node-worker/files/prisma.config.ts +13 -0
- package/templates/node-worker/files/src/config.ts +55 -0
- package/templates/node-worker/files/src/db.ts +118 -0
- package/templates/node-worker/files/src/index.ts +7 -0
- package/templates/node-worker/files/src/logger.ts +45 -0
- package/templates/node-worker/files/src/types.ts +23 -0
- package/templates/node-worker/files/src/utils/browser.ts +16 -0
- package/templates/node-worker/files/src/utils/date.ts +68 -0
- package/templates/node-worker/files/src/utils/helpers.ts +17 -0
- package/templates/node-worker/files/src/utils/number.ts +47 -0
- package/templates/node-worker/files/src/utils/worker.ts +29 -0
- package/templates/node-worker/files/src/workers/constants.ts +6 -0
- package/templates/node-worker/files/src/workers/example/index.ts +27 -0
- package/templates/node-worker/files/src/workers/index.ts +1 -0
- package/templates/node-worker/files/tsconfig.json +30 -0
- package/templates/node-worker/template.json +135 -0
- package/templates/py-worker/files/.dockerignore +176 -0
- package/templates/py-worker/files/.env.example +20 -0
- package/templates/py-worker/files/.forgejo/workflows/build-amd-push.yml +39 -0
- package/templates/py-worker/files/Dockerfile +10 -0
- package/templates/py-worker/files/README.md +3 -0
- package/templates/py-worker/files/_gitignore +176 -0
- package/templates/py-worker/files/config.py +89 -0
- package/templates/py-worker/files/models/decorators.py +42 -0
- package/templates/py-worker/files/models/enums.py +32 -0
- package/templates/py-worker/files/models/mixins.py +61 -0
- package/templates/py-worker/files/requirements.txt +18 -0
- package/templates/py-worker/files/utils/db.py +67 -0
- package/templates/py-worker/files/utils/logger.py +97 -0
- package/templates/py-worker/files/utils/misc.py +45 -0
- package/templates/py-worker/files/utils/worker.py +80 -0
- package/templates/py-worker/files/worker.py +41 -0
- package/templates/py-worker/files/workers/hello/process.py +22 -0
- package/templates/py-worker/template.json +87 -0
- package/templates/react-app/README.md +78 -0
- package/templates/react-app/derive.sh +128 -0
- package/templates/react-app/files/.bin/openapi.js +32 -0
- package/templates/react-app/files/.bin/secrets.js +47 -0
- package/templates/react-app/files/.dockerignore +16 -0
- package/templates/react-app/files/.editorconfig +12 -0
- package/templates/react-app/files/.env.example +34 -0
- package/templates/react-app/files/.forgejo/workflows/build-and-push.yml +55 -0
- package/templates/react-app/files/.forgejo/workflows/ci.yml +39 -0
- package/templates/react-app/files/.husky/pre-commit +1 -0
- package/templates/react-app/files/.nvmrc +1 -0
- package/templates/react-app/files/.prettierignore +6 -0
- package/templates/react-app/files/.prettierrc +9 -0
- package/templates/react-app/files/.yarnrc.yml +2 -0
- package/templates/react-app/files/Dockerfile +52 -0
- package/templates/react-app/files/_gitignore +33 -0
- package/templates/react-app/files/build.sh +53 -0
- package/templates/react-app/files/docker/nginx.conf +134 -0
- package/templates/react-app/files/eslint.config.mjs +75 -0
- package/templates/react-app/files/index.html +62 -0
- package/templates/react-app/files/openapi-ts.config.ts +22 -0
- package/templates/react-app/files/package.json +80 -0
- package/templates/react-app/files/src/app/nav.tsx +32 -0
- package/templates/react-app/files/src/app/providers.tsx +39 -0
- package/templates/react-app/files/src/app/router.tsx +142 -0
- package/templates/react-app/files/src/app/store.ts +20 -0
- package/templates/react-app/files/src/features/auth/components/auth-card.tsx +23 -0
- package/templates/react-app/files/src/features/auth/components/checkbox-field.tsx +32 -0
- package/templates/react-app/files/src/features/auth/components/google-button.tsx +41 -0
- package/templates/react-app/files/src/features/auth/components/or-divider.tsx +9 -0
- package/templates/react-app/files/src/features/auth/components/password-field.tsx +70 -0
- package/templates/react-app/files/src/features/auth/components/submit-button.tsx +16 -0
- package/templates/react-app/files/src/features/auth/lib/auth-actions.ts +68 -0
- package/templates/react-app/files/src/features/auth/lib/password-strength.ts +20 -0
- package/templates/react-app/files/src/features/auth/lib/redirect.ts +6 -0
- package/templates/react-app/files/src/features/auth/lib/schemas.ts +37 -0
- package/templates/react-app/files/src/features/seo/routes.ts +65 -0
- package/templates/react-app/files/src/lib/env.ts +28 -0
- package/templates/react-app/files/src/lib/paths.ts +58 -0
- package/templates/react-app/files/src/main.tsx +19 -0
- package/templates/react-app/files/src/routes/admin/home.tsx +24 -0
- package/templates/react-app/files/src/routes/auth/forgot-password.tsx +93 -0
- package/templates/react-app/files/src/routes/auth/login.tsx +87 -0
- package/templates/react-app/files/src/routes/auth/reset-password.tsx +83 -0
- package/templates/react-app/files/src/routes/auth/signup.tsx +117 -0
- package/templates/react-app/files/src/routes/auth/verified.tsx +31 -0
- package/templates/react-app/files/src/routes/auth/verify-email.tsx +97 -0
- package/templates/react-app/files/src/routes/home.tsx +17 -0
- package/templates/react-app/files/src/routes/legal/privacy.tsx +26 -0
- package/templates/react-app/files/src/routes/legal/terms.tsx +28 -0
- package/templates/react-app/files/src/routes/marketing/home.tsx +18 -0
- package/templates/react-app/files/src/routes/settings.tsx +10 -0
- package/templates/react-app/files/src/shared/api/base-api.ts +24 -0
- package/templates/react-app/files/src/shared/api/base-query.ts +29 -0
- package/templates/react-app/files/src/shared/api/config.ts +21 -0
- package/templates/react-app/files/src/shared/api/index.ts +15 -0
- package/templates/react-app/files/src/shared/api/realtime-events.ts +34 -0
- package/templates/react-app/files/src/shared/api/socket.ts +41 -0
- package/templates/react-app/files/src/shared/api/store.ts +39 -0
- package/templates/react-app/files/src/shared/api/upload-query.ts +115 -0
- package/templates/react-app/files/src/shared/auth/client.ts +27 -0
- package/templates/react-app/files/src/shared/auth/config.ts +28 -0
- package/templates/react-app/files/src/shared/auth/guards.tsx +75 -0
- package/templates/react-app/files/src/shared/auth/hooks.ts +13 -0
- package/templates/react-app/files/src/shared/auth/index.ts +20 -0
- package/templates/react-app/files/src/shared/auth/logout.ts +23 -0
- package/templates/react-app/files/src/shared/auth/provider.tsx +52 -0
- package/templates/react-app/files/src/shared/auth/redirect.ts +65 -0
- package/templates/react-app/files/src/shared/auth/slice.ts +54 -0
- package/templates/react-app/files/src/shared/hooks/index.ts +6 -0
- package/templates/react-app/files/src/shared/layouts/app-shell/app-shell.tsx +225 -0
- package/templates/react-app/files/src/shared/layouts/app-shell/user-menu.tsx +74 -0
- package/templates/react-app/files/src/shared/layouts/auth-layout.tsx +137 -0
- package/templates/react-app/files/src/shared/layouts/error-boundary.tsx +38 -0
- package/templates/react-app/files/src/shared/layouts/index.ts +23 -0
- package/templates/react-app/files/src/shared/layouts/marketing/marketing-layout.tsx +29 -0
- package/templates/react-app/files/src/shared/layouts/marketing/section-heading.tsx +38 -0
- package/templates/react-app/files/src/shared/layouts/marketing/section.tsx +30 -0
- package/templates/react-app/files/src/shared/layouts/states/app-shell-skeleton.tsx +47 -0
- package/templates/react-app/files/src/shared/layouts/states/empty.tsx +22 -0
- package/templates/react-app/files/src/shared/layouts/states/forbidden.tsx +14 -0
- package/templates/react-app/files/src/shared/layouts/states/loading.tsx +11 -0
- package/templates/react-app/files/src/shared/layouts/states/not-found.tsx +21 -0
- package/templates/react-app/files/src/shared/layouts/states/page-skeleton.tsx +39 -0
- package/templates/react-app/files/src/shared/layouts/states/route-fallback.tsx +26 -0
- package/templates/react-app/files/src/shared/layouts/states/state-screen.tsx +22 -0
- package/templates/react-app/files/src/shared/types/README.md +28 -0
- package/templates/react-app/files/src/shared/types/api/index.ts +9 -0
- package/templates/react-app/files/src/shared/types/api/zod.gen.ts +5 -0
- package/templates/react-app/files/src/shared/types/index.ts +4 -0
- package/templates/react-app/files/src/shared/ui/components/brand/logo.tsx +51 -0
- package/templates/react-app/files/src/shared/ui/components/brand/social-icons.tsx +37 -0
- package/templates/react-app/files/src/shared/ui/components/form/form.tsx +120 -0
- package/templates/react-app/files/src/shared/ui/components/ui/alert.tsx +25 -0
- package/templates/react-app/files/src/shared/ui/components/ui/button.tsx +43 -0
- package/templates/react-app/files/src/shared/ui/components/ui/calendar.tsx +78 -0
- package/templates/react-app/files/src/shared/ui/components/ui/card.tsx +44 -0
- package/templates/react-app/files/src/shared/ui/components/ui/checkbox.tsx +16 -0
- package/templates/react-app/files/src/shared/ui/components/ui/date-picker.tsx +76 -0
- package/templates/react-app/files/src/shared/ui/components/ui/input.tsx +22 -0
- package/templates/react-app/files/src/shared/ui/components/ui/label.tsx +19 -0
- package/templates/react-app/files/src/shared/ui/components/ui/modal.tsx +94 -0
- package/templates/react-app/files/src/shared/ui/components/ui/popover.tsx +31 -0
- package/templates/react-app/files/src/shared/ui/components/ui/progress.tsx +33 -0
- package/templates/react-app/files/src/shared/ui/components/ui/search-input.tsx +69 -0
- package/templates/react-app/files/src/shared/ui/components/ui/select.tsx +80 -0
- package/templates/react-app/files/src/shared/ui/components/ui/skeleton.tsx +12 -0
- package/templates/react-app/files/src/shared/ui/components/ui/sonner.tsx +23 -0
- package/templates/react-app/files/src/shared/ui/components/ui/spinner.tsx +8 -0
- package/templates/react-app/files/src/shared/ui/components/ui/textarea.tsx +22 -0
- package/templates/react-app/files/src/shared/ui/index.ts +45 -0
- package/templates/react-app/files/src/shared/ui/lib/utils.ts +7 -0
- package/templates/react-app/files/src/shared/ui/lib/zod-setup.ts +28 -0
- package/templates/react-app/files/src/shared/ui/meta/brand.ts +18 -0
- package/templates/react-app/files/src/shared/ui/meta/meta-context.ts +30 -0
- package/templates/react-app/files/src/shared/ui/meta/meta-provider.tsx +24 -0
- package/templates/react-app/files/src/shared/ui/meta/page-meta.tsx +69 -0
- package/templates/react-app/files/src/shared/ui/meta/use-meta-config.ts +11 -0
- package/templates/react-app/files/src/shared/ui/styles/globals.css +285 -0
- package/templates/react-app/files/src/shared/ui/styles/theme.css +48 -0
- package/templates/react-app/files/src/shared/ui/theme/init-script.ts +23 -0
- package/templates/react-app/files/src/shared/ui/theme/theme-colors.ts +14 -0
- package/templates/react-app/files/src/shared/ui/theme/theme-context.ts +60 -0
- package/templates/react-app/files/src/shared/ui/theme/theme-provider.tsx +101 -0
- package/templates/react-app/files/src/shared/ui/theme/theme-toggle.tsx +40 -0
- package/templates/react-app/files/src/shared/ui/theme/use-theme.ts +11 -0
- package/templates/react-app/files/src/vite-env.d.ts +13 -0
- package/templates/react-app/files/tsconfig.json +26 -0
- package/templates/react-app/files/tsconfig.node.json +25 -0
- package/templates/react-app/files/vite.config.ts +344 -0
- package/templates/react-app/overrides/src/routes/auth/forgot-password.tsx +93 -0
- package/templates/react-app/overrides/src/routes/auth/login.tsx +87 -0
- package/templates/react-app/overrides/src/routes/auth/signup.tsx +117 -0
- package/templates/react-app/overrides/src/routes/auth/verified.tsx +31 -0
- package/templates/react-app/overrides/src/routes/auth/verify-email.tsx +97 -0
- package/templates/react-app/overrides/src/shared/auth/config.ts +28 -0
- package/templates/react-app/overrides/src/shared/auth/guards.tsx +75 -0
- package/templates/react-app/overrides/src/shared/auth/index.ts +20 -0
- package/templates/react-app/overrides/src/shared/auth/redirect.ts +65 -0
- package/templates/react-app/overrides/src/shared/hooks/index.ts +6 -0
- package/templates/react-app/overrides/src/shared/types/README.md +28 -0
- package/templates/react-app/template.json +222 -0
- package/templates/react-monorepo/files/.dockerignore +19 -0
- package/templates/react-monorepo/files/.editorconfig +12 -0
- package/templates/react-monorepo/files/.forgejo/workflows/build-and-push.yml +98 -0
- package/templates/react-monorepo/files/.forgejo/workflows/ci.yml +35 -0
- package/templates/react-monorepo/files/.husky/pre-commit +1 -0
- package/templates/react-monorepo/files/.nvmrc +1 -0
- package/templates/react-monorepo/files/.prettierignore +6 -0
- package/templates/react-monorepo/files/.prettierrc +9 -0
- package/templates/react-monorepo/files/.yarnrc.yml +2 -0
- package/templates/react-monorepo/files/Dockerfile +90 -0
- package/templates/react-monorepo/files/_gitignore +35 -0
- package/templates/react-monorepo/files/apps/admin/.bin/secrets.js +57 -0
- package/templates/react-monorepo/files/apps/admin/.env.example +16 -0
- package/templates/react-monorepo/files/apps/admin/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/apps/admin/index.html +57 -0
- package/templates/react-monorepo/files/apps/admin/package.json +33 -0
- package/templates/react-monorepo/files/apps/admin/src/App.tsx +3 -0
- package/templates/react-monorepo/files/apps/admin/src/app/nav.tsx +14 -0
- package/templates/react-monorepo/files/apps/admin/src/app/providers.tsx +42 -0
- package/templates/react-monorepo/files/apps/admin/src/app/router.tsx +50 -0
- package/templates/react-monorepo/files/apps/admin/src/app/store.ts +15 -0
- package/templates/react-monorepo/files/apps/admin/src/lib/env.ts +12 -0
- package/templates/react-monorepo/files/apps/admin/src/main.tsx +14 -0
- package/templates/react-monorepo/files/apps/admin/src/routes/home.tsx +17 -0
- package/templates/react-monorepo/files/apps/admin/src/routes/settings.tsx +10 -0
- package/templates/react-monorepo/files/apps/admin/src/vite-env.d.ts +14 -0
- package/templates/react-monorepo/files/apps/admin/tsconfig.json +10 -0
- package/templates/react-monorepo/files/apps/admin/vite.config.ts +13 -0
- package/templates/react-monorepo/files/apps/auth/.bin/secrets.js +57 -0
- package/templates/react-monorepo/files/apps/auth/.env.example +16 -0
- package/templates/react-monorepo/files/apps/auth/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/apps/auth/index.html +57 -0
- package/templates/react-monorepo/files/apps/auth/package.json +32 -0
- package/templates/react-monorepo/files/apps/auth/src/App.tsx +3 -0
- package/templates/react-monorepo/files/apps/auth/src/app/providers.tsx +42 -0
- package/templates/react-monorepo/files/apps/auth/src/app/router.tsx +19 -0
- package/templates/react-monorepo/files/apps/auth/src/app/store.ts +15 -0
- package/templates/react-monorepo/files/apps/auth/src/components/auth-card.tsx +23 -0
- package/templates/react-monorepo/files/apps/auth/src/components/checkbox-field.tsx +32 -0
- package/templates/react-monorepo/files/apps/auth/src/components/google-button.tsx +41 -0
- package/templates/react-monorepo/files/apps/auth/src/components/or-divider.tsx +9 -0
- package/templates/react-monorepo/files/apps/auth/src/components/password-field.tsx +70 -0
- package/templates/react-monorepo/files/apps/auth/src/components/submit-button.tsx +16 -0
- package/templates/react-monorepo/files/apps/auth/src/lib/auth-actions.ts +68 -0
- package/templates/react-monorepo/files/apps/auth/src/lib/env.ts +8 -0
- package/templates/react-monorepo/files/apps/auth/src/lib/password-strength.ts +20 -0
- package/templates/react-monorepo/files/apps/auth/src/lib/redirect.ts +6 -0
- package/templates/react-monorepo/files/apps/auth/src/lib/schemas.ts +37 -0
- package/templates/react-monorepo/files/apps/auth/src/main.tsx +14 -0
- package/templates/react-monorepo/files/apps/auth/src/routes/forgot-password.tsx +91 -0
- package/templates/react-monorepo/files/apps/auth/src/routes/login.tsx +82 -0
- package/templates/react-monorepo/files/apps/auth/src/routes/reset-password.tsx +83 -0
- package/templates/react-monorepo/files/apps/auth/src/routes/signup.tsx +117 -0
- package/templates/react-monorepo/files/apps/auth/src/routes/verified.tsx +30 -0
- package/templates/react-monorepo/files/apps/auth/src/routes/verify-email.tsx +96 -0
- package/templates/react-monorepo/files/apps/auth/src/vite-env.d.ts +14 -0
- package/templates/react-monorepo/files/apps/auth/tsconfig.json +10 -0
- package/templates/react-monorepo/files/apps/auth/vite.config.ts +13 -0
- package/templates/react-monorepo/files/apps/client/.bin/secrets.js +57 -0
- package/templates/react-monorepo/files/apps/client/.env.example +16 -0
- package/templates/react-monorepo/files/apps/client/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/apps/client/index.html +57 -0
- package/templates/react-monorepo/files/apps/client/package.json +35 -0
- package/templates/react-monorepo/files/apps/client/src/App.tsx +3 -0
- package/templates/react-monorepo/files/apps/client/src/app/nav.tsx +14 -0
- package/templates/react-monorepo/files/apps/client/src/app/providers.tsx +42 -0
- package/templates/react-monorepo/files/apps/client/src/app/router.tsx +50 -0
- package/templates/react-monorepo/files/apps/client/src/app/store.ts +15 -0
- package/templates/react-monorepo/files/apps/client/src/lib/env.ts +12 -0
- package/templates/react-monorepo/files/apps/client/src/main.tsx +19 -0
- package/templates/react-monorepo/files/apps/client/src/routes/home.tsx +17 -0
- package/templates/react-monorepo/files/apps/client/src/routes/settings.tsx +10 -0
- package/templates/react-monorepo/files/apps/client/src/vite-env.d.ts +15 -0
- package/templates/react-monorepo/files/apps/client/tsconfig.json +10 -0
- package/templates/react-monorepo/files/apps/client/vite.config.ts +120 -0
- package/templates/react-monorepo/files/apps/landing/.bin/secrets.js +57 -0
- package/templates/react-monorepo/files/apps/landing/.env.example +20 -0
- package/templates/react-monorepo/files/apps/landing/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/apps/landing/index.html +51 -0
- package/templates/react-monorepo/files/apps/landing/package.json +33 -0
- package/templates/react-monorepo/files/apps/landing/src/App.tsx +3 -0
- package/templates/react-monorepo/files/apps/landing/src/app/providers.tsx +42 -0
- package/templates/react-monorepo/files/apps/landing/src/app/router.tsx +21 -0
- package/templates/react-monorepo/files/apps/landing/src/app/store.ts +18 -0
- package/templates/react-monorepo/files/apps/landing/src/features/seo/routes.ts +65 -0
- package/templates/react-monorepo/files/apps/landing/src/lib/env.ts +10 -0
- package/templates/react-monorepo/files/apps/landing/src/main.tsx +14 -0
- package/templates/react-monorepo/files/apps/landing/src/routes/home.tsx +18 -0
- package/templates/react-monorepo/files/apps/landing/src/vite-env.d.ts +15 -0
- package/templates/react-monorepo/files/apps/landing/tsconfig.json +10 -0
- package/templates/react-monorepo/files/apps/landing/vite.config.ts +169 -0
- package/templates/react-monorepo/files/build.sh +61 -0
- package/templates/react-monorepo/files/docker/nginx.conf +136 -0
- package/templates/react-monorepo/files/package.json +37 -0
- package/templates/react-monorepo/files/packages/.gitkeep +0 -0
- package/templates/react-monorepo/files/packages/api/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/packages/api/package.json +30 -0
- package/templates/react-monorepo/files/packages/api/src/base-api.ts +24 -0
- package/templates/react-monorepo/files/packages/api/src/base-query.ts +29 -0
- package/templates/react-monorepo/files/packages/api/src/config.ts +21 -0
- package/templates/react-monorepo/files/packages/api/src/index.ts +15 -0
- package/templates/react-monorepo/files/packages/api/src/realtime-events.ts +34 -0
- package/templates/react-monorepo/files/packages/api/src/socket.ts +41 -0
- package/templates/react-monorepo/files/packages/api/src/store.ts +39 -0
- package/templates/react-monorepo/files/packages/api/src/upload-query.ts +115 -0
- package/templates/react-monorepo/files/packages/api/tsconfig.json +4 -0
- package/templates/react-monorepo/files/packages/auth/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/packages/auth/package.json +33 -0
- package/templates/react-monorepo/files/packages/auth/src/client.ts +27 -0
- package/templates/react-monorepo/files/packages/auth/src/config.ts +22 -0
- package/templates/react-monorepo/files/packages/auth/src/guards.tsx +61 -0
- package/templates/react-monorepo/files/packages/auth/src/hooks.ts +13 -0
- package/templates/react-monorepo/files/packages/auth/src/index.ts +20 -0
- package/templates/react-monorepo/files/packages/auth/src/logout.ts +23 -0
- package/templates/react-monorepo/files/packages/auth/src/provider.tsx +52 -0
- package/templates/react-monorepo/files/packages/auth/src/redirect.ts +37 -0
- package/templates/react-monorepo/files/packages/auth/src/slice.ts +54 -0
- package/templates/react-monorepo/files/packages/auth/tsconfig.json +4 -0
- package/templates/react-monorepo/files/packages/config/README.md +47 -0
- package/templates/react-monorepo/files/packages/config/eslint/base.mjs +83 -0
- package/templates/react-monorepo/files/packages/config/package.json +37 -0
- package/templates/react-monorepo/files/packages/config/tailwind/theme.css +48 -0
- package/templates/react-monorepo/files/packages/config/tsconfig/base.json +22 -0
- package/templates/react-monorepo/files/packages/config/tsconfig/node.json +9 -0
- package/templates/react-monorepo/files/packages/config/tsconfig/react.json +9 -0
- package/templates/react-monorepo/files/packages/config/vite/index.d.ts +18 -0
- package/templates/react-monorepo/files/packages/config/vite/index.mjs +129 -0
- package/templates/react-monorepo/files/packages/hooks/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/packages/hooks/package.json +16 -0
- package/templates/react-monorepo/files/packages/hooks/src/index.ts +3 -0
- package/templates/react-monorepo/files/packages/hooks/tsconfig.json +4 -0
- package/templates/react-monorepo/files/packages/layouts/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/packages/layouts/package.json +30 -0
- package/templates/react-monorepo/files/packages/layouts/src/app-shell/app-shell.tsx +225 -0
- package/templates/react-monorepo/files/packages/layouts/src/app-shell/user-menu.tsx +74 -0
- package/templates/react-monorepo/files/packages/layouts/src/auth-layout.tsx +137 -0
- package/templates/react-monorepo/files/packages/layouts/src/error-boundary.tsx +38 -0
- package/templates/react-monorepo/files/packages/layouts/src/index.ts +23 -0
- package/templates/react-monorepo/files/packages/layouts/src/marketing/marketing-layout.tsx +29 -0
- package/templates/react-monorepo/files/packages/layouts/src/marketing/section-heading.tsx +38 -0
- package/templates/react-monorepo/files/packages/layouts/src/marketing/section.tsx +30 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/app-shell-skeleton.tsx +47 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/empty.tsx +22 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/forbidden.tsx +14 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/loading.tsx +11 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/not-found.tsx +21 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/page-skeleton.tsx +39 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/route-fallback.tsx +26 -0
- package/templates/react-monorepo/files/packages/layouts/src/states/state-screen.tsx +22 -0
- package/templates/react-monorepo/files/packages/layouts/tsconfig.json +4 -0
- package/templates/react-monorepo/files/packages/types/.bin/openapi.js +32 -0
- package/templates/react-monorepo/files/packages/types/.bin/secrets.js +57 -0
- package/templates/react-monorepo/files/packages/types/README.md +28 -0
- package/templates/react-monorepo/files/packages/types/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/packages/types/openapi-ts.config.ts +22 -0
- package/templates/react-monorepo/files/packages/types/package.json +29 -0
- package/templates/react-monorepo/files/packages/types/src/index.ts +4 -0
- package/templates/react-monorepo/files/packages/types/src/types/api/index.ts +9 -0
- package/templates/react-monorepo/files/packages/types/src/types/api/zod.gen.ts +5 -0
- package/templates/react-monorepo/files/packages/types/tsconfig.json +7 -0
- package/templates/react-monorepo/files/packages/ui/components.json +21 -0
- package/templates/react-monorepo/files/packages/ui/eslint.config.mjs +13 -0
- package/templates/react-monorepo/files/packages/ui/package.json +44 -0
- package/templates/react-monorepo/files/packages/ui/src/components/brand/logo.tsx +51 -0
- package/templates/react-monorepo/files/packages/ui/src/components/brand/social-icons.tsx +37 -0
- package/templates/react-monorepo/files/packages/ui/src/components/form/form.tsx +120 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/alert.tsx +25 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/button.tsx +43 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/calendar.tsx +78 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/card.tsx +44 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/checkbox.tsx +16 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/date-picker.tsx +76 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/input.tsx +22 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/label.tsx +19 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/modal.tsx +94 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/popover.tsx +31 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/progress.tsx +33 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/search-input.tsx +69 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/select.tsx +80 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/skeleton.tsx +12 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/sonner.tsx +23 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/spinner.tsx +8 -0
- package/templates/react-monorepo/files/packages/ui/src/components/ui/textarea.tsx +22 -0
- package/templates/react-monorepo/files/packages/ui/src/index.ts +45 -0
- package/templates/react-monorepo/files/packages/ui/src/lib/utils.ts +7 -0
- package/templates/react-monorepo/files/packages/ui/src/lib/zod-setup.ts +28 -0
- package/templates/react-monorepo/files/packages/ui/src/meta/brand.ts +18 -0
- package/templates/react-monorepo/files/packages/ui/src/meta/meta-context.ts +30 -0
- package/templates/react-monorepo/files/packages/ui/src/meta/meta-provider.tsx +24 -0
- package/templates/react-monorepo/files/packages/ui/src/meta/page-meta.tsx +69 -0
- package/templates/react-monorepo/files/packages/ui/src/meta/use-meta-config.ts +11 -0
- package/templates/react-monorepo/files/packages/ui/src/styles/globals.css +285 -0
- package/templates/react-monorepo/files/packages/ui/src/theme/init-script.ts +23 -0
- package/templates/react-monorepo/files/packages/ui/src/theme/theme-colors.ts +14 -0
- package/templates/react-monorepo/files/packages/ui/src/theme/theme-context.ts +60 -0
- package/templates/react-monorepo/files/packages/ui/src/theme/theme-provider.tsx +101 -0
- package/templates/react-monorepo/files/packages/ui/src/theme/theme-toggle.tsx +40 -0
- package/templates/react-monorepo/files/packages/ui/src/theme/use-theme.ts +11 -0
- package/templates/react-monorepo/files/packages/ui/tsconfig.json +9 -0
- package/templates/react-monorepo/files/turbo.json +24 -0
- package/templates/react-monorepo/template.json +230 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
// Pulls this workspace's env from Infisical into the matching .env file.
|
|
7
|
+
// The Infisical path is derived from the package name, e.g.
|
|
8
|
+
// @scope/types -> /frontend/types, landing -> /frontend/landing
|
|
9
|
+
//
|
|
10
|
+
// The project ID comes from the environment rather than being hardcoded: it
|
|
11
|
+
// identifies one organisation's vault, so a value baked in here would follow
|
|
12
|
+
// every copy of this scaffold to projects it has nothing to do with. Put it in
|
|
13
|
+
// your shell profile, or in a committed `.infisical.json`.
|
|
14
|
+
const PROJECT_ID = process.env.INFISICAL_PROJECT_ID;
|
|
15
|
+
const SECRET_ROOT = process.env.INFISICAL_PATH_PREFIX ?? '/frontend';
|
|
16
|
+
|
|
17
|
+
const ENV_FILES = {
|
|
18
|
+
dev: '.env',
|
|
19
|
+
test: '.env.test',
|
|
20
|
+
staging: '.env.staging',
|
|
21
|
+
prod: '.env.production'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const normalizeEnv = (raw) => {
|
|
25
|
+
if (['dev', 'development'].includes(raw)) return 'dev';
|
|
26
|
+
if (['test', 'testing'].includes(raw)) return 'test';
|
|
27
|
+
if (['prod', 'production'].includes(raw)) return 'prod';
|
|
28
|
+
if (raw === 'staging') return 'staging';
|
|
29
|
+
throw new Error(`Unknown env '${raw}' (use one of: dev, test, staging, prod)`);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const parseEnvArg = () => {
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const flagIndex = args.findIndex((a) => a === '-e' || a === '--env');
|
|
35
|
+
if (flagIndex !== -1 && args[flagIndex + 1]) return args[flagIndex + 1];
|
|
36
|
+
const inline = args.find((a) => a.startsWith('--env='));
|
|
37
|
+
if (inline) return inline.slice('--env='.length);
|
|
38
|
+
return 'development';
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
43
|
+
const slug = pkg.name.replace(/^@[^/]+\//, '');
|
|
44
|
+
const secretPath = `${SECRET_ROOT}/${slug}`;
|
|
45
|
+
|
|
46
|
+
if (!PROJECT_ID) {
|
|
47
|
+
console.error('INFISICAL_PROJECT_ID is not set — nothing to fetch from.');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const env = normalizeEnv(parseEnvArg());
|
|
52
|
+
const envFile = ENV_FILES[env];
|
|
53
|
+
|
|
54
|
+
console.log(`Fetching secrets for '${slug}' (${env}) -> ${envFile}...`);
|
|
55
|
+
execSync(`infisical export --projectId="${PROJECT_ID}" --path="${secretPath}" --env=${env} > ${envFile}`, {
|
|
56
|
+
stdio: 'inherit'
|
|
57
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Copy to .env. Vite only exposes variables prefixed VITE_, and inlines them at
|
|
2
|
+
# build time — never put a secret here, it ships to the browser.
|
|
3
|
+
|
|
4
|
+
# Backend base URL. Better Auth lives at ${VITE_APP_API_URL}/auth.
|
|
5
|
+
VITE_APP_API_URL='http://localhost:5000'
|
|
6
|
+
|
|
7
|
+
# Sibling app origins. All four share one session cookie across subdomains, so
|
|
8
|
+
# these must agree with the backend's BETTER_AUTH_COOKIE_DOMAIN and its
|
|
9
|
+
# trustedOrigins list, or login will appear to succeed and then bounce.
|
|
10
|
+
VITE_APP_LANDING='http://localhost:3000'
|
|
11
|
+
VITE_APP_AUTH_HOST='http://localhost:3001'
|
|
12
|
+
VITE_APP_CLIENT_HOST='http://localhost:3002'
|
|
13
|
+
VITE_APP_ADMIN_HOST='http://localhost:3003'
|
|
14
|
+
|
|
15
|
+
# This app's own dev port.
|
|
16
|
+
PORT='3000'
|
|
17
|
+
|
|
18
|
+
# Canonical public URL, used for sitemap/robots/OG tags at build time.
|
|
19
|
+
VITE_APP_SITE_URL='http://localhost:3000'
|
|
20
|
+
VITE_APP_SUPPORT_EMAIL='support@example.com'
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
|
7
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
|
8
|
+
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
|
9
|
+
<link rel="icon" href="/favicon.ico" sizes="any" />
|
|
10
|
+
<link rel="manifest" href="/site.webmanifest" />
|
|
11
|
+
<meta name="theme-color" content="%THEME_LIGHT%" />
|
|
12
|
+
<!--
|
|
13
|
+
Applies the saved (or system) theme before first paint, so the app never
|
|
14
|
+
flashes light then flips to dark. Mirrors `themeInitScript` in @acme/ui —
|
|
15
|
+
inlined because it must run before any module loads. Keep the two in sync.
|
|
16
|
+
%THEME_LIGHT/DARK% come from THEME_COLORS via the shared vite config.
|
|
17
|
+
-->
|
|
18
|
+
<script>
|
|
19
|
+
(function () {
|
|
20
|
+
try {
|
|
21
|
+
var m = localStorage.getItem('acme-theme');
|
|
22
|
+
var d =
|
|
23
|
+
m === 'dark' || ((m === null || m === 'system') && matchMedia('(prefers-color-scheme: dark)').matches);
|
|
24
|
+
var r = document.documentElement;
|
|
25
|
+
r.classList.toggle('dark', d);
|
|
26
|
+
r.style.colorScheme = d ? 'dark' : 'light';
|
|
27
|
+
var t = document.querySelector('meta[name="theme-color"]');
|
|
28
|
+
if (t) t.setAttribute('content', d ? '%THEME_DARK%' : '%THEME_LIGHT%');
|
|
29
|
+
} catch (e) {}
|
|
30
|
+
})();
|
|
31
|
+
</script>
|
|
32
|
+
<!--
|
|
33
|
+
globals.css only styles `body`, so without this the UA canvas paints white
|
|
34
|
+
for the moment before the stylesheet lands — a white flash on a dark load.
|
|
35
|
+
-->
|
|
36
|
+
<style>
|
|
37
|
+
html {
|
|
38
|
+
background: %THEME_LIGHT%;
|
|
39
|
+
}
|
|
40
|
+
html.dark {
|
|
41
|
+
background: %THEME_DARK%;
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
44
|
+
<meta name="description" content="Acme Corp." />
|
|
45
|
+
<title>%APP_TITLE%</title>
|
|
46
|
+
</head>
|
|
47
|
+
<body>
|
|
48
|
+
<div id="root"></div>
|
|
49
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
50
|
+
</body>
|
|
51
|
+
</html>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "landing",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc --noEmit && vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"secrets": "node .bin/secrets.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@acme/api": "workspace:*",
|
|
16
|
+
"@acme/auth": "workspace:*",
|
|
17
|
+
"@acme/layouts": "workspace:*",
|
|
18
|
+
"@acme/types": "workspace:*",
|
|
19
|
+
"@acme/ui": "workspace:*",
|
|
20
|
+
"@reduxjs/toolkit": "^2.12.0",
|
|
21
|
+
"lucide-react": "^1.17.0",
|
|
22
|
+
"react": "^19.2.6",
|
|
23
|
+
"react-dom": "^19.2.6",
|
|
24
|
+
"react-redux": "^9.3.0",
|
|
25
|
+
"react-router": "^8.2.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@acme/config": "workspace:*",
|
|
29
|
+
"@types/react": "^19.2.14",
|
|
30
|
+
"@types/react-dom": "^19.2.3",
|
|
31
|
+
"vite": "^8.0.12"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Provider } from 'react-redux';
|
|
3
|
+
import { configureApi } from '@acme/api';
|
|
4
|
+
import { AuthProvider, configureAuth, redirectToLogin } from '@acme/auth';
|
|
5
|
+
import { MetaProvider, ThemeProvider, Toaster, installZodErrorMap } from '@acme/ui';
|
|
6
|
+
import { store } from './store';
|
|
7
|
+
|
|
8
|
+
const env = import.meta.env;
|
|
9
|
+
const apiBaseUrl = env.VITE_APP_API_URL;
|
|
10
|
+
const authAppUrl = env.VITE_APP_AUTH_HOST as string;
|
|
11
|
+
|
|
12
|
+
const trustedOrigins = [env.VITE_APP_LANDING, env.VITE_APP_CLIENT_HOST, env.VITE_APP_ADMIN_HOST, authAppUrl].filter(
|
|
13
|
+
(value): value is string => Boolean(value)
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// Wire the shared API + auth clients once, before rendering. The landing site is public, so a 401 is not automatically a redirect.
|
|
17
|
+
configureApi({ baseUrl: apiBaseUrl, onUnauthorized: () => redirectToLogin() });
|
|
18
|
+
configureAuth({
|
|
19
|
+
authApiBaseUrl: `${apiBaseUrl}/auth`,
|
|
20
|
+
authAppUrl,
|
|
21
|
+
trustedOrigins,
|
|
22
|
+
defaultRedirect: (env.VITE_APP_LANDING as string | undefined) ?? window.location.origin
|
|
23
|
+
});
|
|
24
|
+
installZodErrorMap();
|
|
25
|
+
|
|
26
|
+
export function Providers({ children }: { children: ReactNode }) {
|
|
27
|
+
return (
|
|
28
|
+
// ThemeProvider above the Redux Provider: it only touches the document
|
|
29
|
+
// (`dark` class, color-scheme, theme-color meta) and must not re-render
|
|
30
|
+
// through the store.
|
|
31
|
+
<ThemeProvider>
|
|
32
|
+
<MetaProvider>
|
|
33
|
+
<Provider store={store}>
|
|
34
|
+
<AuthProvider>
|
|
35
|
+
{children}
|
|
36
|
+
<Toaster />
|
|
37
|
+
</AuthProvider>
|
|
38
|
+
</Provider>
|
|
39
|
+
</MetaProvider>
|
|
40
|
+
</ThemeProvider>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createBrowserRouter } from 'react-router';
|
|
2
|
+
import { ErrorBoundary, MarketingLayout, NotFound } from '@acme/layouts';
|
|
3
|
+
import { HomeRoute } from '../routes/home';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The landing site is public — no auth guard. `MarketingLayout` takes `header`
|
|
7
|
+
* and `footer` slots and falls back to the router's <Outlet /> for content.
|
|
8
|
+
* `ErrorBoundary` wraps it rather than being the route's `errorElement`: it is
|
|
9
|
+
* a plain React error boundary taking `children`.
|
|
10
|
+
*/
|
|
11
|
+
export const router = createBrowserRouter([
|
|
12
|
+
{
|
|
13
|
+
element: (
|
|
14
|
+
<ErrorBoundary>
|
|
15
|
+
<MarketingLayout />
|
|
16
|
+
</ErrorBoundary>
|
|
17
|
+
),
|
|
18
|
+
children: [{ index: true, element: <HomeRoute /> }]
|
|
19
|
+
},
|
|
20
|
+
{ path: '*', element: <NotFound /> }
|
|
21
|
+
]);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { makeStore } from '@acme/api';
|
|
2
|
+
import { authReducer } from '@acme/auth';
|
|
3
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
4
|
+
|
|
5
|
+
// `auth` holds the Better Auth session so the header can swap its CTAs for a
|
|
6
|
+
// signed-in visitor. Landing still has no protected routes and no guards — it
|
|
7
|
+
// reads the session purely to render, and every page stays fully public.
|
|
8
|
+
export const store = makeStore({
|
|
9
|
+
reducer: {
|
|
10
|
+
auth: authReducer
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export type RootState = ReturnType<typeof store.getState>;
|
|
15
|
+
export type AppDispatch = typeof store.dispatch;
|
|
16
|
+
|
|
17
|
+
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
|
|
18
|
+
export const useAppSelector = useSelector.withTypes<RootState>();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every indexable route, with the metadata that describes it.
|
|
3
|
+
*
|
|
4
|
+
* **This is the single source.** `PageSeo` renders per-route tags from it and
|
|
5
|
+
* the build emits `sitemap.xml` from it (see `vite.config.ts`), so the sitemap
|
|
6
|
+
* cannot list a route that does not exist or miss one that does — the classic
|
|
7
|
+
* way a sitemap rots. Add a route here on the same commit that adds it to
|
|
8
|
+
* `app/router.tsx`, and remove it the same way; a sitemap advertising a 404
|
|
9
|
+
* stays invisible until a crawler finds it.
|
|
10
|
+
*
|
|
11
|
+
* Kept free of React imports: `vite.config.ts` imports this directly, and the
|
|
12
|
+
* config is bundled with esbuild.
|
|
13
|
+
*/
|
|
14
|
+
export type SeoRoute = {
|
|
15
|
+
/** Path as it appears in the router, leading slash, no trailing slash. */
|
|
16
|
+
path: string;
|
|
17
|
+
/** Page segment only — `MetaProvider` appends the brand suffix. */
|
|
18
|
+
title: string;
|
|
19
|
+
description: string;
|
|
20
|
+
/** Relative priority for the sitemap; the homepage leads. */
|
|
21
|
+
priority: string;
|
|
22
|
+
changefreq: 'daily' | 'weekly' | 'monthly' | 'yearly';
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const SEO_ROUTES: SeoRoute[] = [
|
|
26
|
+
{
|
|
27
|
+
path: '/',
|
|
28
|
+
title: 'Home',
|
|
29
|
+
description: 'Replace with the description search results should show.',
|
|
30
|
+
priority: '1.0',
|
|
31
|
+
changefreq: 'weekly'
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The one origin that may be indexed. Staging and preview builds compare
|
|
37
|
+
* unequal and are served `noindex`, so a preview deploy cannot outrank
|
|
38
|
+
* production for its own content.
|
|
39
|
+
*/
|
|
40
|
+
export const PRODUCTION_ORIGIN = 'https://example.com';
|
|
41
|
+
|
|
42
|
+
export const seoRouteFor = (path: string) => SEO_ROUTES.find((route) => route.path === path);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Absolute URL for a path — canonical, `og:url` and sitemap entries all need one.
|
|
46
|
+
*
|
|
47
|
+
* `origin` is passed in rather than read here so this file stays free of
|
|
48
|
+
* `import.meta.env`: it is imported by `vite.config.ts`, where `import.meta.env`
|
|
49
|
+
* does not exist. App code gets the origin from its env, the build from its own
|
|
50
|
+
* `loadEnv`.
|
|
51
|
+
*/
|
|
52
|
+
export const absoluteUrl = (path: string, origin: string) => (path === '/' ? `${origin}/` : `${origin}${path}`);
|
|
53
|
+
|
|
54
|
+
/** Strip a trailing slash so joins don't produce `//about`. */
|
|
55
|
+
export const normalizeOrigin = (origin: string) => origin.replace(/\/$/, '');
|
|
56
|
+
|
|
57
|
+
/*
|
|
58
|
+
* Not included here: **prerendering**. `vite build` emits a single SPA shell,
|
|
59
|
+
* so a crawler that does not execute JavaScript sees an empty `#root` for every
|
|
60
|
+
* route. `robots.txt` and `sitemap.xml` are emitted from this table at build
|
|
61
|
+
* time (see `vite.config.ts`), which is the half that matters for discovery —
|
|
62
|
+
* but if these pages need to rank on their content, add a post-build pass that
|
|
63
|
+
* renders each `SEO_ROUTES` entry to `dist/<path>/index.html`. Drive it from
|
|
64
|
+
* this table, not a second list, or the sitemap and the prerendered set drift.
|
|
65
|
+
*/
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const env = import.meta.env;
|
|
2
|
+
|
|
3
|
+
export const API_URL = env.VITE_APP_API_URL;
|
|
4
|
+
export const AUTH_HOST = env.VITE_APP_AUTH_HOST;
|
|
5
|
+
export const CLIENT_HOST = env.VITE_APP_CLIENT_HOST;
|
|
6
|
+
/** Support inbox shown on /contact. Per-environment, so it is never hardcoded —
|
|
7
|
+
* it must match the backend's SMTP_SUPPORT_EMAIL, which is where the form
|
|
8
|
+
* actually posts. Falsy when the build arg is missing; the UI hides the block
|
|
9
|
+
* rather than rendering `mailto:undefined`. */
|
|
10
|
+
export const SUPPORT_EMAIL = env.VITE_APP_SUPPORT_EMAIL;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { StrictMode } from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { RouterProvider } from 'react-router';
|
|
4
|
+
import '@acme/ui/globals.css';
|
|
5
|
+
import { Providers } from './app/providers';
|
|
6
|
+
import { router } from './app/router';
|
|
7
|
+
|
|
8
|
+
createRoot(document.getElementById('root')!).render(
|
|
9
|
+
<StrictMode>
|
|
10
|
+
<Providers>
|
|
11
|
+
<RouterProvider router={router} />
|
|
12
|
+
</Providers>
|
|
13
|
+
</StrictMode>
|
|
14
|
+
);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PageMeta } from '@acme/ui';
|
|
2
|
+
import { Section, SectionHeading } from '@acme/layouts';
|
|
3
|
+
|
|
4
|
+
export function HomeRoute() {
|
|
5
|
+
return (
|
|
6
|
+
<>
|
|
7
|
+
{/* `title` is the page segment only — MetaProvider appends the brand. */}
|
|
8
|
+
<PageMeta title="Home" />
|
|
9
|
+
<Section>
|
|
10
|
+
<SectionHeading
|
|
11
|
+
eyebrow="Acme Corp"
|
|
12
|
+
title="Replace this with your hero"
|
|
13
|
+
lede="One sentence on what this product does, written for someone who has never heard of it."
|
|
14
|
+
/>
|
|
15
|
+
</Section>
|
|
16
|
+
</>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
/// <reference types="vite-plugin-svgr/client" />
|
|
3
|
+
|
|
4
|
+
interface ImportMetaEnv {
|
|
5
|
+
VITE_APP_API_URL: string;
|
|
6
|
+
VITE_APP_ALLOWED_HOSTS: string;
|
|
7
|
+
VITE_APP_AUTH_HOST: string;
|
|
8
|
+
VITE_APP_CLIENT_HOST: string;
|
|
9
|
+
VITE_APP_ADMIN_HOST: string;
|
|
10
|
+
VITE_APP_SUPPORT_EMAIL: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ImportMeta {
|
|
14
|
+
readonly env: ImportMetaEnv;
|
|
15
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { defineAppConfig } from '@acme/config/vite';
|
|
2
|
+
import { BRAND } from '@acme/ui/brand';
|
|
3
|
+
import { THEME_COLORS } from '@acme/ui/theme-colors';
|
|
4
|
+
import { loadEnv, type Plugin } from 'vite';
|
|
5
|
+
import { PRODUCTION_ORIGIN, SEO_ROUTES, absoluteUrl, normalizeOrigin } from './src/features/seo/routes';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Emits `robots.txt` and `sitemap.xml` at build time from `SEO_ROUTES`.
|
|
9
|
+
*
|
|
10
|
+
* Generated rather than committed to `public/` so the sitemap cannot drift from
|
|
11
|
+
* the routes that actually exist. A sitemap advertising a 404, or missing a real
|
|
12
|
+
* page, is the usual failure here and it stays invisible until a crawler finds
|
|
13
|
+
* it. `/roadmap` is absent from the table precisely because its route is
|
|
14
|
+
* currently commented out in `app/router.tsx`.
|
|
15
|
+
*
|
|
16
|
+
* `apply: 'build'` — `public/` isn't written in dev and the dev server has no
|
|
17
|
+
* use for either file.
|
|
18
|
+
*/
|
|
19
|
+
function seoFiles(origin: string, indexable: boolean): Plugin {
|
|
20
|
+
return {
|
|
21
|
+
name: 'acme:seo-files',
|
|
22
|
+
apply: 'build',
|
|
23
|
+
generateBundle() {
|
|
24
|
+
// Non-production keeps the same file, inverted: a dev server serving the
|
|
25
|
+
// same copy as production is duplicate content, and it must not advertise a
|
|
26
|
+
// sitemap it doesn't want crawled.
|
|
27
|
+
const robots = (
|
|
28
|
+
indexable
|
|
29
|
+
? [
|
|
30
|
+
'# Public marketing site — crawling is welcome here.',
|
|
31
|
+
'# The app, auth and admin subdomains are deliberately noindex (MAI-102).',
|
|
32
|
+
'User-agent: *',
|
|
33
|
+
'Allow: /',
|
|
34
|
+
...(origin ? ['', `Sitemap: ${origin}/sitemap.xml`] : []),
|
|
35
|
+
''
|
|
36
|
+
]
|
|
37
|
+
: [
|
|
38
|
+
`# Not production (origin: ${origin || 'unset'}). Only ${PRODUCTION_ORIGIN} should be indexed.`,
|
|
39
|
+
'# Paired with the noindex meta tag in index.html — robots.txt alone does not deindex.',
|
|
40
|
+
'User-agent: *',
|
|
41
|
+
'Disallow: /',
|
|
42
|
+
''
|
|
43
|
+
]
|
|
44
|
+
).join('\n');
|
|
45
|
+
this.emitFile({ type: 'asset', fileName: 'robots.txt', source: robots });
|
|
46
|
+
|
|
47
|
+
// Every <loc> must be absolute per the sitemap spec, so without an origin
|
|
48
|
+
// emit nothing rather than something invalid. robots.txt above then also
|
|
49
|
+
// omits its Sitemap line, keeping the two consistent.
|
|
50
|
+
if (!origin) {
|
|
51
|
+
this.warn('VITE_APP_SITE_URL is not set — skipping sitemap.xml, which requires absolute URLs.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const urls = SEO_ROUTES.map(
|
|
56
|
+
(route) =>
|
|
57
|
+
'\t<url>\n' +
|
|
58
|
+
`\t\t<loc>${absoluteUrl(route.path, origin)}</loc>\n` +
|
|
59
|
+
`\t\t<changefreq>${route.changefreq}</changefreq>\n` +
|
|
60
|
+
`\t\t<priority>${route.priority}</priority>\n` +
|
|
61
|
+
'\t</url>'
|
|
62
|
+
).join('\n');
|
|
63
|
+
|
|
64
|
+
this.emitFile({
|
|
65
|
+
type: 'asset',
|
|
66
|
+
fileName: 'sitemap.xml',
|
|
67
|
+
source:
|
|
68
|
+
'<?xml version="1.0" encoding="UTF-8"?>\n' +
|
|
69
|
+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' +
|
|
70
|
+
`${urls}\n` +
|
|
71
|
+
'</urlset>\n'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* When the security policy expires, as an RFC 9116 `Expires:` field must. A date
|
|
79
|
+
* in the past makes the whole file invalid to the tools that read it, so this
|
|
80
|
+
* needs bumping — along with a review of the policy itself — before it lapses.
|
|
81
|
+
*/
|
|
82
|
+
const SECURITY_TXT_EXPIRES = '2027-09-01T00:00:00.000Z';
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Emits `/.well-known/security.txt` (RFC 9116) pointing at the `/security` route.
|
|
86
|
+
*
|
|
87
|
+
* Researchers and automated scanners look for this file, not for a footer link,
|
|
88
|
+
* so without it the policy page is one nobody arrives at. `Contact:` must stay
|
|
89
|
+
* the same address as the policy states in its Sections 1, 8 and 19.
|
|
90
|
+
*
|
|
91
|
+
* Production-only, unlike `robots.txt`: `Canonical:` must be the URL the file is
|
|
92
|
+
* actually served from, so a preview environment publishing one that claims to
|
|
93
|
+
* live on the production origin would be advertising a false record rather than
|
|
94
|
+
* a harmless duplicate. `docker/nginx.conf` needs its `^~ /.well-known/` block
|
|
95
|
+
* for this to be reachable at all — the dotfile deny would otherwise 403 it.
|
|
96
|
+
*/
|
|
97
|
+
function securityTxt(origin: string, indexable: boolean, securityEmail: string): Plugin {
|
|
98
|
+
return {
|
|
99
|
+
name: 'acme:security-txt',
|
|
100
|
+
apply: 'build',
|
|
101
|
+
generateBundle() {
|
|
102
|
+
if (!indexable || !origin || !securityEmail) return;
|
|
103
|
+
|
|
104
|
+
this.emitFile({
|
|
105
|
+
type: 'asset',
|
|
106
|
+
fileName: '.well-known/security.txt',
|
|
107
|
+
source: [
|
|
108
|
+
'# Acme Corp — Security & Responsible Disclosure',
|
|
109
|
+
`# Full policy: ${absoluteUrl('/security', origin)}`,
|
|
110
|
+
'',
|
|
111
|
+
`Contact: mailto:${securityEmail}`,
|
|
112
|
+
`Expires: ${SECURITY_TXT_EXPIRES}`,
|
|
113
|
+
`Policy: ${absoluteUrl('/security', origin)}`,
|
|
114
|
+
'Preferred-Languages: en',
|
|
115
|
+
`Canonical: ${origin}/.well-known/security.txt`,
|
|
116
|
+
''
|
|
117
|
+
].join('\n')
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Injects `noindex, nofollow` into `index.html` on every non-production build.
|
|
125
|
+
*
|
|
126
|
+
* The meta tag is the half that actually deindexes — `robots.txt` only asks a
|
|
127
|
+
* crawler not to fetch, and a URL it already knows can still be listed without
|
|
128
|
+
* ever being fetched. Both halves are emitted together, from the same flag.
|
|
129
|
+
*
|
|
130
|
+
* `index.html` is the shell the prerender pass copies for all seven routes, so
|
|
131
|
+
* one tag here covers the whole site.
|
|
132
|
+
*/
|
|
133
|
+
function noindexOffProduction(indexable: boolean): Plugin {
|
|
134
|
+
return {
|
|
135
|
+
name: 'acme:noindex-off-production',
|
|
136
|
+
apply: 'build',
|
|
137
|
+
transformIndexHtml() {
|
|
138
|
+
if (indexable) return;
|
|
139
|
+
return [{ tag: 'meta', attrs: { name: 'robots', content: 'noindex, nofollow' }, injectTo: 'head' }];
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// `loadEnv` rather than `process.env`: VITE_APP_* live in the app's `.env` files
|
|
145
|
+
// (pulled from Infisical), not the shell, so the build would otherwise never see
|
|
146
|
+
// the site URL.
|
|
147
|
+
const env = loadEnv(process.env.NODE_ENV ?? 'production', import.meta.dirname, '');
|
|
148
|
+
const siteOrigin = normalizeOrigin(env.VITE_APP_SITE_URL ?? '');
|
|
149
|
+
// Omitted rather than guessed: a security.txt pointing at an unmonitored
|
|
150
|
+
// address is worse than none at all, so an unset value skips the file.
|
|
151
|
+
const securityEmail = env.VITE_APP_SUPPORT_EMAIL ?? '';
|
|
152
|
+
|
|
153
|
+
// An unset or unrecognised origin counts as non-production — see PRODUCTION_ORIGIN.
|
|
154
|
+
const indexable = siteOrigin === PRODUCTION_ORIGIN;
|
|
155
|
+
|
|
156
|
+
// `themeColors` substitutes %THEME_LIGHT%/%THEME_DARK% into index.html — the
|
|
157
|
+
// pre-paint theme script and the `html` background both need them, and HTML
|
|
158
|
+
// can't import. `appTitle` substitutes %APP_TITLE% for the same reason: the
|
|
159
|
+
// static <title> must match the suffix MetaProvider appends at runtime.
|
|
160
|
+
export default defineAppConfig({
|
|
161
|
+
rootDir: import.meta.dirname,
|
|
162
|
+
themeColors: THEME_COLORS,
|
|
163
|
+
appTitle: BRAND.name,
|
|
164
|
+
plugins: [
|
|
165
|
+
seoFiles(siteOrigin, indexable),
|
|
166
|
+
securityTxt(siteOrigin, indexable, securityEmail),
|
|
167
|
+
noindexOffProduction(indexable)
|
|
168
|
+
]
|
|
169
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Build + push one frontend app image.
|
|
3
|
+
#
|
|
4
|
+
# ./build.sh <admin|auth|client|landing> [env] [tag]
|
|
5
|
+
#
|
|
6
|
+
# ./build.sh client # uses .env -> :latest
|
|
7
|
+
# ./build.sh client prod # uses .env.production
|
|
8
|
+
# ./build.sh client prod 26_07_26_1
|
|
9
|
+
#
|
|
10
|
+
# The VITE_* URLs are compiled INTO the bundle, so the image is specific to the
|
|
11
|
+
# environment it was built for. That is why the env name is an argument: the same
|
|
12
|
+
# image cannot be promoted from staging to prod. Pull the env file first with
|
|
13
|
+
# yarn workspace <app> secrets -e <env>
|
|
14
|
+
set -eu
|
|
15
|
+
|
|
16
|
+
APP="${1:-}"
|
|
17
|
+
ENV_NAME="${2:-dev}"
|
|
18
|
+
TAG="${3:-latest}"
|
|
19
|
+
REGISTRY="aurostack.dev/acme-corp"
|
|
20
|
+
PLATFORMS="linux/amd64,linux/arm64"
|
|
21
|
+
|
|
22
|
+
case "$APP" in
|
|
23
|
+
admin | auth | client | landing) ;;
|
|
24
|
+
*)
|
|
25
|
+
echo "usage: $0 <admin|auth|client|landing> [dev|staging|prod] [tag]" >&2
|
|
26
|
+
exit 1
|
|
27
|
+
;;
|
|
28
|
+
esac
|
|
29
|
+
|
|
30
|
+
case "$ENV_NAME" in
|
|
31
|
+
dev) ENV_FILE="apps/$APP/.env" ;;
|
|
32
|
+
test) ENV_FILE="apps/$APP/.env.test" ;;
|
|
33
|
+
staging) ENV_FILE="apps/$APP/.env.staging" ;;
|
|
34
|
+
prod | production) ENV_FILE="apps/$APP/.env.production" ;;
|
|
35
|
+
*) echo "unknown env '$ENV_NAME' (dev|test|staging|prod)" >&2; exit 1 ;;
|
|
36
|
+
esac
|
|
37
|
+
|
|
38
|
+
[ -f "$ENV_FILE" ] || {
|
|
39
|
+
echo "missing $ENV_FILE — run: yarn workspace $APP secrets -e $ENV_NAME" >&2
|
|
40
|
+
exit 1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Read a var out of the env file, tolerating the quoted style these files use.
|
|
44
|
+
read_var() {
|
|
45
|
+
sed -n "s/^$1=['\"]\{0,1\}\([^'\"]*\)['\"]\{0,1\}$/\1/p" "$ENV_FILE" | tail -1
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
BUILD_ARGS="--build-arg APP=$APP"
|
|
49
|
+
for VAR in VITE_APP_API_URL VITE_APP_AUTH_HOST VITE_APP_CLIENT_HOST VITE_APP_ADMIN_HOST VITE_APP_LANDING VITE_APP_SUPPORT_EMAIL; do
|
|
50
|
+
VALUE="$(read_var "$VAR")"
|
|
51
|
+
[ -n "$VALUE" ] && BUILD_ARGS="$BUILD_ARGS --build-arg $VAR=$VALUE"
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
echo "Building $APP for '$ENV_NAME' -> $REGISTRY/web-$APP:$TAG"
|
|
55
|
+
echo " (URLs baked in from $ENV_FILE)"
|
|
56
|
+
|
|
57
|
+
# shellcheck disable=SC2086
|
|
58
|
+
docker buildx build --push --platform "$PLATFORMS" \
|
|
59
|
+
$BUILD_ARGS \
|
|
60
|
+
-t "$REGISTRY/web-$APP:$TAG" \
|
|
61
|
+
.
|