Skip to content

Commit e68b65d

Browse files
committed
feat(yarn): use package-json instead of yarn for version resolution
1 parent 5bcab91 commit e68b65d

File tree

6 files changed

+117
-197
lines changed

6 files changed

+117
-197
lines changed

lib/tasks/yarn-install.js

+9-21
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,30 @@
11
'use strict';
22
const fs = require('fs-extra');
3-
const semver = require('semver');
43
const shasum = require('shasum');
54
const download = require('download');
65
const decompress = require('decompress');
76
const cliPackage = require('../../package.json');
7+
const packageInfo = require('package-json');
8+
const {prerelease, satisfies} = require('semver');
89

910
const errors = require('../errors');
1011
const yarn = require('../utils/yarn');
1112

1213
const subTasks = {
13-
dist: ctx => yarn(['info', `ghost@${ctx.version}`, '--json']).then((result) => {
14-
let data;
14+
dist: ctx => packageInfo('ghost', {version: ctx.version}).then(({dist, engines = {}}) => {
15+
const skipNodeVersionCheck = (process.env.GHOST_NODE_VERSION_CHECK === 'false');
16+
const isPrerelease = Boolean(prerelease(cliPackage.version));
1517

16-
try {
17-
const parsed = JSON.parse(result.stdout);
18-
data = parsed && parsed.data;
19-
} catch (e) {
20-
// invalid response from yarn command, ignore JSON parse error
21-
}
22-
23-
if (!data || !data.dist) {
24-
return Promise.reject(new errors.CliError('Ghost download information could not be read correctly.'));
25-
}
26-
27-
if (
28-
process.env.GHOST_NODE_VERSION_CHECK !== 'false' &&
29-
data.engines && data.engines.node && !semver.satisfies(process.versions.node, data.engines.node)
30-
) {
18+
if (!skipNodeVersionCheck && engines.node && !satisfies(process.versions.node, engines.node)) {
3119
return Promise.reject(new errors.SystemError(`Ghost v${ctx.version} is not compatible with the current Node version.`));
3220
}
3321

34-
if (data.engines && data.engines.cli && !semver.prerelease(cliPackage.version) && !semver.satisfies(cliPackage.version, data.engines.cli)) {
22+
if (engines.cli && !isPrerelease && !satisfies(cliPackage.version, engines.cli)) {
3523
return Promise.reject(new errors.SystemError(`Ghost v${ctx.version} is not compatible with this version of the CLI.`));
3624
}
3725

38-
ctx.shasum = data.dist.shasum;
39-
ctx.tarball = data.dist.tarball;
26+
ctx.shasum = dist.shasum;
27+
ctx.tarball = dist.tarball;
4028
}),
4129
download: ctx => download(ctx.tarball).then((data) => {
4230
if (shasum(data) !== ctx.shasum) {

lib/utils/resolve-version.js

+48-56
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use strict';
22
const semver = require('semver');
33
const Promise = require('bluebird');
4+
const packageInfo = require('package-json');
45

5-
const yarn = require('./yarn');
66
const errors = require('../errors');
77
const MIN_RELEASE = '>= 1.0.0';
88

@@ -28,72 +28,64 @@ module.exports = function resolveVersion(version, activeVersion, v1 = false, for
2828
}));
2929
}
3030

31-
return yarn(['info', 'ghost', 'versions', '--json']).then((result) => {
32-
let comparator;
31+
let comparator;
3332

34-
if (!force && activeVersion) {
35-
comparator = `>${activeVersion}`;
36-
} else if (force && activeVersion) {
37-
comparator = `>=${activeVersion}`;
38-
} else {
39-
comparator = MIN_RELEASE;
40-
}
33+
if (!force && activeVersion) {
34+
comparator = `>${activeVersion}`;
35+
} else if (force && activeVersion) {
36+
comparator = `>=${activeVersion}`;
37+
} else {
38+
comparator = MIN_RELEASE;
39+
}
4140

42-
if (v1) {
43-
comparator += ' <2.0.0';
44-
}
41+
if (v1) {
42+
comparator += ' <2.0.0';
43+
}
4544

46-
try {
47-
let versions = JSON.parse(result.stdout).data || [];
48-
versions = versions.filter(availableVersion => semver.satisfies(availableVersion, comparator));
45+
return packageInfo('ghost', {allVersions: true}).then((result) => {
46+
const versions = Object.keys(result.versions).filter(v => semver.satisfies(v, comparator));
4947

50-
if (!versions.length) {
51-
return Promise.reject(new errors.CliError({
52-
message: 'No valid versions found.',
53-
log: false
54-
}));
55-
}
48+
if (!versions.length) {
49+
return Promise.reject(new errors.CliError({
50+
message: 'No valid versions found.',
51+
log: false
52+
}));
53+
}
5654

57-
if (version && !versions.includes(version)) {
58-
return Promise.reject(new errors.CliError({
59-
message: `Invalid version specified: ${version}`,
60-
log: false
61-
}));
62-
}
55+
if (version && !versions.includes(version)) {
56+
return Promise.reject(new errors.CliError({
57+
message: `Invalid version specified: ${version}`,
58+
log: false
59+
}));
60+
}
6361

64-
let versionToReturn = version || versions.pop();
62+
let versionToReturn = version || versions.pop();
6563

66-
// CASE: you haven't passed `--v1` and you are not about to install a fresh blog
67-
if (!v1 && activeVersion) {
68-
const majorVersionJump = semver.major(activeVersion) !== semver.major(versionToReturn);
69-
const v1Versions = versions.filter(version => semver.satisfies(version, `^${activeVersion}`));
64+
// CASE: you haven't passed `--v1` and you are not about to install a fresh blog
65+
if (!v1 && activeVersion) {
66+
const majorVersionJump = semver.major(activeVersion) !== semver.major(versionToReturn);
67+
const v1Versions = versions.filter(version => semver.satisfies(version, `^${activeVersion}`));
7068

71-
// CASE 1: you want to force update and you are not on the latest v1 version
72-
// CASE 2: you don't use force and you are not on the latest v1 version
73-
if (majorVersionJump && force && versions.length > 1) {
74-
const latestV2 = versionToReturn;
75-
while (!semver.satisfies(versionToReturn, '^1.0.0')) {
76-
versionToReturn = versions.pop();
77-
}
69+
// CASE 1: you want to force update and you are not on the latest v1 version
70+
// CASE 2: you don't use force and you are not on the latest v1 version
71+
if (majorVersionJump && force && versions.length > 1) {
72+
const latestV2 = versionToReturn;
73+
while (!semver.satisfies(versionToReturn, '^1.0.0')) {
74+
versionToReturn = versions.pop();
75+
}
7876

79-
// super dirty hack @todo: fixme
80-
if (versionToReturn === activeVersion) {
81-
versionToReturn = latestV2;
82-
}
83-
} else if (majorVersionJump && !force && v1Versions.length) {
84-
return Promise.reject(new errors.CliError({
85-
message: 'You are about to migrate to Ghost 2.0. Your blog is not on the latest Ghost 1.0 version.',
86-
help: 'Instead run "ghost update --v1".'
87-
}));
77+
// super dirty hack @todo: fixme
78+
if (versionToReturn === activeVersion) {
79+
versionToReturn = latestV2;
8880
}
81+
} else if (majorVersionJump && !force && v1Versions.length) {
82+
return Promise.reject(new errors.CliError({
83+
message: 'You are about to migrate to Ghost 2.0. Your blog is not on the latest Ghost 1.0 version.',
84+
help: 'Instead run "ghost update --v1".'
85+
}));
8986
}
90-
91-
return versionToReturn;
92-
} catch (e) {
93-
return Promise.reject(new errors.CliError({
94-
message: 'Ghost-CLI was unable to load versions from Yarn.',
95-
log: false
96-
}));
9787
}
88+
89+
return versionToReturn;
9890
});
9991
};

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"moment": "2.23.0",
7171
"mysql": "2.16.0",
7272
"ora": "3.0.0",
73+
"package-json": "5.0.0",
7374
"path-is-root": "0.1.0",
7475
"portfinder": "1.0.20",
7576
"prettyjson": "1.2.1",

test/unit/tasks/yarn-install-spec.js

+30-70
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22
const expect = require('chai').expect;
33
const sinon = require('sinon');
4-
const proxyquire = require('proxyquire');
4+
const proxyquire = require('proxyquire').noPreserveCache();
55
const Promise = require('bluebird');
66
const {setupTestFolder, cleanupTestFolders} = require('../../utils/test-folder');
77
const path = require('path');
@@ -102,48 +102,14 @@ describe('Unit: Tasks > yarn-install', function () {
102102
});
103103

104104
describe('dist subtask', function () {
105-
it('rejects if yarn util returns invalid json', function () {
106-
const yarnStub = sinon.stub().resolves({stdout: 'not json'});
107-
const dist = proxyquire(modulePath, {
108-
'../utils/yarn': yarnStub
109-
}).subTasks.dist;
110-
111-
return dist({version: '1.5.0'}).then(() => {
112-
expect(false, 'error should have been thrown').to.be.true;
113-
}).catch((error) => {
114-
expect(error).to.be.an.instanceof(errors.CliError);
115-
expect(error.message).to.match(/download information could not be read/);
116-
expect(yarnStub.calledOnce).to.be.true;
117-
expect(yarnStub.calledWithExactly(['info', '[email protected]', '--json'])).to.be.true;
118-
});
119-
});
120-
121-
it('rejects if dist data could not be found', function () {
122-
const yarnStub = sinon.stub().resolves({stdout: '{}'});
123-
const dist = proxyquire(modulePath, {
124-
'../utils/yarn': yarnStub
125-
}).subTasks.dist;
126-
127-
return dist({version: '1.5.0'}).then(() => {
128-
expect(false, 'error should have been thrown').to.be.true;
129-
}).catch((error) => {
130-
expect(error).to.be.an.instanceof(errors.CliError);
131-
expect(error.message).to.match(/download information could not be read/);
132-
expect(yarnStub.calledOnce).to.be.true;
133-
expect(yarnStub.calledWithExactly(['info', '[email protected]', '--json'])).to.be.true;
134-
});
135-
});
136-
137105
it('rejects if Ghost version isn\'t compatible with the current Node version and GHOST_NODE_VERISON_CHECK is not set', function () {
138106
const data = {
139-
data: {
140-
engines: {node: '^0.10.0'},
141-
dist: {shasum: 'asdf1234', tarball: 'something.tgz'}
142-
}
107+
engines: {node: '^0.10.0'},
108+
dist: {shasum: 'asdf1234', tarball: 'something.tgz'}
143109
};
144-
const yarnStub = sinon.stub().resolves({stdout: JSON.stringify(data)});
110+
const infoStub = sinon.stub().resolves(data);
145111
const dist = proxyquire(modulePath, {
146-
'../utils/yarn': yarnStub
112+
'package-json': infoStub
147113
}).subTasks.dist;
148114
const ctx = {version: '1.5.0'};
149115

@@ -152,29 +118,27 @@ describe('Unit: Tasks > yarn-install', function () {
152118
}).catch((error) => {
153119
expect(error).to.be.an.instanceof(errors.SystemError);
154120
expect(error.message).to.equal('Ghost v1.5.0 is not compatible with the current Node version.');
155-
expect(yarnStub.calledOnce).to.be.true;
156-
expect(yarnStub.calledWithExactly(['info', 'ghost@1.5.0', '--json'])).to.be.true;
121+
expect(infoStub.calledOnce).to.be.true;
122+
expect(infoStub.calledWithExactly('ghost', {version: '1.5.0'})).to.be.true;
157123
});
158124
});
159125

160126
it('resolves if Ghost version isn\'t compatible with the current Node version and GHOST_NODE_VERISON_CHECK is set', function () {
161127
const data = {
162-
data: {
163-
engines: {node: '^0.10.0'},
164-
dist: {shasum: 'asdf1234', tarball: 'something.tgz'}
165-
}
128+
engines: {node: '^0.10.0'},
129+
dist: {shasum: 'asdf1234', tarball: 'something.tgz'}
166130
};
167-
const yarnStub = sinon.stub().resolves({stdout: JSON.stringify(data)});
131+
const infoStub = sinon.stub().resolves(data);
168132
const dist = proxyquire(modulePath, {
169-
'../utils/yarn': yarnStub
133+
'package-json': infoStub
170134
}).subTasks.dist;
171135
const ctx = {version: '1.5.0'};
172136
process.env.GHOST_NODE_VERSION_CHECK = 'false';
173137

174138
return dist(ctx).then(() => {
175139
delete process.env.GHOST_NODE_VERSION_CHECK;
176-
expect(yarnStub.calledOnce).to.be.true;
177-
expect(yarnStub.calledWithExactly(['info', 'ghost@1.5.0', '--json'])).to.be.true;
140+
expect(infoStub.calledOnce).to.be.true;
141+
expect(infoStub.calledWithExactly('ghost', {version: '1.5.0'})).to.be.true;
178142
expect(ctx).to.deep.equal({
179143
version: '1.5.0',
180144
shasum: 'asdf1234',
@@ -188,14 +152,12 @@ describe('Unit: Tasks > yarn-install', function () {
188152

189153
it('rejects if Ghost version isn\'t compatible with the current CLI version', function () {
190154
const data = {
191-
data: {
192-
engines: {node: process.versions.node, cli: '^0.0.1'},
193-
dist: {shasum: 'asdf1234', tarball: 'something.tgz'}
194-
}
155+
engines: {node: process.versions.node, cli: '^0.0.1'},
156+
dist: {shasum: 'asdf1234', tarball: 'something.tgz'}
195157
};
196-
const yarnStub = sinon.stub().resolves({stdout: JSON.stringify(data)});
158+
const infoStub = sinon.stub().resolves(data);
197159
const dist = proxyquire(modulePath, {
198-
'../utils/yarn': yarnStub,
160+
'package-json': infoStub,
199161
'../../package.json': {version: '1.0.0'}
200162
}).subTasks.dist;
201163
const ctx = {version: '1.5.0'};
@@ -205,28 +167,26 @@ describe('Unit: Tasks > yarn-install', function () {
205167
}).catch((error) => {
206168
expect(error).to.be.an.instanceof(errors.SystemError);
207169
expect(error.message).to.equal('Ghost v1.5.0 is not compatible with this version of the CLI.');
208-
expect(yarnStub.calledOnce).to.be.true;
209-
expect(yarnStub.calledWithExactly(['info', 'ghost@1.5.0', '--json'])).to.be.true;
170+
expect(infoStub.calledOnce).to.be.true;
171+
expect(infoStub.calledWithExactly('ghost', {version: '1.5.0'})).to.be.true;
210172
});
211173
});
212174

213175
it('resolves if Ghost version isn\'t compatible with CLI version, but CLI is a prerelease version', function () {
214176
const data = {
215-
data: {
216-
engines: {node: process.versions.node, cli: '^1.9.0'},
217-
dist: {shasum: 'asdf1234', tarball: 'something.tgz'}
218-
}
177+
engines: {node: process.versions.node, cli: '^1.9.0'},
178+
dist: {shasum: 'asdf1234', tarball: 'something.tgz'}
219179
};
220-
const yarnStub = sinon.stub().resolves({stdout: JSON.stringify(data)});
180+
const infoStub = sinon.stub().resolves(data);
221181
const dist = proxyquire(modulePath, {
222-
'../utils/yarn': yarnStub,
182+
'package-json': infoStub,
223183
'../../package.json': {version: '1.10.0-beta.0'}
224184
}).subTasks.dist;
225185
const ctx = {version: '1.5.0'};
226186

227187
return dist(ctx).then(() => {
228-
expect(yarnStub.calledOnce).to.be.true;
229-
expect(yarnStub.calledWithExactly(['info', 'ghost@1.5.0', '--json'])).to.be.true;
188+
expect(infoStub.calledOnce).to.be.true;
189+
expect(infoStub.calledWithExactly('ghost', {version: '1.5.0'})).to.be.true;
230190
expect(ctx).to.deep.equal({
231191
version: '1.5.0',
232192
shasum: 'asdf1234',
@@ -236,16 +196,16 @@ describe('Unit: Tasks > yarn-install', function () {
236196
});
237197

238198
it('adds shasum and tarball values to context', function () {
239-
const data = {data: {dist: {shasum: 'asdf1234', tarball: 'something.tgz'}}};
240-
const yarnStub = sinon.stub().resolves({stdout: JSON.stringify(data)});
199+
const data = {dist: {shasum: 'asdf1234', tarball: 'something.tgz'}};
200+
const infoStub = sinon.stub().resolves(data);
241201
const dist = proxyquire(modulePath, {
242-
'../utils/yarn': yarnStub
202+
'package-json': infoStub
243203
}).subTasks.dist;
244204
const ctx = {version: '1.5.0'};
245205

246206
return dist(ctx).then(() => {
247-
expect(yarnStub.calledOnce).to.be.true;
248-
expect(yarnStub.calledWithExactly(['info', 'ghost@1.5.0', '--json'])).to.be.true;
207+
expect(infoStub.calledOnce).to.be.true;
208+
expect(infoStub.calledWithExactly('ghost', {version: '1.5.0'})).to.be.true;
249209
expect(ctx).to.deep.equal({
250210
version: '1.5.0',
251211
shasum: 'asdf1234',

0 commit comments

Comments
 (0)