node/pathUtils.js

  1. const path = require('path');
  2. const { providerCreator } = require('../shared/jimpleFns');
  3. /**
  4. * @module node/pathUtils
  5. */
  6. /**
  7. * @typedef {import('../shared/jimpleFns').ProviderCreator<O>} ProviderCreator
  8. * @template O
  9. */
  10. /**
  11. * @typedef {Object} PathUtilsProviderOptions
  12. * @property {string} serviceName The name that will be used to register an instance of
  13. * {@link PathUtils}. Its default value is `pathUtils`.
  14. * @property {string} [home] The path to the new home location.
  15. * @parent module:node/pathUtils
  16. */
  17. /**
  18. * A utility services to manage paths on a project. It allows for path building relatives
  19. * to the project root or from where the app executable is located.
  20. *
  21. * @parent module:node/pathUtils
  22. * @tutorial pathUtils
  23. */
  24. class PathUtils {
  25. /**
  26. * @param {string} [home=''] The location of the project's `home`(root) directory. By
  27. * default it uses `process.cwd()`.
  28. */
  29. constructor(home = '') {
  30. /**
  31. * The root path from where the app is being executed.
  32. *
  33. * @type {string}
  34. * @access protected
  35. * @ignore
  36. */
  37. this._path = process.cwd();
  38. /**
  39. * A dictionary of different path locations.
  40. *
  41. * @type {Object.<string, string>}
  42. * @access protected
  43. * @ignore
  44. */
  45. this._locations = {};
  46. this._addAppLocation();
  47. this.addLocation('home', home || this.path);
  48. }
  49. /**
  50. * Adds a new location.
  51. *
  52. * @param {string} name The reference name.
  53. * @param {string} locationPath The path of the location. It must be inside the path
  54. * from the app is being executed.
  55. */
  56. addLocation(name, locationPath) {
  57. let location = locationPath;
  58. /**
  59. * If it doesn't starts with the root location, then prefix it with it. The project should
  60. * never attempt to access a location outside its directory.
  61. */
  62. if (location !== this.path && !location.startsWith(this.path)) {
  63. location = path.join(this.path, location);
  64. }
  65. // Fix it so all the locations will end with a separator.
  66. if (!location.endsWith(path.sep)) {
  67. location = `${location}${path.sep}`;
  68. }
  69. // Add it to the dictionary.
  70. this.locations[name] = location;
  71. }
  72. /**
  73. * Gets a location path by its name.
  74. *
  75. * @param {string} name The location name.
  76. * @returns {string}
  77. * @throws {Error} If there location hasn't been added.
  78. */
  79. getLocation(name) {
  80. const location = this.locations[name];
  81. if (!location) {
  82. throw new Error(`There's no location with the following name: ${name}`);
  83. }
  84. return location;
  85. }
  86. /**
  87. * Alias to `joinFrom` that uses the `home` location by default.
  88. *
  89. * @param {...string} paths The rest of the path components to join.
  90. * @returns {string}
  91. */
  92. join(...paths) {
  93. return this.joinFrom('home', ...paths);
  94. }
  95. /**
  96. * Builds a path using a location path as base.
  97. *
  98. * @param {string} location The location name.
  99. * @param {...string} paths The rest of the path components to join.
  100. * @returns {string}
  101. */
  102. joinFrom(location, ...paths) {
  103. const locationPath = this.getLocation(location);
  104. return path.join(locationPath, ...paths);
  105. }
  106. /**
  107. * The path to the directory where the app executable is located.
  108. *
  109. * @type {string}
  110. */
  111. get app() {
  112. return this.getLocation('app');
  113. }
  114. /**
  115. * The project root path.
  116. *
  117. * @type {string}
  118. */
  119. get home() {
  120. return this.getLocation('home');
  121. }
  122. /**
  123. * A dictionary of different path locations.
  124. *
  125. * @type {Object.<string, string>}
  126. * @access protected
  127. * @ignore
  128. */
  129. get locations() {
  130. return this._locations;
  131. }
  132. /**
  133. * The root path from where the app is being executed.
  134. *
  135. * @type {string}
  136. * @access protected
  137. * @ignore
  138. */
  139. get path() {
  140. return this._path;
  141. }
  142. /**
  143. * Finds and register the location for the app executable directory.
  144. *
  145. * @access protected
  146. * @ignore
  147. */
  148. _addAppLocation() {
  149. let current = module;
  150. while (current.parent && current.parent.filename !== current.filename) {
  151. current = current.parent;
  152. }
  153. this.addLocation('app', path.dirname(current.filename));
  154. }
  155. }
  156. /**
  157. * The service provider to register an instance of {@link PathUtils} on the container.
  158. *
  159. * @type {ProviderCreator<PathUtilsProviderOptions>}
  160. * @tutorial pathUtils
  161. */
  162. const pathUtils = providerCreator((options = {}) => (app) => {
  163. app.set(options.serviceName || 'pathUtils', () => new PathUtils(options.home));
  164. });
  165. module.exports.PathUtils = PathUtils;
  166. module.exports.pathUtils = pathUtils;