Skip to content

Commit

Permalink
feat(channel): add ability to specify release channel for local installs
Browse files Browse the repository at this point in the history
  • Loading branch information
acburdine committed Feb 11, 2021
1 parent b82c76c commit 29848c0
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 32 deletions.
42 changes: 28 additions & 14 deletions lib/commands/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class InstallCommand extends Command {
return yargs;
}

run(argv) {
async run(argv) {
const errors = require('../errors');
const yarnInstall = require('../tasks/yarn-install');
const dirIsEmpty = require('../utils/dir-is-empty');
Expand All @@ -24,7 +24,7 @@ class InstallCommand extends Command {

// Check if the directory is empty
if (!dirIsEmpty(process.cwd()) && argv['check-empty']) {
return Promise.reject(new errors.SystemError('Current directory is not empty, Ghost cannot be installed here.'));
throw new errors.SystemError('Current directory is not empty, Ghost cannot be installed here.');
}

let local = false;
Expand All @@ -37,11 +37,19 @@ class InstallCommand extends Command {
this.system.setEnvironment(true, true);
}

return this.runCommand(DoctorCommand, Object.assign({
if (argv.channel && argv.channel !== 'stable' && !local) {
throw new errors.CliError(`Specifying a channel of '${argv.channel}' is only supported for local installs at this time.`);
}

await this.runCommand(DoctorCommand, {
categories: ['install'],
skipInstanceCheck: true,
quiet: true
}, argv, {local})).then(() => this.ui.listr([{
quiet: true,
...argv,
local
});

await this.ui.listr([{
title: 'Checking for latest Ghost version',
task: this.version
}, {
Expand All @@ -65,22 +73,21 @@ class InstallCommand extends Command {
}], {
argv: {...argv, version},
cliVersion: this.system.cliVersion
})).then(() => {
if (!argv.setup) {
return Promise.resolve();
}
});

argv.local = local;
if (!argv.setup) {
return;
}

return this.runCommand(SetupCommand, argv);
});
argv.local = local;
return this.runCommand(SetupCommand, argv);
}

async version(ctx) {
const semver = require('semver');
const {SystemError} = require('../errors');
const {resolveVersion, versionFromZip} = require('../utils/version');
let {version, zip, v1, fromExport, force} = ctx.argv;
let {version, zip, v1, fromExport, force, channel} = ctx.argv;
let exportVersion = null;

if (fromExport) {
Expand All @@ -105,7 +112,7 @@ class InstallCommand extends Command {
if (zip) {
resolvedVersion = await versionFromZip(zip);
} else {
resolvedVersion = await resolveVersion(version, null, {v1, force});
resolvedVersion = await resolveVersion(version, null, {v1, force, channel});
}

if (exportVersion && semver.lt(resolvedVersion, exportVersion)) {
Expand All @@ -131,6 +138,7 @@ class InstallCommand extends Command {

instance.version = ctx.version;
instance.cliVersion = this.system.cliVersion;
instance.channel = ctx.argv.channel || 'stable';
}
}

Expand Down Expand Up @@ -178,6 +186,12 @@ InstallCommand.options = {
force: {
description: 'Force installing a particular version',
type: 'boolean'
},
channel: {
description: 'Specifies a release channel to use when selecting Ghost versions',
type: 'string',
choices: ['stable', 'next'],
default: 'stable'
}
};
InstallCommand.runPreChecks = true;
Expand Down
10 changes: 7 additions & 3 deletions lib/commands/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class UpdateCommand extends Command {

if (argv.rollback) {
if (!instance.previousVersion) {
return Promise.reject(new Error('No previous version found'));
throw new Error('No previous version found');
}

context.rollback = true;
Expand Down Expand Up @@ -165,7 +165,7 @@ class UpdateCommand extends Command {

async version(context) {
const {resolveVersion, versionFromZip} = require('../utils/version');
const {rollback, zip, v1, version, force, activeVersion} = context;
const {rollback, zip, v1, version, force, activeVersion, instance} = context;

if (rollback) {
return Promise.resolve(true);
Expand All @@ -175,7 +175,11 @@ class UpdateCommand extends Command {
if (zip) {
resolvedVersion = await versionFromZip(zip, activeVersion, {force});
} else {
resolvedVersion = await resolveVersion(version, activeVersion, {v1, force});
resolvedVersion = await resolveVersion(version, activeVersion, {
v1,
force,
channel: instance.channel
});
}

if (resolvedVersion === null) {
Expand Down
14 changes: 14 additions & 0 deletions lib/instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@ class Instance {
this._cliConfig.set('node-version', value).save();
}

/**
* Release Channel
*
* @property channel
* @type string
* @public
*/
get channel() {
return this._cliConfig.get('channel', 'stable');
}
set channel(value) {
this._cliConfig.set('channel', value).save();
}

/**
* Returns whether or not the instance has been setup
*
Expand Down
6 changes: 3 additions & 3 deletions lib/utils/version.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ const getProxyAgent = require('./get-proxy-agent');
const MIN_RELEASE = '>= 1.0.0';

const utils = {
async loadVersions() {
async loadVersions(includePrerelease = false) {
const result = await packageJson('ghost', {
allVersions: true,
agent: getProxyAgent()
});
const versions = Object.keys(result.versions)
.filter(v => semver.satisfies(v, MIN_RELEASE))
.filter(v => semver.satisfies(v, MIN_RELEASE, {includePrerelease}))
.sort(semver.rcompare);

const deprecations = Object.keys(result.versions)
Expand Down Expand Up @@ -127,7 +127,7 @@ const utils = {
},

async resolveVersion(customVersion = null, activeVersion = null, opts = {}) {
const versions = await utils.loadVersions();
const versions = await utils.loadVersions(opts.channel === 'next');

if (!versions.all.length) {
return null;
Expand Down
53 changes: 46 additions & 7 deletions test/unit/commands/install-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,27 @@ describe('Unit: Commands > Install', function () {
});

const testInstance = new InstallCommand({}, {});
const context = {argv: {version: '1.0.0', v1: false, force: false}};
const context = {argv: {version: '1.0.0', v1: false, force: false, channel: 'stable'}};

await testInstance.version(context);
expect(resolveVersion.calledOnce).to.be.true;
expect(resolveVersion.calledWithExactly('1.0.0', null, {v1: false, force: false})).to.be.true;
expect(resolveVersion.calledWithExactly('1.0.0', null, {v1: false, force: false, channel: 'stable'})).to.be.true;
expect(context.version).to.equal('1.5.0');
expect(context.installPath).to.equal(path.join(process.cwd(), 'versions/1.5.0'));
});

it('calls resolveVersion, sets version and install path (prereleases)', async function () {
const resolveVersion = sinon.stub().resolves('1.5.0');
const InstallCommand = proxyquire(modulePath, {
'../utils/version': {resolveVersion}
});

const testInstance = new InstallCommand({}, {});
const context = {argv: {version: '1.0.0', v1: false, force: false, channel: 'next'}};

await testInstance.version(context);
expect(resolveVersion.calledOnce).to.be.true;
expect(resolveVersion.calledWithExactly('1.0.0', null, {v1: false, force: false, channel: 'next'})).to.be.true;
expect(context.version).to.equal('1.5.0');
expect(context.installPath).to.equal(path.join(process.cwd(), 'versions/1.5.0'));
});
Expand Down Expand Up @@ -337,7 +353,7 @@ describe('Unit: Commands > Install', function () {
const context = {argv: {version: '2.0.0', fromExport: 'test-export.json'}, ui: {log}};

await testInstance.version(context);
expect(resolveVersion.calledOnceWithExactly('v1', null, {v1: undefined, force: undefined})).to.be.true;
expect(resolveVersion.calledOnceWithExactly('v1', null, {v1: undefined, force: undefined, channel: undefined})).to.be.true;
expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
expect(context.version).to.equal('1.5.0');
expect(context.installPath).to.equal(path.join(process.cwd(), 'versions/1.5.0'));
Expand All @@ -357,7 +373,7 @@ describe('Unit: Commands > Install', function () {
const context = {argv: {fromExport: 'test-export.json'}, ui: {log}};

await testInstance.version(context);
expect(resolveVersion.calledOnceWithExactly('2.0.0', null, {v1: undefined, force: undefined})).to.be.true;
expect(resolveVersion.calledOnceWithExactly('2.0.0', null, {v1: undefined, force: undefined, channel: undefined})).to.be.true;
expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
expect(context.version).to.equal('2.0.0');
expect(context.installPath).to.equal(path.join(process.cwd(), 'versions/2.0.0'));
Expand All @@ -381,7 +397,7 @@ describe('Unit: Commands > Install', function () {
} catch (error) {
expect(error).to.be.an.instanceof(errors.SystemError);
expect(error.message).to.include('v3.0.0 into v2.0.0');
expect(resolveVersion.calledOnceWithExactly('v2', null, {v1: undefined, force: undefined})).to.be.true;
expect(resolveVersion.calledOnceWithExactly('v2', null, {v1: undefined, force: undefined, channel: undefined})).to.be.true;
expect(parseExport.calledOnceWithExactly('test-export.json')).to.be.true;
expect(log.called).to.be.false;
return;
Expand Down Expand Up @@ -421,13 +437,36 @@ describe('Unit: Commands > Install', function () {

const testInstance = new InstallCommand({}, {getInstance: getInstanceStub, cliVersion: '1.0.0'});

testInstance.link({version: '1.5.0', installPath: '/some/dir/1.5.0'});
testInstance.link({version: '1.5.0', installPath: '/some/dir/1.5.0', argv: {}});
expect(symlinkSyncStub.calledOnce).to.be.true;
expect(symlinkSyncStub.calledWithExactly('/some/dir/1.5.0', path.join(process.cwd(), 'current')));
expect(getInstanceStub.calledOnce).to.be.true;
expect(config).to.deep.equal({
version: '1.5.0',
cliVersion: '1.0.0',
channel: 'stable'
});
});

it('creates current link and updates versions (using channel = next)', function () {
const symlinkSyncStub = sinon.stub();
const config = {};
const getInstanceStub = sinon.stub().returns(config);

const InstallCommand = proxyquire(modulePath, {
'symlink-or-copy': {sync: symlinkSyncStub}
});

const testInstance = new InstallCommand({}, {getInstance: getInstanceStub, cliVersion: '1.0.0'});

testInstance.link({version: '1.5.0', installPath: '/some/dir/1.5.0', argv: {channel: 'next'}});
expect(symlinkSyncStub.calledOnce).to.be.true;
expect(symlinkSyncStub.calledWithExactly('/some/dir/1.5.0', path.join(process.cwd(), 'current')));
expect(getInstanceStub.calledOnce).to.be.true;
expect(config).to.deep.equal({
version: '1.5.0',
cliVersion: '1.0.0'
cliVersion: '1.0.0',
channel: 'next'
});
});
});
Expand Down
10 changes: 6 additions & 4 deletions test/unit/commands/update-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -876,14 +876,15 @@ describe('Unit: Commands > Update', function () {
force: false,
version: null,
activeVersion: '1.0.0',
v1: true
v1: true,
instance: {channel: 'stable'}
};
sinon.stub(process, 'cwd').returns('/var/www/ghost');

const result = await instance.version(context);
expect(result).to.be.true;
expect(resolveVersion.calledOnce).to.be.true;
expect(resolveVersion.calledWithExactly(null, '1.0.0', {v1: true, force: false})).to.be.true;
expect(resolveVersion.calledWithExactly(null, '1.0.0', {v1: true, force: false, channel: 'stable'})).to.be.true;
expect(context.version).to.equal('1.0.1');
expect(context.installPath).to.equal('/var/www/ghost/versions/1.0.1');
});
Expand Down Expand Up @@ -925,13 +926,14 @@ describe('Unit: Commands > Update', function () {
force: true,
version: null,
activeVersion: '1.0.0',
v1: false
v1: false,
instance: {channel: 'stable'}
};

const result = await instance.version(context);
expect(result).to.be.false;
expect(resolveVersion.calledOnce).to.be.true;
expect(resolveVersion.calledWithExactly(null, '1.0.0', {v1: false, force: true})).to.be.true;
expect(resolveVersion.calledWithExactly(null, '1.0.0', {v1: false, force: true, channel: 'stable'})).to.be.true;
});
});

Expand Down
26 changes: 25 additions & 1 deletion test/unit/utils/version-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('Unit: Utils: version', function () {

it('returns correct all versions/latest versions, sorted desc', async function () {
const loadVersions = stub(
['0.11.0', '1.0.0', '1.0.1', '1.0.2', '1.0.3', '1.0.4', '2.0.0', '2.1.0', '2.22.0', '3.0.0', '3.1.0'],
['0.11.0', '1.0.0', '1.0.1', '1.0.2', '1.0.3', '1.0.4', '2.0.0', '2.1.0', '2.22.0', '3.0.0', '3.1.0', '4.0.0-rc.1'],
['1.0.4', '2.22.0', '3.1.0']
);
const result = await loadVersions();
Expand All @@ -66,6 +66,30 @@ describe('Unit: Utils: version', function () {
}
});
});

it('includes prereleases if requested', async function () {
const loadVersions = stub(
['0.11.0', '1.0.0', '1.0.1', '1.0.2', '1.0.3', '1.0.4', '2.0.0', '2.1.0', '2.22.0', '3.0.0', '3.1.0', '4.0.0-rc.1'],
['1.0.4', '2.22.0', '3.1.0']
);
const result = await loadVersions(true);

expect(result).to.deep.equal({
latest: '4.0.0-rc.1',
latestMajor: {
v1: '1.0.3',
v2: '2.1.0',
v3: '3.0.0',
v4: '4.0.0-rc.1'
},
all: ['4.0.0-rc.1', '3.1.0', '3.0.0', '2.22.0', '2.1.0', '2.0.0', '1.0.4', '1.0.3', '1.0.2', '1.0.1', '1.0.0'],
deprecations: {
'1.0.4': 'test deprecation notice',
'2.22.0': 'test deprecation notice',
'3.1.0': 'test deprecation notice'
}
});
});
});

describe('checkCustomVersion', function () {
Expand Down

0 comments on commit 29848c0

Please sign in to comment.