diff --git a/src/commands/setup.ts b/src/commands/setup.ts index 83c5b6e..6b2ba77 100644 --- a/src/commands/setup.ts +++ b/src/commands/setup.ts @@ -1,4 +1,4 @@ -import { execSync } from "node:child_process"; +import { execFileSync, execSync } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import stream from "node:stream/promises"; @@ -10,11 +10,8 @@ import semver from "semver"; import * as tar from "tar"; import { fetchTags } from "../lib/fetch-tags.js"; -import { - getCurrentNodeCGVersion, - getNodeCGRelease, - pathContainsNodeCG, -} from "../lib/util.js"; +import type { NpmRelease } from "../lib/sample/npm-release.js"; +import { getCurrentNodeCGVersion, pathContainsNodeCG } from "../lib/util.js"; const NODECG_GIT_URL = "https://github.com/nodecg/nodecg.git"; @@ -124,24 +121,14 @@ async function decideActionVersion( } if (semver.lt(target, "v2.0.0")) { - if (current && semver.gte(current, "v2.0.0")) { - console.error( - `You are attempting to downgrade NodeCG from v2.x to v1.x, which is not supported.`, - ); - return; - } - - actionV1(current, target, isUpdate); - } else if (semver.lt(target, "v3.0.0")) { - await actionV2(current, target, isUpdate); - } else { console.error( - `Unknown NodeCG verison ${chalk.magenta( - version, - )}, perhaps you need to update nodecg-cli? (${chalk.cyan.bold("npm i -g nodecg-cli@latest")})`, + "nodecg-cli does not support NodeCG versions older than v2.0.0.", ); + return; } + await installNodecg(current, target, isUpdate); + // Install NodeCG's dependencies // This operation takes a very long time, so we don't test it. if (!options.skipDependencies) { @@ -156,52 +143,7 @@ async function decideActionVersion( } } -function actionV1( - current: string | undefined, - target: string, - isUpdate: boolean, -) { - const isGitRepo = fs.existsSync(".git"); - if (isGitRepo && isUpdate) { - process.stdout.write("Downloading latest release..."); - try { - execSync("git fetch", { stdio: ["pipe", "pipe", "pipe"] }); - process.stdout.write(chalk.green("done!") + os.EOL); - } catch (e) { - process.stdout.write(chalk.red("failed!") + os.EOL); - throw e; - } - - if (current) { - logDownOrUpgradeMessage(current, target, semver.lt(target, current)); - } - - gitCheckoutUpdate(target); - } else { - process.stdout.write("Cloning NodeCG... "); - try { - execSync(`git clone ${NODECG_GIT_URL} .`, { - stdio: ["pipe", "pipe", "pipe"], - }); - process.stdout.write(chalk.green("done!") + os.EOL); - } catch (e) { - process.stdout.write(chalk.red("failed!") + os.EOL); - throw e; - } - - // Check out the target version. - process.stdout.write(`Checking out version ${target}... `); - try { - execSync(`git checkout ${target}`, { stdio: ["pipe", "pipe", "pipe"] }); - process.stdout.write(chalk.green("done!") + os.EOL); - } catch (e) { - process.stdout.write(chalk.red("failed!") + os.EOL); - throw e; - } - } -} - -async function actionV2( +async function installNodecg( current: string | undefined, target: string, isUpdate: boolean, @@ -216,22 +158,43 @@ async function actionV2( } process.stdout.write(`Downloading ${target} from npm... `); - const release = await getNodeCGRelease(target); + + const targetVersion = semver.coerce(target)?.version; + if (!targetVersion) { + throw new Error(`Failed to determine target NodeCG version`); + } + const releaseResponse = await fetch( + `http://registry.npmjs.org/nodecg/${targetVersion}`, + ); + if (!releaseResponse.ok) { + throw new Error( + `Failed to fetch NodeCG release information from npm, status code ${releaseResponse.status}`, + ); + } + const release = (await releaseResponse.json()) as NpmRelease; process.stdout.write(chalk.green("done!") + os.EOL); if (current) { - logDownOrUpgradeMessage(current, target, semver.lt(target, current)); + const verb = semver.lt(target, current) ? "Downgrading" : "Upgrading"; + process.stdout.write( + `${verb} from ${chalk.magenta(current)} to ${chalk.magenta(target)}... `, + ); } - await downloadAndExtractReleaseTarball(release.dist.tarball); + const tarballResponse = await fetch(release.dist.tarball); + if (!tarballResponse.ok || !tarballResponse.body) { + throw new Error( + `Failed to fetch release tarball from ${release.dist.tarball}, status code ${tarballResponse.status}`, + ); + } + await stream.pipeline(tarballResponse.body, tar.x({ strip: 1 })); } -/* istanbul ignore next: takes forever, not worth testing */ function installDependencies() { try { process.stdout.write("Installing production npm dependencies... "); - execSync("npm i --production", { stdio: ["pipe", "pipe", "pipe"] }); + execFileSync("npm", ["install", "--production"]); process.stdout.write(chalk.green("done!") + os.EOL); } catch (e: any) { @@ -253,37 +216,3 @@ function installDependencies() { } } } - -function gitCheckoutUpdate(target: string) { - try { - execSync(`git checkout ${target}`, { stdio: ["pipe", "pipe", "pipe"] }); - process.stdout.write(chalk.green("done!") + os.EOL); - } catch (e: any) { - /* istanbul ignore next */ - process.stdout.write(chalk.red("failed!") + os.EOL); - /* istanbul ignore next */ - console.error(e.stack); - } -} - -async function downloadAndExtractReleaseTarball(tarballUrl: string) { - const res = await fetch(tarballUrl); - if (!res.body) { - throw new Error( - `Failed to fetch release tarball from ${tarballUrl}, status code ${res.status}`, - ); - } - - await stream.pipeline(res.body, tar.x({ strip: 1 })); -} - -function logDownOrUpgradeMessage( - current: string, - target: string, - downgrade: boolean, -): void { - const verb = downgrade ? "Downgrading" : "Upgrading"; - process.stdout.write( - `${verb} from ${chalk.magenta(current)} to ${chalk.magenta(target)}... `, - ); -} diff --git a/src/lib/util.ts b/src/lib/util.ts index 4e61e31..df307fd 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -1,8 +1,6 @@ import fs from "node:fs"; import path from "node:path"; -import semver from "semver"; - import type { NpmRelease } from "./sample/npm-release.js"; /** @@ -68,25 +66,6 @@ export function getCurrentNodeCGVersion(): string { .version; } -/** - * Gets the latest NodeCG release information from npm, including tarball download link. - */ -export async function getNodeCGRelease(target: string): Promise { - const targetVersion = semver.coerce(target)?.version; - if (!targetVersion) { - throw new Error(`Failed to determine target NodeCG version`); - } - - const res = await fetch(`http://registry.npmjs.org/nodecg/${targetVersion}`); - if (res.status !== 200) { - throw new Error( - `Failed to fetch NodeCG release information from npm, status code ${res.status}`, - ); - } - - return res.json() as Promise; -} - export async function getLatestCLIRelease(): Promise { const res = await fetch("http://registry.npmjs.org/nodecg-cli/latest"); if (res.status !== 200) { diff --git a/test/commands/setup.spec.ts b/test/commands/setup.spec.ts index 32188b7..ebb84ad 100644 --- a/test/commands/setup.spec.ts +++ b/test/commands/setup.spec.ts @@ -38,61 +38,58 @@ test("should install the latest NodeCG when no version is specified", async () = test("should install v2 NodeCG when specified", async () => { chdir(); - await program.runWith("setup v2.0.0 --skip-dependencies"); + await program.runWith("setup 2.0.0 --skip-dependencies"); expect(readPackageJson().name).toBe("nodecg"); expect(readPackageJson().version).toBe("2.0.0"); -}); -test("should install v1 NodeCG when specified", async () => { - chdir(); - await program.runWith("setup 1.9.0 -u --skip-dependencies"); - expect(readPackageJson().name).toBe("nodecg"); - expect(readPackageJson().version).toBe("1.9.0"); -}); + await program.runWith("setup 2.1.0 -u --skip-dependencies"); + expect(readPackageJson().version).toBe("2.1.0"); -test("should ask the user for confirmation when downgrading versions", async () => { - await program.runWith("setup 0.8.1 -u --skip-dependencies"); - expect(readPackageJson().version).toBe("0.8.1"); + await program.runWith("setup 2.0.0 -u --skip-dependencies"); + expect(readPackageJson().version).toBe("2.0.0"); }); -test("should let the user change upgrade versions", async () => { - await program.runWith("setup 0.8.2 -u --skip-dependencies"); - expect(readPackageJson().version).toBe("0.8.2"); +test("should throw when trying to install v1 NodeCG", async () => { + chdir(); + const consoleError = vi.spyOn(console, "error"); + await program.runWith("setup 1.9.0 -u --skip-dependencies"); + expect(consoleError.mock.calls[0]).toMatchInlineSnapshot(` + [ + "nodecg-cli does not support NodeCG versions older than v2.0.0.", + ] + `); }); test("should print an error when the target version is the same as current", async () => { + chdir(); const spy = vi.spyOn(console, "log"); - await program.runWith("setup 0.8.2 -u --skip-dependencies"); - expect(spy.mock.calls[0]).toMatchInlineSnapshot(` + await program.runWith("setup 2.1.0 --skip-dependencies"); + await program.runWith("setup 2.1.0 -u --skip-dependencies"); + expect(spy.mock.calls[1]).toMatchInlineSnapshot(` [ - "The target version (v0.8.2) is equal to the current version (0.8.2). No action will be taken.", + "The target version (v2.1.0) is equal to the current version (2.1.0). No action will be taken.", ] `); spy.mockRestore(); }); -test("should correctly handle and refuse when you try to downgrade from v2 to v1", async () => { - chdir(); - 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"); - expect(readPackageJson().version).toBe("2.0.0"); -}); - test("should print an error when the target version doesn't exist", async () => { + chdir(); const spy = vi.spyOn(console, "error"); - await program.runWith("setup 0.0.99 -u --skip-dependencies"); + await program.runWith("setup 999.999.999 --skip-dependencies"); expect(spy.mock.calls[0]).toMatchInlineSnapshot(` [ - "No releases match the supplied semver range (0.0.99)", + "No releases match the supplied semver range (999.999.999)", ] `); spy.mockRestore(); }); test("should print an error and exit, when nodecg is already installed in the current directory ", async () => { + chdir(); const spy = vi.spyOn(console, "error"); - await program.runWith("setup 0.7.0 --skip-dependencies"); + await program.runWith("setup 2.0.0 --skip-dependencies"); + await program.runWith("setup 2.0.0 --skip-dependencies"); expect(spy).toBeCalledWith("NodeCG is already installed in this directory."); spy.mockRestore(); });