From f6b7deff032186ddb565851f7206788ba698a15a Mon Sep 17 00:00:00 2001 From: Keiichiro Amemiya Date: Fri, 27 Dec 2024 11:09:43 +0100 Subject: [PATCH] refactor: replace inquirer with @inquirer/prompts (#122) --- package-lock.json | 47 +-------------------------------- package.json | 2 +- src/commands/setup.ts | 44 +++++++++--------------------- src/commands/uninstall.ts | 25 ++++++------------ src/lib/fetch-tags.ts | 11 ++++---- src/lib/util.ts | 8 +++--- test/commands/setup.spec.ts | 15 +++-------- test/commands/uninstall.spec.ts | 7 ++--- 8 files changed, 37 insertions(+), 122 deletions(-) diff --git a/package-lock.json b/package-lock.json index fa81355..c39d725 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,11 @@ "version": "8.7.0", "license": "MIT", "dependencies": { + "@inquirer/prompts": "^7.2.1", "ajv": "^8.17.1", "chalk": "^5.4.1", "commander": "^12.1.0", "hosted-git-info": "^8.0.2", - "inquirer": "^12.3.0", "json-schema-to-typescript": "^15.0.3", "npm-package-arg": "^12.0.1", "semver": "^7.6.3", @@ -2821,42 +2821,6 @@ "node": ">=0.8.19" } }, - "node_modules/inquirer": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.3.0.tgz", - "integrity": "sha512-3NixUXq+hM8ezj2wc7wC37b32/rHq1MwNZDYdvx+d6jokOD+r+i8Q4Pkylh9tISYP114A128LCX8RKhopC5RfQ==", - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.1.2", - "@inquirer/prompts": "^7.2.1", - "@inquirer/type": "^3.0.2", - "ansi-escapes": "^4.3.2", - "mute-stream": "^2.0.0", - "run-async": "^3.0.0", - "rxjs": "^7.8.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - } - }, - "node_modules/inquirer/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/inquirer/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3847,15 +3811,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/package.json b/package.json index 5ca3b7a..73afea1 100644 --- a/package.json +++ b/package.json @@ -50,11 +50,11 @@ }, "prettier": {}, "dependencies": { + "@inquirer/prompts": "^7.2.1", "ajv": "^8.17.1", "chalk": "^5.4.1", "commander": "^12.1.0", "hosted-git-info": "^8.0.2", - "inquirer": "^12.3.0", "json-schema-to-typescript": "^15.0.3", "npm-package-arg": "^12.0.1", "semver": "^7.6.3", diff --git a/src/commands/setup.ts b/src/commands/setup.ts index 13bc053..af55318 100644 --- a/src/commands/setup.ts +++ b/src/commands/setup.ts @@ -1,11 +1,11 @@ +import { execSync } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import stream from "node:stream/promises"; +import { confirm } from "@inquirer/prompts"; import chalk from "chalk"; -import { execSync } from "child_process"; import { Command } from "commander"; -import inquirer from "inquirer"; import semver from "semver"; import * as tar from "tar"; @@ -44,9 +44,7 @@ async function decideActionVersion( if (!options.update) { console.error("NodeCG is already installed in this directory."); console.error( - "Use " + - chalk.cyan("nodecg setup [version] -u") + - " if you want update your existing install.", + `Use ${chalk.cyan("nodecg setup [version] -u")} if you want update your existing install.`, ); return; } @@ -56,9 +54,7 @@ async function decideActionVersion( if (version) { process.stdout.write( - "Finding latest release that satisfies semver range " + - chalk.magenta(version) + - "... ", + `Finding latest release that satisfies semver range ${chalk.magenta(version)}... `, ); } else if (isUpdate) { process.stdout.write("Checking against local install for updates... "); @@ -69,12 +65,9 @@ async function decideActionVersion( let tags; try { tags = fetchTags(NODECG_GIT_URL); - } catch (e: any) { - /* istanbul ignore next */ + } catch (error) { process.stdout.write(chalk.red("failed!") + os.EOL); - /* istanbul ignore next */ - console.error(e.stack); - /* istanbul ignore next */ + console.error(error instanceof Error ? error.message : error); return; } @@ -87,9 +80,7 @@ async function decideActionVersion( if (!maxSatisfying) { process.stdout.write(chalk.red("failed!") + os.EOL); console.error( - "No releases match the supplied semver range (" + - chalk.magenta(version) + - ")", + `No releases match the supplied semver range (${chalk.magenta(version)})`, ); return; } @@ -109,30 +100,21 @@ async function decideActionVersion( if (semver.eq(target, current)) { console.log( - "The target version (%s) is equal to the current version (%s). No action will be taken.", - chalk.magenta(target), - chalk.magenta(current), + `The target version (${chalk.magenta(target)}) is equal to the current version (${chalk.magenta(current)}). No action will be taken.`, ); return; } if (semver.lt(target, current)) { console.log( - chalk.red("WARNING: ") + - "The target version (%s) is older than the current version (%s)", - chalk.magenta(target), - chalk.magenta(current), + `${chalk.red("WARNING:")} The target version (${chalk.magenta(target)}) is older than the current version (${chalk.magenta(current)})`, ); - const answers = await inquirer.prompt<{ installOlder: boolean }>([ - { - name: "installOlder", - message: "Are you sure you wish to continue?", - type: "confirm", - }, - ]); + const answer = await confirm({ + message: "Are you sure you wish to continue?", + }); - if (!answers.installOlder) { + if (!answer) { console.log("Setup cancelled."); return; } diff --git a/src/commands/uninstall.ts b/src/commands/uninstall.ts index 9756fd2..c073bb5 100644 --- a/src/commands/uninstall.ts +++ b/src/commands/uninstall.ts @@ -2,9 +2,9 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { confirm } from "@inquirer/prompts"; import chalk from "chalk"; import { Command } from "commander"; -import inquirer from "inquirer"; import { getNodeCGPath } from "../lib/util.js"; @@ -32,22 +32,13 @@ function action(bundleName: string, options: { force: boolean }) { if (options.force) { deleteBundle(bundleName, bundlePath); } else { - void inquirer - .prompt<{ confirmUninstall: boolean }>([ - { - name: "confirmUninstall", - message: - "Are you sure you wish to uninstall " + - chalk.magenta(bundleName) + - "?", - type: "confirm", - }, - ]) - .then((answers) => { - if (answers.confirmUninstall) { - deleteBundle(bundleName, bundlePath); - } - }); + void confirm({ + message: `Are you sure you wish to uninstall ${chalk.magenta(bundleName)}?`, + }).then((answer) => { + if (answer) { + deleteBundle(bundleName, bundlePath); + } + }); } } diff --git a/src/lib/fetch-tags.ts b/src/lib/fetch-tags.ts index 638efc0..37272e3 100644 --- a/src/lib/fetch-tags.ts +++ b/src/lib/fetch-tags.ts @@ -1,11 +1,10 @@ -import { execSync } from "child_process"; +import { execFileSync } from "child_process"; export function fetchTags(repoUrl: string) { - const rawTags = execSync(`git ls-remote --refs --tags ${repoUrl}`) - .toString() + return execFileSync("git", ["ls-remote", "--refs", "--tags", repoUrl]) + .toString("utf-8") .trim() - .split("\n"); - return rawTags - .map((rawTag) => rawTag.split("refs/tags/").pop()) + .split("\n") + .map((rawTag) => rawTag.split("refs/tags/").at(-1)) .filter((t) => typeof t === "string"); } diff --git a/src/lib/util.ts b/src/lib/util.ts index a7b4a70..4e61e31 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -11,12 +11,12 @@ import type { NpmRelease } from "./sample/npm-release.js"; */ export function pathContainsNodeCG(pathToCheck: string): boolean { const pjsonPath = path.join(pathToCheck, "package.json"); - if (fs.existsSync(pjsonPath)) { - const pjson = JSON.parse(fs.readFileSync(pjsonPath, "utf8")); + try { + const pjson = JSON.parse(fs.readFileSync(pjsonPath, "utf-8")); return pjson.name.toLowerCase() === "nodecg"; + } catch { + return false; } - - return false; } /** diff --git a/test/commands/setup.spec.ts b/test/commands/setup.spec.ts index 0eb3fa8..ee61dc2 100644 --- a/test/commands/setup.spec.ts +++ b/test/commands/setup.spec.ts @@ -1,7 +1,6 @@ import fs from "node:fs"; import { Command } from "commander"; -import inquirer from "inquirer"; import type { PackageJson } from "type-fest"; import { beforeEach, expect, test, vi } from "vitest"; @@ -9,6 +8,8 @@ import { setupCommand } from "../../src/commands/setup.js"; import { createMockProgram, MockCommand } from "../mocks/program.js"; import { setupTmpDir } from "./tmp-dir.js"; +vi.mock("@inquirer/prompts", () => ({ confirm: () => Promise.resolve(true) })); + let program: MockCommand; let currentDir = setupTmpDir(); const chdir = (keepCurrentDir = false) => { @@ -50,13 +51,8 @@ test("should install v1 NodeCG when specified", async () => { }); test("should ask the user for confirmation when downgrading versions", async () => { - const spy = vi - .spyOn(inquirer, "prompt") - .mockReturnValue(Promise.resolve({ installOlder: true }) as any); await program.runWith("setup 0.8.1 -u --skip-dependencies"); - expect(spy).toBeCalled(); expect(readPackageJson().version).toBe("0.8.1"); - spy.mockRestore(); }); test("should let the user change upgrade versions", async () => { @@ -69,9 +65,7 @@ test("should print an error when the target version is the same as current", asy await program.runWith("setup 0.8.2 -u --skip-dependencies"); expect(spy.mock.calls[0]).toMatchInlineSnapshot(` [ - "The target version (%s) is equal to the current version (%s). No action will be taken.", - "v0.8.2", - "0.8.2", + "The target version (v0.8.2) is equal to the current version (0.8.2). No action will be taken.", ] `); spy.mockRestore(); @@ -79,9 +73,6 @@ test("should print an error when the target version is the same as current", asy test("should correctly handle and refuse when you try to downgrade from v2 to v1", async () => { chdir(); - vi.spyOn(inquirer, "prompt").mockReturnValue( - Promise.resolve({ installOlder: true }) as any, - ); await program.runWith("setup 2.0.0 --skip-dependencies"); expect(readPackageJson().version).toBe("2.0.0"); await program.runWith("setup 1.9.0 -u --skip-dependencies"); diff --git a/test/commands/uninstall.spec.ts b/test/commands/uninstall.spec.ts index adeb45a..5cd6ac9 100644 --- a/test/commands/uninstall.spec.ts +++ b/test/commands/uninstall.spec.ts @@ -3,13 +3,14 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import { Command } from "commander"; -import inquirer from "inquirer"; import { beforeEach, expect, it, vi } from "vitest"; import { uninstallCommand } from "../../src/commands/uninstall.js"; import { createMockProgram, MockCommand } from "../mocks/program.js"; import { setupTmpDir } from "./tmp-dir.js"; +vi.mock("@inquirer/prompts", () => ({ confirm: () => Promise.resolve(true) })); + const dirname = path.dirname(fileURLToPath(import.meta.url)); let program: MockCommand; @@ -29,12 +30,8 @@ beforeEach(() => { }); it("should delete the bundle's folder after prompting for confirmation", async () => { - const spy = vi.spyOn(inquirer, "prompt").mockImplementation(() => { - return Promise.resolve({ confirmUninstall: true }) as any; - }); await program.runWith("uninstall uninstall-test"); expect(fs.existsSync("./bundles/uninstall-test")).toBe(false); - spy.mockRestore(); }); it("should print an error when the target bundle is not installed", async () => {