Skip to content

Commit

Permalink
🧪
Browse files Browse the repository at this point in the history
  • Loading branch information
thibaultyou committed Oct 25, 2024
1 parent a8a26c6 commit 3a6e549
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 89 deletions.
7 changes: 7 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"cli-spinner": "0.2.10",
"commander": "12.1.0",
"dotenv": "16.4.5",
"fp-ts": "2.16.9",
"fs-extra": "11.2.0",
"js-yaml": "4.1.0",
"nunjucks": "3.2.4",
Expand Down
17 changes: 10 additions & 7 deletions src/cli/commands/base-command.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// command.ts
import { pipe } from 'fp-ts/lib/function';
import { Command } from 'commander';
import * as A from 'fp-ts/lib/Array';
import * as E from 'fp-ts/lib/Either';
import * as TE from 'fp-ts/lib/TaskEither';
import { pipe } from 'fp-ts/lib/function';
import * as O from 'fp-ts/lib/Option';
import * as T from 'fp-ts/lib/Task';
import { Command } from 'commander';
import * as TE from 'fp-ts/lib/TaskEither';

import { ApiResult } from '../../shared/types';
import * as O from 'fp-ts/lib/Option';
import * as A from 'fp-ts/lib/Array';

// Core types
export type CommandContext = {
Expand All @@ -27,6 +28,7 @@ export interface BaseCommand<A> {
}

export type CommandResult<A> = E.Either<CommandError, A>;

export type CommandTask<A> = TE.TaskEither<CommandError, A>;

// Command creation helper
Expand All @@ -43,7 +45,6 @@ export const createCommand = <A>(
args: args.slice(0, -1),
options: args[args.length - 1] || {}
};

await pipe(
execute(ctx),
TE.fold(
Expand All @@ -63,7 +64,6 @@ export const createCommand = <A>(
)
)();
});

return command;
};

