From e2bb13c011670436e153aa4a33cf16a609544072 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Mon, 16 Sep 2024 16:20:57 -0300 Subject: [PATCH 01/14] chore: remove workflow comments --- .github/workflows/release.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 80d31bd..2450ab5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,22 +2,22 @@ name: Release on: push: branches: - - master # or main + - master - next - beta permissions: - contents: read # for checkout + contents: read jobs: release: name: Release runs-on: ubuntu-latest permissions: - contents: write # to be able to publish a GitHub release - issues: write # to be able to comment on released issues - pull-requests: write # to be able to comment on released pull requests - id-token: write # to enable use of OIDC for npm provenance + contents: write + issues: write + pull-requests: write + id-token: write steps: - name: Checkout uses: actions/checkout@v4 From 610e4e501cd6ee203f43e31d0c22cfbe21fed6ba Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Tue, 17 Sep 2024 08:48:22 -0300 Subject: [PATCH 02/14] refactor: move `editConfigCommand` to `editCommand` --- src/commands/editCommand.ts | 11 +++++++++-- src/lib/utils.ts | 10 ---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/commands/editCommand.ts b/src/commands/editCommand.ts index ee623b0..c7b76c3 100644 --- a/src/commands/editCommand.ts +++ b/src/commands/editCommand.ts @@ -1,5 +1,6 @@ import { Command } from 'commander'; -import { editConfigCommand } from '../lib/utils'; +import { Config, createConfig, getConfig } from '../lib/utils'; +import { error } from 'console'; const editCommand = new Command('edit') .alias('e') @@ -7,7 +8,13 @@ const editCommand = new Command('edit') .argument('name', 'Command name') .argument('command', 'New command') .action((name: string, command: string) => { - editConfigCommand(name, command); + const newConfig: Config = getConfig(); + + if (!newConfig.commands[name]) error(`Command "${name}" not found`); + + newConfig.commands[name] = command; + + createConfig(newConfig); }); export default editCommand; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c24448f..0c8718c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -56,16 +56,6 @@ export async function addConfigCommand(key: string, command: string) { createConfig(newConfig); } -export async function editConfigCommand(key: string, command: string) { - const newConfig: Config = getConfig(); - - if (!newConfig.commands[key]) error(`Command "${key}" not found`); - - newConfig.commands[key] = command; - - createConfig(newConfig); -} - export function changeGistUrl(newUrl: string) { const newConfig: Config = getConfig(); From 4b6f7522c1be392a0d08aa69220ee3334e9cf708 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Tue, 17 Sep 2024 08:52:40 -0300 Subject: [PATCH 03/14] feat: show suggestion on not found error --- src/commands/editCommand.ts | 20 +++++++++++++++++--- src/commands/removeCommand.ts | 13 +++++++++++-- src/commands/renameCommand.ts | 13 +++++++++++-- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/commands/editCommand.ts b/src/commands/editCommand.ts index c7b76c3..4a9c159 100644 --- a/src/commands/editCommand.ts +++ b/src/commands/editCommand.ts @@ -1,6 +1,11 @@ import { Command } from 'commander'; -import { Config, createConfig, getConfig } from '../lib/utils'; -import { error } from 'console'; +import { + Config, + createConfig, + error, + getClosestWord, + getConfig, +} from '../lib/utils'; const editCommand = new Command('edit') .alias('e') @@ -10,7 +15,16 @@ const editCommand = new Command('edit') .action((name: string, command: string) => { const newConfig: Config = getConfig(); - if (!newConfig.commands[name]) error(`Command "${name}" not found`); + if (!newConfig.commands[name]) { + const closestWord = getClosestWord(name, Object.keys(newConfig.commands)); + + error( + `Command "${name}" not found`, + isFinite(closestWord.distance) + ? `Did you mean: "${closestWord.word}"?` + : undefined + ); + } newConfig.commands[name] = command; diff --git a/src/commands/removeCommand.ts b/src/commands/removeCommand.ts index 8dfbe89..cbe6e6f 100644 --- a/src/commands/removeCommand.ts +++ b/src/commands/removeCommand.ts @@ -1,5 +1,5 @@ import { Command } from 'commander'; -import { createConfig, error, getConfig } from '../lib/utils'; +import { createConfig, error, getClosestWord, getConfig } from '../lib/utils'; const removeCommand = new Command('remove') .alias('rm') @@ -8,7 +8,16 @@ const removeCommand = new Command('remove') .action((name: string) => { const config = getConfig(); - if (!config.commands[name]) error(`Command "${name}" not found`); + if (!config.commands[name]) { + const closestWord = getClosestWord(name, Object.keys(config.commands)); + + error( + `Command "${name}" not found`, + isFinite(closestWord.distance) + ? `Did you mean: "${closestWord.word}"?` + : undefined + ); + } delete config.commands[name]; diff --git a/src/commands/renameCommand.ts b/src/commands/renameCommand.ts index 91af2c2..d81675e 100644 --- a/src/commands/renameCommand.ts +++ b/src/commands/renameCommand.ts @@ -1,5 +1,5 @@ import { Command } from 'commander'; -import { createConfig, error, getConfig } from '../lib/utils'; +import { createConfig, error, getClosestWord, getConfig } from '../lib/utils'; const renameCommand = new Command('rename') .alias('rn') @@ -9,7 +9,16 @@ const renameCommand = new Command('rename') .action((oldName: string, newName: string) => { const config = getConfig(); - if (!config.commands[oldName]) error('Command not found'); + if (!config.commands[oldName]) { + const closestWord = getClosestWord(oldName, Object.keys(config.commands)); + + error( + `Command "${oldName}" not found`, + isFinite(closestWord.distance) + ? `Did you mean: "${closestWord.word}"?` + : undefined + ); + } config.commands[newName] = config.commands[oldName]; delete config.commands[oldName]; From 7e76107412e7d08a67869d049b37589183c75702 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Fri, 20 Sep 2024 04:04:13 -0300 Subject: [PATCH 04/14] feat: add letters saved stats refactor: change `runUserCommand` to receive final command --- src/commands/downloadConfig.ts | 8 +++++-- src/commands/index.ts | 21 ++++++++++++++++-- src/commands/syncConfig.ts | 14 ++++++++++-- src/lib/utils.ts | 39 ++++++++++++++++++++++------------ 4 files changed, 62 insertions(+), 20 deletions(-) diff --git a/src/commands/downloadConfig.ts b/src/commands/downloadConfig.ts index e118eba..4d952c6 100644 --- a/src/commands/downloadConfig.ts +++ b/src/commands/downloadConfig.ts @@ -1,7 +1,7 @@ import { Command } from 'commander'; import { basicGithubVerifications, getConfiguration } from '../lib/github'; import { confirm } from '@inquirer/prompts'; -import { createConfig, error } from '../lib/utils'; +import { Config, createConfig, error } from '../lib/utils'; const downloadConfig = new Command('download') .alias('d') @@ -23,7 +23,11 @@ const downloadConfig = new Command('download') if (!update) process.exit(0); try { - createConfig(JSON.parse(gist)); + const newConfig: Config = JSON.parse(gist); + + delete newConfig.lettersSaved; + + createConfig(newConfig); } catch { error('Something went wrong...'); } diff --git a/src/commands/index.ts b/src/commands/index.ts index efa77b2..0e98b9b 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -7,7 +7,9 @@ import openConfig from './openConfig'; import uploadConfig from './uploadConfig'; import downloadConfig from './downloadConfig'; import { + changeLettersSaved, error, + formatCommand, getClosestWord, getConfig, logList, @@ -22,7 +24,8 @@ const program = new Command() .argument('[args...]', 'Arguments for the command') .option('-d, --debug', 'Enable debugging features', false) .action(async (commandName?: string, args?: string[]) => { - const configCommands = getConfig().commands; + const userConfig = getConfig(); + const configCommands = userConfig.commands; if (!commandName) { program.outputHelp(); @@ -52,6 +55,7 @@ const program = new Command() } console.log('You can use "_" to skip a optional argument'); + console.log(`Letters saved: ${userConfig.lettersSaved ?? 0}`); process.exit(0); } @@ -71,7 +75,20 @@ const program = new Command() error(`command "${commandName}" not found.`, suggestion); } - runUserCommand(command, args, program.getOptionValue('debug')); + const finalCommand = formatCommand( + command, + args, + program.getOptionValue('debug') + ); + + changeLettersSaved( + Math.max( + finalCommand.length - `fj ${commandName} ${args.join(' ')}`.length, + 0 + ) + ); + + runUserCommand(finalCommand); }); program diff --git a/src/commands/syncConfig.ts b/src/commands/syncConfig.ts index c7cdd85..874cab9 100644 --- a/src/commands/syncConfig.ts +++ b/src/commands/syncConfig.ts @@ -5,7 +5,13 @@ import { getConfiguration, basicGithubVerifications, } from '../lib/github'; -import { changeGistUrl, createConfig, error, getConfig } from '../lib/utils'; +import { + changeGistUrl, + Config, + createConfig, + error, + getConfig, +} from '../lib/utils'; const syncConfig = new Command('sync') .alias('s') @@ -34,7 +40,11 @@ const syncConfig = new Command('sync') error('Something went wrong fetching you cloud configuration...'); try { - createConfig(JSON.parse(newGist)); + const newConfig: Config = JSON.parse(newGist); + + delete newConfig.lettersSaved; + + createConfig(newConfig); console.log('Configuration synced!'); } catch { error('Something went wrong'); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 0c8718c..6ff3ab9 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -7,6 +7,7 @@ import * as path from 'node:path'; export type Config = { gistUrl?: string; + lettersSaved?: number; commands: { [key: string]: string }; }; type CommandArg = { @@ -24,10 +25,16 @@ export const HAS_CONFIGURATION = fs.existsSync( path.join(CONFIG_DIRECTORY, 'foji.json') ); -export function createConfig(config: Config = { commands: {} }): Config { +export function createConfig( + newConfig: Config = { commands: {} }, + useLettersSaved = false +): Config { if (!HAS_CONFIGURATION) fs.mkdirSync(CONFIG_DIRECTORY, { recursive: true }); - fs.writeFileSync(CONFIG_FILE_PATH, JSON.stringify(config, null, 2)); - return config; + + if (!useLettersSaved) newConfig.lettersSaved = getConfig().lettersSaved; + + fs.writeFileSync(CONFIG_FILE_PATH, JSON.stringify(newConfig, null, 2)); + return newConfig; } export function getConfig(): Config { @@ -64,6 +71,14 @@ export function changeGistUrl(newUrl: string) { createConfig(newConfig); } +export function changeLettersSaved(letters: number) { + const newConfig: Config = getConfig(); + + newConfig.lettersSaved = (newConfig.lettersSaved ?? 0) + letters; + + createConfig(newConfig, true); +} + export function logList( listName: string, items: { [key: string]: any }, @@ -104,6 +119,8 @@ export function formatCommand( const isTernary = !hasDefaultValue && hasElse && arg.includes('?'); const isOptional = !hasDefaultValue && !hasElse && arg.includes('?'); + // TODO - make linting function + if (hasDefaultValue) { const [name, defaultValue] = arg.split('??'); object.name = name.trim(); @@ -156,28 +173,22 @@ export function formatCommand( splitCommand[index * 2 + 1] = argValue; } + const finalCommand = splitCommand.join(''); + if (debug) { console.log('Command:', command); console.log('Command arguments:', commandArguments); console.log('Received arguments:', args); - console.log(`Formatted command: ${splitCommand.join('')}`); + console.log(`Formatted command: ${finalCommand}`); console.log(); console.log('Running command...'); console.log(); } - return splitCommand.join(''); + return finalCommand; } -export function runUserCommand( - command: string, - args: string[], - debug = false -): void { - command = formatCommand(command, args, debug); - - if (!command) return; - +export function runUserCommand(command: string): void { const childProcess = spawn(command, { shell: true, stdio: 'inherit', From 9c7732288554fdc9feee2990993e073c9b9f7aa8 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Fri, 20 Sep 2024 04:12:53 -0300 Subject: [PATCH 05/14] style: make letters saved space equal command's --- src/commands/index.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/commands/index.ts b/src/commands/index.ts index 0e98b9b..30440c6 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -54,8 +54,19 @@ const program = new Command() console.log(); } + let lettersSavedText = 'Letters saved:'; + + lettersSavedText += + ' '.repeat( + exampleHelpLine.indexOf(addCommand.description()) - + lettersSavedText.length + ) + (userConfig.lettersSaved ?? 0); + + console.log(lettersSavedText); + + console.log(); + console.log('You can use "_" to skip a optional argument'); - console.log(`Letters saved: ${userConfig.lettersSaved ?? 0}`); process.exit(0); } From 126e424738f7b127e35017bedacd23acbbcee1f4 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Fri, 20 Sep 2024 04:14:29 -0300 Subject: [PATCH 06/14] chore: add TODO --- src/lib/github.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/github.ts b/src/lib/github.ts index 318f285..19843cc 100644 --- a/src/lib/github.ts +++ b/src/lib/github.ts @@ -56,6 +56,8 @@ export function updateCloudConfiguration(): boolean { return false; } + // TODO - do not save lettersSaved on cloud + const { status } = spawnSync( `gh gist edit ${config.gistUrl} -f foji.json ${CONFIG_FILE_PATH}`, { From 842731a187fa32e831cb36011403b5ee48d72649 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Fri, 20 Sep 2024 09:26:54 -0300 Subject: [PATCH 07/14] docs: update README --- README.md | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index d3979cc..ebc430b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Foji +# Foji βš’οΈ -Foji is a command-line interface (CLI) tool designed to help you automate and manage your coding tasks. It allows you to run custom codes and handle custom parameters. +Foji is a powerful command-line interface (CLI) tool crafted to streamline and automate long or repetitive commands in your daily workflow. With Foji, you can define and execute custom commands, integrate custom parameters, and simplify complex processes, reducing the need to repeatedly type lengthy commands. ## Features @@ -16,11 +16,13 @@ Foji is a command-line interface (CLI) tool designed to help you automate and ma - [x] Configuration can be downloaded from cloud - [x] Local configuration can be synced from cloud -## Installation +## System Dependencies + +Foji makes use of [Node.js](https://nodejs.org/) package manager to be installed. -Before installing Foji, make sure you have [Node.js](https://nodejs.org/) installed on your machine. +## Installation -To install Foji globally, run the following command: +To install Foji run the following command: ```shell npm i foji -g @@ -28,7 +30,7 @@ npm i foji -g ## Usage -Foji saves your commands and your configuration url at it's configuration file (`~/.config/foji.json`). You can access the `.config` directory using `foji config` or open the file directly using `foji config -f` command. +Foji saves your commands and your configurations at it's configuration file (`~/.config/foji.json`). You can access the `.config` directory using `foji config` or open the file directly using `foji config -f` command. ### Running a Command @@ -37,13 +39,14 @@ To run a `command` just use: ```shell foji [command name] [...command args] ``` + Simple as that. If you don't provide a valid command name Foji will list all available commands, it includes default commands (eg.: `add`, `remove` and `sync`) and your own commands. ### Skipping a Argument -You also can skip a (optional) argument using "_": +You also can skip a (optional) argument using "\_": ```shell foji [command name] [argument one] _ [argument three] @@ -97,18 +100,6 @@ All `commands` can have four types of `arguments`: Note that all `Required arguments` must be provided **BEFORE** any of the other arguments -incorrect: - -```json -"command": "do command " -``` - -correct: - -```json -"command": "do command " -``` - ### Remove a Command To remove a `command` you can use the `remove` command From ae440524db974f37df1719846c51c94b1ef63b0b Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Fri, 20 Sep 2024 09:58:40 -0300 Subject: [PATCH 08/14] docs: update CONTRIBUTING --- CONTRIBUTING.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e0dc745..1dd789f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,15 +4,25 @@ Welcome to the project! We're glad you're here and interested in contributing. B ## How to Contribute +### Repository Structure + +This repository uses a automated version release using the mains 3 branches: + +- **master**: Contains the code on the actual `@latest` package channel. +- **beta**: Contains the code on the actual `@beta` package channel. +- **dev**: Contains the code of actual development, every `BREAKING CHANGE` and `feat` commit must be done on this branch. + +### Step by Step + 1. **Fork the repository:** Click on the "Fork" button on the top right corner of the repository's page, then clone your fork locally. -2. **Create a new branch:** Make your changes in a new branch created from the `main` branch. Choose a descriptive name for your branch related to the issue or feature you're working on. +2. **Create a new branch:** Make your changes in a new branch created from the `dev` branch or `main` for `fix` or others specific situations. Choose a descriptive name for your branch related to the issue or feature you're working on. 3. **Make your changes:** Implement the changes or additions you'd like to contribute. Please follow any existing coding style and conventions. 4. **Test your changes:** Ensure that your changes do not break existing functionality. If you're adding new features, include tests to cover your code. -5. **Commit your changes:** Make meaningful and atomic commits. Please provide a clear and descriptive commit message for each commit. +5. **Commit your changes:** Make meaningful and atomic commits. Please provide a [clear and descriptive commit message](https://www.conventionalcommits.org/en/v1.0.0/#summary) for each commit. 6. **Push your changes:** Once your changes are ready, push your branch to your fork of the repository. @@ -20,7 +30,7 @@ Welcome to the project! We're glad you're here and interested in contributing. B 8. **Review and address feedback:** Be open to feedback and iterate on your changes if necessary. Respond promptly to any comments or requests for changes from the maintainers. -9. **Merge your PR:** Once your PR has been approved and all checks have passed, a maintainer will merge your changes into the main branch. Congratulations on your contribution! +9. **Merge your PR:** Once your PR has been approved and all checks have passed, a maintainer will merge your changes into the respective branch. Congratulations on your contribution! ## Code of Conduct @@ -28,7 +38,7 @@ Please note that this project is governed by our [Code of Conduct](CODE_OF_CONDU ## Contributor License Agreement (CLA) -By contributing to this project, you agree to license your contributions under the [project's license](LICENSE.md). +By contributing to this project, you agree to license your contributions under the [project's license](LICENSE). ## Help and Support From 627bea3dd94052e21ee8900dd77b3bb3874c819a Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Tue, 24 Sep 2024 09:11:20 -0300 Subject: [PATCH 09/14] docs: update docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ebc430b..8197f00 100644 --- a/README.md +++ b/README.md @@ -84,10 +84,10 @@ All `commands` can have four types of `arguments`: 3. Optional arguments with default values: -- Similar to optional arguments, but if not provided, they will add a default value. `` +- Similar to optional arguments, but if not provided, they will add a default value. `` ```json -"command":"echo " +"command":"echo " ``` 4. Ternaries: From d8c17fbb2ece5d497c46ec014709356d151e7fe3 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Thu, 26 Sep 2024 17:53:27 -0300 Subject: [PATCH 10/14] feat: pass options to custom commands --- src/commands/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands/index.ts b/src/commands/index.ts index 30440c6..2e2aa70 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -23,6 +23,7 @@ const program = new Command() .argument('[command]', 'Command that you want to run') .argument('[args...]', 'Arguments for the command') .option('-d, --debug', 'Enable debugging features', false) + .passThroughOptions(true) .action(async (commandName?: string, args?: string[]) => { const userConfig = getConfig(); const configCommands = userConfig.commands; From 06e1af01bf93bf98b84d37993af9c8e72bafc670 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Thu, 26 Sep 2024 17:53:44 -0300 Subject: [PATCH 11/14] feat: add spread arguments --- src/lib/utils.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 6ff3ab9..2473e49 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -15,6 +15,7 @@ type CommandArg = { isOptional?: boolean; defaultValue?: string; alternativeValue?: string; + isSpread?: boolean; }; export const USER_DIRECTORY = os.homedir(); @@ -111,13 +112,14 @@ export function formatCommand( let allowRequired = true; - const commandArguments: CommandArg[] = commandArgs.map((arg) => { + const commandArguments: CommandArg[] = commandArgs.map((arg, index) => { const object: CommandArg = { name: '' }; const hasDefaultValue = arg.includes('??'); const hasElse = !hasDefaultValue && arg.includes(':'); const isTernary = !hasDefaultValue && hasElse && arg.includes('?'); const isOptional = !hasDefaultValue && !hasElse && arg.includes('?'); + const isSpread = arg.includes('...'); // TODO - make linting function @@ -141,6 +143,16 @@ export function formatCommand( object.name = name; object.isOptional = true; + allowRequired = false; + } else if (isSpread) { + if (index !== commandArgs.length - 1) + error('You can only use a spread argument at the last position'); + + const name = arg.replace('...', '').trim(); + object.name = name; + object.isOptional = true; + object.isSpread = true; + allowRequired = false; } else { if (!allowRequired) @@ -164,7 +176,10 @@ export function formatCommand( for (let index = 0; index < commandArguments.length; index++) { const arg = commandArguments[index]; - let argValue = arg.alternativeValue + + let argValue = arg.isSpread + ? args.slice(index).join(' ') + : arg.alternativeValue ? args[index] ? arg.defaultValue : arg.alternativeValue From d2fdef7868dc515d43505aafcafbafe1035b7c6a Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Fri, 27 Sep 2024 11:03:19 -0300 Subject: [PATCH 12/14] docs: update documentation --- README.md | 207 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 118 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 8197f00..3c1df2d 100644 --- a/README.md +++ b/README.md @@ -1,176 +1,205 @@ # Foji βš’οΈ -Foji is a powerful command-line interface (CLI) tool crafted to streamline and automate long or repetitive commands in your daily workflow. With Foji, you can define and execute custom commands, integrate custom parameters, and simplify complex processes, reducing the need to repeatedly type lengthy commands. +Foji is a powerful command-line interface (CLI) tool designed to streamline and automate long or repetitive commands in your daily workflow. With Foji, you can define and execute custom commands, integrate custom parameters, and simplify complex processes, reducing the need to repeatedly type lengthy commands. -## Features +## πŸš€ Features - [x] Run custom codes - [x] Handle custom parameters - [x] Conditional handler for arguments - [x] Support for default arguments - [x] Support for optional arguments -- [x] Add commands to configuration file +- [x] Support for options as arguments +- [x] Add commands to a configuration file - [x] Remove commands from the configuration file - [x] Can run commands directly -- [x] Configuration can be saved on cloud -- [x] Configuration can be downloaded from cloud -- [x] Local configuration can be synced from cloud +- [x] Configuration can be saved on the cloud +- [x] Configuration can be downloaded from the cloud +- [x] Local configuration can be synced from the cloud -## System Dependencies +## πŸ“¦ Installation -Foji makes use of [Node.js](https://nodejs.org/) package manager to be installed. +> [!IMPORTANT] +> Foji requires [Node.js](https://nodejs.org/) to be installed on your system. Make sure you have it installed before proceeding. -## Installation +To install Foji, run the following command: -To install Foji run the following command: - -```shell +```bash npm i foji -g ``` -## Usage +## 🚦 Usage + +Foji stores your commands and configurations in its configuration file (`~/.config/foji.json`). You can access it using: + +```bash +foji config +``` + +Or open it directly: -Foji saves your commands and your configurations at it's configuration file (`~/.config/foji.json`). You can access the `.config` directory using `foji config` or open the file directly using `foji config -f` command. +```bash +foji config -f +``` ### Running a Command -To run a `command` just use: +To execute a saved `command`: -```shell +```bash foji [command name] [...command args] ``` -Simple as that. +> [!TIP] +> If you don’t provide a valid command name, Foji will list all available commands, including default commands like `add`, `remove`, and `sync`, along with any custom commands you've added. -If you don't provide a valid command name Foji will list all available commands, it includes default commands (eg.: `add`, `remove` and `sync`) and your own commands. +### Skipping an Argument -### Skipping a Argument +If you want to skip an optional argument, use the `_` symbol: -You also can skip a (optional) argument using "\_": - -```shell -foji [command name] [argument one] _ [argument three] +```bash +foji [command name] [arg1] _ [arg3] ``` -## Creating and Updating the Configuration +## βš™οΈ Configuration Management -Foji provides easy ways to add `commands` to the configuration file (or create it if it does not exist): +Foji allows you to easily create and update your command configurations. -### Creating a New Command +### Adding a New Command -To add a new `command` to the configuration file you can use the `add` command: +To add a new command to the configuration: -```shell +```bash foji add [command name] [command] ``` -All `commands` can have four types of `arguments`: +Supported argument types: -1. Required arguments: +1. **Required Arguments**: + These arguments **must** be provided for the command to run. -- These arguments **must** be provided for the command to run: + ```json + "command": "echo " + ``` -```json -"command":"echo " -``` +2. **Optional Arguments**: + These arguments **are not mandatory**. If not provided, they will be skipped. -2. Optional arguments: + ```json + "command": "echo " + ``` -- These arguments **are not** mandatory. If not provided, they will add nothing to the command. +3. **Optional Arguments with Default Values**: + If not provided, a default value will be used. -```json -"command":"echo " -``` + ```json + "command": "echo " + ``` -3. Optional arguments with default values: +4. **Ternary Arguments**: + Works as a boolean argument, only checking whether it was passed. -- Similar to optional arguments, but if not provided, they will add a default value. `` + ```json + "command": "echo i want pizza of with " + ``` -```json -"command":"echo " -``` +5. **Spread Argument**: + Catches all the remaining arguments. -4. Ternaries: + ```json + "command": "echo [] {}" + ``` -- These arguments function like boolean arguments. Any value passed will be ignored by the CLI; it only checks **whether the argument was passed or not**. +Example of usage: -```json -"command":"echo i want pizza of with " +```bash +foji command "my arg one" one two three --my --options ``` -Note that all `Required arguments` must be provided **BEFORE** any of the other arguments +The resulting final command would be: -### Remove a Command +```bash +echo [my arg one] {one two three --my --options} +``` -To remove a `command` you can use the `remove` command +> [!IMPORTANT] > **Always** provide required arguments **before** any optional ones. -```shell -foji remove [command name] [command] +### Removing a Command + +To remove a command: + +```bash +foji remove [command name] ``` -### Configuration File Format +## 🌐 Cloud Sync -Foji's configuration manage your `commands` and it's `url`: +You can easily sync your configurations using cloud services. -```json -{ - "gistUrl": "https://gist.github.com/.../...", - "commands": { - "next:create": "npx create-next-app@latest ", - "build": "npm run build:local", - "next:dev": "npm run dev" - } -} -``` +> [!NOTE] +> Foji uses [Github CLI](https://cli.github.com/) to create, read and update your configuration gist. -### Upload Your Configuration File +### Upload Configuration -To Upload your configuration to your gist (or create one if your configuration do not have yet) just run: +To upload your configuration to a gist (or create a new gist if one doesn’t exist): -```shell +```bash foji upload ``` -### Download Your Configuration File +### Download Configuration -To download a configuration file from someone else just run this command: +To download a configuration file from a gist: -```shell +```bash foji download [gist url] ``` -### Sync Your Configuration File +### Sync Configuration -Update you configuration file from it's url +To sync your local configuration with its URL: -```shell +```bash foji sync ``` -## Development +> [!CAUTION] +> Be careful when syncing from external sources. Always verify the origin of the gist to avoid overriding your custom commands. -If you want to contribute to Foji or customize it to your needs, you can clone the repository and build it yourself: +## πŸ› οΈ Development + +If you want to contribute or customize Foji, follow these steps: 1. Clone the repository: -```shell -git clone https://github.com/imLymei/foji.git -cd foji -``` + ```bash + git clone https://github.com/imLymei/foji.git + cd foji + ``` -2. Install the dependencies: +2. Install dependencies: -```shell -npm install -``` + ```bash + npm install + ``` -3. Build and install the project: +3. Build the project: -```shell -npm run build:local -``` + ```bash + npm run build:local + ``` + +## πŸ“œ Credits + +This project makes use of several open-source libraries and tools. Special thanks to the following: + +- [TypeScript](https://www.typescriptlang.org/) - Type-safe JavaScript. +- [Commander](https://www.npmjs.com/package/commander) - CLI framework. +- [@inquirer/prompts](https://www.npmjs.com/package/@inquirer/prompts) - Command-line prompts. +- [semantic-release](https://www.npmjs.com/package/semantic-release) - Automates versioning and package publishing. +- [fast-levenshtein](https://www.npmjs.com/package/fast-levenshtein) - Fast string distance algorithm. -## License +## πŸ“„ License Foji is licensed under the MIT License. From a6ab778291f16f0c2b1e17d485a10459146d664c Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Fri, 27 Sep 2024 11:05:42 -0300 Subject: [PATCH 13/14] docs: fix `IMPORTANT` tag bad formatting --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c1df2d..1186e0f 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,8 @@ The resulting final command would be: echo [my arg one] {one two three --my --options} ``` -> [!IMPORTANT] > **Always** provide required arguments **before** any optional ones. +> [!IMPORTANT] +> **Always** provide required arguments **before** any optional ones. ### Removing a Command From fad50d969d4a9fe1e93e1be394542e7e06656aa3 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Fri, 27 Sep 2024 11:11:15 -0300 Subject: [PATCH 14/14] docs: make `features` section more concise --- README.md | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1186e0f..3370c80 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,10 @@ Foji is a powerful command-line interface (CLI) tool designed to streamline and ## πŸš€ Features -- [x] Run custom codes -- [x] Handle custom parameters -- [x] Conditional handler for arguments -- [x] Support for default arguments -- [x] Support for optional arguments -- [x] Support for options as arguments -- [x] Add commands to a configuration file -- [x] Remove commands from the configuration file -- [x] Can run commands directly -- [x] Configuration can be saved on the cloud -- [x] Configuration can be downloaded from the cloud -- [x] Local configuration can be synced from the cloud +- [x] Run custom commands with flexible argument handling. +- [x] Supports options, conditional, and spread arguments for advanced customization. +- [x] Easily add, edit and remove commands in the CLI. +- [x] Sync configuration to the cloud. ## πŸ“¦ Installation