Skip to content

Commit

Permalink
[feat] Added fuzzy update all for mongo db urls
Browse files Browse the repository at this point in the history
  • Loading branch information
erdemkosk committed Dec 11, 2023
1 parent a3d0f43 commit 9f85ed9
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 48 deletions.
5 changes: 3 additions & 2 deletions bin/index.ts
Expand Up @@ -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)
})

Expand Down
4 changes: 3 additions & 1 deletion lib/command/command.ts
Expand Up @@ -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<void> {
Expand Down
16 changes: 8 additions & 8 deletions lib/command/commandFactory.ts
Expand Up @@ -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
Expand Down
65 changes: 39 additions & 26 deletions 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<any> {
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<void> {
Expand Down
36 changes: 36 additions & 0 deletions lib/handler/envHandler.ts
Expand Up @@ -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]
Expand Down Expand Up @@ -135,6 +137,40 @@ export async function updateAllEnvFile ({
return effectedServices
}

export async function updateAllEnvFileInFuzzy ({
newValue
}: {
newValue: string
}): Promise<string[]> {
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
}: {
Expand Down
16 changes: 16 additions & 0 deletions 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
}
}
11 changes: 2 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions 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": {
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit 9f85ed9

Please sign in to comment.