UNPKG

@keyshade/cli

Version:
164 lines (144 loc) 4.56 kB
import type { CommandActionData, CommandArgument, CommandOption } from '@/types/command/command.types' import BaseCommand from '../base.command' import ControllerInstance from '@/util/controller-instance' import { Logger } from '@/util/logger' import { z } from 'zod' import { writeFileSync } from 'fs' import { join } from 'path' export default class ExportProject extends BaseCommand { getName(): string { return 'export' } getDescription(): string { return 'Exports configuration (secrets and variables) of a project' } getArguments(): CommandArgument[] { return [ { name: '<Project Slug>', description: 'Slug of the project that you want to export configuration from' } ] } getOptions(): CommandOption[] { return [ { short: '-e', long: '--environment [entries...]', description: 'Slugs of the environments you want to export' }, { short: '-f', long: '--format <string>', description: 'Format of the export (example: json)' }, { short: '-o', long: '--output <string>', description: 'Output file name. If multiple environments are selected, their name will be prepended to the file name' }, { short: '-p', long: '--private-key <string>', description: 'Private key to decrypt secrets' } ] } canMakeHttpRequests(): boolean { return true } async action({ args, options }: CommandActionData): Promise<void> { const [projectSlug] = args const { output, ...parsedOptions } = this.parseInput(options) Logger.info(`Exporting project ${projectSlug} configuration...`) const { data, error, success } = await ControllerInstance.getInstance().projectController.exportProjectConfigurations( { projectSlug, ...parsedOptions }, this.headers ) if (!success) { this.logError(error) return } Logger.info(`Project ${projectSlug} configuration exported successfully!`) this.writeConfigFiles(data, output) } private parseInput(options: CommandActionData['options']): { environmentSlugs: string[] format: string output: string privateKey?: string } { const { environment: environmentSlugs, format, output, privateKey } = options const inputSchema = z.object({ environmentSlugs: z .array(z.string().min(1, { message: 'Environment cannot be empty' })) .nonempty({ message: 'You must specify at least one environment' }), format: z.string(), output: z .string() .min(1, { message: 'Output filename cannot be empty' }) // [^/\\\u0000-\u001F]+ one or more characters that are NOT: // - a forward slash `/` // - a backslash `\` // - any control character from U+0000 to U+001F // u flag treat the pattern as Unicode (so \u0000–\u001F is recognized properly) /* eslint-disable-next-line no-control-regex */ .regex(/^[^/\\\u0000-\u001F]+$/u, { message: 'Invalid output filename: no slashes, backslashes, or control chars allowed' }), privateKey: z.string().optional() }) const parsed = inputSchema.parse({ environmentSlugs, format, output, privateKey }) if (!parsed.environmentSlugs || !parsed.format || !parsed.output) { throw new Error('Missing required options') } return { environmentSlugs: parsed.environmentSlugs, format: parsed.format, output: parsed.output, privateKey: parsed.privateKey } } private writeConfigFiles( configurations: Record<string, string>, output: string ) { const entries = Object.entries(configurations) const multiple = entries.length > 1 entries.forEach(([environmentSlug, rawBase64]) => { const fileName = multiple ? `${environmentSlug}.${output}` : output const filePath = join(process.cwd(), fileName) try { const content = Buffer.from(rawBase64, 'base64').toString('utf-8') writeFileSync(filePath, content, 'utf-8') Logger.info(`Wrote environment ${environmentSlug} to file ${fileName}`) } catch (err) { const msg = err instanceof Error ? err.message : String(err) Logger.error( `Failed to write environment ${environmentSlug} to file ${fileName}: ${msg}` ) } }) } }