Repository: ember-cli/ember-cli
Pull request #7665 (2018-03-07 02:46:55)

Package config
This change is related to the [spike](https://github.com/ember-cli/ember-cli/pull/7562) and is backwards compatible. To avoid making changes to `ember-app` in one go, we can move processing/packaging logic (one tree at a time) to `DefaultPackager` while adding tests/documentation and deprecating private undocumented methods.

Should be merged after https://github.com/ember-cli/ember-cli/pull/7655.




Patch:
diff --git a/lib/broccoli/default-packager.js b/lib/broccoli/default-packager.js
index a89cb67ac..295241df4 100644
--- a/lib/broccoli/default-packager.js
+++ b/lib/broccoli/default-packager.js
@@ -1,8 +1,10 @@
 'use strict';
 
 const p = require('ember-cli-preprocess-registry/preprocessors');
+const path = require('path');
 const Funnel = require('broccoli-funnel');
 const mergeTrees = require('./merge-trees');
+const ConfigLoader = require('broccoli-config-loader');
 const addonProcessTree = require('../utilities/addon-process-tree');
 
 const preprocessJs = p.preprocessJs;
@@ -22,9 +24,11 @@ module.exports = class DefaultPackager {
     this._cachedBower = null;
     this._cachedVendor = null;
     this._cachedPublic = null;
+    this._cachedConfig = null;
 
     this.options = options || {};
 
+    this.env = this.options.env;
     this.name = this.options.name;
     this.project = this.options.project;
     this.registry = this.options.registry;
@@ -206,4 +210,52 @@ module.exports = class DefaultPackager {
 
     return this._cachedPublic;
   }
+
+  /*
+   * Given an input tree, returns a properly assembled Broccoli tree with
+   * configuration files.
+   *
+   * Given a tree:
+   *
+   * ```
+   * environments/
+   * ├── development.json
+   * └── test.json
+   * ```
+   *
+   * Returns:
+   *
+   * ```
+   * └── [name]
+   *     └── config
+   *         └── environments
+   *             ├── development.json
+   *             └── test.json
+   * ```
+   * @private
+   * @method packageConfig
+   * @param {Boolean} testsEnabled Boolean flag to control the inclusion of
+   *                  `test.json` file in the resulting tree.
+  */
+  packageConfig(testsEnabled) {
+    let env = this.env;
+    let name = this.name;
+    let project = this.project;
+    let configPath = this.project.configPath();
+
+    if (this._cachedConfig === null) {
+      let configTree = new ConfigLoader(path.dirname(configPath), {
+        env,
+        tests: testsEnabled || false,
+        project,
+      });
+
+      this._cachedConfig = new Funnel(configTree, {
+        destDir: `${name}/config`,
+        annotation: 'Packaged Config',
+      });
+    }
+
+    return this._cachedConfig;
+  }
 };
diff --git a/lib/broccoli/ember-app.js b/lib/broccoli/ember-app.js
index 01d90654b..f025bdea5 100644
--- a/lib/broccoli/ember-app.js
+++ b/lib/broccoli/ember-app.js
@@ -26,7 +26,6 @@ const BroccoliDebug = require('broccoli-debug');
 const ModuleNormalizer = require('broccoli-module-normalizer');
 const AmdFunnel = require('broccoli-amd-funnel');
 const ConfigReplace = require('broccoli-config-replace');
-const ConfigLoader = require('broccoli-config-loader');
 const mergeTrees = require('./merge-trees');
 const WatchedDir = require('broccoli-source').WatchedDir;
 const UnwatchedDir = require('broccoli-source').UnwatchedDir;
@@ -175,6 +174,7 @@ class EmberApp {
     this._debugTree = BroccoliDebug.buildDebugCallback('ember-app');
 
     this._defaultPackager = new DefaultPackager({
+      env: this.env,
       name: this.name,
       project: this.project,
       registry: this.registry,
@@ -838,7 +838,7 @@ class EmberApp {
       index = mergeTrees([appIndex, srcIndex], { overwrite: true });
     }
 
-    return new ConfigReplace(index, this._configTree(), {
+    return new ConfigReplace(index, this._defaultPackager.packageConfig(this.tests), {
       configPath: path.join(this.name, 'config', 'environments', `${this.env}.json`),
       files: [htmlName],
       patterns: this._configReplacePatterns(),
@@ -978,7 +978,7 @@ class EmberApp {
       annotation: 'Funnel (test index)',
     });
 
-    return new ConfigReplace(index, this._configTree(), {
+    return new ConfigReplace(index, this._defaultPackager.packageConfig(this.tests), {
       configPath: path.join(this.name, 'config', 'environments', 'test.json'),
       files: ['tests/index.html'],
       env: 'test',
@@ -1285,30 +1285,6 @@ class EmberApp {
     return this._cachedExternalTree;
   }
 
-  /**
-    @private
-    @method _configTree
-    @return
-  */
-  _configTree() {
-    if (!this._cachedConfigTree) {
-      let configPath = this.project.configPath();
-      let configTree = new ConfigLoader(path.dirname(configPath), {
-        env: this.env,
-        tests: this.tests,
-        project: this.project,
-      });
-
-      this._cachedConfigTree = new Funnel(configTree, {
-        srcDir: '/',
-        destDir: `${this.name}/config`,
-        annotation: 'Funnel (config)',
-      });
-    }
-
-    return this._cachedConfigTree;
-  }
-
   /**
     @private
     @method _processedEmberCLITree
@@ -1328,7 +1304,7 @@ class EmberApp {
         'tests-prefix.js',
         'tests-suffix.js',
       ];
-      let emberCLITree = new ConfigReplace(new UnwatchedDir(__dirname), this._configTree(), {
+      let emberCLITree = new ConfigReplace(new UnwatchedDir(__dirname), this._defaultPackager.packageConfig(this.tests), {
         configPath: path.join(this.name, 'config', 'environments', `${this.env}.json`),
         files,
 
@@ -1355,7 +1331,7 @@ class EmberApp {
     if (!this._cachedTestAppConfigTree) {
       let files = ['app-config.js'];
 
-      let emberCLITree = new ConfigReplace(new UnwatchedDir(__dirname), this._configTree(), {
+      let emberCLITree = new ConfigReplace(new UnwatchedDir(__dirname), this._defaultPackager.packageConfig(this.tests), {
         configPath: path.join(this.name, 'config', 'environments', `test.json`),
         files,
 
@@ -1381,7 +1357,7 @@ class EmberApp {
     @return {Tree} Merged tree
   */
   appAndDependencies() {
-    let config = this._configTree();
+    let config = this._defaultPackager.packageConfig(this.tests);
     let templates = this._processedTemplatesTree();
 
     let srcTree = this._processedSrcTree();


Test patch:
diff --git a/tests/unit/broccoli/default-packager/config-test.js b/tests/unit/broccoli/default-packager/config-test.js
new file mode 100644
index 000000000..585898aac
--- /dev/null
+++ b/tests/unit/broccoli/default-packager/config-test.js
@@ -0,0 +1,104 @@
+'use strict';
+
+const co = require('co');
+const expect = require('chai').expect;
+const DefaultPackager = require('../../../../lib/broccoli/default-packager');
+const broccoliTestHelper = require('broccoli-test-helper');
+
+const buildOutput = broccoliTestHelper.buildOutput;
+const createTempDir = broccoliTestHelper.createTempDir;
+
+describe('Default Packager: Config', function() {
+  let input, output;
+  let name = 'the-best-app-ever';
+  let env = 'development';
+
+  let CONFIG = {
+    config: {
+      'environment.js': '',
+    },
+  };
+  let project = {
+    configPath() {
+      return `${input.path()}/config/environment`;
+    },
+
+    config() {
+      return { a: 1 };
+    },
+  };
+
+  before(co.wrap(function *() {
+    input = yield createTempDir();
+
+    input.write(CONFIG);
+  }));
+
+  after(co.wrap(function *() {
+    yield input.dispose();
+  }));
+
+  afterEach(co.wrap(function *() {
+    yield output.dispose();
+  }));
+
+  it('caches packaged config tree', co.wrap(function *() {
+    let defaultPackager = new DefaultPackager({
+      name,
+      project,
+      env,
+    });
+
+    expect(defaultPackager._cachedConfig).to.equal(null);
+
+    output = yield buildOutput(defaultPackager.packageConfig());
+
+    expect(defaultPackager._cachedConfig).to.not.equal(null);
+    expect(defaultPackager._cachedConfig._annotation).to.equal('Packaged Config');
+  }));
+
+  it('packages config files w/ tests disabled', co.wrap(function *() {
+    let defaultPackager = new DefaultPackager({
+      name,
+      project,
+      env,
+    });
+
+    output = yield buildOutput(defaultPackager.packageConfig(false));
+
+    let outputFiles = output.read();
+
+    expect(outputFiles).to.deep.equal({
+      [name]: {
+        config: {
+          environments: {
+            'development.json': '{"a":1}',
+          },
+        },
+      },
+    });
+  }));
+
+  it('packages config files w/ tests enabled', co.wrap(function *() {
+    let defaultPackager = new DefaultPackager({
+      name,
+      project,
+      env,
+    });
+
+    output = yield buildOutput(defaultPackager.packageConfig(true));
+
+    let outputFiles = output.read();
+
+    expect(outputFiles).to.deep.equal({
+      [name]: {
+        config: {
+          environments: {
+            'development.json': '{"a":1}',
+            'test.json': '{"a":1}',
+          },
+        },
+      },
+    });
+  }));
+});
diff --git a/tests/unit/broccoli/ember-app/import-test.js b/tests/unit/broccoli/ember-app/import-test.js
index 24d7c8189..f9153c437 100644
--- a/tests/unit/broccoli/ember-app/import-test.js
+++ b/tests/unit/broccoli/ember-app/import-test.js
@@ -112,6 +112,10 @@ describe('EmberApp.import()', function() {
     let cli = new MockCLI();
     let project = new Project(path, pkg, cli.ui, cli);
 
+    EmberApp.env = function() {
+      return options.env || 'development';
+    };
+
     let app = new EmberApp({
       project,
       _ignoreMissingLoader: true,
@@ -166,9 +170,10 @@ describe('EmberApp.import()', function() {
   }));
 
   it('handles imports with different environments (production)', co.wrap(function *() {
-    let app = createApp();
+    let app = createApp({
+      env: 'production',
+    });
 
-    app.env = 'production';
     app.import({
       development: 'bower_components/moment/moment.js',
       production: 'bower_components/moment/moment.min.js',
@@ -213,9 +218,10 @@ describe('EmberApp.import()', function() {
   }));
 
   it('handles imports from node with different environments (production)', co.wrap(function *() {
-    let app = createApp();
+    let app = createApp({
+      env: 'production',
+    });
 
-    app.env = 'production';
     app.import({
       development: 'node_modules/moment/moment.js',
       production: 'node_modules/moment/moment.min.js',