Skip to content

Commit

Permalink
refactor(setup): remove support for nodecg less than 2.0.0 (#124)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Drop support for nodecg 0.x.x and 1.x.x.
  • Loading branch information
Hoishin authored Dec 27, 2024
1 parent b247fbd commit 4536527
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 154 deletions.
139 changes: 34 additions & 105 deletions src/commands/setup.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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";

Expand Down Expand Up @@ -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) {
Expand All @@ -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,
Expand All @@ -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) {
Expand All @@ -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)}... `,
);
}
21 changes: 0 additions & 21 deletions src/lib/util.ts
Original file line number Diff line number Diff line change
@@ -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";

/**
Expand Down Expand Up @@ -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<NpmRelease> {
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<NpmRelease>;
}

export async function getLatestCLIRelease(): Promise<NpmRelease> {
const res = await fetch("http://registry.npmjs.org/nodecg-cli/latest");
if (res.status !== 200) {
Expand Down
53 changes: 25 additions & 28 deletions test/commands/setup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

0 comments on commit 4536527

Please sign in to comment.