Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions frontend/lib/env/runtime-env.generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import 'server-only';

export const RUNTIME_ENV: Readonly<Record<string, string>> = {};
29 changes: 28 additions & 1 deletion frontend/lib/env/server-env.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'server-only';

import { RUNTIME_ENV } from './runtime-env.generated';

type NetlifyEnv = {
get?: (key: string) => string | undefined;
};
Expand All @@ -25,9 +27,34 @@ function readFromNetlifyEnv(key: string): string | undefined {
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
}

const GENERATED_FALLBACK_KEYS = new Set([
'APP_ENV',
'CONTEXT',
'NETLIFY',
'DATABASE_URL',
'DATABASE_URL_LOCAL',
'AUTH_SECRET',
'CSRF_SECRET',
]);


function canUseGeneratedFallback(key: string): boolean {
return GENERATED_FALLBACK_KEYS.has(key);
}

export function readServerEnv(key: string): string | undefined {
const fromProcess = process.env[key]?.trim();
if (fromProcess) return fromProcess;

return readFromNetlifyEnv(key);
const fromNetlify = readFromNetlifyEnv(key);
if (fromNetlify) return fromNetlify;

if (!canUseGeneratedFallback(key)) return undefined;
return readFromGeneratedRuntimeEnv(key);

}

function readFromGeneratedRuntimeEnv(key: string): string | undefined {
const value = RUNTIME_ENV[key];
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
}
56 changes: 56 additions & 0 deletions frontend/scripts/generate-env-runtime.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';

const root = process.cwd();
const examplePath = resolve(root, '.env.example');
const outputPath = resolve(root, 'lib/env/runtime-env.generated.ts');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Tracked status:"
git ls-files --stage frontend/lib/env/runtime-env.generated.ts || true

echo
echo "Ignore rules mentioning runtime-env.generated.ts:"
fd -i .gitignore | xargs -I{} sh -c 'echo "--- {} ---"; rg -n "runtime-env\\.generated\\.ts" "{}" || true'

Repository: DevLoversTeam/devlovers.net

Length of output: 229


🏁 Script executed:

cat -n frontend/scripts/generate-env-runtime.mjs | head -50

Repository: DevLoversTeam/devlovers.net

Length of output: 1551


🏁 Script executed:

cat frontend/lib/env/runtime-env.generated.ts

Repository: DevLoversTeam/devlovers.net

Length of output: 158


🏁 Script executed:

rg "runtime-env\.generated" --type ts --type js --type json

Repository: DevLoversTeam/devlovers.net

Length of output: 156


🏁 Script executed:

find . -name "*.yml" -o -name "*.yaml" -o -name "*.json" -o -name "*.mjs" -o -name "Makefile" | grep -E "(github|gitlab|circleci|jenkins|\.github|\.gitlab|build|ci)" | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 219


🏁 Script executed:

find . -path "./.git" -prune -o -type f \( -name "package.json" -o -name ".github/workflows/*.yml" \) -print | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 116


🏁 Script executed:

cat frontend/package.json | grep -A 20 '"scripts"'

Repository: DevLoversTeam/devlovers.net

Length of output: 1104


🏁 Script executed:

cat .github/workflows/security.yml

Repository: DevLoversTeam/devlovers.net

Length of output: 1363


🏁 Script executed:

rg "generate-env-runtime" --type ts --type js --type mjs --type json

Repository: DevLoversTeam/devlovers.net

Length of output: 98


🏁 Script executed:

find .github/workflows -type f -name "*.yml" -exec cat {} \;

Repository: DevLoversTeam/devlovers.net

Length of output: 3587


🏁 Script executed:

rg "generate-env-runtime" --type ts --type js

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

git log --oneline frontend/lib/env/runtime-env.generated.ts | head -5

Repository: DevLoversTeam/devlovers.net

Length of output: 149


🏁 Script executed:

git show f6aabc8 --stat

Repository: DevLoversTeam/devlovers.net

Length of output: 50383


🏁 Script executed:

find . -name "next.config.*" -o -name "netlify.toml" | xargs cat 2>/dev/null || true

Repository: DevLoversTeam/devlovers.net

Length of output: 2862


Remove lib/env/runtime-env.generated.ts from git tracking and add it to .gitignore.

This file is generated by generate-env-runtime.mjs during Netlify builds and contains actual environment variable values. It's currently tracked in git without .gitignore protection, creating a clear path for developers to accidentally commit secrets if they run the generation script locally.

The file is already part of the Netlify build pipeline (netlify.toml calls the script before npm run build), so removing it from tracking won't break the build—Netlify will regenerate it with its own environment variables.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/scripts/generate-env-runtime.mjs` at line 6, Remove the generated
file from git tracking and ignore it: stop tracking
lib/env/runtime-env.generated.ts (use git rm --cached on that file) and add
lib/env/runtime-env.generated.ts to .gitignore so commits won't include
regenerated secrets; ensure the generate-env-runtime.mjs script remains
unchanged (Netlify will still regenerate the file during its build) and commit
the .gitignore and removal as a single change.

const appEnv = (process.env.APP_ENV ?? '').trim().toLowerCase();
const isDevelop = appEnv === 'develop';

const keyRegex = /^([A-Z][A-Z0-9_]*)=/;

const keys = Array.from(
new Set(
readFileSync(examplePath, 'utf8')
.split(/\r?\n/)
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'))
.map(line => {
const match = line.match(keyRegex);
return match ? match[1] : null;
})
.filter(Boolean)
)
);

const entries = [];

for (const key of keys) {
const value = process.env[key];
if (typeof value !== 'string' || value.length === 0) continue;
entries.push([key, value]);
}

if (!isDevelop) {
const fileContent = `import 'server-only';

export const RUNTIME_ENV: Readonly<Record<string, string>> = {};
`;
writeFileSync(outputPath, fileContent, 'utf8');
console.log(`[env] skipped runtime env generation (APP_ENV=${appEnv || '<empty>'})`);
process.exit(0);
}

const objectBody = entries
.map(([key, value]) => ` ${JSON.stringify(key)}: ${JSON.stringify(value)},`)
.join('\n');

const fileContent = `import 'server-only';

export const RUNTIME_ENV: Readonly<Record<string, string>> = {
${objectBody}
};
`;

writeFileSync(outputPath, fileContent, 'utf8');
console.log(`[env] generated runtime-env.generated.ts with ${entries.length} keys`);
2 changes: 1 addition & 1 deletion netlify.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[build]
base = "frontend"
command = "npm ci --include=optional && npm run build"
command = "npm ci --include=optional && node scripts/generate-env-runtime.mjs && npm run build"
publish = ".next"

[build.environment]
Expand Down
Loading