{"action":"created","comment":{"url":"https://api.github.com/repos/ember-cli/ember-cli/pulls/comments/183919086","pull_request_review_id":114999184,"id":183919086,"diff_hunk":"@@ -0,0 +1,1049 @@\n+'use strict';\n+\n+/*\n+ * Performance cache for information about packages (projects/addons/\"apps\"/modules)\n+ * under an initial root directory and resolving addon/dependency links to other packages.\n+ */\n+\n+const fs = require('fs-extra');\n+const path = require('path');\n+\n+let realFilePathCache;\n+let realDirectoryPathCache;\n+\n+/**\n+ * Resolve the real path for a file, return null if does not\n+ * exist or is not a file or FIFO, return the real path otherwise.\n+ *\n+ * @private\n+ * @method getRealFilePath\n+ * @param  {String} filePath the path to the file of interest\n+ * @return {String} real path or null\n+ */\n+function getRealFilePath(filePath) {\n+  let realPath;\n+\n+  try {\n+    realPath = realFilePathCache[filePath];\n+\n+    if (realPath) {\n+      return realPath;\n+    }\n+\n+    let stat = fs.statSync(filePath);\n+\n+    if (stat.isFile() || stat.isFIFO()) {\n+      realPath = fs.realpathSync(filePath);\n+    }\n+  } catch (e) {\n+    if (\n+      e !== null &&\n+      typeof e === 'object' &&\n+      (e.code === 'ENOENT' || e.code === 'ENOTDIR')\n+    ) {\n+      realPath = null;\n+    } else {\n+      throw e;\n+    }\n+  }\n+\n+  realFilePathCache[filePath] = realPath;\n+  return realPath;\n+}\n+\n+/**\n+ * Resolve the real path for a directory, return null if does not\n+ * exist or is not a directory, return the real path otherwise.\n+ *\n+ * @private\n+ * @method getRealDirectoryPath\n+ * @param  {String} directoryPath the path to the directory of interest\n+ * @return {String} real path or null\n+ */\n+function getRealDirectoryPath(directoryPath) {\n+  let realPath;\n+\n+  try {\n+    realPath = realDirectoryPathCache[directoryPath];\n+\n+    if (realPath) {\n+      return realPath;\n+    }\n+\n+    let stat = fs.statSync(directoryPath);\n+\n+    if (stat.isDirectory()) {\n+      realPath = fs.realpathSync(directoryPath);\n+    }\n+  } catch (e) {\n+    if (\n+      e !== null &&\n+      typeof e === 'object' &&\n+      (e.code === 'ENOENT' || e.code === 'ENOTDIR')\n+    ) {\n+      realPath = null;\n+    } else {\n+      throw e;\n+    }\n+  }\n+\n+  realDirectoryPathCache[directoryPath] = realPath;\n+  return realPath;\n+}\n+\n+/*\n+ * Small utility class to store a single error during processing.\n+ *\n+ * @private\n+ * @class ErrorEntry\n+ */\n+class ErrorEntry {\n+  constructor(type, data) {\n+    this.type = type;\n+    this.data = data;\n+  }\n+}\n+\n+/*\n+ * Small utility class to store a list of errors during processing.\n+ * Instances of this exist in the PackageInfo and PackageInfoCache\n+ * objects.\n+ *\n+ * @private\n+ * @class ErrorEntry\n+ */\n+class ErrorList {\n+  constructor() {\n+    this.errors = [];\n+  }\n+\n+  /*\n+   * Add an error. The errorData obj is optional, and can be anything.\n+   * We do this so we don't really need to create a series of error\n+   * classes.\n+   */\n+  addError(errorType, errorData) {\n+    this.errors.push(new ErrorEntry(errorType, errorData));\n+  }\n+\n+  insertError(errorEntry) {\n+    this.errors.push(errorEntry);\n+  }\n+\n+  getErrors() {\n+    return this.errors;\n+  }\n+\n+  hasErrors() {\n+    return this.errors.length > 0;\n+  }\n+}\n+\n+const PACKAGE_JSON = 'package.json';\n+\n+const ERROR_PACKAGE_DIR_MISSING = 'packageDirectoryMissing';\n+const ERROR_PACKAGE_JSON_MISSING = 'packageJsonMissing';\n+const ERROR_PACKAGE_JSON_PARSE = 'packageJsonParse';\n+const ERROR_EMBER_ADDON_MAIN_MISSING = 'emberAddonMainMissing';\n+const ERROR_DEPENDENCIES_MISSING = 'dependenciesMissing';\n+const ERROR_NODEMODULES_ENTRY_MISSING = 'modulesEntryMissing';\n+\n+class PackageInfo {\n+  constructor(pkgObj, realPath, cache) {\n+    this.pkg = pkgObj;\n+    this.pkg['ember-addon'] = this.pkg['ember-addon'] || {};\n+    this.realPath = realPath;\n+    this.cache = cache;\n+    this.errors = new ErrorList();\n+\n+    // other fields that will be set as needed. For JIT we'll define\n+    // them here.\n+    this.addonConstructor = undefined; // (ctor function or object - addons only)\n+    this.addonMainPath = undefined; // (when setting up addonConstructor - addons only)\n+    this.inRepoAddons = undefined; // (list of PackageInfo - project only)\n+    this.internalAddons = undefined; // (list of PackageInfo - project only)\n+    this.cliInfo = undefined; // (PackageInfo - project only)\n+    this.dependencyPackages = undefined; // (obj keyed by dependency name: PackageInfo)\n+    // NOTE: ALL dependencies, not just addons\n+    this.devDependencyPackages = undefined; // (obj keyed by devDependency name: PackageInfo)\n+    // NOTE: these are ALL dependencies, not just addons\n+    this.nodeModules = undefined; // (NodeModulesList, set only if pkg contains node_modules)\n+\n+    // flag indicating that the packageInfo is considered valid. This will\n+    // be true as long as we have a valid directory and our package.json file\n+    // is okay and, if we're an ember addon, that we have a valid 'main' file.\n+    // Missing dependencies will not be considered an error, since they may\n+    // not actually be used.\n+    this.valid = true;\n+  }\n+\n+  // Make various fields of the pkg object available.\n+  get name() {\n+    return this.pkg.name;\n+  }\n+\n+  _addError(errorType, errorData) {\n+    this.errors.addError(errorType, errorData);\n+  }\n+\n+  _insertError(errorEntry) {\n+    this.errors.insertError(errorEntry);\n+  }\n+\n+  _hasErrors() {\n+    return this.errors.hasErrors();\n+  }\n+\n+  /**\n+   * Add a reference to an in-repo addon PackageInfo object.\n+   *\n+   * @private\n+   * @method _addInRepoAddon\n+   * @param {PackageInfo} inRepoAddonPkg the PackageInfo for the in-repo addon\n+   * @return null\n+   */\n+  _addInRepoAddon(inRepoAddonPkg) {\n+    if (!this.inRepoAddons) {\n+      this.inRepoAddons = [];\n+    }\n+    this.inRepoAddons.push(inRepoAddonPkg);\n+  }\n+\n+  /**\n+   * Add a reference to an internal addon PackageInfo object.\n+   * \"internal\" addons (note: not in-repo addons) only exist in\n+   * Projects, not other packages. Since the cache is loaded from\n+   * 'loadProject', this can be done appropriately.\n+   *\n+   * @private\n+   * @method _addInternalAddon\n+   * @param {PackageInfo} internalAddonPkg the PackageInfo for the internal addon\n+   * @return null\n+   */\n+  _addInternalAddon(internalAddonPkg) {\n+    if (!this.internalAddons) {\n+      this.internalAddons = [];\n+    }\n+    this.internalAddons.push(internalAddonPkg);\n+  }\n+\n+  /**\n+   * For each dependency in the given list, find the corresponding\n+   * PackageInfo object in the cache (going up the file tree if\n+   * necessary, as in the node resolution algorithm). Return a map\n+   * of the dependencyName to PackageInfo object. Caller can then\n+   * store it wherever they like.\n+   *\n+   * Note: this is not to be  called until all packages that can be have\n+   * been added to the cache.\n+   *\n+   * Note: this is for ALL dependencies, not just addons. To get just\n+   * addons, filter the result by calling pkgInfo.isAddon().\n+   *\n+   * @private\n+   * @method _addDependencies\n+   * @param {PackageInfo} dependencies value of 'dependencies' or 'devDependencies'\n+   *        attributes of a package.json.\n+   * @return {Object} a JavaScript object keyed on dependency name/path with\n+   *    values the corresponding PackageInfo object from the cache.\n+   */\n+  _addDependencies(dependencies) {\n+    if (!dependencies) {\n+      return null;\n+    }\n+\n+    let dependencyNames = Object.keys(dependencies);\n+\n+    if (dependencyNames.length === 0) {\n+      return null;\n+    }\n+\n+    let packages = Object.create(null);\n+\n+    let missingDependencies = [];\n+\n+    dependencyNames.forEach(dependencyName => {\n+      let dependencyPackage;\n+\n+      // much of the time the package will have dependencies in\n+      // a node_modules inside it, so check there first because it's\n+      // quicker since we have the reference. Only check externally\n+      // if we don't find it there.\n+      if (this.nodeModules) {\n+        dependencyPackage = this.nodeModules._findPackage(dependencyName);\n+      }\n+\n+      if (!dependencyPackage) {\n+        dependencyPackage = this.cache._findPackage(\n+          dependencyName,\n+          path.dirname(this.realPath)\n+        );\n+      }\n+\n+      if (dependencyPackage) {\n+        packages[dependencyName] = dependencyPackage;\n+      } else {\n+        missingDependencies.push(dependencyName);\n+      }\n+    });\n+\n+    if (missingDependencies.length > 0) {\n+      this._addError(ERROR_DEPENDENCIES_MISSING, missingDependencies);\n+    }\n+\n+    return packages;\n+  }\n+\n+  isAddon() {\n+    let val =\n+      Array.isArray(this.pkg.keywords) &&\n+      this.pkg.keywords.indexOf('ember-addon') >= 0;\n+    return val;\n+  }\n+\n+  // XXX Should this move to Addon.js (and change appropriately to use the cache here?)\n+  getAddonConstructor() {\n+    if (this.isAddon() && !this.addonConstructor) {\n+      if (!this.addonMainPath) {\n+        return null;\n+      }\n+\n+      let Addon = require('./addon');\n+\n+      // Load the addon.\n+      // TODO: Future work - allow a time budget for loading each addon and warn\n+      // or error for those that take too long.\n+      let module = require(this.addonMainPath);\n+      let mainDir = path.dirname(this.addonMainPath);\n+\n+      let ctor;\n+\n+      if (typeof module === 'function') {\n+        ctor = module;\n+        ctor.prototype.root = ctor.prototype.root || mainDir;\n+        ctor.prototype.pkg = ctor.prototype.pkg || this.pkg;\n+      } else {\n+        ctor = Addon.extend(\n+          Object.assign({ root: mainDir, pkg: this.pkg }, module)\n+        );\n+      }\n+\n+      ctor._meta_ = {\n+        modulePath: this.addonMainPath,\n+        //        lookupDuration,\n+        initializeIn: 0,\n+      };\n+\n+      this.addonConstructor = ctor;\n+    }\n+\n+    return this.addonConstructor;\n+  }\n+}\n+\n+class NodeModulesList {\n+  constructor(realPath, cache) {\n+    this.realPath = realPath;\n+    this.cache = cache;\n+    this.entries = Object.create(null);\n+    this.errors = new ErrorList();\n+  }\n+\n+  _addError(errorType, errorData) {\n+    this.errors.addError(errorType, errorData);\n+  }\n+\n+  _insertError(errorEntry) {\n+    this.errors.insertError(errorEntry);\n+  }\n+\n+  _hasErrors() {\n+    return this.errors.hasErrors();\n+  }\n+\n+  _addEntry(entryName, entryVal) {\n+    this.entries[entryName] = entryVal;\n+  }\n+\n+  /**\n+   * Return a PackageInfo object for a given package name (which may include\n+   * a scope)\n+   *\n+   * @private\n+   * @method _findPackage\n+   * @param {String} packageName the name (possibly including a scope) of\n+   *    the PackageInfo the caller wants returned.\n+   * @return the desired PackageInfo if one exists for the given name, else null.\n+   */\n+  _findPackage(packageName) {","path":"lib/models/package-info-cache.js","position":378,"original_position":378,"commit_id":"59b0eaa2450a4148c51c8b8facfcba8d3cf7cd72","original_commit_id":"59b0eaa2450a4148c51c8b8facfcba8d3cf7cd72","user":{"login":"stefanpenner","id":1377,"avatar_url":"https://avatars2.githubusercontent.com/u/1377?v=4","gravatar_id":"","url":"https://api.github.com/users/stefanpenner","html_url":"https://github.com/stefanpenner","followers_url":"https://api.github.com/users/stefanpenner/followers","following_url":"https://api.github.com/users/stefanpenner/following{/other_user}","gists_url":"https://api.github.com/users/stefanpenner/gists{/gist_id}","starred_url":"https://api.github.com/users/stefanpenner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stefanpenner/subscriptions","organizations_url":"https://api.github.com/users/stefanpenner/orgs","repos_url":"https://api.github.com/users/stefanpenner/repos","events_url":"https://api.github.com/users/stefanpenner/events{/privacy}","received_events_url":"https://api.github.com/users/stefanpenner/received_events","type":"User","site_admin":false},"body":"unit test","created_at":"2018-04-25T00:46:08Z","updated_at":"2018-04-25T00:52:27Z","html_url":"https://github.com/ember-cli/ember-cli/pull/7729#discussion_r183919086","pull_request_url":"https://api.github.com/repos/ember-cli/ember-cli/pulls/7729","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/ember-cli/ember-cli/pulls/comments/183919086"},"html":{"href":"https://github.com/ember-cli/ember-cli/pull/7729#discussion_r183919086"},"pull_request":{"href":"https://api.github.com/repos/ember-cli/ember-cli/pulls/7729"}}},"pull_request":{"url":"https://api.github.com/repos/ember-cli/ember-cli/pulls/7729","id":178975672,"html_url":"https://github.com/ember-cli/ember-cli/pull/7729","diff_url":"https://github.com/ember-cli/ember-cli/pull/7729.diff","patch_url":"https://github.com/ember-cli/ember-cli/pull/7729.patch","issue_url":"https://api.github.com/repos/ember-cli/ember-cli/issues/7729","number":7729,"state":"open","locked":false,"title":"new PR (master branch) for package-info-cache (fast package caching)","user":{"login":"dcombslinkedin","id":34755123,"avatar_url":"https://avatars3.githubusercontent.com/u/34755123?v=4","gravatar_id":"","url":"https://api.github.com/users/dcombslinkedin","html_url":"https://github.com/dcombslinkedin","followers_url":"https://api.github.com/users/dcombslinkedin/followers","following_url":"https://api.github.com/users/dcombslinkedin/following{/other_user}","gists_url":"https://api.github.com/users/dcombslinkedin/gists{/gist_id}","starred_url":"https://api.github.com/users/dcombslinkedin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dcombslinkedin/subscriptions","organizations_url":"https://api.github.com/users/dcombslinkedin/orgs","repos_url":"https://api.github.com/users/dcombslinkedin/repos","events_url":"https://api.github.com/users/dcombslinkedin/events{/privacy}","received_events_url":"https://api.github.com/users/dcombslinkedin/received_events","type":"User","site_admin":false},"body":"This is now at the point where all previous ember-cli tests pass (I will shortly add tests for package-info-cache). I would like @stefanpenner and @rwjblue to take a look and suggest improvements before the final PR with new tests goes in. Here's the feature description:\r\n\r\nDoing profiling of build time processing for a large project at LinkedIn found that the addon discovery process was taking over 50 seconds just to determine all where all the addon packages were on disk and read and parse their package.json files. I found that much of this was due to repeated validation of the path to the addon package.json and index.js files (fs.realpathSync turns out to be fairly expensive when done a lot, and the project was calling it something like 80K times). The implementation was already re-reading the package information and then throwing it away many times.\r\n\r\nThis PR creates a new PackageInfoCache class, with a singleton instance added to either CLI (if the build is being done through the CLI or an individual project (if the build is done on a single project but not through the CLI). If a build, like ours here at LinkedIn, includes multiple projects, the fact the instance is in CLI means they all take advantage of the same cache (I'll call it PIC from here out) and don't need to rediscover any packages.\r\n\r\nWhen a Project (ember-cli/lib/models/project) is constructed, a reference to a local or CLI version of PIC is made and PIC.loadProject(projectPath) is called. loadProject then creates an internal instance of a PackageInfo class for the project. It parses the package.json file, then creates separate ProjectInfo instances for all internal addons, all CLI in-repo addons and all 'dependency' and 'devDependency' entries. In the process of that it does the same thing that the node resolution algorithm does to find package.json files, but tries as much as possible to not use 'fs.realpathSync' more than once per file path, caching the result of each path resolution for speed.\r\n\r\nBecause of the cache, reading all the dependencies, parsing their package.json files, determining the addons and collecting information about them (prior to addon-instantiation), current performance on my Mac Pro machine (new 2017 model) has gone from 54 seconds down to 900 milliseconds. Obviously the precise performance will differ based on machine size, but overall it's about a 60x increase.\r\n\r\nThe result of all the above processing is that the PackageInfoCache contains a number of entries, one per package for each real file path (i.e. its cache entries are keyed on the real path for a package's root directory--all symlinks and .. entries are resolved). A PackageInfo object contains the following fields of interest:\r\n\r\npkg - the object resulting from the package.json file\r\nrealPath - (string) the resolved path for the package's root directory\r\ncache - a reference to the PIC object that contains the packageInfo instance\r\nerrors - an ErrorList object containing ErrorEntry objects, one per error discovered during the processing of the package and all its various dependencies. When a packageInfo object is being created, if the package itself is bad (e.g. no or malformed package.json file) the error is created and put in the parent. If the package is generally constructed correctly, anything wrong with the contents of the object itself (e.g. one of its dependencies cannot be found), the error is registered with the object. The PIC has a method 'showErrors' to dump a list of all the errors in all the PackageInfo objects. The caller could then decide if any of those errors is enough to fail the build.\r\nVarious types of PackageInfo objects can have other fields:\r\n\r\naddonConstructor - filled out but not yet used, this would replace part of addons-factory so that constructing an instance of an addon would be done through the PIC/PackageInfo, simplifying the code\r\naddonMainPath - for an addon package, the real path to the package's 'main' file\r\ninRepoAddons - an array of PackageInfo objects, one per in-repo package definition (note that the in-repo addon definition is included in the PIC-level cache by its file path already, this is just a reference to that same set of objects)\r\ninternalAddons - same as above, but for internal addons (project only)\r\ncliInfo - same as above, but for the package that corresponds to the path .cli.root\r\ndependencyPackages - a JS object keyed by dependency name of references to PackageInfo objects, one per entry in the package's 'dependency' attribute list)\r\ndevDependencyPackages - similar, but for the 'devDependency' attribute list\r\nnodeModules - a NodeModulesList object (basically contains an array of PackageInfo and other NodeModulesList objects) of all packages and subdirectories in a node_modules directory.","created_at":"2018-04-03T02:05:44Z","updated_at":"2018-04-25T00:52:27Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d548cb621e1a137f9cb20ff89d645a873ddbfb50","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/ember-cli/ember-cli/pulls/7729/commits","review_comments_url":"https://api.github.com/repos/ember-cli/ember-cli/pulls/7729/comments","review_comment_url":"https://api.github.com/repos/ember-cli/ember-cli/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ember-cli/ember-cli/issues/7729/comments","statuses_url":"https://api.github.com/repos/ember-cli/ember-cli/statuses/59b0eaa2450a4148c51c8b8facfcba8d3cf7cd72","head":{"label":"dcombslinkedin:master","ref":"master","sha":"59b0eaa2450a4148c51c8b8facfcba8d3cf7cd72","user":{"login":"dcombslinkedin","id":34755123,"avatar_url":"https://avatars3.githubusercontent.com/u/34755123?v=4","gravatar_id":"","url":"https://api.github.com/users/dcombslinkedin","html_url":"https://github.com/dcombslinkedin","followers_url":"https://api.github.com/users/dcombslinkedin/followers","following_url":"https://api.github.com/users/dcombslinkedin/following{/other_user}","gists_url":"https://api.github.com/users/dcombslinkedin/gists{/gist_id}","starred_url":"https://api.github.com/users/dcombslinkedin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dcombslinkedin/subscriptions","organizations_url":"https://api.github.com/users/dcombslinkedin/orgs","repos_url":"https://api.github.com/users/dcombslinkedin/repos","events_url":"https://api.github.com/users/dcombslinkedin/events{/privacy}","received_events_url":"https://api.github.com/users/dcombslinkedin/received_events","type":"User","site_admin":false},"repo":{"id":116295140,"name":"ember-cli","full_name":"dcombslinkedin/ember-cli","owner":{"login":"dcombslinkedin","id":34755123,"avatar_url":"https://avatars3.githubusercontent.com/u/34755123?v=4","gravatar_id":"","url":"https://api.github.com/users/dcombslinkedin","html_url":"https://github.com/dcombslinkedin","followers_url":"https://api.github.com/users/dcombslinkedin/followers","following_url":"https://api.github.com/users/dcombslinkedin/following{/other_user}","gists_url":"https://api.github.com/users/dcombslinkedin/gists{/gist_id}","starred_url":"https://api.github.com/users/dcombslinkedin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dcombslinkedin/subscriptions","organizations_url":"https://api.github.com/users/dcombslinkedin/orgs","repos_url":"https://api.github.com/users/dcombslinkedin/repos","events_url":"https://api.github.com/users/dcombslinkedin/events{/privacy}","received_events_url":"https://api.github.com/users/dcombslinkedin/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/dcombslinkedin/ember-cli","description":"The Ember.js command line utility","fork":true,"url":"https://api.github.com/repos/dcombslinkedin/ember-cli","forks_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/forks","keys_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/teams","hooks_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/hooks","issue_events_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/issues/events{/number}","events_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/events","assignees_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/assignees{/user}","branches_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/branches{/branch}","tags_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/tags","blobs_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/git/refs{/sha}","trees_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/statuses/{sha}","languages_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/languages","stargazers_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/stargazers","contributors_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/contributors","subscribers_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/subscribers","subscription_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/subscription","commits_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/commits{/sha}","git_commits_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/git/commits{/sha}","comments_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/comments{/number}","issue_comment_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/issues/comments{/number}","contents_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/contents/{+path}","compare_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/merges","archive_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/downloads","issues_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/issues{/number}","pulls_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/pulls{/number}","milestones_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/milestones{/number}","notifications_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/labels{/name}","releases_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/releases{/id}","deployments_url":"https://api.github.com/repos/dcombslinkedin/ember-cli/deployments","created_at":"2018-01-04T18:44:58Z","updated_at":"2018-04-25T00:12:37Z","pushed_at":"2018-04-25T00:12:34Z","git_url":"git://github.com/dcombslinkedin/ember-cli.git","ssh_url":"git@github.com:dcombslinkedin/ember-cli.git","clone_url":"https://github.com/dcombslinkedin/ember-cli.git","svn_url":"https://github.com/dcombslinkedin/ember-cli","homepage":"http://ember-cli.com/","size":12615,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"ember-cli:master","ref":"master","sha":"71465cf36303075328fc5dd32b791648238f01a7","user":{"login":"ember-cli","id":10262982,"avatar_url":"https://avatars3.githubusercontent.com/u/10262982?v=4","gravatar_id":"","url":"https://api.github.com/users/ember-cli","html_url":"https://github.com/ember-cli","followers_url":"https://api.github.com/users/ember-cli/followers","following_url":"https://api.github.com/users/ember-cli/following{/other_user}","gists_url":"https://api.github.com/users/ember-cli/gists{/gist_id}","starred_url":"https://api.github.com/users/ember-cli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ember-cli/subscriptions","organizations_url":"https://api.github.com/users/ember-cli/orgs","repos_url":"https://api.github.com/users/ember-cli/repos","events_url":"https://api.github.com/users/ember-cli/events{/privacy}","received_events_url":"https://api.github.com/users/ember-cli/received_events","type":"Organization","site_admin":false},"repo":{"id":14089735,"name":"ember-cli","full_name":"ember-cli/ember-cli","owner":{"login":"ember-cli","id":10262982,"avatar_url":"https://avatars3.githubusercontent.com/u/10262982?v=4","gravatar_id":"","url":"https://api.github.com/users/ember-cli","html_url":"https://github.com/ember-cli","followers_url":"https://api.github.com/users/ember-cli/followers","following_url":"https://api.github.com/users/ember-cli/following{/other_user}","gists_url":"https://api.github.com/users/ember-cli/gists{/gist_id}","starred_url":"https://api.github.com/users/ember-cli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ember-cli/subscriptions","organizations_url":"https://api.github.com/users/ember-cli/orgs","repos_url":"https://api.github.com/users/ember-cli/repos","events_url":"https://api.github.com/users/ember-cli/events{/privacy}","received_events_url":"https://api.github.com/users/ember-cli/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ember-cli/ember-cli","description":"The Ember.js command line utility","fork":false,"url":"https://api.github.com/repos/ember-cli/ember-cli","forks_url":"https://api.github.com/repos/ember-cli/ember-cli/forks","keys_url":"https://api.github.com/repos/ember-cli/ember-cli/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ember-cli/ember-cli/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ember-cli/ember-cli/teams","hooks_url":"https://api.github.com/repos/ember-cli/ember-cli/hooks","issue_events_url":"https://api.github.com/repos/ember-cli/ember-cli/issues/events{/number}","events_url":"https://api.github.com/repos/ember-cli/ember-cli/events","assignees_url":"https://api.github.com/repos/ember-cli/ember-cli/assignees{/user}","branches_url":"https://api.github.com/repos/ember-cli/ember-cli/branches{/branch}","tags_url":"https://api.github.com/repos/ember-cli/ember-cli/tags","blobs_url":"https://api.github.com/repos/ember-cli/ember-cli/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ember-cli/ember-cli/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ember-cli/ember-cli/git/refs{/sha}","trees_url":"https://api.github.com/repos/ember-cli/ember-cli/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ember-cli/ember-cli/statuses/{sha}","languages_url":"https://api.github.com/repos/ember-cli/ember-cli/languages","stargazers_url":"https://api.github.com/repos/ember-cli/ember-cli/stargazers","contributors_url":"https://api.github.com/repos/ember-cli/ember-cli/contributors","subscribers_url":"https://api.github.com/repos/ember-cli/ember-cli/subscribers","subscription_url":"https://api.github.com/repos/ember-cli/ember-cli/subscription","commits_url":"https://api.github.com/repos/ember-cli/ember-cli/commits{/sha}","git_commits_url":"https://api.github.com/repos/ember-cli/ember-cli/git/commits{/sha}","comments_url":"https://api.github.com/repos/ember-cli/ember-cli/comments{/number}","issue_comment_url":"https://api.github.com/repos/ember-cli/ember-cli/issues/comments{/number}","contents_url":"https://api.github.com/repos/ember-cli/ember-cli/contents/{+path}","compare_url":"https://api.github.com/repos/ember-cli/ember-cli/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ember-cli/ember-cli/merges","archive_url":"https://api.github.com/repos/ember-cli/ember-cli/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ember-cli/ember-cli/downloads","issues_url":"https://api.github.com/repos/ember-cli/ember-cli/issues{/number}","pulls_url":"https://api.github.com/repos/ember-cli/ember-cli/pulls{/number}","milestones_url":"https://api.github.com/repos/ember-cli/ember-cli/milestones{/number}","notifications_url":"https://api.github.com/repos/ember-cli/ember-cli/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ember-cli/ember-cli/labels{/name}","releases_url":"https://api.github.com/repos/ember-cli/ember-cli/releases{/id}","deployments_url":"https://api.github.com/repos/ember-cli/ember-cli/deployments","created_at":"2013-11-03T17:34:43Z","updated_at":"2018-04-24T14:32:00Z","pushed_at":"2018-04-25T00:12:36Z","git_url":"git://github.com/ember-cli/ember-cli.git","ssh_url":"git@github.com:ember-cli/ember-cli.git","clone_url":"https://github.com/ember-cli/ember-cli.git","svn_url":"https://github.com/ember-cli/ember-cli","homepage":"http://ember-cli.com/","size":12538,"stargazers_count":3218,"watchers_count":3218,"language":"JavaScript","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1039,"mirror_url":null,"archived":false,"open_issues_count":265,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":1039,"open_issues":265,"watchers":3218,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ember-cli/ember-cli/pulls/7729"},"html":{"href":"https://github.com/ember-cli/ember-cli/pull/7729"},"issue":{"href":"https://api.github.com/repos/ember-cli/ember-cli/issues/7729"},"comments":{"href":"https://api.github.com/repos/ember-cli/ember-cli/issues/7729/comments"},"review_comments":{"href":"https://api.github.com/repos/ember-cli/ember-cli/pulls/7729/comments"},"review_comment":{"href":"https://api.github.com/repos/ember-cli/ember-cli/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ember-cli/ember-cli/pulls/7729/commits"},"statuses":{"href":"https://api.github.com/repos/ember-cli/ember-cli/statuses/59b0eaa2450a4148c51c8b8facfcba8d3cf7cd72"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"stefanpenner"}}