diff --git a/bin/index.ts b/bin/index.ts index 7a9022c..aa3231d 100755 --- a/bin/index.ts +++ b/bin/index.ts @@ -39,8 +39,9 @@ program .command('update-all') .description(`${chalk.yellow('UPDATE-ALL')} occurrences of a specific environment variable across multiple service-specific .env files.`) .alias('ua') - .action(async () => { - const command: Command | null = factory.createCommand(CommandTypes.UPDATE_ALL) + .option('-f, --fuzzy [value]', 'without naming the env value, mongo db etc. automatically detects the small changing parts of the derivative urls and changes them all.e') + .action(async (cmd) => { + const command: Command | null = factory.createCommand(CommandTypes.UPDATE_ALL, cmd.fuzzy) command !== null && CommandInvoker.executeCommands(command) }) diff --git a/lib/command/command.ts b/lib/command/command.ts index f466296..e890020 100644 --- a/lib/command/command.ts +++ b/lib/command/command.ts @@ -3,9 +3,11 @@ import inquirer from 'inquirer' export default abstract class Command { protected readonly baseFolder: string + protected readonly params: any [] - constructor () { + constructor (...params: any[]) { this.baseFolder = getBaseFolder() + this.params = params } async execute (): Promise { diff --git a/lib/command/commandFactory.ts b/lib/command/commandFactory.ts index a7ddfb6..2d9af7f 100644 --- a/lib/command/commandFactory.ts +++ b/lib/command/commandFactory.ts @@ -9,22 +9,22 @@ import UpdateAllCommand from './commandTypes/updateAllCommand' import UpdateCommand from './commandTypes/updateCommand' export default class CommandFactory { - createCommand (commandType: number): Command | null { + createCommand (commandType: number, ...params: any []): Command | null { switch (commandType) { case CommandTypes.LS: - return new LsCommand() + return new LsCommand(params) case CommandTypes.SYNC: - return new SyncCommand() + return new SyncCommand(params) case CommandTypes.COMPARE: - return new CompareCommand() + return new CompareCommand(params) case CommandTypes.UPDATE: - return new UpdateCommand() + return new UpdateCommand(params) case CommandTypes.UPDATE_ALL: - return new UpdateAllCommand() + return new UpdateAllCommand(params) case CommandTypes.REVERT: - return new RevertCommand() + return new RevertCommand(params) case CommandTypes.RESTORE_ENV: - return new RestoreCommand() + return new RestoreCommand(params) default: return null diff --git a/lib/command/commandTypes/updateAllCommand.ts b/lib/command/commandTypes/updateAllCommand.ts index c4ef8b3..ff328cb 100644 --- a/lib/command/commandTypes/updateAllCommand.ts +++ b/lib/command/commandTypes/updateAllCommand.ts @@ -1,41 +1,54 @@ import Command from '../command' -import { updateAllEnvFile, promptForEnvVariable } from '../../handler/envHandler' +import { updateAllEnvFile, promptForEnvVariable, updateAllEnvFileInFuzzy } from '../../handler/envHandler' import chalk from 'chalk' import inquirer from 'inquirer' import { consola } from 'consola' export default class UpdateAllCommand extends Command { protected async beforeExecute (): Promise { - const envOptions = await promptForEnvVariable() - - const { envValue, newValue } = await inquirer.prompt([ - { - type: 'autocomplete', - name: 'envValue', - message: 'Select the env value to change:', - source: (answers: any, input: string) => { - if (input === undefined) { - return envOptions - } + if (this.params[0][0] !== undefined) { + const newValue = this.params[0][0] - return envOptions.filter(option => option.includes(input)) - } - }, - { - type: 'input', - name: 'newValue', - message: 'Enter the new value:' + const effectedServices = await updateAllEnvFileInFuzzy({ newValue }) + + effectedServices.forEach((service) => { + consola.success(`Environment variables updated in "${chalk.blue(service)}"`) + }) + + if (effectedServices.length === 0) { + consola.error('There is no effected service') } - ]) + } else { + const envOptions = await promptForEnvVariable() + + const { envValue, newValue } = await inquirer.prompt([ + { + type: 'autocomplete', + name: 'envValue', + message: 'Select the env value to change:', + source: (answers: any, input: string) => { + if (input === undefined) { + return envOptions + } + + return envOptions.filter(option => option.includes(input)) + } + }, + { + type: 'input', + name: 'newValue', + message: 'Enter the new value:' + } + ]) + const isConfirmed = await this.askForConfirmation() - const isConfirmed = await this.askForConfirmation() + if (!isConfirmed) { + consola.error(`Operation is ${chalk.red('cancelled!')}`) + return + } - if (!isConfirmed) { - console.log(`Operation is ${chalk.red('cancelled!')}`) - return + return await updateAllEnvFile({ envValue, newValue }) } - - return await updateAllEnvFile({ envValue, newValue }) } protected async onExecute (beforeExecuteReturnValue: any): Promise { diff --git a/lib/handler/envHandler.ts b/lib/handler/envHandler.ts index c48cd4c..3682a55 100644 --- a/lib/handler/envHandler.ts +++ b/lib/handler/envHandler.ts @@ -17,6 +17,8 @@ import { saveFieldVersionsInSync } from './historyHandler' +import MongoDBURIComparerLogic from '../logic/fuzzy.logic' + function getServiceNameFromUrl ({ targetPath }: { targetPath: string }): string { const parts = targetPath.split('/') return parts[parts.length - 2] @@ -135,6 +137,40 @@ export async function updateAllEnvFile ({ return effectedServices } +export async function updateAllEnvFileInFuzzy ({ + newValue +}: { + newValue: string +}): Promise { + const files = await getEnvFilesRecursively({ directory: getBaseFolder() }) + const effectedServices: string[] = [] + + for (const file of files) { + const fileContents = await readFile({ file }) + + if (fileContents !== undefined) { + const lines = fileContents.split('\n') + for (const line of lines) { + const [currentEnvName, currentEnvValue] = extractEnvVariable(line) + + if (MongoDBURIComparerLogic.compareURIs(currentEnvValue, newValue)) { + const newFileContents = changeValuesInEnv({ + contents: fileContents, + envValue: currentEnvName, + newValue + }) + + await saveFieldVersion(file, currentEnvName, newValue) + await writeFile({ file, newFileContents }) + effectedServices.push(file) + } + } + } + } + + return effectedServices +} + export async function getValuesInEnv ({ targetPath }: { diff --git a/lib/logic/fuzzy.logic.ts b/lib/logic/fuzzy.logic.ts new file mode 100644 index 0000000..9eb9858 --- /dev/null +++ b/lib/logic/fuzzy.logic.ts @@ -0,0 +1,16 @@ +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export default class MongoDBURIComparerLogic { + static compareURIs (uri1: string, uri2: string): boolean { + const userPassRegEx = /\/\/(.*):(.*)@/ + const match1 = uri1.match(userPassRegEx) + const match2 = uri2.match(userPassRegEx) + + if ((match1 != null) && (match2 != null) && match1[1] !== match2[1]) return false + if ((match1 != null) && (match2 != null) && match1[2] !== match2[2]) return false + + const uri1WithoutUserInfo = uri1.replace(userPassRegEx, '//') + const uri2WithoutUserInfo = uri2.replace(userPassRegEx, '//') + + return uri1WithoutUserInfo === uri2WithoutUserInfo + } +} diff --git a/package-lock.json b/package-lock.json index 958910e..2232a7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,8 @@ "date-fns": "^2.30.0", "inquirer": "^8.2.6", "inquirer-autocomplete-prompt": "^2.0.1", - "table": "^6.8.1" + "table": "^6.8.1", + "yargs": "^17.7.2" }, "bin": { "envolve": "dist/index.js" @@ -2579,7 +2580,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -2593,7 +2593,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -3013,7 +3012,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -3726,7 +3724,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -6061,7 +6058,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7089,7 +7085,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -7104,7 +7099,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -7122,7 +7116,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } diff --git a/package.json b/package.json index 224b20c..9010560 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "envolve", - "version": "1.1.7", + "version": "1.1.8", "description": "Envolve CLI is a powerful tool for managing environment variables in your projects. It allows you to easily create, update, compare, and sync environment files across different services.", "main": "index.ts", "scripts": { @@ -30,7 +30,8 @@ "date-fns": "^2.30.0", "inquirer": "^8.2.6", "inquirer-autocomplete-prompt": "^2.0.1", - "table": "^6.8.1" + "table": "^6.8.1", + "yargs": "^17.7.2" }, "devDependencies": { "@types/eslint": "^8.44.6",