From eeea527829c7ba4e7ca6f7d033659fbdee88a129 Mon Sep 17 00:00:00 2001 From: Dhwanik Panchal Date: Sat, 28 Jun 2025 13:20:23 +0530 Subject: [PATCH 1/5] feat(cli-command-editor): Added a new tool: CLI command editor --- locales/en.yml | 6 + .../cli-command-editor.e2e.spec.ts | 15 ++ .../cli-command-editor.service.test.ts | 142 ++++++++++++++++++ .../cli-command-editor.service.ts | 104 +++++++++++++ .../cli-command-editor/cli-command-editor.vue | 54 +++++++ src/tools/cli-command-editor/index.ts | 12 ++ src/tools/index.ts | 2 + 7 files changed, 335 insertions(+) create mode 100644 src/tools/cli-command-editor/cli-command-editor.e2e.spec.ts create mode 100644 src/tools/cli-command-editor/cli-command-editor.service.test.ts create mode 100644 src/tools/cli-command-editor/cli-command-editor.service.ts create mode 100644 src/tools/cli-command-editor/cli-command-editor.vue create mode 100644 src/tools/cli-command-editor/index.ts diff --git a/locales/en.yml b/locales/en.yml index d03d80d3..ca3362f6 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -392,3 +392,9 @@ tools: text-to-binary: title: Text to ASCII binary description: Convert text to its ASCII binary representation and vice-versa. + + cli-command-editor: + title: CLI command editor + description: Convert CLI commands with options into an easily editable form and generate the resulting command with input values. + command: Command + placeholder: Paste command here diff --git a/src/tools/cli-command-editor/cli-command-editor.e2e.spec.ts b/src/tools/cli-command-editor/cli-command-editor.e2e.spec.ts new file mode 100644 index 00000000..15255768 --- /dev/null +++ b/src/tools/cli-command-editor/cli-command-editor.e2e.spec.ts @@ -0,0 +1,15 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Tool - Cli command editor', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/cli-command-editor'); + }); + + test('Has correct title', async ({ page }) => { + await expect(page).toHaveTitle('Cli command editor - IT Tools'); + }); + + test('', async ({ page }) => { + + }); +}); \ No newline at end of file diff --git a/src/tools/cli-command-editor/cli-command-editor.service.test.ts b/src/tools/cli-command-editor/cli-command-editor.service.test.ts new file mode 100644 index 00000000..6d638bb7 --- /dev/null +++ b/src/tools/cli-command-editor/cli-command-editor.service.test.ts @@ -0,0 +1,142 @@ +import { expect, describe, it } from 'vitest'; +import { extractOptions, buildOptionsObject, sanitizeOption, buildEditedCommand, isOption } from './cli-command-editor.service'; + +describe('cli-command-editor', () => { + describe("extractOptions", () => { + it ("extracts all the options from a command", () => { + expect( + extractOptions("aws elb describe-load-balancers --load-balancer-name my-load-balancer")[0] + ).toContain("--load-balancer-name"); + + expect( + extractOptions("aws elb describe-load-balancers --load-balancer-name my-load-balancer --debug --query my-queryyy")[0] + ).toContain("--load-balancer-name"); + + expect( + extractOptions("aws elb describe-load-balancers --load-balancer-name my-load-balancer --debug --query my-queryyy")[1] + ).toContain("--debug"); + + expect( + extractOptions("aws elb describe-load-balancers --load-balancer-name my-load-balancer --debug --query my-queryyy")[2] + ).toContain("--query"); + }); + + it("extracts all the option from a command with a mix of hyphen and double hyphens", () => { + expect( + extractOptions("npm i lodash -g --legacy-peer-deps")[0] + ).toContain("-g"); + + expect( + extractOptions("npm i lodash -g --legacy-peer-deps")[1] + ).toContain("--legacy-peer-deps"); + }); + + it("shouldn't extract any options from a command without options", () => { + expect( + extractOptions("npm i lodash") + ).toEqual([]); + }); + + it("shouldn't return any options if command is not passed", () => { + expect(extractOptions()).toEqual([]); + }); + }); + + describe("buildOptionsObject", () => { + it("returns a valid options object with the given options", () => { + expect( + buildOptionsObject(["--debug", "--load-balancer-names"]) + ).toEqual({ + "--debug": "", + "--load-balancer-names": "", + }); + }); + + it("returns an empty obnject with blank options array", () => { + expect( + buildOptionsObject([]) + ).toEqual({}); + }); + }); + + describe("sanitizeOption", () => { + it("returns the sanitized option without 'id' suffix", () => { + expect(sanitizeOption("--debug-id-1dfsj")) + .toEqual("--debug"); + }); + + it("returns the blank string", () => { + expect(sanitizeOption("")).toEqual(""); + }); + }); + + describe("isOption", () => { + it("returns true for a valid double hyphen option token", () => { + expect(isOption("--debug")).toBe(true); + }); + + it("returns true for a valid single hyphen option token", () => { + expect(isOption("-i")).toBe(true); + }); + + it("returns false for an non-option token", () => { + expect(isOption("hello-world")).toBe(false); + }); + }); + + describe("buildEditedCommand", () => { + it("returns the edited command", () => { + expect( + buildEditedCommand({ + "--debug-id-1dfsj": "stdin", + "-p": "", + "-m": "nahhhh", + }, { + "--debug-id-1dfsj": "stdin", + "-p": "", + "-m": "nahhhh", + }, "aws node --debug stdio -p -m okayyy") + ).toEqual("aws node --debug stdin -p -m nahhhh"); + + expect( + buildEditedCommand({ + "-d-id-1dfsj": "", + "-p-id-fdsd": "4444:3333", + "-p-id-fddd": "3333:4444", + "-e-id-ckslc": "CLICKHOUSE_PASSWORD=nopassword", + "--name-id-nnnn": "clickhouse-server", + "--ulimit-id-uuuu": "nofile=3333:4444", + }, { + "-d-id-1dfsj": "", + "-p-id-fdsd": "4444:3333", + "-p-id-fddd": "3333:4444", + "-e-id-ckslc": "CLICKHOUSE_PASSWORD=nopassword", + "--name-id-nnnn": "clickhouse-server", + "--ulimit-id-uuuu": "nofile=3333:4444", + }, "docker run -d -p 18123:8123 -p 19000:9000 -e CLICKHOUSE_PASSWORD=changeme --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server") + ).toEqual("docker run -d -p 4444:3333 -p 3333:4444 -e CLICKHOUSE_PASSWORD=nopassword --name clickhouse-server --ulimit nofile=3333:4444 clickhouse/clickhouse-server"); + }); + + it("returns the edited command when options object and CLI options order doesn't match", () => { + expect( + buildEditedCommand({ + "-d-id-t1dd3": "true", + "--install-id-only123": "nodemon", + }, { + "--install-id-only123": "nodem", + "-d-id-t1dd3": "false", + }, "npm --install nodem -d false") + ).toBe("npm --install nodemon -d true"); + }); + + it("returns the original command", () => { + expect( + buildEditedCommand({}, {}, "npm install nodemon") + ).toBe("npm install nodemon"); + + expect( + buildEditedCommand({}, {}, "aws load-balancer describe-load-balancers all") + ).toBe("aws load-balancer describe-load-balancers all"); + }); + }); +}); \ No newline at end of file diff --git a/src/tools/cli-command-editor/cli-command-editor.service.ts b/src/tools/cli-command-editor/cli-command-editor.service.ts new file mode 100644 index 00000000..a4ec8c07 --- /dev/null +++ b/src/tools/cli-command-editor/cli-command-editor.service.ts @@ -0,0 +1,104 @@ +import { generateRandomId } from "@/utils/random"; + +export function isOption(token: string): boolean { + return token?.startsWith("--") || token?.startsWith("-") +} + +export function extractOptions(command: string = ""): string[] { + /* + aws elb describe-load-balancers --load-balancer-name my-load-balancer + npm i forever -g + docker run -d -p 18123:8123 -p 19000:9000 -e CLICKHOUSE_PASSWORD=changeme --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server + + in a CLI, the options are either written with a hyphen or double hyphens, however, + script names or package/library sometimes include a hyphen, too, for example 'describe-load-balancers' + */ + // split into tokens first + const tokens = command.split(" "); + + // map each token of the command to an option + const options = tokens.map((token: string) => { + // every option in a starts with either a hyphen or double hyphens + if (isOption(token)) { + const randomId = generateRandomId(); + return `${token}-${randomId}`; + } + + return ""; + }).filter((option: string): boolean => !!option); + return options; +} + +export function buildOptionsObject(options: string[]): Record { + const optionsObject: Record = {}; + + for (const option of options) { + optionsObject[option] = ""; + } + + return optionsObject; +} + +export function sanitizeOption(option: string): string { + return option.split("-id")?.[0]; +} + +export function buildEditedCommand(options: Record, originalOptions: Record, command: string): string { + if (!Object.keys(options).length) return command; + + const tokens = command.split(" "); + const editedTokens = []; + + // user may input the option value in any order, from the form + // preserve the original object with options in the correct + // order as they appear in the original command, this is done + // to handle the interpolation of edited option values into the + // command + originalOptions = Object.entries(options) + .reduce((previousValue: Record, currentValue: string[]) => { + previousValue[currentValue[0]] = currentValue[1] + + return previousValue + }, originalOptions) + + const defaultValues: Record = {}; + // replacing the options and their values (if any) with formatter ($i) to + // help in interpolation of the command + for (let i = 0, j = 0, n = tokens.length; i < n; ++i) { + const token = tokens[i]; + const nextToken = tokens[i+1]; + + if (isOption(token)) { + editedTokens.push(`$${j}`) + + if (!isOption(nextToken)) { + ++i; + defaultValues[`$${j}`] = nextToken; + } + + ++j; + continue; + } + + editedTokens.push(token); + } + + let editedCommand = editedTokens.join(" "); + + const originalOptionKeys = Object.keys(originalOptions); + + for (let i = 0, n = originalOptionKeys.length; i < n; ++i) { + const key = originalOptionKeys[i]; + const keyWithoutIdSuffix = key.split("-id-")[0] || key; + + if (originalOptions[key]) { + editedCommand = editedCommand.replace(`$${i}`, `${keyWithoutIdSuffix} ${originalOptions[key]}`); + } else { + const value = defaultValues[`$${i}`]; + const replaceValue = value ? `${keyWithoutIdSuffix} ${value}` : keyWithoutIdSuffix; + editedCommand = editedCommand.replace(`$${i}`, replaceValue); + } + } + + return editedCommand; +} \ No newline at end of file diff --git a/src/tools/cli-command-editor/cli-command-editor.vue b/src/tools/cli-command-editor/cli-command-editor.vue new file mode 100644 index 00000000..c8614b41 --- /dev/null +++ b/src/tools/cli-command-editor/cli-command-editor.vue @@ -0,0 +1,54 @@ + + + + + \ No newline at end of file diff --git a/src/tools/cli-command-editor/index.ts b/src/tools/cli-command-editor/index.ts new file mode 100644 index 00000000..a47a70e8 --- /dev/null +++ b/src/tools/cli-command-editor/index.ts @@ -0,0 +1,12 @@ +import { Terminal2 } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'CLI command editor', + path: '/cli-command-editor', + description: '', + keywords: ['cli', 'command', 'editor'], + component: () => import('./cli-command-editor.vue'), + icon: Terminal2, + createdAt: new Date('2025-06-21'), +}); \ No newline at end of file diff --git a/src/tools/index.ts b/src/tools/index.ts index 388cfaf4..de28c2ed 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,6 +1,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; +import { tool as cliCommandEditor } from './cli-command-editor'; import { tool as emailNormalizer } from './email-normalizer'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -160,6 +161,7 @@ export const toolsByCategory: ToolCategory[] = [ emailNormalizer, regexTester, regexMemo, + cliCommandEditor, ], }, { From 3e2e1efd775ce44985c368c3cb2f751faad0cb84 Mon Sep 17 00:00:00 2001 From: Dhwanik Panchal Date: Sat, 28 Jun 2025 13:24:18 +0530 Subject: [PATCH 2/5] fix(cli-command-editor): add 'key' to mapped Vue component --- src/tools/cli-command-editor/cli-command-editor.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/cli-command-editor/cli-command-editor.vue b/src/tools/cli-command-editor/cli-command-editor.vue index c8614b41..189384bb 100644 --- a/src/tools/cli-command-editor/cli-command-editor.vue +++ b/src/tools/cli-command-editor/cli-command-editor.vue @@ -26,6 +26,7 @@
Date: Sat, 28 Jun 2025 13:49:00 +0530 Subject: [PATCH 3/5] feat(i18n): added localization for cli-command-editor --- components.d.ts | 1 + locales/de.yml | 7 +++++++ locales/es.yml | 5 +++++ locales/fr.yml | 5 +++++ locales/no.yml | 6 ++++++ locales/pt.yml | 5 +++++ locales/uk.yml | 5 +++++ locales/vi.yml | 6 ++++++ locales/zh.yml | 6 ++++++ 9 files changed, 46 insertions(+) diff --git a/components.d.ts b/components.d.ts index 3e65c3cc..87f73674 100644 --- a/components.d.ts +++ b/components.d.ts @@ -42,6 +42,7 @@ declare module '@vue/runtime-core' { CKeyValueList: typeof import('./src/ui/c-key-value-list/c-key-value-list.vue')['default'] CKeyValueListItem: typeof import('./src/ui/c-key-value-list/c-key-value-list-item.vue')['default'] CLabel: typeof import('./src/ui/c-label/c-label.vue')['default'] + CliCommandEditor: typeof import('./src/tools/cli-command-editor/cli-command-editor.vue')['default'] CLink: typeof import('./src/ui/c-link/c-link.vue')['default'] 'CLink.demo': typeof import('./src/ui/c-link/c-link.demo.vue')['default'] CMarkdown: typeof import('./src/ui/c-markdown/c-markdown.vue')['default'] diff --git a/locales/de.yml b/locales/de.yml index 0ccdb71d..d37a1cf6 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -454,3 +454,10 @@ tools: text-to-binary: title: Text zu ASCII-Binär description: Konvertiere Text in seine ASCII-Binärrepräsentation und umgekehrt. + + cli-command-editor: + title: CLI-Befehlseditor + description: Wandeln Sie CLI-Befehle mit Optionen in eine leicht bearbeitbare Form um und generieren Sie den Befehl mit Eingabewerten. + command: Befehl + placeholder: Befehl hier einfügen + diff --git a/locales/es.yml b/locales/es.yml index 14e2bb66..aec440fa 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -70,3 +70,8 @@ tools: measurement: Measurement text: Text data: Data + cli-command-editor: + title: editor de comandos CLI + description: Convierta comandos CLI con opciones en un formato fácilmente editable y genere el comando resultante con valores de entrada. + command: Dominio + placeholder: Pegar comando aquí diff --git a/locales/fr.yml b/locales/fr.yml index 86bb47d6..8d83d350 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -80,3 +80,8 @@ tools: copied: Le token a été copié length: Longueur tokenPlaceholder: Le token... + cli-command-editor: + title: Éditeur de commandes CLI + description: Convertissez les commandes CLI avec des options dans un format facilement modifiable et générez la commande résultante avec des valeurs d'entrée. + command: Commande + placeholder: Coller la commande ici diff --git a/locales/no.yml b/locales/no.yml index ba4f9e47..9956682e 100644 --- a/locales/no.yml +++ b/locales/no.yml @@ -392,3 +392,9 @@ tools: text-to-binary: title: Tekst til ASCII binært description: Konverter tekst til sin ASCII binære representasjon og visa-versa. + + cli-command-editor: + title: CLI kommando editor + description: Konverter CLI-kommandoer med alternativer til et enkelt redigerbart format og generer den resulterende kommandoen med inndataverdier. + command: Kommando + placeholder: Lim inn kommando her diff --git a/locales/pt.yml b/locales/pt.yml index 5845eb2f..571f2598 100644 --- a/locales/pt.yml +++ b/locales/pt.yml @@ -70,3 +70,8 @@ tools: measurement: 'Medidas' text: 'Texto' data: 'Dados' + cli-command-editor: + title: Editor de comando CLI + description: Converta comandos CLI com opções em um formato facilmente editável e gere o comando resultante com valores de entrada. + command: Comando + placeholder: Cole o comando aqui diff --git a/locales/uk.yml b/locales/uk.yml index b0086226..400db440 100644 --- a/locales/uk.yml +++ b/locales/uk.yml @@ -70,3 +70,8 @@ tools: measurement: Вимірювання text: Текст data: Дані + cli-command-editor: + title: Редактор команд CLI + description: Перетворіть команди CLI з опціями у форму, яку легко редагувати, та згенеруйте результуючу команду з вхідними значеннями. + command: Команда + placeholder: Вставте команду сюди diff --git a/locales/vi.yml b/locales/vi.yml index 59514cd7..1a4af032 100644 --- a/locales/vi.yml +++ b/locales/vi.yml @@ -381,3 +381,9 @@ tools: text-to-binary: title: Chuyển đổi văn bản thành nhị phân ASCII description: Chuyển đổi văn bản thành biểu diễn nhị phân ASCII của nó và ngược lại. + + cli-command-editor: + title: Trình soạn thảo lệnh CLI + description: Chuyển đổi các lệnh CLI có tùy chọn thành dạng dễ chỉnh sửa và tạo lệnh kết quả với các giá trị đầu vào. + command: Yêu cầu + placeholder: Dán lệnh vào đây diff --git a/locales/zh.yml b/locales/zh.yml index 97968eb5..b5c2b380 100644 --- a/locales/zh.yml +++ b/locales/zh.yml @@ -388,3 +388,9 @@ tools: text-to-binary: title: 文本到 ASCII 二进制 description: 将文本转换为其 ASCII 二进制表示形式,反之亦然。 + + cli-command-editor: + title: CLI 命令编辑器 + description: 将带有选项的 CLI 命令转换为易于编辑的形式,并生成带有输入值的结果命令。 + command: 命令 + placeholder: 将命令粘贴到此处 From 2e91373d6b2a4a503157f15dff215f274c72af30 Mon Sep 17 00:00:00 2001 From: Dhwanik Panchal Date: Sat, 28 Jun 2025 14:01:37 +0530 Subject: [PATCH 4/5] fix(cli-command-editor): remove password option from test --- .../cli-command-editor/cli-command-editor.service.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tools/cli-command-editor/cli-command-editor.service.test.ts b/src/tools/cli-command-editor/cli-command-editor.service.test.ts index 6d638bb7..912db682 100644 --- a/src/tools/cli-command-editor/cli-command-editor.service.test.ts +++ b/src/tools/cli-command-editor/cli-command-editor.service.test.ts @@ -103,18 +103,16 @@ describe('cli-command-editor', () => { "-d-id-1dfsj": "", "-p-id-fdsd": "4444:3333", "-p-id-fddd": "3333:4444", - "-e-id-ckslc": "CLICKHOUSE_PASSWORD=nopassword", "--name-id-nnnn": "clickhouse-server", "--ulimit-id-uuuu": "nofile=3333:4444", }, { "-d-id-1dfsj": "", "-p-id-fdsd": "4444:3333", "-p-id-fddd": "3333:4444", - "-e-id-ckslc": "CLICKHOUSE_PASSWORD=nopassword", "--name-id-nnnn": "clickhouse-server", "--ulimit-id-uuuu": "nofile=3333:4444", - }, "docker run -d -p 18123:8123 -p 19000:9000 -e CLICKHOUSE_PASSWORD=changeme --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server") - ).toEqual("docker run -d -p 4444:3333 -p 3333:4444 -e CLICKHOUSE_PASSWORD=nopassword --name clickhouse-server --ulimit nofile=3333:4444 clickhouse/clickhouse-server"); + }, "docker run -d -p 18123:8123 -p 19000:9000 --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server") + ).toEqual("docker run -d -p 4444:3333 -p 3333:4444 --name clickhouse-server --ulimit nofile=3333:4444 clickhouse/clickhouse-server"); }); it("returns the edited command when options object and CLI options order doesn't match", () => { From e66702ec9f73bca433b9811e86c98cf574c7e59f Mon Sep 17 00:00:00 2001 From: Dhwanik Panchal Date: Sat, 28 Jun 2025 14:51:38 +0530 Subject: [PATCH 5/5] fix(lint): lint fixes --- .../cli-command-editor.e2e.spec.ts | 8 +- .../cli-command-editor.service.test.ts | 150 +++++++++--------- .../cli-command-editor.service.ts | 48 +++--- .../cli-command-editor/cli-command-editor.vue | 28 ++-- src/tools/cli-command-editor/index.ts | 2 +- 5 files changed, 114 insertions(+), 122 deletions(-) diff --git a/src/tools/cli-command-editor/cli-command-editor.e2e.spec.ts b/src/tools/cli-command-editor/cli-command-editor.e2e.spec.ts index 15255768..b80e444b 100644 --- a/src/tools/cli-command-editor/cli-command-editor.e2e.spec.ts +++ b/src/tools/cli-command-editor/cli-command-editor.e2e.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +import { expect, test } from '@playwright/test'; test.describe('Tool - Cli command editor', () => { test.beforeEach(async ({ page }) => { @@ -8,8 +8,4 @@ test.describe('Tool - Cli command editor', () => { test('Has correct title', async ({ page }) => { await expect(page).toHaveTitle('Cli command editor - IT Tools'); }); - - test('', async ({ page }) => { - - }); -}); \ No newline at end of file +}); diff --git a/src/tools/cli-command-editor/cli-command-editor.service.test.ts b/src/tools/cli-command-editor/cli-command-editor.service.test.ts index 912db682..9524564a 100644 --- a/src/tools/cli-command-editor/cli-command-editor.service.test.ts +++ b/src/tools/cli-command-editor/cli-command-editor.service.test.ts @@ -1,140 +1,140 @@ -import { expect, describe, it } from 'vitest'; -import { extractOptions, buildOptionsObject, sanitizeOption, buildEditedCommand, isOption } from './cli-command-editor.service'; +import { describe, expect, it } from 'vitest'; +import { buildEditedCommand, buildOptionsObject, extractOptions, isOption, sanitizeOption } from './cli-command-editor.service'; describe('cli-command-editor', () => { - describe("extractOptions", () => { - it ("extracts all the options from a command", () => { + describe('extractOptions', () => { + it ('extracts all the options from a command', () => { expect( - extractOptions("aws elb describe-load-balancers --load-balancer-name my-load-balancer")[0] - ).toContain("--load-balancer-name"); + extractOptions('aws elb describe-load-balancers --load-balancer-name my-load-balancer')[0], + ).toContain('--load-balancer-name'); expect( - extractOptions("aws elb describe-load-balancers --load-balancer-name my-load-balancer --debug --query my-queryyy")[0] - ).toContain("--load-balancer-name"); + extractOptions('aws elb describe-load-balancers --load-balancer-name my-load-balancer --debug --query my-queryyy')[0], + ).toContain('--load-balancer-name'); expect( - extractOptions("aws elb describe-load-balancers --load-balancer-name my-load-balancer --debug --query my-queryyy")[1] - ).toContain("--debug"); + extractOptions('aws elb describe-load-balancers --load-balancer-name my-load-balancer --debug --query my-queryyy')[1], + ).toContain('--debug'); expect( - extractOptions("aws elb describe-load-balancers --load-balancer-name my-load-balancer --debug --query my-queryyy")[2] - ).toContain("--query"); + extractOptions('aws elb describe-load-balancers --load-balancer-name my-load-balancer --debug --query my-queryyy')[2], + ).toContain('--query'); }); - it("extracts all the option from a command with a mix of hyphen and double hyphens", () => { + it('extracts all the option from a command with a mix of hyphen and double hyphens', () => { expect( - extractOptions("npm i lodash -g --legacy-peer-deps")[0] - ).toContain("-g"); + extractOptions('npm i lodash -g --legacy-peer-deps')[0], + ).toContain('-g'); expect( - extractOptions("npm i lodash -g --legacy-peer-deps")[1] - ).toContain("--legacy-peer-deps"); + extractOptions('npm i lodash -g --legacy-peer-deps')[1], + ).toContain('--legacy-peer-deps'); }); - it("shouldn't extract any options from a command without options", () => { + it('shouldn\'t extract any options from a command without options', () => { expect( - extractOptions("npm i lodash") + extractOptions('npm i lodash'), ).toEqual([]); }); - it("shouldn't return any options if command is not passed", () => { + it('shouldn\'t return any options if command is not passed', () => { expect(extractOptions()).toEqual([]); }); }); - describe("buildOptionsObject", () => { - it("returns a valid options object with the given options", () => { + describe('buildOptionsObject', () => { + it('returns a valid options object with the given options', () => { expect( - buildOptionsObject(["--debug", "--load-balancer-names"]) + buildOptionsObject(['--debug', '--load-balancer-names']), ).toEqual({ - "--debug": "", - "--load-balancer-names": "", + '--debug': '', + '--load-balancer-names': '', }); }); - it("returns an empty obnject with blank options array", () => { + it('returns an empty obnject with blank options array', () => { expect( - buildOptionsObject([]) + buildOptionsObject([]), ).toEqual({}); }); }); - describe("sanitizeOption", () => { - it("returns the sanitized option without 'id' suffix", () => { - expect(sanitizeOption("--debug-id-1dfsj")) - .toEqual("--debug"); + describe('sanitizeOption', () => { + it('returns the sanitized option without `id` suffix', () => { + expect(sanitizeOption('--debug-id-1dfsj')) + .toEqual('--debug'); }); - it("returns the blank string", () => { - expect(sanitizeOption("")).toEqual(""); + it('returns the blank string', () => { + expect(sanitizeOption('')).toEqual(''); }); }); - describe("isOption", () => { - it("returns true for a valid double hyphen option token", () => { - expect(isOption("--debug")).toBe(true); + describe('isOption', () => { + it('returns true for a valid double hyphen option token', () => { + expect(isOption('--debug')).toBe(true); }); - it("returns true for a valid single hyphen option token", () => { - expect(isOption("-i")).toBe(true); + it('returns true for a valid single hyphen option token', () => { + expect(isOption('-i')).toBe(true); }); - it("returns false for an non-option token", () => { - expect(isOption("hello-world")).toBe(false); + it('returns false for an non-option token', () => { + expect(isOption('hello-world')).toBe(false); }); }); - describe("buildEditedCommand", () => { - it("returns the edited command", () => { + describe('buildEditedCommand', () => { + it('returns the edited command', () => { expect( buildEditedCommand({ - "--debug-id-1dfsj": "stdin", - "-p": "", - "-m": "nahhhh", + '--debug-id-1dfsj': 'stdin', + '-p': '', + '-m': 'nahhhh', }, { - "--debug-id-1dfsj": "stdin", - "-p": "", - "-m": "nahhhh", - }, "aws node --debug stdio -p -m okayyy") - ).toEqual("aws node --debug stdin -p -m nahhhh"); + '--debug-id-1dfsj': 'stdin', + '-p': '', + '-m': 'nahhhh', + }, 'aws node --debug stdio -p -m okayyy'), + ).toEqual('aws node --debug stdin -p -m nahhhh'); expect( buildEditedCommand({ - "-d-id-1dfsj": "", - "-p-id-fdsd": "4444:3333", - "-p-id-fddd": "3333:4444", - "--name-id-nnnn": "clickhouse-server", - "--ulimit-id-uuuu": "nofile=3333:4444", + '-d-id-1dfsj': '', + '-p-id-fdsd': '4444:3333', + '-p-id-fddd': '3333:4444', + '--name-id-nnnn': 'clickhouse-server', + '--ulimit-id-uuuu': 'nofile=3333:4444', }, { - "-d-id-1dfsj": "", - "-p-id-fdsd": "4444:3333", - "-p-id-fddd": "3333:4444", - "--name-id-nnnn": "clickhouse-server", - "--ulimit-id-uuuu": "nofile=3333:4444", - }, "docker run -d -p 18123:8123 -p 19000:9000 --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server") - ).toEqual("docker run -d -p 4444:3333 -p 3333:4444 --name clickhouse-server --ulimit nofile=3333:4444 clickhouse/clickhouse-server"); + '-d-id-1dfsj': '', + '-p-id-fdsd': '4444:3333', + '-p-id-fddd': '3333:4444', + '--name-id-nnnn': 'clickhouse-server', + '--ulimit-id-uuuu': 'nofile=3333:4444', + }, 'docker run -d -p 18123:8123 -p 19000:9000 --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server'), + ).toEqual('docker run -d -p 4444:3333 -p 3333:4444 --name clickhouse-server --ulimit nofile=3333:4444 clickhouse/clickhouse-server'); }); - it("returns the edited command when options object and CLI options order doesn't match", () => { + it('returns the edited command when options object and CLI options order doesn\'t match', () => { expect( buildEditedCommand({ - "-d-id-t1dd3": "true", - "--install-id-only123": "nodemon", + '-d-id-t1dd3': 'true', + '--install-id-only123': 'nodemon', }, { - "--install-id-only123": "nodem", - "-d-id-t1dd3": "false", - }, "npm --install nodem -d false") - ).toBe("npm --install nodemon -d true"); + '--install-id-only123': 'nodem', + '-d-id-t1dd3': 'false', + }, 'npm --install nodem -d false'), + ).toBe('npm --install nodemon -d true'); }); - it("returns the original command", () => { + it('returns the original command', () => { expect( - buildEditedCommand({}, {}, "npm install nodemon") - ).toBe("npm install nodemon"); + buildEditedCommand({}, {}, 'npm install nodemon'), + ).toBe('npm install nodemon'); expect( - buildEditedCommand({}, {}, "aws load-balancer describe-load-balancers all") - ).toBe("aws load-balancer describe-load-balancers all"); + buildEditedCommand({}, {}, 'aws load-balancer describe-load-balancers all'), + ).toBe('aws load-balancer describe-load-balancers all'); }); }); -}); \ No newline at end of file +}); diff --git a/src/tools/cli-command-editor/cli-command-editor.service.ts b/src/tools/cli-command-editor/cli-command-editor.service.ts index a4ec8c07..9793ac24 100644 --- a/src/tools/cli-command-editor/cli-command-editor.service.ts +++ b/src/tools/cli-command-editor/cli-command-editor.service.ts @@ -1,20 +1,17 @@ -import { generateRandomId } from "@/utils/random"; +import { generateRandomId } from '@/utils/random'; export function isOption(token: string): boolean { - return token?.startsWith("--") || token?.startsWith("-") + return token?.startsWith('--') || token?.startsWith('-'); } -export function extractOptions(command: string = ""): string[] { +export function extractOptions(command: string = ''): string[] { /* - aws elb describe-load-balancers --load-balancer-name my-load-balancer - npm i forever -g - docker run -d -p 18123:8123 -p 19000:9000 -e CLICKHOUSE_PASSWORD=changeme --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server - in a CLI, the options are either written with a hyphen or double hyphens, however, script names or package/library sometimes include a hyphen, too, for example 'describe-load-balancers' */ + // split into tokens first - const tokens = command.split(" "); + const tokens = command.split(' '); // map each token of the command to an option const options = tokens.map((token: string) => { @@ -24,7 +21,7 @@ export function extractOptions(command: string = ""): string[] { return `${token}-${randomId}`; } - return ""; + return ''; }).filter((option: string): boolean => !!option); return options; } @@ -33,20 +30,22 @@ export function buildOptionsObject(options: string[]): Record { const optionsObject: Record = {}; for (const option of options) { - optionsObject[option] = ""; + optionsObject[option] = ''; } return optionsObject; } export function sanitizeOption(option: string): string { - return option.split("-id")?.[0]; + return option.split('-id')?.[0]; } export function buildEditedCommand(options: Record, originalOptions: Record, command: string): string { - if (!Object.keys(options).length) return command; + if (!Object.keys(options).length) { + return command; + } - const tokens = command.split(" "); + const tokens = command.split(' '); const editedTokens = []; // user may input the option value in any order, from the form @@ -56,26 +55,26 @@ export function buildEditedCommand(options: Record, originalOpti // command originalOptions = Object.entries(options) .reduce((previousValue: Record, currentValue: string[]) => { - previousValue[currentValue[0]] = currentValue[1] + previousValue[currentValue[0]] = currentValue[1]; - return previousValue - }, originalOptions) + return previousValue; + }, originalOptions); const defaultValues: Record = {}; // replacing the options and their values (if any) with formatter ($i) to // help in interpolation of the command for (let i = 0, j = 0, n = tokens.length; i < n; ++i) { const token = tokens[i]; - const nextToken = tokens[i+1]; + const nextToken = tokens[i + 1]; if (isOption(token)) { - editedTokens.push(`$${j}`) + editedTokens.push(`$${j}`); if (!isOption(nextToken)) { ++i; defaultValues[`$${j}`] = nextToken; } - + ++j; continue; } @@ -83,17 +82,18 @@ export function buildEditedCommand(options: Record, originalOpti editedTokens.push(token); } - let editedCommand = editedTokens.join(" "); + let editedCommand = editedTokens.join(' '); const originalOptionKeys = Object.keys(originalOptions); for (let i = 0, n = originalOptionKeys.length; i < n; ++i) { const key = originalOptionKeys[i]; - const keyWithoutIdSuffix = key.split("-id-")[0] || key; - + const keyWithoutIdSuffix = key.split('-id-')[0] || key; + if (originalOptions[key]) { editedCommand = editedCommand.replace(`$${i}`, `${keyWithoutIdSuffix} ${originalOptions[key]}`); - } else { + } + else { const value = defaultValues[`$${i}`]; const replaceValue = value ? `${keyWithoutIdSuffix} ${value}` : keyWithoutIdSuffix; editedCommand = editedCommand.replace(`$${i}`, replaceValue); @@ -101,4 +101,4 @@ export function buildEditedCommand(options: Record, originalOpti } return editedCommand; -} \ No newline at end of file +} diff --git a/src/tools/cli-command-editor/cli-command-editor.vue b/src/tools/cli-command-editor/cli-command-editor.vue index 189384bb..8ed0b2c2 100644 --- a/src/tools/cli-command-editor/cli-command-editor.vue +++ b/src/tools/cli-command-editor/cli-command-editor.vue @@ -1,10 +1,11 @@ - - \ No newline at end of file diff --git a/src/tools/cli-command-editor/index.ts b/src/tools/cli-command-editor/index.ts index a47a70e8..32d6cdb9 100644 --- a/src/tools/cli-command-editor/index.ts +++ b/src/tools/cli-command-editor/index.ts @@ -9,4 +9,4 @@ export const tool = defineTool({ component: () => import('./cli-command-editor.vue'), icon: Terminal2, createdAt: new Date('2025-06-21'), -}); \ No newline at end of file +});