提交学习笔记专用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

569 lines
17 KiB

  1. import { createRequire } from "module";
  2. import { basename, dirname, normalize, relative, resolve, sep } from "path";
  3. import * as nativeFs 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.slice(root.length) + 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, fs, 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, fs, 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. const { fs } = state;
  220. state.visited.push(crawlPath);
  221. state.counts.directories++;
  222. fs.readdir(crawlPath || ".", readdirOpts, (error, entries = []) => {
  223. callback$1(entries, directoryPath, currentDepth);
  224. state.queue.dequeue(state.options.suppressErrors ? null : error, state);
  225. });
  226. };
  227. const walkSync = (state, crawlPath, directoryPath, currentDepth, callback$1) => {
  228. const { fs } = state;
  229. if (currentDepth < 0) return;
  230. state.visited.push(crawlPath);
  231. state.counts.directories++;
  232. let entries = [];
  233. try {
  234. entries = fs.readdirSync(crawlPath || ".", readdirOpts);
  235. } catch (e) {
  236. if (!state.options.suppressErrors) throw e;
  237. }
  238. callback$1(entries, directoryPath, currentDepth);
  239. };
  240. function build(isSynchronous) {
  241. return isSynchronous ? walkSync : walkAsync;
  242. }
  243. //#endregion
  244. //#region src/api/queue.ts
  245. /**
  246. * This is a custom stateless queue to track concurrent async fs calls.
  247. * It increments a counter whenever a call is queued and decrements it
  248. * as soon as it completes. When the counter hits 0, it calls onQueueEmpty.
  249. */
  250. var Queue = class {
  251. count = 0;
  252. constructor(onQueueEmpty) {
  253. this.onQueueEmpty = onQueueEmpty;
  254. }
  255. enqueue() {
  256. this.count++;
  257. return this.count;
  258. }
  259. dequeue(error, output) {
  260. if (this.onQueueEmpty && (--this.count <= 0 || error)) {
  261. this.onQueueEmpty(error, output);
  262. if (error) {
  263. output.controller.abort();
  264. this.onQueueEmpty = void 0;
  265. }
  266. }
  267. }
  268. };
  269. //#endregion
  270. //#region src/api/counter.ts
  271. var Counter = class {
  272. _files = 0;
  273. _directories = 0;
  274. set files(num) {
  275. this._files = num;
  276. }
  277. get files() {
  278. return this._files;
  279. }
  280. set directories(num) {
  281. this._directories = num;
  282. }
  283. get directories() {
  284. return this._directories;
  285. }
  286. /**
  287. * @deprecated use `directories` instead
  288. */
  289. /* c8 ignore next 3 */
  290. get dirs() {
  291. return this._directories;
  292. }
  293. };
  294. //#endregion
  295. //#region src/api/aborter.ts
  296. /**
  297. * AbortController is not supported on Node 14 so we use this until we can drop
  298. * support for Node 14.
  299. */
  300. var Aborter = class {
  301. aborted = false;
  302. abort() {
  303. this.aborted = true;
  304. }
  305. };
  306. //#endregion
  307. //#region src/api/walker.ts
  308. var Walker = class {
  309. root;
  310. isSynchronous;
  311. state;
  312. joinPath;
  313. pushDirectory;
  314. pushFile;
  315. getArray;
  316. groupFiles;
  317. resolveSymlink;
  318. walkDirectory;
  319. callbackInvoker;
  320. constructor(root, options, callback$1) {
  321. this.isSynchronous = !callback$1;
  322. this.callbackInvoker = build$1(options, this.isSynchronous);
  323. this.root = normalizePath(root, options);
  324. this.state = {
  325. root: isRootDirectory(this.root) ? this.root : this.root.slice(0, -1),
  326. paths: [""].slice(0, 0),
  327. groups: [],
  328. counts: new Counter(),
  329. options,
  330. queue: new Queue((error, state) => this.callbackInvoker(state, error, callback$1)),
  331. symlinks: /* @__PURE__ */ new Map(),
  332. visited: [""].slice(0, 0),
  333. controller: new Aborter(),
  334. fs: options.fs || nativeFs
  335. };
  336. this.joinPath = build$7(this.root, options);
  337. this.pushDirectory = build$6(this.root, options);
  338. this.pushFile = build$5(options);
  339. this.getArray = build$4(options);
  340. this.groupFiles = build$3(options);
  341. this.resolveSymlink = build$2(options, this.isSynchronous);
  342. this.walkDirectory = build(this.isSynchronous);
  343. }
  344. start() {
  345. this.pushDirectory(this.root, this.state.paths, this.state.options.filters);
  346. this.walkDirectory(this.state, this.root, this.root, this.state.options.maxDepth, this.walk);
  347. return this.isSynchronous ? this.callbackInvoker(this.state, null) : null;
  348. }
  349. walk = (entries, directoryPath, depth) => {
  350. const { paths, options: { filters, resolveSymlinks: resolveSymlinks$1, excludeSymlinks, exclude, maxFiles, signal, useRealPaths, pathSeparator }, controller } = this.state;
  351. if (controller.aborted || signal && signal.aborted || maxFiles && paths.length > maxFiles) return;
  352. const files = this.getArray(this.state.paths);
  353. for (let i = 0; i < entries.length; ++i) {
  354. const entry = entries[i];
  355. if (entry.isFile() || entry.isSymbolicLink() && !resolveSymlinks$1 && !excludeSymlinks) {
  356. const filename = this.joinPath(entry.name, directoryPath);
  357. this.pushFile(filename, files, this.state.counts, filters);
  358. } else if (entry.isDirectory()) {
  359. let path = joinDirectoryPath(entry.name, directoryPath, this.state.options.pathSeparator);
  360. if (exclude && exclude(entry.name, path)) continue;
  361. this.pushDirectory(path, paths, filters);
  362. this.walkDirectory(this.state, path, path, depth - 1, this.walk);
  363. } else if (this.resolveSymlink && entry.isSymbolicLink()) {
  364. let path = joinPathWithBasePath(entry.name, directoryPath);
  365. this.resolveSymlink(path, this.state, (stat, resolvedPath) => {
  366. if (stat.isDirectory()) {
  367. resolvedPath = normalizePath(resolvedPath, this.state.options);
  368. if (exclude && exclude(entry.name, useRealPaths ? resolvedPath : path + pathSeparator)) return;
  369. this.walkDirectory(this.state, resolvedPath, useRealPaths ? resolvedPath : path + pathSeparator, depth - 1, this.walk);
  370. } else {
  371. resolvedPath = useRealPaths ? resolvedPath : path;
  372. const filename = basename(resolvedPath);
  373. const directoryPath$1 = normalizePath(dirname(resolvedPath), this.state.options);
  374. resolvedPath = this.joinPath(filename, directoryPath$1);
  375. this.pushFile(resolvedPath, files, this.state.counts, filters);
  376. }
  377. });
  378. }
  379. }
  380. this.groupFiles(this.state.groups, directoryPath, files);
  381. };
  382. };
  383. //#endregion
  384. //#region src/api/async.ts
  385. function promise(root, options) {
  386. return new Promise((resolve$1, reject) => {
  387. callback(root, options, (err, output) => {
  388. if (err) return reject(err);
  389. resolve$1(output);
  390. });
  391. });
  392. }
  393. function callback(root, options, callback$1) {
  394. let walker = new Walker(root, options, callback$1);
  395. walker.start();
  396. }
  397. //#endregion
  398. //#region src/api/sync.ts
  399. function sync(root, options) {
  400. const walker = new Walker(root, options);
  401. return walker.start();
  402. }
  403. //#endregion
  404. //#region src/builder/api-builder.ts
  405. var APIBuilder = class {
  406. constructor(root, options) {
  407. this.root = root;
  408. this.options = options;
  409. }
  410. withPromise() {
  411. return promise(this.root, this.options);
  412. }
  413. withCallback(cb) {
  414. callback(this.root, this.options, cb);
  415. }
  416. sync() {
  417. return sync(this.root, this.options);
  418. }
  419. };
  420. //#endregion
  421. //#region src/builder/index.ts
  422. let pm = null;
  423. /* c8 ignore next 6 */
  424. try {
  425. __require.resolve("picomatch");
  426. pm = __require("picomatch");
  427. } catch {}
  428. var Builder = class {
  429. globCache = {};
  430. options = {
  431. maxDepth: Infinity,
  432. suppressErrors: true,
  433. pathSeparator: sep,
  434. filters: []
  435. };
  436. globFunction;
  437. constructor(options) {
  438. this.options = {
  439. ...this.options,
  440. ...options
  441. };
  442. this.globFunction = this.options.globFunction;
  443. }
  444. group() {
  445. this.options.group = true;
  446. return this;
  447. }
  448. withPathSeparator(separator) {
  449. this.options.pathSeparator = separator;
  450. return this;
  451. }
  452. withBasePath() {
  453. this.options.includeBasePath = true;
  454. return this;
  455. }
  456. withRelativePaths() {
  457. this.options.relativePaths = true;
  458. return this;
  459. }
  460. withDirs() {
  461. this.options.includeDirs = true;
  462. return this;
  463. }
  464. withMaxDepth(depth) {
  465. this.options.maxDepth = depth;
  466. return this;
  467. }
  468. withMaxFiles(limit) {
  469. this.options.maxFiles = limit;
  470. return this;
  471. }
  472. withFullPaths() {
  473. this.options.resolvePaths = true;
  474. this.options.includeBasePath = true;
  475. return this;
  476. }
  477. withErrors() {
  478. this.options.suppressErrors = false;
  479. return this;
  480. }
  481. withSymlinks({ resolvePaths = true } = {}) {
  482. this.options.resolveSymlinks = true;
  483. this.options.useRealPaths = resolvePaths;
  484. return this.withFullPaths();
  485. }
  486. withAbortSignal(signal) {
  487. this.options.signal = signal;
  488. return this;
  489. }
  490. normalize() {
  491. this.options.normalizePath = true;
  492. return this;
  493. }
  494. filter(predicate) {
  495. this.options.filters.push(predicate);
  496. return this;
  497. }
  498. onlyDirs() {
  499. this.options.excludeFiles = true;
  500. this.options.includeDirs = true;
  501. return this;
  502. }
  503. exclude(predicate) {
  504. this.options.exclude = predicate;
  505. return this;
  506. }
  507. onlyCounts() {
  508. this.options.onlyCounts = true;
  509. return this;
  510. }
  511. crawl(root) {
  512. return new APIBuilder(root || ".", this.options);
  513. }
  514. withGlobFunction(fn) {
  515. this.globFunction = fn;
  516. return this;
  517. }
  518. /**
  519. * @deprecated Pass options using the constructor instead:
  520. * ```ts
  521. * new fdir(options).crawl("/path/to/root");
  522. * ```
  523. * This method will be removed in v7.0
  524. */
  525. /* c8 ignore next 4 */
  526. crawlWithOptions(root, options) {
  527. this.options = {
  528. ...this.options,
  529. ...options
  530. };
  531. return new APIBuilder(root || ".", this.options);
  532. }
  533. glob(...patterns) {
  534. if (this.globFunction) return this.globWithOptions(patterns);
  535. return this.globWithOptions(patterns, ...[{ dot: true }]);
  536. }
  537. globWithOptions(patterns, ...options) {
  538. const globFn = this.globFunction || pm;
  539. /* c8 ignore next 5 */
  540. if (!globFn) throw new Error("Please specify a glob function to use glob matching.");
  541. var isMatch = this.globCache[patterns.join("\0")];
  542. if (!isMatch) {
  543. isMatch = globFn(patterns, ...options);
  544. this.globCache[patterns.join("\0")] = isMatch;
  545. }
  546. this.options.filters.push((path) => isMatch(path));
  547. return this;
  548. }
  549. };
  550. //#endregion
  551. export { Builder as fdir };