Fluent Commerce Logo
Docs

CLI Active Profile

Essential knowledge

Intended Audience:

Technical User

Author:

Marco Heuer

Changed on:

30 Apr 2026

Overview

Learn the technical mechanics of the Active Profile feature and how it streamlines CLI workflows. This article details the shift from rigid "Commander-enforced" validation to a flexible "Defaults-first" resolution, allowing implementing partners to work faster without sacrificing accuracy.For the business, this context-aware CLI reduces the learning curve and prevents manual entry errors. Note: While flags are no longer required at the command level (`required: false`), they remain mandatory for execution; the CLI will exit if no context is found.

Key points

  • The Shift: Validation has moved from the Commander parsing stage to the Configuration Utility stage.
  • Precedence: CLI flags always override the Active Profile. If you explicitly pass `--profile`, the active context is ignored for that command.
  • Seamless Integration: The `loadConfigs` utility now calls `resolveDefaults` first to populate missing options before handing them off to existing validation logic.
  • Unchanged Security: Standard validation functions like `loadProfile()` and `checkConfigForAccountProfile()` are still in place; they simply receive their data from a default source if flags are missing.
  • Developer Impact: Task definitions must have `required: true` removed from their options to allow the Active Profile logic to trigger.
  • Error States: If an Active Profile points to a nonexistent profile, the system will catch it during the `loadProfile` phase and exit with a `process.exit(1)`.

The Question

How does the Active Profile feature work with existing tasks that have `required: true` for profile/retailer options?

Current Flow (Before Active Profile)

`1. User runs: fluent module list --profile apsear2dev --retailer ATEST2`
2. Commander parses CLI args
  - Validates required options exist
  - Builds allOpts object: { profile: 'apsear2dev', retailer: 'ATEST2' }
3. Action handler calls configurationUtil.loadConfigs(config, allOpts, modulePath)
4. loadConfigs checks: if (allOpts.profile)
  - Calls loadProfile(allOpts.profile)
  - loadProfile validates profile exists, calls process.exit(1) if not
  - Calls loadRetailer(allOpts.profile, allOpts.retailer)
  - loadRetailer validates retailer exists, calls process.exit(1) if not
5. Task's run() function executes with populated config
Key point: Validation happens in TWO places:
  • Commander level`required: true` ensures flags are provided
  • Configuration level`loadProfile()` and `loadRetailer()` validate they exist

New Flow (with Active Profile)

Key changes:
  • Remove `required: true` from task option definitions
  • Add Active Profile resolution BEFORE existing validation
  • Existing validation in `loadProfile()` and `loadRetailer()` is UNCHANGED

Error Handling Examples

Scenario 1: No Active Profile set, no flags

`$ fluent module list`

`# Flow:
# 1. allOpts.profile = undefined
# 2. Active Profile doesn't exist
# 3. allOpts.profile still undefined after resolution
# 4. loadConfigs checks: if (allOpts.profile) - FALSE
# 5. config.account is undefined
# 6. Task calls checkConfigForAccountProfile(config)
# 7. Error: "Profile must be set. Use --profile flag or fluent profile use"
`

Scenario 2: Active Profile exists, profile invalid

`$ fluent profile use nonexistent_profile`
$ fluent module list

`# Flow:
# 1. allOpts.profile = undefined
# 2. Active Profile: { activeProfile: 'nonexistent_profile' }
# 3. allOpts.profile = 'nonexistent_profile' (from Active Profile)
# 4. loadConfigs calls loadProfile('nonexistent_profile')
# 5. loadProfile checks if profile exists
# 6. Error: "Profile 'nonexistent_profile' does not exist. Use 'profile' command..."
# 7. process.exit(1)
`

Scenario 3: Active Profile exists, retailer required but missing

`$ fluent profile use apsear2dev  # No retailer set
`
$ fluent setup payment-service-provider

`# Flow:
# 1. allOpts.profile = 'apsear2dev', allOpts.retailer = undefined
# 2. loadConfigs loads profile successfully
# 3. allOpts.retailer still undefined
# 4. config.retailer = {} (empty)
# 5. Task calls checkConfigForRetailerProfile(config)
# 6. Error: "Retailer must be set. Use --retailer flag or fluent profile use <profile> [-r|--retailer] <retailer>"
# 7. Throws error
`

Scenario 4: CLI flags override Active Profile

`$ fluent profile use apsear2dev --retailer ATEST2`
$ fluent module list --profile production --retailer PRODRETAILER

