LogoSkills

config-agent

환경 설정 관리 전문가입니다. Envied 어노테이션과 flavor 전환 패턴 구현에 사용합니다.

/shared:config — 환경별 설정 정리 도우미#

항목내용
실행 명령/shared:config
별칭/env:create, /config:setup
모델sonnet
사용 도구 Read, Edit, Write, Glob, Grep

한마디로#

같은 앱이라도 개발용 / 테스트용 / 실서비스용으로 접속하는 서버 주소나 비밀 키가 다릅니다. 이 도우미는 그 "환경별 설정"을 한곳에 깔끔하게 정리하고, 빌드할 때 알아서 맞는 값을 골라 쓰도록 자동으로 세팅해 줍니다. 콘센트 어댑터가 나라에 맞는 플러그를 자동으로 골라 끼워주는 것과 비슷합니다.

누가·언제 쓰나요#

  • 새 앱·기능에 서버 주소, API 키 같은 환경 변수를 추가해야 할 때
  • 개발(dev) / 스테이징(stg) / 운영(prod) 세 가지 환경을 나눠서 관리하고 싶을 때
  • /shared:config 명령을 실행하면 이 도우미가 활성화됩니다. (/env:create, /config:setup로도 부를 수 있습니다.)

무엇을 해주나요#

  • 환경별 설정 클래스 3종 세트를 만들어 줍니다 — EnvDev(개발), EnvStg(테스트), EnvProd(운영)
  • 빌드 시점에 자동으로 맞는 환경을 골라 쓰는 전환 스위치(EnvConfig)를 구성합니다
  • 환경 변수 파일(.env.dev, .env.stg, .env.prod)과 폴더 구조를 표준 형태로 정리합니다
  • API 키처럼 민감한 값은 난독화(숨김 처리) 해서 외부에 그대로 노출되지 않게 합니다
  • iOS/안드로이드/웹 등 플랫폼별 분기를 다루는 설정(PlatformConfig)도 함께 만들어 줍니다

어떻게 쓰나요#

# 기본 (개발 환경 설정 작업)
/shared:config

# 운영 환경에 새 환경 변수를 추가하고, 비밀값으로 숨김 처리
/shared:config env_type=prod variable_name=API_KEY is_secret=true
  • env_type — 어떤 환경인지 (dev, stg, prod / 기본값 dev)
  • variable_name — 추가할 환경 변수 이름
  • is_secret — 값을 숨길지 여부 (기본값 false, API 키 등에 권장)

설정 코드를 만든 뒤에는 빌드 명령에 환경을 지정해 실행합니다.

# 환경 변수 코드 생성
melos run generate:env

# 특정 환경으로 빌드/실행
flutter build apk --dart-define=FLAVOR=production
flutter run --dart-define=FLAVOR=development

안에서 무슨 일이 벌어지나요#

  1. 환경별 설정 클래스 3종(dev/stg/prod)을 만들어, 각 환경의 .env 파일에서 값을 읽어오게 합니다.
  2. 전환 스위치(EnvConfig) 를 만들어, 빌드할 때 지정한 환경(--dart-define=FLAVOR=...)에 따라 알맞은 값을 자동으로 골라 쓰게 합니다.
  3. 민감한 값은 난독화 처리하고, 환경 변수 파일은 실수로 공유되지 않도록 .gitignore에 넣도록 안내합니다.
  4. 플랫폼별 분기 설정을 추가해, iOS·안드로이드·웹 등 환경에 따라 다르게 동작해야 할 부분을 정리합니다.
  5. 마지막으로 코드 생성을 돌리고, 빠뜨린 게 없는지 체크리스트로 확인합니다.

⚙️ 상세 옵션·실행 명세 (개발자 / AI 에이전트용)

Role#

Manages and creates environment-specific configurations.

  • Environment variable management based on Envied annotations
  • 3 environment classes (EnvProd, EnvStg, EnvDev)
  • EnvConfig flavor switching pattern
  • Platform-specific branching

Activation Conditions#

  • /shared:config Activated when command is invoked
  • Invoked during environment variable and config file work

Parameters#

ParameterRequiredDescription
env_typedev, stg, prod (default: dev)
variable_nameEnvironment variable name to add
is_secretWhether to obfuscate (default: false)

Package Structure#

shared/config/
├── lib/
│   ├── config.dart                   # Export 파일
│   └── src/
│       ├── env/
│       │   ├── env_config.dart       # 플레이버 스위칭
│       │   ├── env_prod.dart         # Production 환경
│       │   ├── env_stg.dart          # Staging 환경
│       │   └── env_dev.dart          # Development 환경
│       ├── generated/                # 자동 생성
│       │   ├── env_prod.g.dart
│       │   ├── env_stg.g.dart
│       │   └── env_dev.g.dart
│       └── platform/
│           └── platform_config.dart  # 플랫폼별 분기
└── env/
    ├── .env.dev                      # 개발 환경 변수
    ├── .env.stg                      # 스테이징 환경 변수
    └── .env.prod                     # 프로덕션 환경 변수

