< Summary - Envilder CLI

Information
Class: src/envilder/apps/cli/entry/Cli.ts
Assembly: Default
File(s): src/envilder/apps/cli/entry/Cli.ts
Tag: 427_29134414720
Line coverage
88%
Covered lines: 24
Uncovered lines: 3
Coverable lines: 27
Total lines: 153
Line coverage: 88.8%
Branch coverage
57%
Covered branches: 8
Total branches: 14
Branch coverage: 57.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

File(s)

src/envilder/apps/cli/entry/Cli.ts

#LineLine coverage
 1import 'reflect-metadata';
 2import { dirname, join } from 'node:path';
 3import { fileURLToPath } from 'node:url';
 4import { Command } from 'commander';
 5import type { Container } from 'inversify';
 6import pc from 'picocolors';
 7import { DispatchActionCommand } from '../../../core/application/dispatch/DispatchActionCommand.js';
 8import type { DispatchActionCommandHandler } from '../../../core/application/dispatch/DispatchActionCommandHandler.js';
 9import type { CliOptions } from '../../../core/domain/CliOptions.js';
 10import type { MapFileConfig } from '../../../core/domain/MapFileConfig.js';
 11import { PackageVersionReader } from '../../../core/infrastructure/package/PackageVersionReader.js';
 12import { readMapFileConfig } from '../../../core/infrastructure/variableStore/FileVariableStore.js';
 13import { TYPES } from '../../../core/types.js';
 14import { executeWithSsoRecovery } from '../recovery/SsoLoginRecovery.js';
 15import { Startup } from '../Startup.js';
 16
 17let serviceProvider: Container;
 18
 19async function executeCommand(options: CliOptions): Promise<void> {
 320  const commandHandler = serviceProvider.get<DispatchActionCommandHandler>(
 21    TYPES.DispatchActionCommandHandler,
 22  );
 23
 324  const command = DispatchActionCommand.fromCliOptions(options);
 325  await commandHandler.handleCommand(command);
 26}
 27
 28export async function main() {
 429  const program = new Command();
 430  const version = await readPackageVersion();
 31
 432  const banner = `
 33  ${pc.green('███████╗')}${pc.cyan('███╗   ██╗')}${pc.magenta('██╗   ██╗')}${pc.yellow('██╗')}${pc.red('██╗     ')}${pc.
 34  ${pc.green('██╔════╝')}${pc.cyan('████╗  ██║')}${pc.magenta('██║   ██║')}${pc.yellow('██║')}${pc.red('██║     ')}${pc.
 35  ${pc.green('█████╗  ')}${pc.cyan('██╔██╗ ██║')}${pc.magenta('██║   ██║')}${pc.yellow('██║')}${pc.red('██║     ')}${pc.
 36  ${pc.green('██╔══╝  ')}${pc.cyan('██║╚██╗██║')}${pc.magenta('╚██╗ ██╔╝')}${pc.yellow('██║')}${pc.red('██║     ')}${pc.
 37  ${pc.green('███████╗')}${pc.cyan('██║ ╚████║')}${pc.magenta(' ╚████╔╝ ')}${pc.yellow('██║')}${pc.red('███████╗')}${pc.
 38  ${pc.green('╚══════╝')}${pc.cyan('╚═╝  ╚═══╝')}${pc.magenta('  ╚═══╝  ')}${pc.yellow('╚═╝')}${pc.red('╚══════╝')}${pc.
 39  ${pc.dim('Your secrets, one command away')}          ${pc.dim('aws & azure')}
 40
 41  ${pc.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
 42  ${pc.green('WORLD 1-1')} ${pc.dim('— SELECT YOUR MISSION')}
 43  ${pc.yellow('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}
 44
 45  ${pc.green('>')} ${pc.bold('Generate a .env file')}  ${pc.dim('(pull secrets from the cloud)')}
 46    ${pc.cyan('envilder --map=envilder.json --envfile=.env')}
 47
 48  ${pc.magenta('>')} ${pc.bold('Sync .env back to cloud')}  ${pc.dim('(push secrets up)')}
 49    ${pc.cyan('envilder --push --map=envilder.json --envfile=.env')}
 50
 51  ${pc.red('>')} ${pc.bold('Push a single secret')}
 52    ${pc.cyan('envilder --push --key=API_KEY --value=s3cret --secret-path=/my/path')}
 53
 54  ${pc.blue('>')} ${pc.bold('Use Azure Key Vault')}
 55    ${pc.cyan('envilder --provider=azure --map=envilder.json --envfile=.env')}
 56`;
 57
 458  program
 59    .name('envilder')
 60    .description(banner)
 61    .version(version)
 62    .option(
 63      '--map <path>',
 64      'Path to the JSON file with environment variable mapping (required for most commands)',
 65    )
 66    .option(
 67      '--envfile <path>',
 68      'Path to the .env file to be generated or imported (required for most commands)',
 69    )
 70    .option('--profile <name>', 'AWS CLI profile to use (optional)')
 71    .option(
 72      '--provider <name>',
 73      'Cloud provider to use: aws or azure (default: aws)',
 74    )
 75    .option(
 76      '--vault-url <url>',
 77      'Azure Key Vault URL (overrides $config.vaultUrl in map file)',
 78    )
 79    .option('--push', 'Push local .env file back to cloud provider')
 80    .option(
 81      '--key <name>',
 82      'Single environment variable name to push (only with --push)',
 83    )
 84    .option(
 85      '--value <value>',
 86      'Value of the single environment variable to push (only with --push)',
 87    )
 88    .option(
 89      '--secret-path <path>',
 90      'Secret path in your cloud provider for the single variable (only with --push)',
 91    )
 92    .option(
 93      '--ssm-path <path>',
 94      '[DEPRECATED: use --secret-path] Alias for --secret-path',
 95    )
 96    .hook('preAction', (thisCommand) => {
 397      const opts = thisCommand.opts();
 398      if (opts.ssmPath) {
 099        console.warn(
 100          pc.yellow(
 101            '⚠️  --ssm-path is deprecated and will be removed in a future release. Use --secret-path instead.',
 102          ),
 103        );
 0104        if (!opts.secretPath) {
 0105          thisCommand.setOptionValue('secretPath', opts.ssmPath);
 106        }
 107      }
 108    })
 109    .action(
 110      async ({
 111        provider,
 112        vaultUrl,
 113        ...options
 114      }: CliOptions & { provider?: string; vaultUrl?: string }) => {
 3115        const fileConfig = options.map
 116          ? await readMapFileConfig(options.map)
 117          : {};
 118
 3119        const config: MapFileConfig = {
 120          ...fileConfig,
 121          ...(provider && { provider }),
 122          ...(vaultUrl && { vaultUrl }),
 123          ...(options.profile && { profile: options.profile }),
 124        };
 125
 3126        const infraOptions: Record<string, unknown> = {};
 3127        const extraHosts = process.env.ENVILDER_ALLOWED_VAULT_HOSTS;
 3128        if (extraHosts) {
 1129          infraOptions.allowedVaultHosts = extraHosts
 130            .split(',')
 2131            .map((h) => h.trim());
 1132          infraOptions.disableChallengeResourceVerification = true;
 133        }
 134
 3135        serviceProvider = Startup.build()
 136          .configureServices()
 137          .configureInfrastructure(config, infraOptions)
 138          .create();
 139
 3140        await executeWithSsoRecovery(() => executeCommand(options));
 141      },
 142    );
 143
 4144  await program.parseAsync(process.argv);
 145}
 146
 147function readPackageVersion(): Promise<string> {
 4148  const __filename = fileURLToPath(import.meta.url);
 4149  const __dirname = dirname(__filename);
 4150  const packageJsonPath = join(__dirname, '../../../../../package.json');
 151
 4152  return new PackageVersionReader().getVersion(packageJsonPath);
 153}