UNPKG

@keyshade/cli

Version:
184 lines (164 loc) 4.95 kB
import type { CommandActionData, CommandArgument, CommandOption } from '@/types/command/command.types' import BaseCommand from '../base.command' import { confirm, text } from '@clack/prompts' import ControllerInstance from '@/util/controller-instance' import { Logger } from '@/util/logger' import fs from 'node:fs/promises' import path from 'node:path' import dotenv from 'dotenv' import secretDetector from '@keyshade/secret-scan' export default class ImportFromEnv extends BaseCommand { getName(): string { return 'import' } getDescription(): string { return 'Imports environment secrets and variables from .env file to a project.' } getArguments(): CommandArgument[] { return [ { name: '<Project Slug>', description: 'Slug of the project where envs will be imported.' } ] } getOptions(): CommandOption[] { return [ { short: '-f', long: '--env-file <string>', description: 'Path to the .env file' } ] } canMakeHttpRequests(): boolean { return true } async action({ args, options }: CommandActionData): Promise<void> { const [projectSlug] = args try { const parsedOptions = await this.parseOptions(options) if (!parsedOptions) return const envFileContent = await fs.readFile( parsedOptions.envFilePath, 'utf-8' ) const envVariables = dotenv.parse(envFileContent) if (Object.keys(envVariables).length === 0) { Logger.warn('No environment variables found in the provided file') return } const secretsAndVariables = secretDetector.scanJsObject(envVariables) Logger.info( 'Detected secrets:\n' + Object.entries(secretsAndVariables.secrets) .map(([key, value]) => key + ' = ' + JSON.stringify(value)) .join('\n') + '\n' ) Logger.info( 'Detected variables:\n' + Object.entries(secretsAndVariables.variables) .map(([key, value]) => key + ' = ' + JSON.stringify(value)) .join('\n') ) const confirmImport = await confirm({ message: 'Do you want to proceed with importing the environment variables? (y/N)', initialValue: false }) if (!confirmImport) { Logger.info('Import cancelled by the user.') return } const environmentSlug = (await text({ message: 'Enter the environment slug to import to:' })) as string Logger.info( `Importing secrets and variables to project: ${projectSlug} and environment: ${environmentSlug} with default settings` ) let noOfSecrets = 0 let noOfVariables = 0 const errors: string[] = [] for (const [key, value] of Object.entries(secretsAndVariables.secrets)) { const { error, success } = await ControllerInstance.getInstance().secretController.createSecret( { projectSlug, name: key, entries: [ { value, environmentSlug } ] }, this.headers ) if (success) { ++noOfSecrets } else { errors.push( `Failed to create secret for ${key}. Error: ${error.message}.` ) } } for (const [key, value] of Object.entries( secretsAndVariables.variables )) { const { error, success } = await ControllerInstance.getInstance().variableController.createVariable( { projectSlug, name: key, entries: [ { value, environmentSlug } ] }, this.headers ) if (success) { ++noOfVariables } else { errors.push( `Failed to create variable for ${key}. Error: ${error.message}.` ) } } Logger.info( `Imported ${noOfSecrets} secrets and ${noOfVariables} variables.` ) } catch (error) { const errorMessage = (error as Error)?.message Logger.error( `Failed to import secrets and variables.${errorMessage ? '\n' + errorMessage : ''}` ) } } private async parseOptions(options: CommandActionData['options']): Promise<{ envFilePath: string } | null> { const { envFile } = options if (!envFile) { Logger.error('No .env file path provided.') return null } const resolvedPath = path.resolve(envFile) const exists = await fs .access(resolvedPath) .then(() => true) .catch(() => false) if (!exists) { Logger.error(`The .env file does not exist at path: ${resolvedPath}`) return null } return { envFilePath: resolvedPath } } }