Import Order (Required)#

// 1. Dart standard
import 'dart:io';

// 2. Envied package
import 'package:envied/envied.dart';

// 3. Generated files
part 'env_dev.g.dart';

Core Patterns#

1. Environment Class Definition (Envied)#

import 'package:envied/envied.dart';

part 'env_dev.g.dart';

/// Development environment 설정
///
/// [Envied]를 사용하여 `.env.dev` 파일의 환경 변수를 로드합니다.
@Envied(path: 'env/.env.dev', useConstantCase: true)
abstract class EnvDev {
  /// API base URL
  @EnviedField(varName: 'API_BASE_URL')
  static const String apiBaseUrl = _EnvDev.apiBaseUrl;

  /// API key (난독화)
  @EnviedField(varName: 'API_KEY', obfuscate: true)
  static final String apiKey = _EnvDev.apiKey;

  /// Firebase 프로젝트 ID
  @EnviedField(varName: 'FIREBASE_PROJECT_ID')
  static const String firebaseProjectId = _EnvDev.firebaseProjectId;

  /// Debug mode enabled
  @EnviedField(varName: 'DEBUG_MODE', defaultValue: 'true')
  static const String debugMode = _EnvDev.debugMode;

  /// Serverpod 서버 URL
  @EnviedField(varName: 'SERVERPOD_URL')
  static const String serverpodUrl = _EnvDev.serverpodUrl;

  /// Serverpod 포트
  @EnviedField(varName: 'SERVERPOD_PORT', defaultValue: '8080')
  static const String serverpodPort = _EnvDev.serverpodPort;
}

2. Environment Class (Staging)#

import 'package:envied/envied.dart';

part 'env_stg.g.dart';

/// Staging environment 설정
@Envied(path: 'env/.env.stg', useConstantCase: true)
abstract class EnvStg {
  @EnviedField(varName: 'API_BASE_URL')
  static const String apiBaseUrl = _EnvStg.apiBaseUrl;

  @EnviedField(varName: 'API_KEY', obfuscate: true)
  static final String apiKey = _EnvStg.apiKey;

  @EnviedField(varName: 'FIREBASE_PROJECT_ID')
  static const String firebaseProjectId = _EnvStg.firebaseProjectId;

  @EnviedField(varName: 'DEBUG_MODE', defaultValue: 'false')
  static const String debugMode = _EnvStg.debugMode;

  @EnviedField(varName: 'SERVERPOD_URL')
  static const String serverpodUrl = _EnvStg.serverpodUrl;

  @EnviedField(varName: 'SERVERPOD_PORT', defaultValue: '8080')
  static const String serverpodPort = _EnvStg.serverpodPort;
}

3. Environment Class (Production)#

import 'package:envied/envied.dart';

part 'env_prod.g.dart';

/// Production environment 설정
@Envied(path: 'env/.env.prod', useConstantCase: true)
abstract class EnvProd {
  @EnviedField(varName: 'API_BASE_URL')
  static const String apiBaseUrl = _EnvProd.apiBaseUrl;

  @EnviedField(varName: 'API_KEY', obfuscate: true)
  static final String apiKey = _EnvProd.apiKey;

  @EnviedField(varName: 'FIREBASE_PROJECT_ID')
  static const String firebaseProjectId = _EnvProd.firebaseProjectId;

  @EnviedField(varName: 'DEBUG_MODE', defaultValue: 'false')
  static const String debugMode = _EnvProd.debugMode;

  @EnviedField(varName: 'SERVERPOD_URL')
  static const String serverpodUrl = _EnvProd.serverpodUrl;

  @EnviedField(varName: 'SERVERPOD_PORT', defaultValue: '443')
  static const String serverpodPort = _EnvProd.serverpodPort;
}

4. Flavor Switching Pattern#

/// Environment type enum
enum Flavor {
  /// Development environment
  development,

  /// Staging environment
  staging,

  /// Production environment
  production,
}

/// Environment configuration manager
///
/// Provides configuration values based on current environment at runtime.
abstract final class EnvConfig {
  /// Current environment (set via --dart-define at build time)
  static Flavor get flavor {
    const flavorString = String.fromEnvironment(
      'FLAVOR',
      defaultValue: 'development',
    );
    return switch (flavorString) {
      'production' => Flavor.production,
      'staging' => Flavor.staging,
      _ => Flavor.development,
    };
  }

  /// API base URL
  static String get apiBaseUrl => switch (flavor) {
        Flavor.production => EnvProd.apiBaseUrl,
        Flavor.staging => EnvStg.apiBaseUrl,
        Flavor.development => EnvDev.apiBaseUrl,
      };

  /// API key
  static String get apiKey => switch (flavor) {
        Flavor.production => EnvProd.apiKey,
        Flavor.staging => EnvStg.apiKey,
        Flavor.development => EnvDev.apiKey,
      };

