Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(setup): remove support for nodecg less than 2.0.0 #124

Merged
merged 1 commit into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 * 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 @@
}

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 @@
}
}

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 @@
}

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`);
}

Check warning on line 165 in src/commands/setup.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/setup.ts#L164-L165

Added lines #L164 - L165 were not covered by tests
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}`,
);
}

Check warning on line 173 in src/commands/setup.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/setup.ts#L170-L173

Added lines #L170 - L173 were not covered by tests
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}`,
);
}

Check warning on line 190 in src/commands/setup.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/setup.ts#L187-L190

Added lines #L187 - L190 were not covered by tests
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"]);

Check warning on line 197 in src/commands/setup.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/setup.ts#L197

Added line #L197 was not covered by tests

process.stdout.write(chalk.green("done!") + os.EOL);
} catch (e: any) {
Expand All @@ -253,37 +216,3 @@
}
}
}

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();
});
Loading