diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9cd9792..516900a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,9 +10,13 @@ jobs: - uses: pnpm/action-setup@v2 with: run_install: true + - uses: zerodays/action-infisical@v1 + with: + infisical_token: ${{ secrets.INFISICAL_TOKEN }} + environment: "staging" - name: Lint run: | - pnpm lint + source .env && pnpm tsc typecheck: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 978d26d..1401084 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,6 +14,9 @@ jobs: - uses: pnpm/action-setup@v2 with: run_install: true - - uses: ArtiomTr/jest-coverage-report-action@v2 + - uses: zerodays/action-infisical@v1 with: - package-manager: pnpm + infisical_token: ${{ secrets.INFISICAL_TOKEN }} + environment: 'staging' + - name: Run tests + run: source .env && pnpm jest --reporters="summary" --reporters="github-actions" diff --git a/.infisical.json b/.infisical.json new file mode 100644 index 0000000..c42101f --- /dev/null +++ b/.infisical.json @@ -0,0 +1,5 @@ +{ + "workspaceId": "65521d699e945b505e4731d8", + "defaultEnvironment": "", + "gitBranchToEnvironmentMapping": null +} \ No newline at end of file diff --git a/next.config.js b/next.config.js deleted file mode 100644 index 733d478..0000000 --- a/next.config.js +++ /dev/null @@ -1,46 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const { withSentryConfig } = require('@sentry/nextjs'); -const {withAxiom} = require("next-axiom") -const withBundleAnalyzer = require('@next/bundle-analyzer')({ - enabled: process.env.ANALYZE === 'true', -}); - -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, -}; - -module.exports = withBundleAnalyzer( - withAxiom(withSentryConfig( - nextConfig, - { - // For all available options, see: - // https://github.com/getsentry/sentry-webpack-plugin#options - - // Suppresses source map uploading logs during build - silent: true, - - org: 'zerodays', - project: 'next-template', - }, - { - // For all available options, see: - // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ - - // Upload a larger set of source maps for prettier stack traces (increases build time) - widenClientFileUpload: true, - - // Transpiles SDK to be compatible with IE11 (increases bundle size) - transpileClientSDK: true, - - // Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load) - tunnelRoute: '/monitoring', - - // Hides source maps from generated client bundles - hideSourceMaps: true, - - // Automatically tree-shake Sentry logger statements to reduce bundle size - disableLogger: true, - }, - ), -)); diff --git a/next.config.mjs b/next.config.mjs new file mode 100644 index 0000000..b3f1e68 --- /dev/null +++ b/next.config.mjs @@ -0,0 +1,33 @@ +await import('./src/env.mjs'); +import { withSentryConfig } from '@sentry/nextjs'; +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import { withAxiom } from 'next-axiom'; + +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + sentry: { + // Upload a larger set of source maps for prettier stack traces (increases build time) + widenClientFileUpload: true, + + // Transpiles SDK to be compatible with IE11 (increases bundle size) + transpileClientSDK: true, + + // Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load) + tunnelRoute: '/monitoring', + + // Hides source maps from generated client bundles + hideSourceMaps: true, + + // Automatically tree-shake Sentry logger statements to reduce bundle size + disableLogger: true, + }, +}; + +export default withAxiom( + withSentryConfig(nextConfig, { + silent: true, + org: 'zerodays', + project: 'next-template', + }), +); diff --git a/package.json b/package.json index 92dca77..73b9b4b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "packageManager": "pnpm@8.10.2", "scripts": { - "dev": "next dev", + "dev": "infisical run -- next dev", "build": "next build", "start": "next start", "lint": "next lint", @@ -18,6 +18,7 @@ "@hookform/resolvers": "^3.1.0", "@next/bundle-analyzer": "^14.0.0", "@sentry/nextjs": "7.77.0", + "@t3-oss/env-nextjs": "^0.7.1", "autoprefixer": "10.4.14", "next": "^14.0.0", "next-axiom": "^1.1.0", @@ -28,7 +29,7 @@ "react-hook-form": "^7.44.3", "tailwindcss": "3.3.2", "typescript": "^5.2.2", - "zod": "^3.22.3" + "zod": "^3.22.4" }, "devDependencies": { "@commitlint/cli": "^18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db13c15..7f040bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ dependencies: '@sentry/nextjs': specifier: 7.77.0 version: 7.77.0(next@14.0.2)(react@18.2.0) + '@t3-oss/env-nextjs': + specifier: ^0.7.1 + version: 0.7.1(typescript@5.2.2)(zod@3.22.4) autoprefixer: specifier: 10.4.14 version: 10.4.14(postcss@8.4.31) @@ -45,7 +48,7 @@ dependencies: specifier: ^5.2.2 version: 5.2.2 zod: - specifier: ^3.22.3 + specifier: ^3.22.4 version: 3.22.4 devDependencies: @@ -1525,6 +1528,33 @@ packages: tslib: 2.6.2 dev: false + /@t3-oss/env-core@0.7.1(typescript@5.2.2)(zod@3.22.4): + resolution: {integrity: sha512-3+SQt39OlmSaRLqYVFv8uRm1BpFepM5TIiMytRqO9cjH+wB77o6BIJdeyM5h5U4qLBMEzOJWCY4MBaU/rLwbYw==} + peerDependencies: + typescript: '>=4.7.2' + zod: ^3.0.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.2.2 + zod: 3.22.4 + dev: false + + /@t3-oss/env-nextjs@0.7.1(typescript@5.2.2)(zod@3.22.4): + resolution: {integrity: sha512-tQDbNLGCOvKGi+JoGuJ/CJInJI7/kLWJqtgGppAKS7ZFLdVOqZYR/uRjxlXOWPnxmUKF8VswOAsq7fXUpNZDhA==} + peerDependencies: + typescript: '>=4.7.2' + zod: ^3.0.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@t3-oss/env-core': 0.7.1(typescript@5.2.2)(zod@3.22.4) + typescript: 5.2.2 + zod: 3.22.4 + dev: false + /@testing-library/dom@9.3.3: resolution: {integrity: sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==} engines: {node: '>=14'} diff --git a/src/env.mjs b/src/env.mjs new file mode 100644 index 0000000..e61b324 --- /dev/null +++ b/src/env.mjs @@ -0,0 +1,90 @@ +import { createEnv } from '@t3-oss/env-nextjs'; +import { z } from 'zod'; + +// Base schema for common variables +const baseSchema = { + server: { + NODE_ENV: z + .enum(['development', 'test', 'production']) + .default('development'), + SENTRY_AUTH_TOKEN: z.string(), + }, + client: { + NEXT_PUBLIC_NODE_ENV: z + .enum(['development', 'test', 'production']) + .default('development'), + }, + // ... other shared properties +}; + +// Development environment-specific schema +const devSchema = { + client: { + NEXT_PUBLIC_SENTRY_DNS: z.string().optional(), + NEXT_PUBLIC_SENTRY_TRACE_SAMPLE_RATE: z.coerce.number().optional(), + }, + // ... other dev/test-specific properties +}; + +// Staging environment-specific schema +const stagingSchema = { + client: { + NEXT_PUBLIC_SENTRY_DNS: z.string().optional(), + NEXT_PUBLIC_SENTRY_TRACE_SAMPLE_RATE: z.coerce.number().optional(), + }, + // ... other staging-specific properties +}; + +// Production environment-specific schema +const productionSchema = { + client: { + NEXT_PUBLIC_SENTRY_DNS: z + .string() + .min(1, { message: 'Sentry DNS is required in production' }), + NEXT_PUBLIC_SENTRY_TRACE_SAMPLE_RATE: z.coerce.number(), + }, + // ... other production-specific properties +}; + +// Determine the current environment and merge schemas accordingly +const currentEnv = process.env.NODE_ENV || 'development'; // Default to 'development' if NODE_ENV is not set + +let envSchema; +switch (currentEnv) { + case 'development': + envSchema = { ...baseSchema, ...devSchema }; + break; + case 'test': + case 'staging': + envSchema = { ...baseSchema, ...stagingSchema }; + break; + case 'production': + envSchema = { ...baseSchema, ...productionSchema }; + break; + default: + throw new Error(`Unknown environment: ${currentEnv}`); +} + +// Runtime environment variables (for Next.js edge runtimes or client-side) +const runtimeEnv = { + NODE_ENV: process.env.NODE_ENV, + NEXT_PUBLIC_NODE_ENV: process.env.NEXT_PUBLIC_NODE_ENV, + SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN, + NEXT_PUBLIC_SENTRY_DNS: process.env.NEXT_PUBLIC_SENTRY_DNS, + NEXT_PUBLIC_SENTRY_TRACE_SAMPLE_RATE: + process.env.NEXT_PUBLIC_SENTRY_TRACE_SAMPLE_RATE, +}; + +// Skip env validation flag (useful for Docker builds) +const skipValidation = !!process.env.SKIP_ENV_VALIDATION; + +// Treat empty strings as undefined +const emptyStringAsUndefined = true; + +// Create and export the environment object +export const env = createEnv({ + ...envSchema, + runtimeEnv, + skipValidation, + emptyStringAsUndefined, +});