  /// Firebase 프로젝트 ID
  static String get firebaseProjectId => switch (flavor) {
        Flavor.production => EnvProd.firebaseProjectId,
        Flavor.staging => EnvStg.firebaseProjectId,
        Flavor.development => EnvDev.firebaseProjectId,
      };

  /// Debug mode enabled
  static bool get isDebugMode => switch (flavor) {
        Flavor.production => EnvProd.debugMode == 'true',
        Flavor.staging => EnvStg.debugMode == 'true',
        Flavor.development => EnvDev.debugMode == 'true',
      };

  /// Serverpod URL
  static String get serverpodUrl => switch (flavor) {
        Flavor.production => EnvProd.serverpodUrl,
        Flavor.staging => EnvStg.serverpodUrl,
        Flavor.development => EnvDev.serverpodUrl,
      };

  /// Serverpod 포트
  static int get serverpodPort => switch (flavor) {
        Flavor.production => int.parse(EnvProd.serverpodPort),
        Flavor.staging => int.parse(EnvStg.serverpodPort),
        Flavor.development => int.parse(EnvDev.serverpodPort),
      };

  /// Production environment 여부
  static bool get isProduction => flavor == Flavor.production;

  /// Development environment 여부
  static bool get isDevelopment => flavor == Flavor.development;

  /// Staging environment 여부
  static bool get isStaging => flavor == Flavor.staging;
}

5. Platform-specific Branching#

import 'dart:io';

import 'package:flutter/foundation.dart';

/// 플랫폼 설정
abstract final class PlatformConfig {
  /// Current platform
  static TargetPlatform get platform {
    if (kIsWeb) return TargetPlatform.android; // 웹은 Android로 취급
    if (Platform.isIOS) return TargetPlatform.iOS;
    if (Platform.isAndroid) return TargetPlatform.android;
    if (Platform.isMacOS) return TargetPlatform.macOS;
    if (Platform.isWindows) return TargetPlatform.windows;
    if (Platform.isLinux) return TargetPlatform.linux;
    return TargetPlatform.android;
  }

  /// Is iOS
  static bool get isIOS => !kIsWeb && Platform.isIOS;

  /// Is Android
  static bool get isAndroid => !kIsWeb && Platform.isAndroid;

  /// Is Web
  static bool get isWeb => kIsWeb;

  /// Is Desktop
  static bool get isDesktop =>
      !kIsWeb &&
      (Platform.isMacOS || Platform.isWindows || Platform.isLinux);

  /// Is Mobile
  static bool get isMobile =>
      !kIsWeb && (Platform.isIOS || Platform.isAndroid);
}

6. .env File Format#

# .env.dev
API_BASE_URL=https://dev-api.example.com
API_KEY=dev_api_key_12345
FIREBASE_PROJECT_ID=my-app-dev
DEBUG_MODE=true
SERVERPOD_URL=http://localhost
SERVERPOD_PORT=8080

# .env.stg
API_BASE_URL=https://stg-api.example.com
API_KEY=stg_api_key_67890
FIREBASE_PROJECT_ID=my-app-stg
DEBUG_MODE=false
SERVERPOD_URL=https://stg.example.com
SERVERPOD_PORT=8080

# .env.prod
API_BASE_URL=https://api.example.com
API_KEY=prod_api_key_secret
FIREBASE_PROJECT_ID=my-app-prod
DEBUG_MODE=false
SERVERPOD_URL=https://api.example.com
SERVERPOD_PORT=443

Build Commands#

# Generate environment variable code
melos run generate:env

# Build for specific environment
flutter build apk --dart-define=FLAVOR=production
flutter build ios --dart-define=FLAVOR=staging
flutter run --dart-define=FLAVOR=development

# Clean build
cd shared/config  & &   dart run build_runner build --delete-conflicting-outputs

Envied Annotation Types#

AnnotationPurposeExample
@EnviedEnvironment class definition@Envied(path: 'env/.env.dev')
@EnviedFieldEnvironment variable field@EnviedField(varName: 'API_KEY')
obfuscate: trueValue obfuscationUsed for API keys, secrets
defaultValueDefault value settingUsed for optional variables
useConstantCase: trueAuto constant case conversionapiBaseUrl -> API_BASE_URL

Reference Files#

shared/config/lib/src/env/env_dev.dart
shared/config/lib/src/env/env_stg.dart
shared/config/lib/src/env/env_prod.dart
shared/config/lib/src/env/env_config.dart
shared/config/lib/src/platform/platform_config.dart

Checklist#

  • Define 3 environment classes (dev, stg, prod)
  • Connect .g.dart via part directive
  • Apply obfuscate: true for sensitive info
  • Implement EnvConfig flavor switching
  • Add .env files to .gitignore
  • Run build_runner (melos run generate:env)
  • Verify --dart-define usage during build