From df2c4568ee6a38a9aefeb9c498a1049895a2356c Mon Sep 17 00:00:00 2001 From: chouchouji <1305974212@qq.com> Date: Tue, 10 Dec 2024 16:08:29 +0800 Subject: [PATCH 1/3] feat: support deleting multiple custom registries --- README.md | 2 +- package.json | 2 ++ pnpm-lock.yaml | 36 +++++++++++++++++++++++++ src/actions.ts | 67 ++++++++++++++++++++++++++++++++++------------- src/index.ts | 2 +- tests/cli.test.ts | 18 ++++++++++--- 6 files changed, 103 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 16ee4a8..3092176 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ Usage: nrm [options] [command] set Set custom registry attribute -a --attr Set custom registry attribute -v --value Set custom registry value - del Delete one custom registry + del [registry] Delete one custom registry rename Set custom registry name home [browser] Open the homepage of registry with optional browser publish [|] Publish package to current registry if current registry is a custom registry. The field 'repository' of current custom registry is required running this command. If you're not using custom registry, this command will run npm publish directly diff --git a/package.json b/package.json index 537c855..b8824ca 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "scripts": { "clean": "rimraf node_modules dist", "test": "pnpm build && vitest run", + "dev": "tsc --watch", "build": "rimraf dist && tsc && rimraf dist/types.js", "test:watch": "vitest", "changeset:version": "changeset version", @@ -33,6 +34,7 @@ }, "homepage": "https://github.com/Pana/nrm", "dependencies": { + "@inquirer/checkbox": "^4.0.3", "@inquirer/select": "^4.0.2", "chalk": "4.1.2", "commander": "^8.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b492cfc..5e32278 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@inquirer/checkbox': + specifier: ^4.0.3 + version: 4.0.3(@types/node@18.19.67) '@inquirer/select': specifier: ^4.0.2 version: 4.0.2(@types/node@18.19.67) @@ -324,10 +327,20 @@ packages: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} + '@inquirer/checkbox@4.0.3': + resolution: {integrity: sha512-CEt9B4e8zFOGtc/LYeQx5m8nfqQeG/4oNNv0PUvXGG0mys+wR/WbJ3B4KfSQ4Fcr3AQfpiuFOi3fVvmPfvNbxw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + '@inquirer/core@10.1.0': resolution: {integrity: sha512-I+ETk2AL+yAVbvuKx5AJpQmoaWhpiTFOg/UJb7ZkMAK4blmtG8ATh5ct+T/8xNld0CZG/2UhtkdMwpgvld92XQ==} engines: {node: '>=18'} + '@inquirer/core@10.1.1': + resolution: {integrity: sha512-rmZVXy9iZvO3ZStEe/ayuuwIJ23LSF13aPMlLMTQARX6lGUBDHGV8UB5i9MRrfy0+mZwt5/9bdy8llszSD3NQA==} + engines: {node: '>=18'} + '@inquirer/figures@1.0.8': resolution: {integrity: sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==} engines: {node: '>=18'} @@ -1456,6 +1469,15 @@ snapshots: '@fastify/busboy@2.1.1': {} + '@inquirer/checkbox@4.0.3(@types/node@18.19.67)': + dependencies: + '@inquirer/core': 10.1.1(@types/node@18.19.67) + '@inquirer/figures': 1.0.8 + '@inquirer/type': 3.0.1(@types/node@18.19.67) + '@types/node': 18.19.67 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + '@inquirer/core@10.1.0(@types/node@18.19.67)': dependencies: '@inquirer/figures': 1.0.8 @@ -1470,6 +1492,20 @@ snapshots: transitivePeerDependencies: - '@types/node' + '@inquirer/core@10.1.1(@types/node@18.19.67)': + dependencies: + '@inquirer/figures': 1.0.8 + '@inquirer/type': 3.0.1(@types/node@18.19.67) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + transitivePeerDependencies: + - '@types/node' + '@inquirer/figures@1.0.8': {} '@inquirer/select@4.0.2(@types/node@18.19.67)': diff --git a/src/actions.ts b/src/actions.ts index 5780c4f..a941668 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -1,3 +1,4 @@ +import checkbox from '@inquirer/checkbox'; import select from '@inquirer/select'; import chalk from 'chalk'; import open from 'open'; @@ -56,14 +57,18 @@ export async function onCurrent({ showUrl }: { showUrl: boolean }) { if (!matchedRegistry) { printMessages([ `Your current registry(${currentRegistry}) is not included in the nrm registries.`, - `Use the ${chalk.green('nrm add [home]')} command to add your registry.`, + `Use the ${chalk.green( + 'nrm add [home]', + )} command to add your registry.`, ]); return; } const [name, registry] = matchedRegistry; printMessages([ - `You are using ${chalk.green(showUrl ? registry[REGISTRY] : name)} registry.`, + `You are using ${chalk.green( + showUrl ? registry[REGISTRY] : name, + )} registry.`, ]); } @@ -73,7 +78,7 @@ export async function onUse(name: string) { // if alias is undefined, select the registry alias from list if (alias === undefined) { - alias = await select({ + alias = await select({ message: 'Please select the registry you want to use', choices: Object.keys(registries), pageSize: 10, @@ -91,23 +96,45 @@ export async function onUse(name: string) { printSuccess(`The registry has been changed to '${alias}'.`); } -export async function onDelete(name: string) { - if ( - (await isRegistryNotFound(name)) || - (await isInternalRegistry(name, 'delete')) - ) { +export async function onDelete(name: string | undefined) { + const customRegistries = await readFile(NRMRC); + + const deleteKeys: string[] = []; + if (name) { + deleteKeys.push(name); + } + + const choices = Object.keys(customRegistries); + if (name === undefined && !choices.length) { + printMessages(['No any custom registries can be deleted.']); return; } - const customRegistries = await readFile(NRMRC); - const registry = customRegistries[name]; - delete customRegistries[name]; - await writeFile(NRMRC, customRegistries); - printSuccess(`The registry '${name}' has been deleted successfully.`); + if (name === undefined) { + const selectedKeys = await checkbox({ + message: 'Please select the registries you want to delete', + choices, + }); + deleteKeys.push(...selectedKeys); + } - const currentRegistry = await getCurrentRegistry(); - if (currentRegistry === registry[REGISTRY]) { - await onUse('npm'); + for (const key of deleteKeys) { + if ( + (await isRegistryNotFound(key)) || + (await isInternalRegistry(key, 'delete')) + ) { + continue; + } + + const registry = customRegistries[key]; + delete customRegistries[key]; + await writeFile(NRMRC, customRegistries); + printSuccess(`The registry '${key}' has been deleted successfully.`); + + const currentRegistry = await getCurrentRegistry(); + if (currentRegistry === registry[REGISTRY]) { + await onUse('npm'); + } } } @@ -135,7 +162,9 @@ export async function onAdd(name: string, url: string, home?: string) { }); await writeFile(NRMRC, newCustomRegistries); printSuccess( - `Add registry ${name} success, run ${chalk.green(`nrm use ${name}`)} command to use ${name} registry.`, + `Add registry ${name} success, run ${chalk.green( + `nrm use ${name}`, + )} command to use ${name} registry.`, ); } @@ -250,7 +279,9 @@ export async function onSetAttribute( if (REPOSITORY === attr) { return exit( - `Use the ${chalk.green('nrm set-hosted-repo ')} command to set repository.`, + `Use the ${chalk.green( + 'nrm set-hosted-repo ', + )} command to set repository.`, ); } const customRegistries = await readFile(NRMRC); diff --git a/src/index.ts b/src/index.ts index b5cf940..675f294 100644 --- a/src/index.ts +++ b/src/index.ts @@ -82,7 +82,7 @@ program .action(onRename); program - .command('del ') + .command('del [name]') .description('Delete custom registry') .action(onDelete); diff --git a/tests/cli.test.ts b/tests/cli.test.ts index 23ae448..8554359 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -15,8 +15,8 @@ import { } from 'vitest'; import { onHome, onTest } from '../src/actions'; -import { readFile, writeFile } from '../src/helpers'; import { NPMRC, REGISTRIES } from '../src/constants'; +import { readFile, writeFile } from '../src/helpers'; const isWin = process.platform === 'win32'; @@ -98,7 +98,11 @@ it('nrm use local', async () => { expect(npmrc.registry).toBe(REGISTRIES.cnpm.registry); - await coffee.spawn('nrm', ['current'], { shell: isWin }).expect('stdout', /cnpm/g).expect('code', 0).end(); + await coffee + .spawn('nrm', ['current'], { shell: isWin }) + .expect('stdout', /cnpm/g) + .expect('code', 0) + .end(); }); it('nrm use local with user config', async () => { @@ -115,7 +119,11 @@ it('nrm use local with user config', async () => { expect(npmrc.registry).toBe(REGISTRIES.cnpm.registry); expect(npmrc.abc).toBe('123'); - await coffee.spawn('nrm', ['current'], { shell: isWin }).expect('stdout', /cnpm/g).expect('code', 0).end(); + await coffee + .spawn('nrm', ['current'], { shell: isWin }) + .expect('stdout', /cnpm/g) + .expect('code', 0) + .end(); }); it('nrm use without argument', async () => { @@ -127,7 +135,9 @@ it('nrm use without argument', async () => { }); }); - expect(message).toBe(`? Please select the registry you want to use (Use arrow keys) + expect( + message, + ).toBe(`? Please select the registry you want to use (Use arrow keys) ${isWin ? '>' : '❯'} npm yarn tencent From 19eff3ce2d8f3218242b430f2f06a9169823ef2a Mon Sep 17 00:00:00 2001 From: iosh Date: Sat, 14 Dec 2024 23:11:14 +0800 Subject: [PATCH 2/3] add changeset and test --- .changeset/grumpy-bags-travel.md | 5 ++ src/helpers.ts | 21 +++++++-- tests/cli.test.ts | 78 ++++++++++++++++++++++++++++++-- 3 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 .changeset/grumpy-bags-travel.md diff --git a/.changeset/grumpy-bags-travel.md b/.changeset/grumpy-bags-travel.md new file mode 100644 index 0000000..76ded81 --- /dev/null +++ b/.changeset/grumpy-bags-travel.md @@ -0,0 +1,5 @@ +--- +"nrm": patch +--- + +Added delete multiple registry. Thanks @chouchouji diff --git a/src/helpers.ts b/src/helpers.ts index 7b20b1e..badea92 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -53,9 +53,7 @@ export function printError(error: string) { } export function printMessages(messages: string[]) { - for (const message of messages) { - console.log(message); - } + console.log(messages.join('\n')); } export function geneDashLine(message: string, length: number) { @@ -105,3 +103,20 @@ export function exit(error?: string) { error && printError(error); process.exit(1); } + +export function isUnicodeSupported() { + if (process.platform !== 'win32') { + return process.env['TERM'] !== 'linux'; + } + + return ( + Boolean(process.env.WT_SESSION) || + Boolean(process.env.TERMINUS_SUBLIME) || + process.env.ConEmuTask === '{cmd::Cmder}' || + process.env.TERM_PROGRAM === 'Terminus-Sublime' || + process.env.TERM_PROGRAM === 'vscode' || + process.env.TERM === 'xterm-256color' || + process.env.TERM === 'alacritty' || + process.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm' + ); +} diff --git a/tests/cli.test.ts b/tests/cli.test.ts index 8554359..876f14e 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -14,12 +14,19 @@ import { vi, } from 'vitest'; +import { unlink } from 'node:fs/promises'; import { onHome, onTest } from '../src/actions'; -import { NPMRC, REGISTRIES } from '../src/constants'; -import { readFile, writeFile } from '../src/helpers'; +import { NPMRC, NRMRC, REGISTRIES } from '../src/constants'; +import { isUnicodeSupported, readFile, writeFile } from '../src/helpers'; const isWin = process.platform === 'win32'; +const shouldUseMain = isUnicodeSupported(); + +const pointer = shouldUseMain ? '❯' : '>'; +const radioOff = shouldUseMain ? '◯' : '( )'; +const radioOn = shouldUseMain ? '◉' : '(*)'; + vi.setConfig({ testTimeout: 20000, }); @@ -138,7 +145,7 @@ it('nrm use without argument', async () => { expect( message, ).toBe(`? Please select the registry you want to use (Use arrow keys) -${isWin ? '>' : '❯'} npm +${pointer} npm yarn tencent cnpm @@ -281,3 +288,68 @@ it('nrm home [browser]', async () => { await onHome('cnpm'); expect(open).toHaveBeenCalled(); }); + +describe('nrm delete without argument (use keyword to select delete)', () => { + const registries = [ + { name: 'test', url: 'http://localhost:3000' }, + { name: 'test1', url: 'http://localhost:3001' }, + { name: 'test2', url: 'http://localhost:3002' }, + ]; + beforeEach(async () => { + for (const registry of registries) { + await coffee + .spawn('nrm', ['add', `${registry.name}`, `${registry.url}`], { + shell: isWin, + }) + .expect('stdout', /success/g) + .expect('code', 0) + .end(); + } + }); + + afterEach(async () => { + await unlink(NRMRC); + }); + + it('nrm delete', async () => { + const { stdout } = spawn('nrm', ['del'], { shell: isWin }); + + const message = await new Promise((resolve) => { + stdout.on('data', (data) => { + resolve(stripAnsi(data.toString()).trim()); + }); + }); + + expect(message).toMatchInlineSnapshot(` + "? Please select the registries you want to delete (Press to select, + to toggle all, to invert selection, and to proceed) + ${pointer}${radioOff} test + ${radioOff} test1 + ${radioOff} test2" + `); + }); + + it('nrm delete (with keyword input)', async () => { + const { stdout, stdin } = spawn('nrm', ['del'], { shell: isWin }); + stdin.write('\u001b[B'); + + const message = await new Promise((resolve) => { + const m: string[] = []; + stdout.on('data', (data) => { + m.push(stripAnsi(data.toString()).trim()); + // get the last output + if (m.length === 2) { + resolve(m[m.length - 1]); + } + }); + }); + + expect(message).toMatchInlineSnapshot(` + "? Please select the registries you want to delete (Press to select, + to toggle all, to invert selection, and to proceed) + ${radioOff} test + ${pointer}${radioOff} test1 + ${radioOff} test2" + `); + }); +}); From b224d76eca75c39ac457a7cce7d0034503bf9773 Mon Sep 17 00:00:00 2001 From: iosh Date: Sat, 14 Dec 2024 23:15:40 +0800 Subject: [PATCH 3/3] fix lint --- src/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers.ts b/src/helpers.ts index badea92..f6cabe0 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -106,7 +106,7 @@ export function exit(error?: string) { export function isUnicodeSupported() { if (process.platform !== 'win32') { - return process.env['TERM'] !== 'linux'; + return process.env.TERM !== 'linux'; } return (