build: env file improve

This commit is contained in:
Johannes Millan 2025-07-14 19:47:48 +02:00
parent 8432e1bbb4
commit 97dd02fe7a
10 changed files with 241 additions and 108 deletions

View file

@ -1,14 +1,9 @@
# Environment configuration example
# Copy this file to .env and adjust values as needed
# NODE_ENV - Set the Node environment (development, production, staging)
NODE_ENV=development
# PRODUCTION - Set to true for production builds
PRODUCTION=false
# STAGE - Set to true for staging builds
STAGE=false
# Additional environment variables can be added here
# They will be accessible in the Angular app via process.env
# Example API keys and tokens (add your actual values in .env)
# GOOGLE_DRIVE_TOKEN=your-token-here
# DROPBOX_API_KEY=your-api-key-here
# WEBDAV_URL=https://your-webdav-server.com
# WEBDAV_USERNAME=your-username
# WEBDAV_PASSWORD=your-password

5
.gitignore vendored
View file

@ -2,14 +2,9 @@
# environment files
.env
.env.production
.env.stage
.env.local
.env.*.local
# auto-generated environment file
src/environments/environment.ts
# compiled output
/.tmp
/dist

View file

@ -139,7 +139,7 @@
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
"with": "src/environments/environment.stage.ts"
}
],
"optimization": {
@ -171,7 +171,7 @@
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
"with": "src/environments/environment.stage.ts"
}
],
"optimization": {

View file

@ -1,62 +1,122 @@
# Environment Configuration
# Environment Configuration Setup
Super Productivity now uses `.env` files for environment-specific configuration instead of Angular's traditional environment files.
This project uses a hybrid approach for environment configuration:
## Setup
- **Base configuration** (production/stage flags) are in static TypeScript files
- **Secrets and dynamic values** are loaded from `.env` files at build time
1. Copy `.env.example` to `.env` for local development:
## Overview
### Static Environment Files
- `src/environments/environment.ts` - Development configuration
- `src/environments/environment.prod.ts` - Production configuration
- `src/environments/environment.stage.ts` - Staging configuration
These files contain base configuration like `production`, `stage`, and `version` flags.
### Dynamic Environment Variables
- `.env` - Environment variables for all environments
This file contains secrets and environment-specific values that should not be committed to version control.
## Setup Instructions
1. **Create your .env file**
```bash
cp .env.example .env
```
2. Create environment-specific files as needed:
- `.env` - Default development environment
- `.env.production` - Production environment
- `.env.stage` - Staging environment
2. **Add your environment variables**
## How it Works
```bash
# .env
GOOGLE_DRIVE_TOKEN=your-token-here
DROPBOX_API_KEY=your-api-key-here
```
The build process automatically generates `src/environments/environment.ts` from the appropriate `.env` file using `tools/generate-env.js`.
3. **Access environment variables in your code**
## Available Variables
```typescript
// Direct access (works everywhere)
const googleToken = process.env.GOOGLE_DRIVE_TOKEN;
- `NODE_ENV` - Environment name (development, production, staging)
- `PRODUCTION` - Set to "true" for production builds
- `STAGE` - Set to "true" for staging builds
// Or using utility functions
import { getEnv, getEnvOrDefault } from './app/util/env';
## Usage
const googleToken = getEnv('GOOGLE_DRIVE_TOKEN');
const dropboxKey = getEnvOrDefault('DROPBOX_API_KEY', 'default-key');
```
### Development
## Running the Application
The npm scripts automatically load the `.env` file:
```bash
# Development
npm run startFrontend
```
### Production
```bash
# Production configuration
npm run startFrontend:prod
# or
npm run buildFrontend:prod:es6
# Staging configuration
npm run startFrontend:stage
```
### Staging
Note: All commands use the same `.env` file. The difference between environments is controlled by the Angular configuration (production/stage flags).
## Build Commands
Build commands also load the appropriate environment:
```bash
npm run startFrontend:stage
# or
# Production build
npm run buildFrontend:prod:es6
# Staging build
npm run buildFrontend:stage:es6
```
## How It Works
1. **dotenv-run** loads variables from `.env` file before running Angular commands
2. **webpack DefinePlugin** injects these variables as `process.env.*` at build time
3. **Pure utility functions** in `src/app/util/env.ts` provide helper functions (optional)
4. **TypeScript declarations** in `src/types/environment.d.ts` provide type safety
## Security Notes
- Never commit `.env` files to version control
- Secrets are injected at build time, not included in source code
- Access variables directly via `process.env` or use the utility functions
- Add new environment variables to both `.env.example` and `src/types/environment.d.ts`
## Adding New Environment Variables
1. Add the variable to your `.env` files
2. Update `tools/generate-env.js` to include the new variable in the generated environment
3. Use the variable in your code via `environment.yourVariable`
1. Add to `.env.example` as documentation:
## Important Notes
```bash
NEW_API_KEY=your-api-key-here
```
- The `src/environments/environment.ts` file is auto-generated and should not be edited directly
- All `.env` files are gitignored for security
- Always use `.env.example` as a template for required variables
2. Add TypeScript declaration in `src/types/environment.d.ts`:
```typescript
interface ProcessEnv {
NEW_API_KEY?: string;
// ... other variables
}
```
3. Use in your code:
```typescript
// Direct access
const apiKey = process.env.NEW_API_KEY;
// Or with utility function
import { getEnv } from './app/util/env';
const apiKey = getEnv('NEW_API_KEY');
```

View file

@ -32,12 +32,12 @@
"buildAllElectron:noTests:prod": "npm run lint && npm run buildFrontend:prod:es6 && npm run electron:build",
"buildAllElectron:prod": "npm run preCheck && npm run buildFrontend:prod:es6 && npm run electron:build",
"buildAllElectron:stage": "npm run preCheck && npm run buildFrontend:stage:es6 && npm run electron:build",
"buildFrontend:prod:es6": "npm run prebuild && node tools/generate-env.js production && cross-env BROWSERSLIST_ENV='modern' ng build --configuration production && npm run removeWOFF1",
"buildFrontend:prod:watch": "npm run prebuild && node tools/generate-env.js production && ng build --configuration production --watch",
"buildFrontend:prodWeb": "npm run prebuild && node tools/generate-env.js production && ng build --configuration productionWeb",
"buildFrontend:stage:es6": "npm run prebuild && node tools/generate-env.js stage && cross-env BROWSERSLIST_ENV='modern' ng build --configuration stage && npm run removeWOFF1",
"buildFrontend:stageWeb": "npm run prebuild && node tools/generate-env.js stage && ng build --configuration stageWeb",
"buildFrontend:stageWeb:unminified": "npm run prebuild && node tools/generate-env.js stage && ng build --configuration stageWeb --optimization=false --aot=false",
"buildFrontend:prod:es6": "npm run prebuild && dotenv-run -- cross-env BROWSERSLIST_ENV='modern' ng build --configuration production && npm run removeWOFF1",
"buildFrontend:prod:watch": "npm run prebuild && ng build --configuration production --watch",
"buildFrontend:prodWeb": "npm run prebuild && dotenv-run -- ng build --configuration productionWeb",
"buildFrontend:stage:es6": "npm run prebuild && dotenv-run -- cross-env BROWSERSLIST_ENV='modern' ng build --configuration stage && npm run removeWOFF1",
"buildFrontend:stageWeb": "npm run prebuild && dotenv-run -- ng build --configuration stageWeb",
"buildFrontend:stageWeb:unminified": "npm run prebuild && ng build --configuration stageWeb --optimization=false --aot=false",
"dist": "npm run buildAllElectron:prod && electron-builder",
"dist:android": "npm run buildFrontend:stageWeb:unminified && npm run sync:android && npm run assemble:android:stage && echo 'Staging Android APK generated at android/app/build/outputs/apk/debug/'",
"dist:android:prod": "npm run buildFrontend:prodWeb && npm run sync:android && npm run assemble:android:prod && echo 'Production Android APK generated at android/app/build/outputs/apk/release/'",
@ -85,9 +85,9 @@
"removeWOFF1": "node ./tools/remove-woff.js",
"serveProd": "ng serve --configuration production",
"start": "npm run electron:build && cross-env NODE_ENV=DEV electron .",
"startFrontend": "node tools/generate-env.js development && ng serve",
"startFrontend:prod": "node tools/generate-env.js production && ng serve --configuration production",
"startFrontend:stage": "node tools/generate-env.js stage && ng serve --configuration stage",
"startFrontend": "dotenv-run -- ng serve",
"startFrontend:prod": "dotenv-run -- ng serve --configuration production",
"startFrontend:stage": "dotenv-run -- ng serve --configuration stage",
"sync:android": "npx cap sync android",
"stats": "ng build --configuration production --source-map --stats-json && npx esbuild-visualizer --metadata .tmp/angular-dist/stats.json && xdg-open stats.html",
"test": "cross-env TZ='Europe/Berlin' ng test --watch=false && npm run test:tz:ci",

59
src/app/core/env/env.service.ts vendored Normal file
View file

@ -0,0 +1,59 @@
import { Injectable } from '@angular/core';
declare const process: {
env: {
[key: string]: string | undefined;
};
};
@Injectable({
providedIn: 'root',
})
export class EnvService {
/**
* Get an environment variable value.
* Returns undefined if the variable is not set.
*/
get(key: string): string | undefined {
// Check if we're in a browser environment with process.env injected by webpack
if (typeof process !== 'undefined' && process.env) {
return process.env[key];
}
return undefined;
}
/**
* Get an environment variable value with a default fallback.
*/
getOrDefault(key: string, defaultValue: string): string {
return this.get(key) ?? defaultValue;
}
/**
* Check if an environment variable is set.
*/
has(key: string): boolean {
return this.get(key) !== undefined;
}
/**
* Get an environment variable as a boolean.
* Returns true for 'true', '1', 'yes', 'on' (case-insensitive).
* Returns false for any other value or if not set.
*/
getBoolean(key: string): boolean {
const value = this.get(key)?.toLowerCase();
return value === 'true' || value === '1' || value === 'yes' || value === 'on';
}
/**
* Get an environment variable as a number.
* Returns undefined if the value is not a valid number.
*/
getNumber(key: string): number | undefined {
const value = this.get(key);
if (value === undefined) return undefined;
const num = Number(value);
return isNaN(num) ? undefined : num;
}
}

43
src/app/util/env.ts Normal file
View file

@ -0,0 +1,43 @@
/**
* Pure functions for accessing environment variables.
* These can be used anywhere in the codebase, including outside Angular context.
*/
/**
* Get an environment variable value.
* Returns undefined if the variable is not set.
*/
export const getEnv = (key: string): string | undefined => {
if (typeof process !== 'undefined' && process.env) {
return process.env[key];
}
return undefined;
};
/**
* Get an environment variable value with a default fallback.
*/
export const getEnvOrDefault = (key: string, defaultValue: string): string => {
return getEnv(key) ?? defaultValue;
};
/**
* Get an environment variable as a boolean.
* Returns true for 'true', '1', 'yes', 'on' (case-insensitive).
* Returns false for any other value or if not set.
*/
export const getEnvBoolean = (key: string): boolean => {
const value = getEnv(key)?.toLowerCase();
return value === 'true' || value === '1' || value === 'yes' || value === 'on';
};
/**
* Get an environment variable as a number.
* Returns undefined if the value is not a valid number.
*/
export const getEnvNumber = (key: string): number | undefined => {
const value = getEnv(key);
if (value === undefined) return undefined;
const num = Number(value);
return isNaN(num) ? undefined : num;
};

View file

@ -0,0 +1,8 @@
// This file is the default environment configuration for development
import pkg from '../../package.json';
export const environment = {
production: false,
stage: false,
version: pkg.version,
};

22
src/types/environment.d.ts vendored Normal file
View file

@ -0,0 +1,22 @@
/**
* Type declarations for environment variables.
* Add your environment variable types here.
*/
declare namespace NodeJS {
interface ProcessEnv {
// Example environment variables - add your own here
GOOGLE_DRIVE_TOKEN?: string;
DROPBOX_API_KEY?: string;
WEBDAV_URL?: string;
WEBDAV_USERNAME?: string;
WEBDAV_PASSWORD?: string;
// Add more environment variables as needed
[key: string]: string | undefined;
}
}
// Ensure process is available globally in the browser
declare const process: {
env: NodeJS.ProcessEnv;
};

View file

@ -1,49 +0,0 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// Get environment name from command line or default to development
const envName = process.argv[2] || 'development';
// Map environment names to .env files
const envFileMap = {
development: '.env',
production: '.env.production',
stage: '.env.stage',
};
// Load environment variables manually
const envFile = envFileMap[envName] || '.env';
const envPath = path.join(process.cwd(), envFile);
const envContent = fs.readFileSync(envPath, 'utf8');
const env = {};
// Parse .env file
envContent.split('\n').forEach((line) => {
line = line.trim();
if (line && !line.startsWith('#')) {
const [key, value] = line.split('=');
if (key && value) {
env[key.trim()] = value.trim();
}
}
});
// Generate environment.ts content
const envTsContent = `// This file is auto-generated by tools/generate-env.js
// Do not modify directly
import pkg from '../../package.json';
export const environment = {
production: ${env.PRODUCTION === 'true'},
stage: ${env.STAGE === 'true'},
version: pkg.version,
};
`;
// Write to environment.ts
const targetPath = path.join(__dirname, '../src/environments/environment.ts');
fs.writeFileSync(targetPath, envTsContent);
console.log(`✅ Generated environment.ts from ${envFile}`);