`# Flow:
# 1. allOpts.profile = 'production', allOpts.retailer = 'PRODRETAILER' (from CLI)
# 2. resolveDefaults sees CLI values exist
# 3. Skip Active Profile (CLI takes precedence)
# 4. Use 'production' and 'PRODRETAILER'
`

Task Option Changes

Before

`// packages/cli/src/tasks/module/moduleList.ts
export const taskdef: fluentCliTypes.Taskdef = {`

 command`: 'module',`
 subcommand`: 'list',`
 options`: [`
   {
     flags`: '-p, --profile <profile>',`
     description`: 'The Profile to use.',`
     required`: true  // ← Commander enforces this
`
   }
 ],
 run`: async function (config, args, opts) {`
   checkConfigForAccountProfile(config);  `// ← Also validates
`
   `// ...
`
 }
};

After

`// packages/cli/src/tasks/module/moduleList.ts
export const taskdef: fluentCliTypes.Taskdef = {`

 command`: 'module',`
 subcommand`: 'list',`
 options`: [`
   {
     flags`: '-p, --profile <profile>',`
     description`: 'The Profile to use. Defaults to active profile if set.',`
     required`: false  // ← Allow Commander to skip
`
   }
 ],
 run`: async function (config, args, opts) {`
   checkConfigForAccountProfile(config);  `// ← This STILL validates!
`
   `// ...
`
 }
};

Validation Utilities (Unchanged)

The existing validation utilities still work:
`// packages/cli/src/utils/validationSchemaUtil.ts
export const checkConfigForAccountProfile = (config: fluentCliTypes.Config): void => {`

 `if (!config.account) {`
   `throw new Error('Account profile not configured. Use --profile flag or set active profile with: fluent profile use <profile>');`
 }
};

`export const checkConfigForRetailerProfile = (config: fluentCliTypes.Config): void => {`
 checkConfigForAccountProfile(config);
 `if (!config.retailer || !config.retailer.id) {`
   `throw new Error('Retailer not configured. Use --retailer flag or set active profile with: fluent profile use <profile> [-r|--retailer] <retailer>');`
 }
};
These throw errors if `config.account` or `config.retailer` are missing, which happens when: - No active profile is set - Active profile doesn't have the needed value - loadProfile/loadRetailer failed to populate config

Configuration Flow Diagram

No alt provided

Implementation in configurationUtil.ts

`import activeContextUtil from './activeContextUtil.js';`

`const loadConfigs = (config: fluentCliTypes.Config, allOpts: OptionValues, modulePath: string | null) => {`
 log.debug('loadConfigs, allOpts: %s', allOpts);

 `// NEW: Inject active profile if CLI flags not provided
`
 `const defaults = activeContextUtil.resolveDefaults(allOpts);`

 `if (!allOpts.profile && defaults.profile) {`
   allOpts.profile `= defaults.profile;`
   log.debug('Using active profile: %s', defaults.profile);
 }

 `if (!allOpts.retailer && defaults.retailer) {`
   allOpts.retailer `= defaults.retailer;`
   log.debug('Using active retailer: %s', defaults.retailer);
 }

 `// EXISTING CODE (unchanged)
`
 `if (allOpts.profile) {`
   config.name `= allOpts.profile;`
   `const profileConfig = loadProfile(allOpts.profile);  // ← Validates & exits if bad
`
   config.account `= _.merge(profileConfig);`
   `const retailerConfig = allOpts.retailer && !Array.isArray(allOpts.retailer)`
     `? loadRetailer(allOpts.profile, allOpts.retailer)  // ← Validates & exits if bad
`
     `: {};`
   config.retailer `= _.merge(retailerConfig);`
 }

 `const moduleConfig = modulePath ? loadModule(modulePath, allOpts.retailer, allOpts.config) : {};`
 config.module `= _.merge(moduleConfig);`
 log.debug('loadConfigs: %o', config);
 `return config;`
};

Summary (Answer and Result)

The answer: Active Profile integrates seamlessly because:
  • ✅ We remove `required: true` from Commander option definitions
  • ✅ Active Profile populates `allOpts` BEFORE existing validation
  • ✅ Existing validation in `loadProfile()` and `loadRetailer()` is UNCHANGED
  • ✅ Existing validation in `checkConfigForAccountProfile()` is UNCHANGED
  • ✅ Tasks see no difference - they get `config.account` and `config.retailer` either way
Result: The system validates at the right place (where profile/retailer files are loaded), not at the wrong place (Commander option parsing). Active Profile just provides default values that flow through existing validation.