From 6696c04f3a79f98233af5b97d7df0ea38cb4aad7 Mon Sep 17 00:00:00 2001 From: "James C. Davis" Date: Thu, 22 Feb 2018 20:08:12 -0500 Subject: [PATCH 1/4] Copy in-repo-addon blueprint and tests from ember-cli --- .../in-repo-addon/files/lib/__name__/index.js | 10 + .../files/lib/__name__/package.json | 6 + blueprints/in-repo-addon/index.js | 65 ++++++ node-tests/blueprints/in-repo-addon-test.js | 200 ++++++++++++++++++ package.json | 1 + 5 files changed, 282 insertions(+) create mode 100755 blueprints/in-repo-addon/files/lib/__name__/index.js create mode 100644 blueprints/in-repo-addon/files/lib/__name__/package.json create mode 100755 blueprints/in-repo-addon/index.js create mode 100644 node-tests/blueprints/in-repo-addon-test.js diff --git a/blueprints/in-repo-addon/files/lib/__name__/index.js b/blueprints/in-repo-addon/files/lib/__name__/index.js new file mode 100755 index 000000000..521d920cb --- /dev/null +++ b/blueprints/in-repo-addon/files/lib/__name__/index.js @@ -0,0 +1,10 @@ +/* eslint-env node */ +'use strict'; + +module.exports = { + name: '<%= dasherizedModuleName %>', + + isDevelopingAddon() { + return true; + } +}; diff --git a/blueprints/in-repo-addon/files/lib/__name__/package.json b/blueprints/in-repo-addon/files/lib/__name__/package.json new file mode 100644 index 000000000..2c9da2dec --- /dev/null +++ b/blueprints/in-repo-addon/files/lib/__name__/package.json @@ -0,0 +1,6 @@ +{ + "name": "<%= dasherizedModuleName %>", + "keywords": [ + "ember-addon" + ] +} diff --git a/blueprints/in-repo-addon/index.js b/blueprints/in-repo-addon/index.js new file mode 100755 index 000000000..ac548cbf1 --- /dev/null +++ b/blueprints/in-repo-addon/index.js @@ -0,0 +1,65 @@ +'use strict'; + +const fs = require('fs-extra'); +const path = require('path'); +const stringUtil = require('ember-cli-string-utils'); +const Blueprint = require('../../lib/models/blueprint'); +const stringifyAndNormalize = require('../../lib/utilities/stringify-and-normalize'); + +module.exports = { + description: 'The blueprint for addon in repo ember-cli addons.', + + beforeInstall(options) { + let libBlueprint = Blueprint.lookup('lib', { + ui: this.ui, + analytics: this.analytics, + project: this.project, + }); + + return libBlueprint.install(options); + }, + + afterInstall(options) { + this._generatePackageJson(options, true); + }, + + afterUninstall(options) { + this._generatePackageJson(options, false); + }, + + _generatePackageJson(options, isInstall) { + let packagePath = path.join(this.project.root, 'package.json'); + let contents = this._readJsonSync(packagePath); + let name = stringUtil.dasherize(options.entity.name); + let newPath = ['lib', name].join('/'); + let paths; + + contents['ember-addon'] = contents['ember-addon'] || {}; + paths = contents['ember-addon']['paths'] = contents['ember-addon']['paths'] || []; + + if (isInstall) { + if (paths.indexOf(newPath) === -1) { + paths.push(newPath); + contents['ember-addon']['paths'] = paths.sort(); + } + } else { + let newPathIndex = paths.indexOf(newPath); + if (newPathIndex > -1) { + paths.splice(newPathIndex, 1); + if (paths.length === 0) { + delete contents['ember-addon']['paths']; + } + } + } + + this._writeFileSync(packagePath, stringifyAndNormalize(contents)); + }, + + _readJsonSync(path) { + return fs.readJsonSync(path); + }, + + _writeFileSync(path, content) { + fs.writeFileSync(path, content); + }, +}; diff --git a/node-tests/blueprints/in-repo-addon-test.js b/node-tests/blueprints/in-repo-addon-test.js new file mode 100644 index 000000000..f20b83f89 --- /dev/null +++ b/node-tests/blueprints/in-repo-addon-test.js @@ -0,0 +1,200 @@ +'use strict'; + +const fs = require('fs-extra'); +const path = require('path'); +const blueprintHelpers = require('ember-cli-blueprint-test-helpers/helpers'); +let setupTestHooks = blueprintHelpers.setupTestHooks; +let emberNew = blueprintHelpers.emberNew; +let emberGenerate = blueprintHelpers.emberGenerate; +let emberDestroy = blueprintHelpers.emberDestroy; +const td = require('testdouble'); + +const expect = require('ember-cli-blueprint-test-helpers/chai').expect; +const file = require('ember-cli-blueprint-test-helpers/chai').file; + +describe('Acceptance: ember generate and destroy in-repo-addon', function() { + setupTestHooks(this, { + cliPath: path.resolve(`${__dirname}/../../..`), + }); + + it('in-repo-addon fooBar', function() { + let args = ['in-repo-addon', 'fooBar']; + + return emberNew() + .then(function() { + expect(fs.readJsonSync('package.json')['ember-addon']).to.be.undefined; + }) + .then(function() { + return emberGenerate(args); + }) + .then(function() { + expect(file('lib/foo-bar/package.json')).to.exist; + expect(file('lib/foo-bar/index.js')).to.exist; + + expect(fs.readJsonSync('lib/foo-bar/package.json')).to.deep.equal({ + "name": "foo-bar", + "keywords": [ + "ember-addon", + ], + }); + + expect(fs.readJsonSync('package.json')['ember-addon']).to.deep.equal({ + "paths": [ + "lib/foo-bar", + ], + }); + }) + .then(function() { + return emberDestroy(args); + }) + .then(function() { + expect(file('lib/foo-bar/package.json')).to.not.exist; + expect(file('lib/foo-bar/index.js')).to.not.exist; + + expect(fs.readJsonSync('package.json')['ember-addon']['paths']).to.be.undefined; + }); + }); +}); + +describe('Unit: in-repo-addon blueprint', function() { + let blueprint; + let readJsonSync; + let writeFileSync; + let options; + + beforeEach(function() { + blueprint = require('../../../blueprints/in-repo-addon'); + blueprint.project = { + root: 'test-project-root', + }; + + options = { + entity: { + name: 'test-entity-name', + }, + }; + + readJsonSync = td.replace(blueprint, '_readJsonSync'); + writeFileSync = td.replace(blueprint, '_writeFileSync'); + }); + + afterEach(function() { + td.reset(); + }); + + it('adds to paths', function() { + td.when(readJsonSync(), { ignoreExtraArgs: true }).thenReturn({}); + + blueprint.afterInstall(options); + + let captor = td.matchers.captor(); + + td.verify(readJsonSync(path.normalize('test-project-root/package.json'))); + td.verify(writeFileSync(path.normalize('test-project-root/package.json'), captor.capture())); + + expect(captor.value).to.equal('\ +{\n\ + "ember-addon": {\n\ + "paths": [\n\ + "lib/test-entity-name"\n\ + ]\n\ + }\n\ +}\n'); + }); + + it('ignores if already exists', function() { + td.when(readJsonSync(), { ignoreExtraArgs: true }).thenReturn({ + 'ember-addon': { + paths: ['lib/test-entity-name'], + }, + }); + + blueprint.afterInstall(options); + + let captor = td.matchers.captor(); + + td.verify(readJsonSync(path.normalize('test-project-root/package.json'))); + td.verify(writeFileSync(path.normalize('test-project-root/package.json'), captor.capture())); + + expect(captor.value).to.equal('\ +{\n\ + "ember-addon": {\n\ + "paths": [\n\ + "lib/test-entity-name"\n\ + ]\n\ + }\n\ +}\n'); + }); + + it('removes from paths', function() { + td.when(readJsonSync(), { ignoreExtraArgs: true }).thenReturn({ + 'ember-addon': { + paths: [ + 'lib/test-entity-name', + 'lib/test-entity-name-2', + ], + }, + }); + + blueprint.afterUninstall(options); + + let captor = td.matchers.captor(); + + td.verify(readJsonSync(path.normalize('test-project-root/package.json'))); + td.verify(writeFileSync(path.normalize('test-project-root/package.json'), captor.capture())); + + expect(captor.value).to.equal('\ +{\n\ + "ember-addon": {\n\ + "paths": [\n\ + "lib/test-entity-name-2"\n\ + ]\n\ + }\n\ +}\n'); + }); + + it('removes paths if last one', function() { + td.when(readJsonSync(), { ignoreExtraArgs: true }).thenReturn({ + 'ember-addon': { + paths: ['lib/test-entity-name'], + }, + }); + + blueprint.afterUninstall(options); + + let captor = td.matchers.captor(); + + td.verify(readJsonSync(path.normalize('test-project-root/package.json'))); + td.verify(writeFileSync(path.normalize('test-project-root/package.json'), captor.capture())); + + expect(captor.value).to.equal('\ +{\n\ + "ember-addon": {}\n\ +}\n'); + }); + + it('alphabetizes paths', function() { + td.when(readJsonSync(), { ignoreExtraArgs: true }).thenReturn({ + 'ember-addon': { + paths: ['lib/test-entity-name-2'], + }, + }); + + blueprint.afterInstall(options); + + let captor = td.matchers.captor(); + + td.verify(readJsonSync(path.normalize('test-project-root/package.json'))); + td.verify(writeFileSync(path.normalize('test-project-root/package.json'), captor.capture())); + + expect(captor.value).to.equal('\ +{\n\ + "ember-addon": {\n\ + "paths": [\n\ + "lib/test-entity-name",\n\ + "lib/test-entity-name-2"\n\ + ]\n\ + }\n\ +}\n'); + }); +}); diff --git a/package.json b/package.json index 14d12fe14..c54f075ee 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "broccoli-stew": "^1.4.0", "chalk": "^2.3.0", "debug": "^3.1.0", + "ember-cli": "*", "ember-cli-get-component-path-option": "^1.0.0", "ember-cli-is-package-missing": "^1.0.0", "ember-cli-normalize-entity-name": "^1.0.0", From 3c9728a42ba8d172e7760fe319f125b037f62dbc Mon Sep 17 00:00:00 2001 From: "James C. Davis" Date: Thu, 22 Feb 2018 20:18:08 -0500 Subject: [PATCH 2/4] Get tests to pass and lint clean --- blueprints/in-repo-addon/index.js | 4 ++-- node-tests/blueprints/in-repo-addon-test.js | 6 ++---- package.json | 1 + yarn.lock | 9 +++++++++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/blueprints/in-repo-addon/index.js b/blueprints/in-repo-addon/index.js index ac548cbf1..b21d91b2e 100755 --- a/blueprints/in-repo-addon/index.js +++ b/blueprints/in-repo-addon/index.js @@ -3,8 +3,8 @@ const fs = require('fs-extra'); const path = require('path'); const stringUtil = require('ember-cli-string-utils'); -const Blueprint = require('../../lib/models/blueprint'); -const stringifyAndNormalize = require('../../lib/utilities/stringify-and-normalize'); +const Blueprint = require('ember-cli/lib/models/blueprint'); // eslint-disable-line node/no-unpublished-require +const stringifyAndNormalize = require('ember-cli/lib/utilities/stringify-and-normalize'); // eslint-disable-line node/no-unpublished-require module.exports = { description: 'The blueprint for addon in repo ember-cli addons.', diff --git a/node-tests/blueprints/in-repo-addon-test.js b/node-tests/blueprints/in-repo-addon-test.js index f20b83f89..2624990a6 100644 --- a/node-tests/blueprints/in-repo-addon-test.js +++ b/node-tests/blueprints/in-repo-addon-test.js @@ -13,9 +13,7 @@ const expect = require('ember-cli-blueprint-test-helpers/chai').expect; const file = require('ember-cli-blueprint-test-helpers/chai').file; describe('Acceptance: ember generate and destroy in-repo-addon', function() { - setupTestHooks(this, { - cliPath: path.resolve(`${__dirname}/../../..`), - }); + setupTestHooks(this); it('in-repo-addon fooBar', function() { let args = ['in-repo-addon', 'fooBar']; @@ -63,7 +61,7 @@ describe('Unit: in-repo-addon blueprint', function() { let options; beforeEach(function() { - blueprint = require('../../../blueprints/in-repo-addon'); + blueprint = require('../../blueprints/in-repo-addon'); blueprint.project = { root: 'test-project-root', }; diff --git a/package.json b/package.json index c54f075ee..5abeec1a8 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "eslint-plugin-node": "^6.0.0", "loader.js": "^4.2.3", "mocha": "^5.0.0", + "testdouble": "^3.5.0", "typescript": "^2.7.2" }, "resolutions": { diff --git a/yarn.lock b/yarn.lock index 0936a2927..35b1508ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6628,6 +6628,15 @@ testdouble@^3.2.6: resolve "^1.3.3" stringify-object-es5 "^2.5.0" +testdouble@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/testdouble/-/testdouble-3.5.0.tgz#8f0853c887c370f65d7a6a09b167e5e8485e2b12" + dependencies: + es6-map "^0.1.5" + lodash "^4.17.4" + quibble "^0.5.1" + stringify-object-es5 "^2.5.0" + testem@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/testem/-/testem-2.0.0.tgz#b05c96200c7ac98bae998d71c94c0c5345907d13" From bdff6c0c1b370b9d775bccd7a5a44771bb755df7 Mon Sep 17 00:00:00 2001 From: "James C. Davis" Date: Fri, 23 Feb 2018 16:57:57 -0500 Subject: [PATCH 3/4] Update tsconfig.json on in-repo-addon generate/destroy --- blueprints/ember-cli-typescript/index.js | 6 ++-- blueprints/in-repo-addon/index.js | 29 ++++++++++++++++ lib/utilities/update-paths-for-addon.js | 38 +++++++++++++++++++++ node-tests/blueprints/in-repo-addon-test.js | 22 ++++++++++++ 4 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 lib/utilities/update-paths-for-addon.js diff --git a/blueprints/ember-cli-typescript/index.js b/blueprints/ember-cli-typescript/index.js index cf671da1b..fd1ee7358 100644 --- a/blueprints/ember-cli-typescript/index.js +++ b/blueprints/ember-cli-typescript/index.js @@ -2,6 +2,7 @@ const fs = require('fs'); const path = require('path'); +const updatePathsForAddon = require('../../lib/utilities/update-paths-for-addon'); const APP_DECLARATIONS = ` import Ember from 'ember'; @@ -62,10 +63,7 @@ module.exports = { } for (let addon of inRepoAddons) { - let addonName = path.basename(addon); - paths[addonName] = [`${addon}/addon`]; - paths[`${addonName}/*`] = [`${addon}/addon/*`]; - paths[`${appName}/*`].push(`${addon}/app/*`); + updatePathsForAddon(paths, path.basename(addon), appName); } paths['*'] = ['types/*']; diff --git a/blueprints/in-repo-addon/index.js b/blueprints/in-repo-addon/index.js index b21d91b2e..ee700c794 100755 --- a/blueprints/in-repo-addon/index.js +++ b/blueprints/in-repo-addon/index.js @@ -5,6 +5,7 @@ const path = require('path'); const stringUtil = require('ember-cli-string-utils'); const Blueprint = require('ember-cli/lib/models/blueprint'); // eslint-disable-line node/no-unpublished-require const stringifyAndNormalize = require('ember-cli/lib/utilities/stringify-and-normalize'); // eslint-disable-line node/no-unpublished-require +const updatePathsForAddon = require('../../lib/utilities/update-paths-for-addon'); module.exports = { description: 'The blueprint for addon in repo ember-cli addons.', @@ -21,10 +22,12 @@ module.exports = { afterInstall(options) { this._generatePackageJson(options, true); + this._updateTsconfigJson(options, true); }, afterUninstall(options) { this._generatePackageJson(options, false); + this._updateTsconfigJson(options, false); }, _generatePackageJson(options, isInstall) { @@ -55,6 +58,32 @@ module.exports = { this._writeFileSync(packagePath, stringifyAndNormalize(contents)); }, + _updateTsconfigJson(options, isInstall) { + const tsconfigPath = path.join(this.project.root, 'tsconfig.json'); + const addonName = stringUtil.dasherize(options.entity.name); + const appName = this.project.isEmberCLIAddon() ? 'dummy' : this.project.name(); + const addonPath = ['lib', addonName].join('/'); + let contents = this._readJsonSync(tsconfigPath); + contents['compilerOptions'] = contents['compilerOptions'] || {}; + contents['include'] = contents['include'] || []; + let paths = contents['compilerOptions']['paths']; + + if (isInstall) { + updatePathsForAddon(paths, addonName, appName); + if (contents['include'].indexOf(addonPath) === -1) { + contents['include'].push(addonPath); + } + } else { + updatePathsForAddon(paths, addonName, appName, { removePaths: true }); + let addonPathIndex = contents['include'].indexOf(addonPath); + if (addonPathIndex > -1) { + contents['include'].splice(addonPathIndex, 1); + } + } + + this._writeFileSync(tsconfigPath, stringifyAndNormalize(contents)); + }, + _readJsonSync(path) { return fs.readJsonSync(path); }, diff --git a/lib/utilities/update-paths-for-addon.js b/lib/utilities/update-paths-for-addon.js new file mode 100644 index 000000000..238f71ce2 --- /dev/null +++ b/lib/utilities/update-paths-for-addon.js @@ -0,0 +1,38 @@ +'use strict'; + +module.exports = function(paths, addonName, appName, options) { + options = options || {}; + const addonNameStar = [addonName, '*'].join('/'); + const addonPath = [options.isLinked ? 'node_modules' : 'lib', addonName].join('/'); + const addonAddonPath = [addonPath, 'addon'].join('/'); + const addonAppPath = [addonPath, 'app'].join('/'); + const appNameStar = [appName, '*'].join('/'); + let appStarPaths; + paths = paths || {}; + appStarPaths = paths[appNameStar] = paths[appNameStar] || []; + + if (options.removePaths) { + if (paths.hasOwnProperty(addonName)) { + delete paths[addonName]; + } + if (paths.hasOwnProperty(addonNameStar)) { + delete paths[addonNameStar] + } + let addonAppPathIndex = appStarPaths.indexOf([addonAppPath, '*'].join('/')); + if (addonAppPathIndex > -1) { + appStarPaths.splice(addonAppPathIndex, 1); + paths[appNameStar] = appStarPaths; + } + } else { + if (!paths.hasOwnProperty(addonName)) { + paths[addonName] = [ addonAddonPath ]; + } + if (!paths.hasOwnProperty(addonNameStar)) { + paths[addonNameStar] = [ [addonAddonPath, '*'].join('/') ]; + } + if (appStarPaths.indexOf(addonAppPath) === -1) { + appStarPaths.push([addonAppPath, '*'].join('/')); + paths[appNameStar] = appStarPaths; + } + } +} diff --git a/node-tests/blueprints/in-repo-addon-test.js b/node-tests/blueprints/in-repo-addon-test.js index 2624990a6..9325df4cc 100644 --- a/node-tests/blueprints/in-repo-addon-test.js +++ b/node-tests/blueprints/in-repo-addon-test.js @@ -17,8 +17,16 @@ describe('Acceptance: ember generate and destroy in-repo-addon', function() { it('in-repo-addon fooBar', function() { let args = ['in-repo-addon', 'fooBar']; + let name, nameStar; return emberNew() + .then(function() { + name = fs.readJsonSync('package.json')['name']; + nameStar = [name, '*'].join('/'); + }) + .then(function() { + return emberGenerate(['ember-cli-typescript']); + }) .then(function() { expect(fs.readJsonSync('package.json')['ember-addon']).to.be.undefined; }) @@ -41,6 +49,12 @@ describe('Acceptance: ember generate and destroy in-repo-addon', function() { "lib/foo-bar", ], }); + + const tsconfigJson = fs.readJsonSync('tsconfig.json'); + expect(tsconfigJson['compilerOptions']['paths']['foo-bar']).to.have.all.members(['lib/foo-bar/addon']); + expect(tsconfigJson['compilerOptions']['paths']['foo-bar/*']).to.have.all.members(['lib/foo-bar/addon/*']); + expect(tsconfigJson['compilerOptions']['paths'][nameStar]).to.include.members(['lib/foo-bar/app/*']); + expect(tsconfigJson['include']).to.include.members(['lib/foo-bar']); }) .then(function() { return emberDestroy(args); @@ -50,6 +64,12 @@ describe('Acceptance: ember generate and destroy in-repo-addon', function() { expect(file('lib/foo-bar/index.js')).to.not.exist; expect(fs.readJsonSync('package.json')['ember-addon']['paths']).to.be.undefined; + + const tsconfigJson = fs.readJsonSync('tsconfig.json'); + expect(tsconfigJson['compilerOptions']['paths']['foo-bar']).to.be.undefined; + expect(tsconfigJson['compilerOptions']['paths']['foo-bar/*']).to.be.undefined; + expect(tsconfigJson['compilerOptions']['paths'][nameStar]).to.not.include.members(['lib/foo-bar/app/*']); + expect(tsconfigJson['include']).to.not.include.members(['lib/foo-bar']); }); }); }); @@ -64,6 +84,8 @@ describe('Unit: in-repo-addon blueprint', function() { blueprint = require('../../blueprints/in-repo-addon'); blueprint.project = { root: 'test-project-root', + isEmberCLIAddon: function() { return false; }, + name: function() { return 'foo-bar'; }, }; options = { From f81874007713c61ebbcfe3ea578cfef0ce101058 Mon Sep 17 00:00:00 2001 From: "James C. Davis" Date: Mon, 26 Feb 2018 10:38:10 -0500 Subject: [PATCH 4/4] Add entry to CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 910b9c401..4902a126e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Added + +* Blueprint (and tests) to generate in-repo addons configured for TypeScript + ### Changed * Improve instructions for setting up [Linked Addons](README.md#linking-addons) and [In-repo Addons](README.md#in-repo-addons).