diff --git a/.gitignore b/.gitignore index 091f408..b69ba47 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,28 @@ node_modules/ dist/ .next/ + +# Local environment files. Keep example templates tracked. .env +.env.* +!.env.example +!.env.*.example +!apps/**/.env.example .env.local .env.local-server .env.production + +# Logs and local diagnostics. *.log +logs/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Local IDE/editor state. +.idea/ .turbo/ coverage/ .tmp/ diff --git a/scripts/verify-production-deploy.mjs b/scripts/verify-production-deploy.mjs index 957505f..789e241 100644 --- a/scripts/verify-production-deploy.mjs +++ b/scripts/verify-production-deploy.mjs @@ -19,6 +19,7 @@ const checks = [ 'pnpm --filter @ftb/shared build', 'pnpm --filter web build', 'NEXT_PUBLIC_API_URL', + 'NEXT_API_PROXY_TARGET', 'NEXT_PUBLIC_APP_VERSION', 'ARG APP_VERSION=unknown', 'COPY scripts ./scripts', @@ -50,6 +51,7 @@ const checks = [ 'SERVER_IMAGE', 'WEB_IMAGE', 'APP_VERSION', + 'NEXT_API_PROXY_TARGET', '/api/v1/health/version', 'prometheus:', "profiles: ['monitoring']", @@ -65,6 +67,7 @@ const checks = [ 'appleboy/ssh-action', 'docker compose --env-file .env.production -f docker-compose.prod.yml pull', 'pnpm --filter server db:deploy', + 'NEXT_API_PROXY_TARGET', 'scripts/smoke-test-release.mjs', '--expected-version', ], @@ -77,6 +80,7 @@ const checks = [ file: 'scripts/smoke-test-release.mjs', snippets: [ 'Release smoke test passed', + '/api/v1/health/version', '/api/v1/v2.2/requirements?productId=__smoke__', '/api/v1/config/ai', ], @@ -111,9 +115,29 @@ const checks = [ 'nginx:', 'local_server_data:', 'NEXT_PUBLIC_API_URL', + 'NEXT_API_PROXY_TARGET', '${LOCAL_HTTP_PORT:-8080}:80', ], }, + { + file: 'apps/web/lib/api.ts', + snippets: [ + "env.NODE_ENV === 'production'", + 'isLocalOnlyApiUrl', + "'localhost'", + "'127.0.0.1'", + "'0.0.0.0'", + "return '/api/v1'", + ], + }, + { + file: 'apps/web/next.config.js', + snippets: [ + 'NEXT_API_PROXY_TARGET', + 'toApiProxyDestination', + '/api/v1/:path*', + ], + }, { file: 'deploy/nginx/default.conf.template', snippets: [ @@ -188,7 +212,8 @@ const checks = [ snippets: [ 'POSTGRES_PASSWORD=', 'DATABASE_URL=postgresql://', - 'NEXT_PUBLIC_API_URL=', + 'NEXT_PUBLIC_API_URL=/api/v1', + 'NEXT_API_PROXY_TARGET=', 'APP_VERSION=', 'WEB_IMAGE=', 'SERVER_IMAGE=', @@ -203,8 +228,12 @@ const checks = [ 'LOCAL_ACCESS_HOST=localhost', 'DATABASE_URL=postgresql://', 'NEXT_PUBLIC_API_URL=/api/v1', + 'NEXT_API_PROXY_TARGET=', 'NEXTAUTH_URL=http://localhost:8080', ], + forbiddenSnippets: [ + 'LOCAL_SERVER_PORT=', + ], }, { file: 'docs/deployment.md', @@ -243,6 +272,12 @@ for (const check of checks) { failed = true; } } + for (const snippet of check.forbiddenSnippets ?? []) { + if (content.includes(snippet)) { + console.error(`Forbidden snippet in ${check.file}: ${snippet}`); + failed = true; + } + } } for (const check of requiredRuntimeDependencies) {