index.mjs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. import { createRequire } from "module";
  2. import { basename, dirname, normalize, relative, resolve, sep } from "path";
  3. import fs from "fs";
  4. //#region rolldown:runtime
  5. var __require = /* @__PURE__ */ createRequire(import.meta.url);
  6. //#endregion
  7. //#region src/utils.ts
  8. function cleanPath(path) {
  9. let normalized = normalize(path);
  10. if (normalized.length > 1 && normalized[normalized.length - 1] === sep) normalized = normalized.substring(0, normalized.length - 1);
  11. return normalized;
  12. }
  13. const SLASHES_REGEX = /[\\/]/g;
  14. function convertSlashes(path, separator) {
  15. return path.replace(SLASHES_REGEX, separator);
  16. }
  17. const WINDOWS_ROOT_DIR_REGEX = /^[a-z]:[\\/]$/i;
  18. function isRootDirectory(path) {
  19. return path === "/" || WINDOWS_ROOT_DIR_REGEX.test(path);
  20. }
  21. function normalizePath(path, options) {
  22. const { resolvePaths, normalizePath: normalizePath$1, pathSeparator } = options;
  23. const pathNeedsCleaning = process.platform === "win32" && path.includes("/") || path.startsWith(".");
  24. if (resolvePaths) path = resolve(path);
  25. if (normalizePath$1 || pathNeedsCleaning) path = cleanPath(path);
  26. if (path === ".") return "";
  27. const needsSeperator = path[path.length - 1] !== pathSeparator;
  28. return convertSlashes(needsSeperator ? path + pathSeparator : path, pathSeparator);
  29. }
  30. //#endregion
  31. //#region src/api/functions/join-path.ts
  32. function joinPathWithBasePath(filename, directoryPath) {
  33. return directoryPath + filename;
  34. }
  35. function joinPathWithRelativePath(root, options) {
  36. return function(filename, directoryPath) {
  37. const sameRoot = directoryPath.startsWith(root);
  38. if (sameRoot) return directoryPath.replace(root, "") + filename;
  39. else return convertSlashes(relative(root, directoryPath), options.pathSeparator) + options.pathSeparator + filename;
  40. };
  41. }
  42. function joinPath(filename) {
  43. return filename;
  44. }
  45. function joinDirectoryPath(filename, directoryPath, separator) {
  46. return directoryPath + filename + separator;
  47. }
  48. function build$7(root, options) {
  49. const { relativePaths, includeBasePath } = options;
  50. return relativePaths && root ? joinPathWithRelativePath(root, options) : includeBasePath ? joinPathWithBasePath : joinPath;
  51. }
  52. //#endregion
  53. //#region src/api/functions/push-directory.ts
  54. function pushDirectoryWithRelativePath(root) {
  55. return function(directoryPath, paths) {
  56. paths.push(directoryPath.substring(root.length) || ".");
  57. };
  58. }
  59. function pushDirectoryFilterWithRelativePath(root) {
  60. return function(directoryPath, paths, filters) {
  61. const relativePath = directoryPath.substring(root.length) || ".";
  62. if (filters.every((filter) => filter(relativePath, true))) paths.push(relativePath);
  63. };
  64. }
  65. const pushDirectory = (directoryPath, paths) => {
  66. paths.push(directoryPath || ".");
  67. };
  68. const pushDirectoryFilter = (directoryPath, paths, filters) => {
  69. const path = directoryPath || ".";
  70. if (filters.every((filter) => filter(path, true))) paths.push(path);
  71. };
  72. const empty$2 = () => {};
  73. function build$6(root, options) {
  74. const { includeDirs, filters, relativePaths } = options;
  75. if (!includeDirs) return empty$2;
  76. if (relativePaths) return filters && filters.length ? pushDirectoryFilterWithRelativePath(root) : pushDirectoryWithRelativePath(root);
  77. return filters && filters.length ? pushDirectoryFilter : pushDirectory;
  78. }
  79. //#endregion
  80. //#region src/api/functions/push-file.ts
  81. const pushFileFilterAndCount = (filename, _paths, counts, filters) => {
  82. if (filters.every((filter) => filter(filename, false))) counts.files++;
  83. };
  84. const pushFileFilter = (filename, paths, _counts, filters) => {
  85. if (filters.every((filter) => filter(filename, false))) paths.push(filename);
  86. };
  87. const pushFileCount = (_filename, _paths, counts, _filters) => {
  88. counts.files++;
  89. };
  90. const pushFile = (filename, paths) => {
  91. paths.push(filename);
  92. };
  93. const empty$1 = () => {};
  94. function build$5(options) {
  95. const { excludeFiles, filters, onlyCounts } = options;
  96. if (excludeFiles) return empty$1;
  97. if (filters && filters.length) return onlyCounts ? pushFileFilterAndCount : pushFileFilter;
  98. else if (onlyCounts) return pushFileCount;
  99. else return pushFile;
  100. }
  101. //#endregion
  102. //#region src/api/functions/get-array.ts
  103. const getArray = (paths) => {
  104. return paths;
  105. };
  106. const getArrayGroup = () => {
  107. return [""].slice(0, 0);
  108. };
  109. function build$4(options) {
  110. return options.group ? getArrayGroup : getArray;
  111. }
  112. //#endregion
  113. //#region src/api/functions/group-files.ts
  114. const groupFiles = (groups, directory, files) => {
  115. groups.push({
  116. directory,
  117. files,
  118. dir: directory
  119. });
  120. };
  121. const empty = () => {};
  122. function build$3(options) {
  123. return options.group ? groupFiles : empty;
  124. }
  125. //#endregion
  126. //#region src/api/functions/resolve-symlink.ts
  127. const resolveSymlinksAsync = function(path, state, callback$1) {
  128. const { queue, options: { suppressErrors } } = state;
  129. queue.enqueue();
  130. fs.realpath(path, (error, resolvedPath) => {
  131. if (error) return queue.dequeue(suppressErrors ? null : error, state);
  132. fs.stat(resolvedPath, (error$1, stat) => {
  133. if (error$1) return queue.dequeue(suppressErrors ? null : error$1, state);
  134. if (stat.isDirectory() && isRecursive(path, resolvedPath, state)) return queue.dequeue(null, state);
  135. callback$1(stat, resolvedPath);
  136. queue.dequeue(null, state);
  137. });
  138. });
  139. };
  140. const resolveSymlinks = function(path, state, callback$1) {
  141. const { queue, options: { suppressErrors } } = state;
  142. queue.enqueue();
  143. try {
  144. const resolvedPath = fs.realpathSync(path);
  145. const stat = fs.statSync(resolvedPath);
  146. if (stat.isDirectory() && isRecursive(path, resolvedPath, state)) return;
  147. callback$1(stat, resolvedPath);
  148. } catch (e) {
  149. if (!suppressErrors) throw e;
  150. }
  151. };
  152. function build$2(options, isSynchronous) {
  153. if (!options.resolveSymlinks || options.excludeSymlinks) return null;
  154. return isSynchronous ? resolveSymlinks : resolveSymlinksAsync;
  155. }
  156. function isRecursive(path, resolved, state) {
  157. if (state.options.useRealPaths) return isRecursiveUsingRealPaths(resolved, state);
  158. let parent = dirname(path);
  159. let depth = 1;
  160. while (parent !== state.root && depth < 2) {
  161. const resolvedPath = state.symlinks.get(parent);
  162. const isSameRoot = !!resolvedPath && (resolvedPath === resolved || resolvedPath.startsWith(resolved) || resolved.startsWith(resolvedPath));
  163. if (isSameRoot) depth++;
  164. else parent = dirname(parent);
  165. }
  166. state.symlinks.set(path, resolved);
  167. return depth > 1;
  168. }
  169. function isRecursiveUsingRealPaths(resolved, state) {
  170. return state.visited.includes(resolved + state.options.pathSeparator);
  171. }
  172. //#endregion
  173. //#region src/api/functions/invoke-callback.ts
  174. const onlyCountsSync = (state) => {
  175. return state.counts;
  176. };
  177. const groupsSync = (state) => {
  178. return state.groups;
  179. };
  180. const defaultSync = (state) => {
  181. return state.paths;
  182. };
  183. const limitFilesSync = (state) => {
  184. return state.paths.slice(0, state.options.maxFiles);
  185. };
  186. const onlyCountsAsync = (state, error, callback$1) => {
  187. report(error, callback$1, state.counts, state.options.suppressErrors);
  188. return null;
  189. };
  190. const defaultAsync = (state, error, callback$1) => {
  191. report(error, callback$1, state.paths, state.options.suppressErrors);
  192. return null;
  193. };
  194. const limitFilesAsync = (state, error, callback$1) => {
  195. report(error, callback$1, state.paths.slice(0, state.options.maxFiles), state.options.suppressErrors);
  196. return null;
  197. };
  198. const groupsAsync = (state, error, callback$1) => {
  199. report(error, callback$1, state.groups, state.options.suppressErrors);
  200. return null;
  201. };
  202. function report(error, callback$1, output, suppressErrors) {
  203. if (error && !suppressErrors) callback$1(error, output);
  204. else callback$1(null, output);
  205. }
  206. function build$1(options, isSynchronous) {
  207. const { onlyCounts, group, maxFiles } = options;
  208. if (onlyCounts) return isSynchronous ? onlyCountsSync : onlyCountsAsync;
  209. else if (group) return isSynchronous ? groupsSync : groupsAsync;
  210. else if (maxFiles) return isSynchronous ? limitFilesSync : limitFilesAsync;
  211. else return isSynchronous ? defaultSync : defaultAsync;
  212. }
  213. //#endregion
  214. //#region src/api/functions/walk-directory.ts
  215. const readdirOpts = { withFileTypes: true };
  216. const walkAsync = (state, crawlPath, directoryPath, currentDepth, callback$1) => {
  217. state.queue.enqueue();
  218. if (currentDepth <= 0) return state.queue.dequeue(null, state);
  219. state.visited.push(crawlPath);
  220. state.counts.directories++;
  221. fs.readdir(crawlPath || ".", readdirOpts, (error, entries = []) => {
  222. callback$1(entries, directoryPath, currentDepth);
  223. state.queue.dequeue(state.options.suppressErrors ? null : error, state);
  224. });
  225. };
  226. const walkSync = (state, crawlPath, directoryPath, currentDepth, callback$1) => {
  227. if (currentDepth <= 0) return;
  228. state.visited.push(crawlPath);
  229. state.counts.directories++;
  230. let entries = [];
  231. try {
  232. entries = fs.readdirSync(crawlPath || ".", readdirOpts);
  233. } catch (e) {
  234. if (!state.options.suppressErrors) throw e;
  235. }
  236. callback$1(entries, directoryPath, currentDepth);
  237. };
  238. function build(isSynchronous) {
  239. return isSynchronous ? walkSync : walkAsync;
  240. }
  241. //#endregion
  242. //#region src/api/queue.ts
  243. /**
  244. * This is a custom stateless queue to track concurrent async fs calls.
  245. * It increments a counter whenever a call is queued and decrements it
  246. * as soon as it completes. When the counter hits 0, it calls onQueueEmpty.
  247. */
  248. var Queue = class {
  249. count = 0;
  250. constructor(onQueueEmpty) {
  251. this.onQueueEmpty = onQueueEmpty;
  252. }
  253. enqueue() {
  254. this.count++;
  255. return this.count;
  256. }
  257. dequeue(error, output) {
  258. if (this.onQueueEmpty && (--this.count <= 0 || error)) {
  259. this.onQueueEmpty(error, output);
  260. if (error) {
  261. output.controller.abort();
  262. this.onQueueEmpty = void 0;
  263. }
  264. }
  265. }
  266. };
  267. //#endregion
  268. //#region src/api/counter.ts
  269. var Counter = class {
  270. _files = 0;
  271. _directories = 0;
  272. set files(num) {
  273. this._files = num;
  274. }
  275. get files() {
  276. return this._files;
  277. }
  278. set directories(num) {
  279. this._directories = num;
  280. }
  281. get directories() {
  282. return this._directories;
  283. }
  284. /**
  285. * @deprecated use `directories` instead
  286. */
  287. /* c8 ignore next 3 */
  288. get dirs() {
  289. return this._directories;
  290. }
  291. };
  292. //#endregion
  293. //#region src/api/walker.ts
  294. var Walker = class {
  295. root;
  296. isSynchronous;
  297. state;
  298. joinPath;
  299. pushDirectory;
  300. pushFile;
  301. getArray;
  302. groupFiles;
  303. resolveSymlink;
  304. walkDirectory;
  305. callbackInvoker;
  306. constructor(root, options, callback$1) {
  307. this.isSynchronous = !callback$1;
  308. this.callbackInvoker = build$1(options, this.isSynchronous);
  309. this.root = normalizePath(root, options);
  310. this.state = {
  311. root: isRootDirectory(this.root) ? this.root : this.root.slice(0, -1),
  312. paths: [""].slice(0, 0),
  313. groups: [],
  314. counts: new Counter(),
  315. options,
  316. queue: new Queue((error, state) => this.callbackInvoker(state, error, callback$1)),
  317. symlinks: /* @__PURE__ */ new Map(),
  318. visited: [""].slice(0, 0),
  319. controller: new AbortController()
  320. };
  321. this.joinPath = build$7(this.root, options);
  322. this.pushDirectory = build$6(this.root, options);
  323. this.pushFile = build$5(options);
  324. this.getArray = build$4(options);
  325. this.groupFiles = build$3(options);
  326. this.resolveSymlink = build$2(options, this.isSynchronous);
  327. this.walkDirectory = build(this.isSynchronous);
  328. }
  329. start() {
  330. this.pushDirectory(this.root, this.state.paths, this.state.options.filters);
  331. this.walkDirectory(this.state, this.root, this.root, this.state.options.maxDepth, this.walk);
  332. return this.isSynchronous ? this.callbackInvoker(this.state, null) : null;
  333. }
  334. walk = (entries, directoryPath, depth) => {
  335. const { paths, options: { filters, resolveSymlinks: resolveSymlinks$1, excludeSymlinks, exclude, maxFiles, signal, useRealPaths, pathSeparator }, controller } = this.state;
  336. if (controller.signal.aborted || signal && signal.aborted || maxFiles && paths.length > maxFiles) return;
  337. const files = this.getArray(this.state.paths);
  338. for (let i = 0; i < entries.length; ++i) {
  339. const entry = entries[i];
  340. if (entry.isFile() || entry.isSymbolicLink() && !resolveSymlinks$1 && !excludeSymlinks) {
  341. const filename = this.joinPath(entry.name, directoryPath);
  342. this.pushFile(filename, files, this.state.counts, filters);
  343. } else if (entry.isDirectory()) {
  344. let path = joinDirectoryPath(entry.name, directoryPath, this.state.options.pathSeparator);
  345. if (exclude && exclude(entry.name, path)) continue;
  346. this.pushDirectory(path, paths, filters);
  347. this.walkDirectory(this.state, path, path, depth - 1, this.walk);
  348. } else if (this.resolveSymlink && entry.isSymbolicLink()) {
  349. let path = joinPathWithBasePath(entry.name, directoryPath);
  350. this.resolveSymlink(path, this.state, (stat, resolvedPath) => {
  351. if (stat.isDirectory()) {
  352. resolvedPath = normalizePath(resolvedPath, this.state.options);
  353. if (exclude && exclude(entry.name, useRealPaths ? resolvedPath : path + pathSeparator)) return;
  354. this.walkDirectory(this.state, resolvedPath, useRealPaths ? resolvedPath : path + pathSeparator, depth - 1, this.walk);
  355. } else {
  356. resolvedPath = useRealPaths ? resolvedPath : path;
  357. const filename = basename(resolvedPath);
  358. const directoryPath$1 = normalizePath(dirname(resolvedPath), this.state.options);
  359. resolvedPath = this.joinPath(filename, directoryPath$1);
  360. this.pushFile(resolvedPath, files, this.state.counts, filters);
  361. }
  362. });
  363. }
  364. }
  365. this.groupFiles(this.state.groups, directoryPath, files);
  366. };
  367. };
  368. //#endregion
  369. //#region src/api/async.ts
  370. function promise(root, options) {
  371. return new Promise((resolve$1, reject) => {
  372. callback(root, options, (err, output) => {
  373. if (err) return reject(err);
  374. resolve$1(output);
  375. });
  376. });
  377. }
  378. function callback(root, options, callback$1) {
  379. let walker = new Walker(root, options, callback$1);
  380. walker.start();
  381. }
  382. //#endregion
  383. //#region src/api/sync.ts
  384. function sync(root, options) {
  385. const walker = new Walker(root, options);
  386. return walker.start();
  387. }
  388. //#endregion
  389. //#region src/builder/api-builder.ts
  390. var APIBuilder = class {
  391. constructor(root, options) {
  392. this.root = root;
  393. this.options = options;
  394. }
  395. withPromise() {
  396. return promise(this.root, this.options);
  397. }
  398. withCallback(cb) {
  399. callback(this.root, this.options, cb);
  400. }
  401. sync() {
  402. return sync(this.root, this.options);
  403. }
  404. };
  405. //#endregion
  406. //#region src/builder/index.ts
  407. var pm = null;
  408. /* c8 ignore next 6 */
  409. try {
  410. __require.resolve("picomatch");
  411. pm = __require("picomatch");
  412. } catch (_e) {}
  413. var Builder = class {
  414. globCache = {};
  415. options = {
  416. maxDepth: Infinity,
  417. suppressErrors: true,
  418. pathSeparator: sep,
  419. filters: []
  420. };
  421. globFunction;
  422. constructor(options) {
  423. this.options = {
  424. ...this.options,
  425. ...options
  426. };
  427. this.globFunction = this.options.globFunction;
  428. }
  429. group() {
  430. this.options.group = true;
  431. return this;
  432. }
  433. withPathSeparator(separator) {
  434. this.options.pathSeparator = separator;
  435. return this;
  436. }
  437. withBasePath() {
  438. this.options.includeBasePath = true;
  439. return this;
  440. }
  441. withRelativePaths() {
  442. this.options.relativePaths = true;
  443. return this;
  444. }
  445. withDirs() {
  446. this.options.includeDirs = true;
  447. return this;
  448. }
  449. withMaxDepth(depth) {
  450. this.options.maxDepth = depth;
  451. return this;
  452. }
  453. withMaxFiles(limit) {
  454. this.options.maxFiles = limit;
  455. return this;
  456. }
  457. withFullPaths() {
  458. this.options.resolvePaths = true;
  459. this.options.includeBasePath = true;
  460. return this;
  461. }
  462. withErrors() {
  463. this.options.suppressErrors = false;
  464. return this;
  465. }
  466. withSymlinks({ resolvePaths = true } = {}) {
  467. this.options.resolveSymlinks = true;
  468. this.options.useRealPaths = resolvePaths;
  469. return this.withFullPaths();
  470. }
  471. withAbortSignal(signal) {
  472. this.options.signal = signal;
  473. return this;
  474. }
  475. normalize() {
  476. this.options.normalizePath = true;
  477. return this;
  478. }
  479. filter(predicate) {
  480. this.options.filters.push(predicate);
  481. return this;
  482. }
  483. onlyDirs() {
  484. this.options.excludeFiles = true;
  485. this.options.includeDirs = true;
  486. return this;
  487. }
  488. exclude(predicate) {
  489. this.options.exclude = predicate;
  490. return this;
  491. }
  492. onlyCounts() {
  493. this.options.onlyCounts = true;
  494. return this;
  495. }
  496. crawl(root) {
  497. return new APIBuilder(root || ".", this.options);
  498. }
  499. withGlobFunction(fn) {
  500. this.globFunction = fn;
  501. return this;
  502. }
  503. /**
  504. * @deprecated Pass options using the constructor instead:
  505. * ```ts
  506. * new fdir(options).crawl("/path/to/root");
  507. * ```
  508. * This method will be removed in v7.0
  509. */
  510. /* c8 ignore next 4 */
  511. crawlWithOptions(root, options) {
  512. this.options = {
  513. ...this.options,
  514. ...options
  515. };
  516. return new APIBuilder(root || ".", this.options);
  517. }
  518. glob(...patterns) {
  519. if (this.globFunction) return this.globWithOptions(patterns);
  520. return this.globWithOptions(patterns, ...[{ dot: true }]);
  521. }
  522. globWithOptions(patterns, ...options) {
  523. const globFn = this.globFunction || pm;
  524. /* c8 ignore next 5 */
  525. if (!globFn) throw new Error("Please specify a glob function to use glob matching.");
  526. var isMatch = this.globCache[patterns.join("\0")];
  527. if (!isMatch) {
  528. isMatch = globFn(patterns, ...options);
  529. this.globCache[patterns.join("\0")] = isMatch;
  530. }
  531. this.options.filters.push((path) => isMatch(path));
  532. return this;
  533. }
  534. };
  535. //#endregion
  536. export { Builder as fdir };