Source

fs-dir.class.ts

  1. import { Dirent, readdir, rmdir, mkdir, access, constants, rename } from 'fs';
  2. import { basename, join } from 'path';
  3. import { FSFile } from './fs-file.class';
  4. import { FSAsyncIterable } from './fs-async-iterable.class';
  5. import { FSPath, FSPathType } from '.';
  6. const readDirPromise = (path: string) =>
  7. new Promise<Dirent[]>((resolve, reject) => {
  8. readdir(path, { withFileTypes: true }, (err, dirents) => {
  9. if (err) return reject(err);
  10. resolve(dirents);
  11. });
  12. });
  13. async function* direntsGen(path: string) {
  14. const dirents = await readDirPromise(path);
  15. for (const dirent of dirents) {
  16. yield dirent;
  17. }
  18. }
  19. /**
  20. * Contains all methods for work with directories
  21. *
  22. */
  23. export class FSDir {
  24. /**
  25. *
  26. * @param {string} path - valid filesystem path string
  27. */
  28. constructor(public readonly path: string) {}
  29. /**
  30. * Directory name
  31. */
  32. public readonly name: string = basename(this.path);
  33. /**
  34. * Return FSPath function for current path. Can be used to countinue joining
  35. * path segments to subdirs or files.
  36. */
  37. public readonly fspath: FSPathType = FSPath(this.path);
  38. /**
  39. * Return async iterator which iterates all dirents in dir.
  40. * Chaining map, filter and forEach
  41. * operators available. toArray operator can be used for resulting chain to array.
  42. * @returns {FSAsyncIterable<Dirent>}
  43. */
  44. public dirents() {
  45. return new FSAsyncIterable(direntsGen(this.path));
  46. }
  47. /**
  48. * Return async iterator which iterates all files in dir. Chaining map, filter and forEach
  49. * operators available. toArray operator can be used for resulting chain to array.
  50. * @returns {FSAsyncIterable}
  51. */
  52. public files() {
  53. return this.dirents()
  54. .filter(async dirent => dirent.isFile())
  55. .map(async dirent => new FSFile(join(this.path, dirent.name)));
  56. }
  57. private async *recursiveDirsGen() {
  58. for await (const dir of this.subdirs()) {
  59. yield dir;
  60. yield* dir.subdirs(true);
  61. }
  62. }
  63. /**
  64. * Return async iterator wich iterates each subdirs. Chaining map, filter and forEach
  65. * operators available. toArray operator can be used for resulting chain to array.
  66. * @param recursive - if true returns each subdir of any deep
  67. * @returns {FSAsyncIterable}
  68. * @example <caption>Extratct all module names and versions from node_modules</caption>
  69. * const { cwd } = require('fstb');
  70. * cwd
  71. * .node_modules()
  72. * .asDir()
  73. * .subdirs(true)
  74. * .map(async dir => dir.fspath['package.json']().asFile())
  75. * .filter(async package_json => await package_json.isReadable())
  76. * .map(async package_json => await package_json.read.json())
  77. * .forEach(async content => console.log(`${content.name}@${content.version}`));
  78. */
  79. public subdirs(recursive: boolean = false): FSAsyncIterable<FSDir> {
  80. if (recursive) {
  81. return new FSAsyncIterable(this.recursiveDirsGen());
  82. } else {
  83. return this.dirents()
  84. .filter(async dirent => dirent.isDirectory())
  85. .map(async dirent => new FSDir(join(this.path, dirent.name)));
  86. }
  87. }
  88. /**
  89. * delete a directory
  90. */
  91. public async rmdir() {
  92. return new Promise<void>((resolve, reject) => {
  93. rmdir(this.path, err => {
  94. if (err) return reject(err);
  95. resolve();
  96. });
  97. });
  98. }
  99. /**
  100. * Asynchronously creates a directory.
  101. * @param recursive - if recursive is true, the first directory path created
  102. */
  103. public async mkdir(recursive: boolean = false) {
  104. return new Promise<FSDir>((resolve, reject) => {
  105. mkdir(this.path, { recursive }, err => {
  106. if (err) return reject(err);
  107. resolve(this);
  108. });
  109. });
  110. }
  111. /**
  112. * Checks is dir exits
  113. * @returns {Promise<boolean>}
  114. */
  115. public async isExists(): Promise<boolean> {
  116. return new Promise<boolean>(resolve => {
  117. access(this.path, constants.F_OK, err => {
  118. if (err) return resolve(false);
  119. resolve(true);
  120. });
  121. });
  122. }
  123. /**
  124. * Compute total size of this directory.
  125. * @example <caption>Total size of node_modules</caption>
  126. * console.log(
  127. * 'Total size of node_modules is ' +
  128. * Math.floor(
  129. * (await cwd
  130. * .node_modules()
  131. * .asDir()
  132. * .totalSize()) /
  133. * 1024 /
  134. * 1024
  135. * ) +
  136. * ' MBytes'
  137. * );
  138. */
  139. public async totalSize(): Promise<number> {
  140. let size = 0;
  141. await this.files()
  142. .map(async file => (await file.stat()).size)
  143. .forEach(async fileSize => {
  144. size += fileSize;
  145. });
  146. await this.subdirs()
  147. .map(async dir => await dir.totalSize())
  148. .forEach(async totalSize => {
  149. size += totalSize;
  150. });
  151. return size;
  152. }
  153. /**
  154. * Removes directory with all content
  155. */
  156. public async rimraf() {
  157. if (await this.isExists()) {
  158. await this.files().forEach(async file => {
  159. await file.unlink();
  160. });
  161. await this.subdirs().forEach(async dir => {
  162. await dir.rimraf();
  163. });
  164. await this.rmdir();
  165. }
  166. }
  167. /**
  168. * Recursively copy current directory to the target dir
  169. * @param targetDir - Directory in which will be copy current
  170. */
  171. public async copyTo(targetDir: FSDir) {
  172. const dir = await targetDir.fspath[this.name]()
  173. .asDir()
  174. .mkdir();
  175. await this.files().forEach(async file => {
  176. await file.copyTo(dir, { COPYFILE_EXCL: true });
  177. });
  178. await this.subdirs().forEach(async srcSubdir => {
  179. await srcSubdir.copyTo(dir);
  180. });
  181. return dir;
  182. }
  183. /**
  184. * Moves diretory to the new path
  185. * @param targetDir - target directory
  186. * @returns moved direrectory
  187. */
  188. public async moveTo(targetDir: FSDir):Promise<FSDir> {
  189. const dir = targetDir.fspath[this.name]().asDir()
  190. return new Promise((resolve, reject)=>{
  191. rename(this.path, dir.path, (err=>{
  192. if(err) return reject(err)
  193. resolve(dir)
  194. }))
  195. })
  196. }
  197. }