diff --git a/.env.example b/.env.example index 05148d4de..607e1ef3d 100644 --- a/.env.example +++ b/.env.example @@ -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 \ No newline at end of file +# 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 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 14b7fae74..1406ac538 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/angular.json b/angular.json index 758d73925..33401d953 100644 --- a/angular.json +++ b/angular.json @@ -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": { diff --git a/docs/ENV_SETUP.md b/docs/ENV_SETUP.md index 6d274d492..f1b8712cd 100644 --- a/docs/ENV_SETUP.md +++ b/docs/ENV_SETUP.md @@ -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'); + ``` diff --git a/package.json b/package.json index 6bfabb5fa..f4848e88a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/app/core/env/env.service.ts b/src/app/core/env/env.service.ts new file mode 100644 index 000000000..8cc022d52 --- /dev/null +++ b/src/app/core/env/env.service.ts @@ -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; + } +} diff --git a/src/app/util/env.ts b/src/app/util/env.ts new file mode 100644 index 000000000..c8f167673 --- /dev/null +++ b/src/app/util/env.ts @@ -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; +}; diff --git a/src/environments/environment.ts b/src/environments/environment.ts new file mode 100644 index 000000000..4aa43b5a6 --- /dev/null +++ b/src/environments/environment.ts @@ -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, +}; diff --git a/src/types/environment.d.ts b/src/types/environment.d.ts new file mode 100644 index 000000000..32e335ace --- /dev/null +++ b/src/types/environment.d.ts @@ -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; +}; diff --git a/tools/generate-env.js b/tools/generate-env.js deleted file mode 100755 index 95627b882..000000000 --- a/tools/generate-env.js +++ /dev/null @@ -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}`);