Expand Down Expand Up @@ -120,6 +120,7 @@ export const taskEitherFromPromise = <A>(
): TE.TaskEither<CommandError, A> =>
TE.tryCatch(async () => {
const result = await promise();

if (result === undefined) {
throw new Error('Unexpected undefined result');
}
Expand All @@ -131,6 +132,7 @@ export const fromApiResult = <A>(promise: Promise<ApiResult<A>>): TE.TaskEither<
TE.tryCatch(
async () => {
const result = await promise;

if (!result.success) {
throw new Error(result.error || 'Operation failed');
}
Expand All @@ -145,6 +147,7 @@ export const fromApiFunction = <A>(fn: () => Promise<ApiResult<A>>): TE.TaskEith
TE.tryCatch(
async () => {
const result = await fn();

if (!result.success || result.data === undefined) {
throw new Error(result.error || 'Operation failed');
}
Expand Down
68 changes: 28 additions & 40 deletions src/cli/commands/env-command.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
// env-command.ts
import { pipe } from 'fp-ts/lib/function';
import * as TE from 'fp-ts/lib/TaskEither';
import chalk from 'chalk';
import * as A from 'fp-ts/lib/Array';
import * as O from 'fp-ts/lib/Option';
import * as Eq from 'fp-ts/lib/Eq';
import { pipe } from 'fp-ts/lib/function';
import * as O from 'fp-ts/lib/Option';
import * as Ord from 'fp-ts/lib/Ord';
import chalk from 'chalk';
import * as TE from 'fp-ts/lib/TaskEither';

import {
CommandContext,
CommandError,
createCommand,
createCommandError,
fromApiResult,
fromApiFunction,
traverseArray
} from './base-command';
import { CommandError, createCommand, fromApiResult, fromApiFunction, traverseArray } from './base-command';
import { EnvVar, Fragment, Variable } from '../../shared/types';
import { formatTitleCase, formatSnakeCase } from '../../shared/utils/string-formatter';
import { FRAGMENT_PREFIX } from '../constants';
import { createInteractivePrompts, MenuChoice } from './interactive';
import { createEnvVar, readEnvVars, updateEnvVar, deleteEnvVar } from '../utils/env-vars';
import { listFragments, viewFragmentContent } from '../utils/fragments';
import { listPrompts, getPromptFiles } from '../utils/prompts';
import { createInteractivePrompts, MenuChoice } from './interactive';

// Types
type EnvAction = 'enter' | 'fragment' | 'unset' | 'back';
Expand All @@ -38,34 +30,33 @@ interface EnvCommandResult {

// Create interactive prompts instance
const prompts = createInteractivePrompts();

// Pure functions
const formatVariableChoices = (
allVariables: ReadonlyArray<EnvVariable>,
envVars: ReadonlyArray<EnvVar>
): ReadonlyArray<MenuChoice<EnvVariable>> => {
const maxNameLength = Math.max(...allVariables.map((v) => formatSnakeCase(v.name).length));

return allVariables.map((variable) => {
const formattedName = formatSnakeCase(variable.name);
const paddedName = formattedName.padEnd(maxNameLength);
const coloredName = chalk.cyan(formattedName);
const paddingLength = maxNameLength - formattedName.length;
const paddedSpaces = paddingLength > 0 ? ' '.repeat(paddingLength) : '';
const envVar = envVars.find((v) => formatSnakeCase(v.name) === formattedName);
const status = getVariableStatus(envVar);

return {
name: `${chalk.cyan(paddedName)}: ${status}`,
name: `${coloredName}${paddedSpaces}: ${status}`,
value: variable
};
});
};

const getVariableStatus = (envVar: EnvVar | undefined): string => {
if (!envVar) return chalk.yellow('Not Set');

if (envVar.value.startsWith(FRAGMENT_PREFIX)) return chalk.blue(envVar.value);
return chalk.green(`Set: ${envVar.value.substring(0, 20)}${envVar.value.length > 20 ? '...' : ''}`);
};
const trimmedValue = envVar.value.trim();

if (trimmedValue.startsWith(FRAGMENT_PREFIX)) return chalk.blue(trimmedValue);
return chalk.green(`Set: ${trimmedValue.substring(0, 20)}${trimmedValue.length > 20 ? '...' : ''}`);
};
// Effects
const getAllUniqueVariables = (): TE.TaskEither<CommandError, ReadonlyArray<EnvVariable>> =>
pipe(
Expand All @@ -89,7 +80,6 @@ const getAllUniqueVariables = (): TE.TaskEither<CommandError, ReadonlyArray<EnvV
)
)
);

const enterValueForVariable = (variable: EnvVariable, envVar: O.Option<EnvVar>): TE.TaskEither<CommandError, void> =>
pipe(
prompts.getMultilineInput(
Expand All @@ -103,6 +93,12 @@ const enterValueForVariable = (variable: EnvVariable, envVar: O.Option<EnvVar>):
)
),
TE.chain((value) => {
// Check if the user canceled the input (e.g., empty string)
if (value.trim() === '') {
console.log(chalk.yellow('Input canceled. Returning to actions menu.'));
return TE.right(undefined);
}

const operation = pipe(
envVar,
O.fold(
Expand Down Expand Up @@ -133,24 +129,23 @@ const enterValueForVariable = (variable: EnvVariable, envVar: O.Option<EnvVar>):
return operation;
})
);

const assignFragmentToVariable = (variable: EnvVariable): TE.TaskEither<CommandError, void> =>
pipe(
fromApiResult(listFragments()),
TE.chain((fragments) =>
prompts.showMenu<Fragment | 'back'>(
'Select a fragment:',
fragments.map((f) => ({
prompts.showMenu<Fragment | 'back'>('Select a fragment:', [
...fragments.map((f) => ({
name: `${formatTitleCase(f.category)} / ${chalk.blue(f.name)}`,
value: f
}))
)
])
),
TE.chain((fragment) => {
if (fragment === 'back') {
console.log(chalk.yellow('Fragment assignment cancelled.'));
return TE.right(undefined);
}

const fragmentRef = `${FRAGMENT_PREFIX}${fragment.category}/${fragment.name}`;
return pipe(
fromApiResult(readEnvVars()),
Expand Down Expand Up @@ -194,7 +189,6 @@ const assignFragmentToVariable = (variable: EnvVariable): TE.TaskEither<CommandE
);
})
);

const unsetVariable = (variable: EnvVariable, envVar: O.Option<EnvVar>): TE.TaskEither<CommandError, void> =>
pipe(
envVar,
Expand All @@ -209,8 +203,7 @@ const unsetVariable = (variable: EnvVariable, envVar: O.Option<EnvVar>): TE.Task
)
)
);

const executeEnvCommand = (ctx: CommandContext): TE.TaskEither<CommandError, EnvCommandResult> => {
const executeEnvCommand = (): TE.TaskEither<CommandError, EnvCommandResult> => {
const loop = (): TE.TaskEither<CommandError, EnvCommandResult> =>
pipe(
TE.Do,
Expand All @@ -220,28 +213,24 @@ const executeEnvCommand = (ctx: CommandContext): TE.TaskEither<CommandError, Env
pipe(
prompts.showMenu<EnvVariable | 'back'>(
'Select a variable to manage:',
[
...formatVariableChoices(variables, envVars),
],
formatVariableChoices(variables, envVars)
),
TE.chain((selectedVariable): TE.TaskEither<CommandError, EnvCommandResult> => {
if (selectedVariable === 'back') {
console.log(chalk.yellow('Returning to main menu.'));
return TE.right({ completed: true });
}

return pipe(
prompts.showMenu<EnvAction>(
`Choose action for ${formatSnakeCase(selectedVariable.name)}:`,
[
{ name: 'Enter value', value: 'enter' },
{ name: 'Use fragment', value: 'fragment' },
{ name: 'Unset', value: 'unset' },
],
{ name: 'Unset', value: 'unset' }
]
),
TE.chain((action) => {
const envVar = O.fromNullable(envVars.find((v) => v.name === selectedVariable.name));

switch (action) {
case 'enter':
return pipe(
Expand Down Expand Up @@ -271,7 +260,6 @@ const executeEnvCommand = (ctx: CommandContext): TE.TaskEither<CommandError, Env
)
)
);

return pipe(
loop(),
TE.chain((result) => {
Expand Down
16 changes: 7 additions & 9 deletions src/cli/commands/interactive.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import os from 'os';
import path from 'path';

import { editor, input, select } from '@inquirer/prompts';
import chalk from 'chalk';
import { pipe } from 'fp-ts/lib/function';
import * as TE from 'fp-ts/lib/TaskEither';
import fs from 'fs-extra';

import { CommandError, createCommandError } from '../commands/base-command';
import { cliConfig } from '../config/cli-config';
import chalk from 'chalk';
import { pipe } from 'fp-ts/lib/function';
import { ENV_PREFIX, FRAGMENT_PREFIX } from '../constants';
import path from 'path';
import os from 'os';
import fs from 'fs-extra';

export interface MenuChoice<T> {
readonly name: string;
Expand Down Expand Up @@ -46,7 +48,6 @@ export const createInteractivePrompts = (): InteractivePrompts => ({
clearConsole = true,
pageSize = cliConfig.MENU_PAGE_SIZE
} = options;

return TE.tryCatch(
async () => {
if (clearConsole) {
Expand All @@ -61,7 +62,6 @@ export const createInteractivePrompts = (): InteractivePrompts => ({
value: goBackValue
});
}

return select<T>({
message,
choices: menuChoices,
Expand Down Expand Up @@ -94,9 +94,7 @@ export const createInteractivePrompts = (): InteractivePrompts => ({
initialValue.startsWith(FRAGMENT_PREFIX) || initialValue.startsWith(ENV_PREFIX)
? ''
: initialValue;

await fs.writeFile(tempFilePath, cleanedInitialValue);

return editor({
message: 'Edit your input',
default: cleanedInitialValue,
Expand Down
Loading

0 comments on commit 3a6e549

Please sign in to comment.