Skip to content

Commit

Permalink
fix(v1): disallow downgrade from v2 to v1 (#92)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hoishin authored Mar 23, 2023
1 parent 7c06f45 commit aedea05
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 58 deletions.
83 changes: 28 additions & 55 deletions src/commands/setup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import util, { NPMRelease } from '../lib/util';
import util from '../lib/util';
import { execSync } from 'child_process';
import os from 'os';
import chalk from 'chalk';
Expand Down Expand Up @@ -84,7 +84,7 @@ async function decideActionVersion(version: string, options: { update: boolean;
let downgrade = false;

if (isUpdate) {
const current = util.getCurrentNodeCGVersion();
current = util.getCurrentNodeCGVersion();

if (semver.eq(target, current)) {
console.log(
Expand Down Expand Up @@ -120,7 +120,12 @@ async function decideActionVersion(version: string, options: { update: boolean;
}

if (semver.lt(target, 'v2.0.0')) {
actionV1(current, target, isUpdate);
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;
}

await actionV1(current, target, isUpdate);
} else if (semver.lt(target, 'v3.0.0')) {
await actionV2(current, target, isUpdate);
} else {
Expand All @@ -145,19 +150,16 @@ async function decideActionVersion(version: string, options: { update: boolean;
}
}

function actionV1(current: string | undefined, target: string, isUpdate: boolean) {
if (isUpdate) {
async 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) {
/* istanbul ignore next */
process.stdout.write(chalk.red('failed!') + os.EOL);
/* istanbul ignore next */
console.error(e.stack);
/* istanbul ignore next */
return;
throw e;
}

if (current) {
Expand All @@ -171,12 +173,8 @@ function actionV1(current: string | undefined, target: string, isUpdate: boolean
execSync(`git clone ${NODECG_GIT_URL} .`, { stdio: ['pipe', 'pipe', 'pipe'] });
process.stdout.write(chalk.green('done!') + os.EOL);
} catch (e) {
/* istanbul ignore next */
process.stdout.write(chalk.red('failed!') + os.EOL);
/* istanbul ignore next */
console.error(e.stack);
/* istanbul ignore next */
return;
throw e;
}

// Check out the target version.
Expand All @@ -185,40 +183,22 @@ function actionV1(current: string | undefined, target: string, isUpdate: boolean
execSync(`git checkout ${target}`, { stdio: ['pipe', 'pipe', 'pipe'] });
process.stdout.write(chalk.green('done!') + os.EOL);
} catch (e) {
/* istanbul ignore next */
process.stdout.write(chalk.red('failed!') + os.EOL);
/* istanbul ignore next */
console.error(e.stack);
throw e;
}
}
}

async function actionV2(current: string | undefined, target: string, _isUpdate: boolean) {
let release: NPMRelease;
async function actionV2(current: string | undefined, target: string, isUpdate: boolean) {
if (isUpdate) {
const deletingDirectories = ['.git', 'build', 'scripts', 'schemas'];
await Promise.all(deletingDirectories.map((dir) => fs.promises.rm(dir, { recursive: true, force: true })));
}

process.stdout.write('Downloading latest release...');
try {
release = await util.getLatestNodeCGRelease();
// target is v1.2.3, release.version is 1.2.3
if (!semver.satisfies(release.version, target)) {
process.stdout.write(chalk.red('failed!') + os.EOL);
console.error(
`Expected latest npm release to be ${chalk.magenta(target)} but instead it was ${chalk.magenta(
release.version,
)}. Aborting.`,
);
return;
}
process.stdout.write(`Downloading ${target} from npm...`);
const release = await util.getNodeCGRelease(target);

process.stdout.write(chalk.green('done!') + os.EOL);
} catch (e) {
/* istanbul ignore next */
process.stdout.write(chalk.red('failed!') + os.EOL);
/* istanbul ignore next */
console.error(e.stack);
/* istanbul ignore next */
return;
}
process.stdout.write(chalk.green('done!') + os.EOL);

if (current) {
logDownOrUpgradeMessage(current, target, semver.lt(target, current));
Expand Down Expand Up @@ -265,20 +245,13 @@ function gitCheckoutUpdate(target: string) {
}

async function downloadAndExtractReleaseTarball(tarballUrl: string) {
try {
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 }));
process.stdout.write(chalk.green('done!') + os.EOL);
} catch (e) {
/* istanbul ignore next */
process.stdout.write(chalk.red('failed!') + os.EOL);
/* istanbul ignore next */
console.error(e.stack);
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 }));
process.stdout.write(chalk.green('done!') + os.EOL);
}

function logDownOrUpgradeMessage(current: string, target: string, downgrade: boolean): void {
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const program = new Command('nodecg');
const packageVersion: string = require('../package.json').version;

// Check for updates, asynchronously, so as to not make the CLI startup time excessively slow
util.getLatestNodeCGRelease()
util.getLatestCLIRelease()
.then((release) => {
if (semver.gt(release.version, packageVersion)) {
console.log(
Expand Down
19 changes: 17 additions & 2 deletions src/lib/util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from 'fs';
import path from 'path';
import fetch from 'node-fetch';
import semver from 'semver';

export default {
/**
Expand Down Expand Up @@ -64,14 +65,28 @@ export default {
/**
* Gets the latest NodeCG release information from npm, including tarball download link.
*/
async getLatestNodeCGRelease(): Promise<NPMRelease> {
const res = await fetch('http://registry.npmjs.org/nodecg/latest');
async 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>;
},

async getLatestCLIRelease(): Promise<NPMRelease> {
const res = await fetch('http://registry.npmjs.org/nodecg-cli/latest');
if (res.status !== 200) {
throw new Error(`Failed to fetch NodeCG release information from npm, status code ${res.status}`);
}

return res.json();
},
};

export interface NPMRelease {
Expand Down
9 changes: 9 additions & 0 deletions test/commands/setup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ test('should print an error when the target version is the same as current', asy
spy.mockRestore();
});

test('should correctly handle and refuse when you try to downgrade from v2 to v1', async () => {
chdir();
jest.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');
expect(readPackageJson().version).toBe('2.0.0');
});

test("should print an error when the target version doesn't exist", async () => {
const spy = jest.spyOn(console, 'error');
await program.runWith('setup 0.0.99 -u --skip-dependencies');
Expand Down

0 comments on commit aedea05

Please sign in to comment.