diff --git a/bin/ember-source-channel-url b/bin/ember-source-channel-url index 17957ba..7be7bdf 100755 --- a/bin/ember-source-channel-url +++ b/bin/ember-source-channel-url @@ -3,26 +3,76 @@ /* eslint-disable no-console */ +const fs = require('fs'); const getChannelURL = require('../src'); const channel = process.argv[2]; +const shouldUpdatePackage = process.argv.includes('-w') || process.argv.includes('--write'); -if (['release', 'beta', 'canary'].indexOf(channel) === -1) { - console.log( - `ember-source-channel-url is a utility module to easily obtain the URL +function printUsage() { + console.log(` +ember-source-channel-url is a utility module to easily obtain the URL to a tarball representing the latest \`ember-source\` build for a given channel. -Usage: +USAGE: + ember-source-channel-url [CHANNEL] [FLAGS] + +FLAGS: + + -w, --write Update the local package.json to use the retrieved ember-source URL + -h, --help Prints help information + +EXAMPLE: + + * Print the most recent URL for the specified channel: + + $ ember-source-channel-url canary + $ ember-source-channel-url beta + $ ember-source-channel-url release + + * Update the local project's \`package.json\` to use the most recent URL for the canary channel: + + $ ember-source-channel-url canary --write +`); +} - ember-source-channel-url canary - ember-source-channel-url beta - ember-source-channel-url release -` - ); +if (['release', 'beta', 'canary'].indexOf(channel) === -1) { + printUsage(); + process.exitCode = 1; } else { - getChannelURL(channel).then(url => + getChannelURL(channel).then(url => { console.log( - `The URL for the latest tarball from ember-source's ${channel} channel is:\n\n\t${url}` - ) - ); + `The URL for the latest tarball from ember-source's ${channel} channel is:\n\n\t${url}\n` + ); + + if (shouldUpdatePackage) { + if (!fs.existsSync('package.json')) { + console.log( + `You passed --write to ember-source-channel-url but no package.json is available to update.` + ); + + process.exitCode = 2; + } + + let contents = fs.readFileSync('package.json', { encoding: 'utf8' }); + let pkg = JSON.parse(contents); + + let dependencyType = ['dependencies', 'devDependencies'].find( + type => pkg[type] && pkg[type]['ember-source'] + ); + + if (dependencyType) { + pkg[dependencyType]['ember-source'] = url; + + let updatedContents = JSON.stringify(pkg, null, 2); + fs.writeFileSync('package.json', updatedContents, { encoding: 'utf8' }); + } else { + console.log( + `You passed --write to ember-source-channel-url but ember-source is not included in dependencies or devDependencies in the package.json.` + ); + + process.exitCode = 3; + } + } + }); } diff --git a/package.json b/package.json index 6dd7e6f..682a08c 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "prettier": "^1.17.1", "qunit": "~2.9.2", "qunit-eslint": "^2.0.0", - "rsvp": "^4.7.0" + "rsvp": "^4.7.0", + "tmp": "^0.1.0" }, "engines": { "node": "6.* || 8.* || >= 10.*" diff --git a/tests/index-test.js b/tests/index-test.js index 59aa2ef..e509f9f 100644 --- a/tests/index-test.js +++ b/tests/index-test.js @@ -1,10 +1,17 @@ 'use strict'; +const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const execa = require('execa'); const createServer = require('./helpers/server').createServer; const getChannelURL = require('../src'); +const tmp = require('tmp'); + +tmp.setGracefulCleanup(); + +const ROOT = process.cwd(); +const EXECUTABLE_PATH = path.join(__dirname, '..', 'bin', 'ember-source-channel-url'); QUnit.module('ember-source-channel-url', function(hooks) { function randomString(length) { @@ -12,6 +19,9 @@ QUnit.module('ember-source-channel-url', function(hooks) { } hooks.beforeEach(function() { + let dir = tmp.dirSync(); + process.chdir(dir.name); + return createServer().then(server => { process.env.EMBER_SOURCE_CHANNEL_URL_HOST = `http://localhost:${server.port}`; @@ -19,6 +29,8 @@ QUnit.module('ember-source-channel-url', function(hooks) { this.fakeSHA = randomString(20); let assetPath = (this.assetPath = `/canary/shas/${this.fakeSHA}.tgz`); + this.expectedURL = `http://${server.host}:${server.port}/builds.emberjs.com${this.assetPath}`; + server.on('/builds.emberjs.com/canary.json', (req, res) => { res.end(JSON.stringify({ assetPath })); }); @@ -28,6 +40,8 @@ QUnit.module('ember-source-channel-url', function(hooks) { }); hooks.afterEach(function() { + process.chdir(ROOT); + return this.server.close(); }); @@ -43,13 +57,92 @@ QUnit.module('ember-source-channel-url', function(hooks) { QUnit.module('binary', function() { QUnit.test('works', function(assert) { - let expected = `http://${this.server.host}:${this.server.port}/builds.emberjs.com${ - this.assetPath - }`; + return execa(EXECUTABLE_PATH, ['canary']).then(results => { + assert.ok(results.stdout.includes(this.expectedURL), 'URL is present in stdout'); + }); + }); + + QUnit.test('updates local package.json when -w is passed (dependencies)', function(assert) { + fs.writeFileSync( + 'package.json', + JSON.stringify({ dependencies: { 'ember-source': '^3.10.0' } }), + { encoding: 'utf8' } + ); + + return execa(EXECUTABLE_PATH, ['canary', '-w']).then(results => { + assert.ok(results.stdout.includes(this.expectedURL), 'URL is present in stdout'); + + assert.deepEqual(JSON.parse(fs.readFileSync('package.json', { encoding: 'utf8' })), { + dependencies: { + 'ember-source': this.expectedURL, + }, + }); + }); + }); + + QUnit.test('updates local package.json when --write is passed (dependencies)', function( + assert + ) { + fs.writeFileSync( + 'package.json', + JSON.stringify({ dependencies: { 'ember-source': '^3.10.0' } }), + { encoding: 'utf8' } + ); + + return execa(EXECUTABLE_PATH, ['canary', '--write']).then(results => { + assert.ok(results.stdout.includes(this.expectedURL), 'URL is present in stdout'); + + assert.deepEqual(JSON.parse(fs.readFileSync('package.json', { encoding: 'utf8' })), { + dependencies: { + 'ember-source': this.expectedURL, + }, + }); + }); + }); + + QUnit.test('updates local package.json when --write is passed (devDependencies)', function( + assert + ) { + fs.writeFileSync( + 'package.json', + JSON.stringify({ devDependencies: { 'ember-source': '^3.10.0' } }), + { encoding: 'utf8' } + ); + + return execa(EXECUTABLE_PATH, ['canary', '--write']).then(results => { + assert.ok(results.stdout.includes(this.expectedURL), 'URL is present in stdout'); + + assert.deepEqual(JSON.parse(fs.readFileSync('package.json', { encoding: 'utf8' })), { + devDependencies: { + 'ember-source': this.expectedURL, + }, + }); + }); + }); + + QUnit.test('fails when package.json is missing', function(assert) { + return execa(EXECUTABLE_PATH, ['canary', '--write']).catch(results => { + assert.ok(results.stdout.includes(this.expectedURL), 'URL is present in stdout'); + + assert.ok( + results.stdout.includes('no package.json is available to update'), + 'warning is printed indicating -w failed' + ); + }); + }); + + QUnit.test('fails when ember-source is not a dep', function(assert) { + fs.writeFileSync('package.json', JSON.stringify({}), { encoding: 'utf8' }); + + return execa(EXECUTABLE_PATH, ['canary', '--write']).catch(results => { + assert.ok(results.stdout.includes(this.expectedURL), 'URL is present in stdout'); - let executablePath = path.join(__dirname, '..', 'bin', 'ember-source-channel-url'); - return execa(executablePath, ['canary']).then(results => { - assert.ok(results.stdout.includes(expected), 'URL is present in stdout'); + assert.ok( + results.stdout.includes( + 'ember-source is not included in dependencies or devDependencies' + ), + 'warning is printed indicating -w failed' + ); }); }); }); diff --git a/yarn.lock b/yarn.lock index 5c7f3f1..1986f4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1192,7 +1192,7 @@ restore-cursor@^2.0.0: onetime "^2.0.0" signal-exit "^3.0.2" -rimraf@2.6.3: +rimraf@2.6.3, rimraf@^2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" dependencies: @@ -1379,6 +1379,13 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" + integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== + dependencies: + rimraf "^2.6.3" + tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"