@zthun/crumbtrail-fs 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ # MIT License
2
+
3
+ Copyright © 2025 Anthony Bonta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # Description
2
+
3
+ Crumbtrail is additional file system methods for node that are helpful and are
4
+ better suited for unit testing.
5
+
6
+ ## Installation
7
+
8
+ ```sh
9
+ npm install @zthun/crumbtrail-fs
10
+ yarn add @zthun/crumbtrail-fs
11
+ ```
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Represents a type of a file system node.
3
+ */
4
+ export declare enum ZFileSystemNodeType {
5
+ File = "file",
6
+ Folder = "folder"
7
+ }
8
+ export interface IZFileSystemNode {
9
+ /**
10
+ * The absolute path of the node.
11
+ */
12
+ path: string;
13
+ /**
14
+ * The type of the node (file or folder).
15
+ */
16
+ type: ZFileSystemNodeType;
17
+ /**
18
+ * The creation date of the node.
19
+ */
20
+ created?: string;
21
+ /**
22
+ * The modification date of the node.
23
+ */
24
+ updated?: string;
25
+ /**
26
+ * The size of the node in bytes.
27
+ */
28
+ size?: bigint;
29
+ }
30
+ /**
31
+ * Represents a builder for a file system node.
32
+ */
33
+ export declare class ZFileSystemNodeBuilder {
34
+ private _node;
35
+ constructor();
36
+ created(at?: Date | string | undefined): this;
37
+ updated(at?: Date | string | undefined): this;
38
+ size(bytes: bigint | number | undefined): this;
39
+ path(path: string): this;
40
+ type(type: ZFileSystemNodeType): this;
41
+ file: () => this;
42
+ folder: () => this;
43
+ build(): IZFileSystemNode;
44
+ }
@@ -0,0 +1,51 @@
1
+ import { IZFileSystemNode } from './file-system-node.mjs';
2
+ /**
3
+ * Represents options for searches.
4
+ */
5
+ export interface IZFileSystemSearchOptions {
6
+ /**
7
+ * The directory to search in.
8
+ *
9
+ * If this is not set, then the cwd of the application is used.
10
+ */
11
+ cwd?: string;
12
+ }
13
+ /**
14
+ * Represents a service to enumerate the file system.
15
+ */
16
+ export interface IZFileSystemService {
17
+ /**
18
+ * Gets information about a node.
19
+ *
20
+ * @param path -
21
+ * The path to get information for.
22
+ *
23
+ * @returns
24
+ * Information about the node.
25
+ */
26
+ info(path: string): Promise<IZFileSystemNode>;
27
+ /**
28
+ * Reads all nodes that match a pattern.
29
+ *
30
+ * @param pattern -
31
+ * The pattern string to match against.
32
+ * @param options -
33
+ * The options for the search.
34
+ *
35
+ * @returns
36
+ * A list of path strings that match the given patterns.
37
+ */
38
+ search(pattern: string, options?: IZFileSystemSearchOptions): Promise<IZFileSystemNode[]>;
39
+ }
40
+ /**
41
+ * A token that you can use with a framework like nest-js to describe
42
+ * a file system service implementation.
43
+ */
44
+ export declare const ZFileSystemToken: unique symbol;
45
+ /**
46
+ * Represents a node implementation of a file system service.
47
+ */
48
+ export declare class ZFileSystemService implements IZFileSystemService {
49
+ info(path: string): Promise<IZFileSystemNode>;
50
+ search(pattern: string | string[], options?: IZFileSystemSearchOptions): Promise<IZFileSystemNode[]>;
51
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const lodashEs = require("lodash-es");
4
+ const glob = require("glob");
5
+ const promises = require("node:fs/promises");
6
+ const node_path = require("node:path");
7
+ const node_process = require("node:process");
8
+ var ZFileSystemNodeType = /* @__PURE__ */ ((ZFileSystemNodeType2) => {
9
+ ZFileSystemNodeType2["File"] = "file";
10
+ ZFileSystemNodeType2["Folder"] = "folder";
11
+ return ZFileSystemNodeType2;
12
+ })(ZFileSystemNodeType || {});
13
+ class ZFileSystemNodeBuilder {
14
+ constructor() {
15
+ this.file = this.type.bind(
16
+ this,
17
+ "file"
18
+ /* File */
19
+ );
20
+ this.folder = this.type.bind(
21
+ this,
22
+ "folder"
23
+ /* Folder */
24
+ );
25
+ this._node = {
26
+ path: "/",
27
+ type: "folder"
28
+ /* Folder */
29
+ };
30
+ }
31
+ created(at) {
32
+ this._node.created = at instanceof Date ? at.toJSON() : at;
33
+ return this;
34
+ }
35
+ updated(at) {
36
+ this._node.updated = at instanceof Date ? at.toJSON() : at;
37
+ return this;
38
+ }
39
+ size(bytes) {
40
+ this._node.size = typeof bytes == "number" ? BigInt(bytes) : bytes;
41
+ return this;
42
+ }
43
+ path(path) {
44
+ this._node.path = path;
45
+ return this;
46
+ }
47
+ type(type) {
48
+ this._node.type = type;
49
+ return this;
50
+ }
51
+ build() {
52
+ const clone = structuredClone(this._node);
53
+ return lodashEs.omitBy(clone, lodashEs.isUndefined);
54
+ }
55
+ }
56
+ const ZFileSystemToken = Symbol("file-system");
57
+ class ZFileSystemService {
58
+ async info(path) {
59
+ const [file] = await this.search(path);
60
+ return file;
61
+ }
62
+ async search(pattern, options) {
63
+ const paths = await glob.glob(pattern, {
64
+ ...options,
65
+ stat: true,
66
+ withFileTypes: true
67
+ });
68
+ return paths.map((path) => {
69
+ let info = new ZFileSystemNodeBuilder().path(path.fullpath()).created(path.birthtime).updated(path.atime).size(path.size);
70
+ info = path.isFile() ? info.file() : info.folder().size(void 0);
71
+ return info.build();
72
+ });
73
+ }
74
+ }
75
+ async function walk(search, options) {
76
+ const start = (options == null ? void 0 : options.start) || node_process.cwd();
77
+ const stop = (options == null ? void 0 : options.stop) || "/";
78
+ const mode = options == null ? void 0 : options.mode;
79
+ const _test = async (path) => {
80
+ try {
81
+ await promises.access(path, mode);
82
+ return true;
83
+ } catch {
84
+ return false;
85
+ }
86
+ };
87
+ if (node_path.isAbsolute(search)) {
88
+ return await _test(search) ? search : null;
89
+ }
90
+ let dir = start;
91
+ do {
92
+ const path = node_path.resolve(dir, search);
93
+ const exists = await _test(path);
94
+ if (exists) {
95
+ return path;
96
+ }
97
+ dir = dir === stop ? "" : node_path.dirname(dir);
98
+ } while (dir.length);
99
+ return null;
100
+ }
101
+ exports.ZFileSystemNodeBuilder = ZFileSystemNodeBuilder;
102
+ exports.ZFileSystemNodeType = ZFileSystemNodeType;
103
+ exports.ZFileSystemService = ZFileSystemService;
104
+ exports.ZFileSystemToken = ZFileSystemToken;
105
+ exports.walk = walk;
106
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/file-system/file-system-node.mts","../src/file-system/file-system-service.mts","../src/walk/walk.mts"],"sourcesContent":["import { isUndefined, omitBy } from \"lodash-es\";\n\n/**\n * Represents a type of a file system node.\n */\nexport enum ZFileSystemNodeType {\n File = \"file\",\n Folder = \"folder\",\n}\n\nexport interface IZFileSystemNode {\n /**\n * The absolute path of the node.\n */\n path: string;\n\n /**\n * The type of the node (file or folder).\n */\n type: ZFileSystemNodeType;\n\n /**\n * The creation date of the node.\n */\n created?: string;\n\n /**\n * The modification date of the node.\n */\n updated?: string;\n\n /**\n * The size of the node in bytes.\n */\n size?: bigint;\n}\n\n/**\n * Represents a builder for a file system node.\n */\nexport class ZFileSystemNodeBuilder {\n private _node: IZFileSystemNode;\n\n public constructor() {\n this._node = {\n path: \"/\",\n type: ZFileSystemNodeType.Folder,\n };\n }\n\n public created(at?: Date | string | undefined) {\n this._node.created = at instanceof Date ? at.toJSON() : at;\n return this;\n }\n\n public updated(at?: Date | string | undefined) {\n this._node.updated = at instanceof Date ? at.toJSON() : at;\n return this;\n }\n\n public size(bytes: bigint | number | undefined) {\n this._node.size = typeof bytes == \"number\" ? BigInt(bytes) : bytes;\n return this;\n }\n\n public path(path: string) {\n this._node.path = path;\n return this;\n }\n\n public type(type: ZFileSystemNodeType) {\n this._node.type = type;\n return this;\n }\n\n public file = this.type.bind(this, ZFileSystemNodeType.File);\n public folder = this.type.bind(this, ZFileSystemNodeType.Folder);\n\n public build() {\n const clone = structuredClone(this._node);\n return omitBy<IZFileSystemNode>(clone, isUndefined) as IZFileSystemNode;\n }\n}\n","import { glob } from \"glob\";\nimport {\n IZFileSystemNode,\n ZFileSystemNodeBuilder,\n} from \"./file-system-node.mjs\";\n\n/**\n * Represents options for searches.\n */\nexport interface IZFileSystemSearchOptions {\n /**\n * The directory to search in.\n *\n * If this is not set, then the cwd of the application is used.\n */\n cwd?: string;\n}\n\n/**\n * Represents a service to enumerate the file system.\n */\nexport interface IZFileSystemService {\n /**\n * Gets information about a node.\n *\n * @param path -\n * The path to get information for.\n *\n * @returns\n * Information about the node.\n */\n info(path: string): Promise<IZFileSystemNode>;\n\n /**\n * Reads all nodes that match a pattern.\n *\n * @param pattern -\n * The pattern string to match against.\n * @param options -\n * The options for the search.\n *\n * @returns\n * A list of path strings that match the given patterns.\n */\n search(\n pattern: string,\n options?: IZFileSystemSearchOptions,\n ): Promise<IZFileSystemNode[]>;\n}\n\n/**\n * A token that you can use with a framework like nest-js to describe\n * a file system service implementation.\n */\nexport const ZFileSystemToken = Symbol(\"file-system\");\n\n/**\n * Represents a node implementation of a file system service.\n */\nexport class ZFileSystemService implements IZFileSystemService {\n public async info(path: string): Promise<IZFileSystemNode> {\n const [file] = await this.search(path);\n return file;\n }\n\n public async search(\n pattern: string | string[],\n options?: IZFileSystemSearchOptions,\n ): Promise<IZFileSystemNode[]> {\n const paths = await glob(pattern, {\n ...options,\n stat: true,\n withFileTypes: true,\n });\n\n return paths.map((path) => {\n let info = new ZFileSystemNodeBuilder()\n .path(path.fullpath())\n .created(path.birthtime)\n .updated(path.atime)\n .size(path.size);\n\n // Note that for folders, node will return the size of the underlying OS metadata,\n // hence, why we 0 out the size on a folder here.\n info = path.isFile() ? info.file() : info.folder().size(undefined);\n\n return info.build();\n });\n }\n}\n","import { access } from \"node:fs/promises\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { cwd } from \"node:process\";\n\n/**\n * Options for where a directory walk starts.\n */\nexport interface ZWalkOptions {\n /**\n * The starting directory.\n *\n * The default is the current working directory\n *\n * @see process.cwd for more information.\n */\n start?: string;\n\n /**\n * The directory to stop at.\n *\n * The default is the root of the file system.\n */\n stop?: string;\n\n /**\n * The type of mode that the path must support in order\n * to be approved.\n *\n * The default is undefined and left up to the node api.\n */\n mode?: number;\n}\n\n/**\n * Walks up a directory tree to search for existence of a given path.\n *\n * @param search -\n * The path to search for. This is the candidate folder path that\n * may or may not exist somewhere from the start directory all the\n * way up the directory tree.\n * @param options -\n * The given options for the search.\n *\n * @returns\n * The fully qualified path to the first path that matches the search\n * starting at the options start or the current working directory if not\n * specified. Returns null if no such directory exists.\n *\n * @example\n *\n * ```ts\n * import { resolve } from 'path'\n *\n * // Find any folder named .vscode from the current working directory all the way\n * // up the directory tree. Returns null if no vscode directory can be found.\n * const vscode = walk('.vscode');\n *\n * // Walk the directory tree from the current script and find the .config folder.\n * const config = walk('.config', { start: __dirname });\n *\n * // Walk the directory tree from the current script and stop when we reach\n * // at most 2 directories up.\n * const start = __dirname;\n * const stop = resolve(__dirname, '../..');\n * const file = walk('path/to/file.json', { start, stop });\n * ```\n */\nexport async function walk(\n search: string,\n options?: ZWalkOptions,\n): Promise<string | null> {\n const start = options?.start || cwd();\n const stop = options?.stop || \"/\";\n const mode = options?.mode;\n\n const _test = async (path: string) => {\n try {\n await access(path, mode);\n return true;\n } catch {\n return false;\n }\n };\n\n if (isAbsolute(search)) {\n return (await _test(search)) ? search : null;\n }\n\n let dir = start;\n do {\n const path = resolve(dir, search);\n const exists = await _test(path);\n\n if (exists) {\n return path;\n }\n\n dir = dir === stop ? \"\" : dirname(dir);\n } while (dir.length);\n\n return null;\n}\n"],"names":["ZFileSystemNodeType","omitBy","isUndefined","glob","cwd","access","isAbsolute","resolve","dirname"],"mappings":";;;;;;;AAKY,IAAA,wCAAAA,yBAAL;AACLA,uBAAA,MAAO,IAAA;AACPA,uBAAA,QAAS,IAAA;AAFCA,SAAAA;AAAA,GAAA,uBAAA,CAAA,CAAA;AAmCL,MAAM,uBAAuB;AAAA,EAG3B,cAAc;AAgCrB,SAAO,OAAO,KAAK,KAAK;AAAA,MAAK;AAAA,MAAM;AAAA;AAAA,IAAwB;AAC3D,SAAO,SAAS,KAAK,KAAK;AAAA,MAAK;AAAA,MAAM;AAAA;AAAA,IAA0B;AAhC7D,SAAK,QAAQ;AAAA,MACX,MAAM;AAAA,MACN,MAAM;AAAA;AAAA,IACR;AAAA,EAAA;AAAA,EAGK,QAAQ,IAAgC;AAC7C,SAAK,MAAM,UAAU,cAAc,OAAO,GAAG,WAAW;AACjD,WAAA;AAAA,EAAA;AAAA,EAGF,QAAQ,IAAgC;AAC7C,SAAK,MAAM,UAAU,cAAc,OAAO,GAAG,WAAW;AACjD,WAAA;AAAA,EAAA;AAAA,EAGF,KAAK,OAAoC;AAC9C,SAAK,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,KAAK,IAAI;AACtD,WAAA;AAAA,EAAA;AAAA,EAGF,KAAK,MAAc;AACxB,SAAK,MAAM,OAAO;AACX,WAAA;AAAA,EAAA;AAAA,EAGF,KAAK,MAA2B;AACrC,SAAK,MAAM,OAAO;AACX,WAAA;AAAA,EAAA;AAAA,EAMF,QAAQ;AACP,UAAA,QAAQ,gBAAgB,KAAK,KAAK;AACjC,WAAAC,SAAA,OAAyB,OAAOC,oBAAW;AAAA,EAAA;AAEtD;AC5Ba,MAAA,mBAAmB,OAAO,aAAa;AAK7C,MAAM,mBAAkD;AAAA,EAC7D,MAAa,KAAK,MAAyC;AACzD,UAAM,CAAC,IAAI,IAAI,MAAM,KAAK,OAAO,IAAI;AAC9B,WAAA;AAAA,EAAA;AAAA,EAGT,MAAa,OACX,SACA,SAC6B;AACvB,UAAA,QAAQ,MAAMC,KAAA,KAAK,SAAS;AAAA,MAChC,GAAG;AAAA,MACH,MAAM;AAAA,MACN,eAAe;AAAA,IAAA,CAChB;AAEM,WAAA,MAAM,IAAI,CAAC,SAAS;AACzB,UAAI,OAAO,IAAI,yBACZ,KAAK,KAAK,SAAU,CAAA,EACpB,QAAQ,KAAK,SAAS,EACtB,QAAQ,KAAK,KAAK,EAClB,KAAK,KAAK,IAAI;AAIV,aAAA,KAAK,OAAO,IAAI,KAAK,SAAS,KAAK,OAAA,EAAS,KAAK,MAAS;AAEjE,aAAO,KAAK,MAAM;AAAA,IAAA,CACnB;AAAA,EAAA;AAEL;ACtBsB,eAAA,KACpB,QACA,SACwB;AAClB,QAAA,SAAQ,mCAAS,UAASC,iBAAI;AAC9B,QAAA,QAAO,mCAAS,SAAQ;AAC9B,QAAM,OAAO,mCAAS;AAEhB,QAAA,QAAQ,OAAO,SAAiB;AAChC,QAAA;AACI,YAAAC,SAAA,OAAO,MAAM,IAAI;AAChB,aAAA;AAAA,IAAA,QACD;AACC,aAAA;AAAA,IAAA;AAAA,EAEX;AAEI,MAAAC,UAAAA,WAAW,MAAM,GAAG;AACtB,WAAQ,MAAM,MAAM,MAAM,IAAK,SAAS;AAAA,EAAA;AAG1C,MAAI,MAAM;AACP,KAAA;AACK,UAAA,OAAOC,UAAAA,QAAQ,KAAK,MAAM;AAC1B,UAAA,SAAS,MAAM,MAAM,IAAI;AAE/B,QAAI,QAAQ;AACH,aAAA;AAAA,IAAA;AAGT,UAAM,QAAQ,OAAO,KAAKC,UAAAA,QAAQ,GAAG;AAAA,WAC9B,IAAI;AAEN,SAAA;AACT;;;;;;"}
@@ -0,0 +1,3 @@
1
+ export * from './file-system/file-system-node.mjs';
2
+ export * from './file-system/file-system-service.mjs';
3
+ export * from './walk/walk.mjs';
package/dist/index.js ADDED
@@ -0,0 +1,106 @@
1
+ import { omitBy, isUndefined } from "lodash-es";
2
+ import { glob } from "glob";
3
+ import { access } from "node:fs/promises";
4
+ import { isAbsolute, resolve, dirname } from "node:path";
5
+ import { cwd } from "node:process";
6
+ var ZFileSystemNodeType = /* @__PURE__ */ ((ZFileSystemNodeType2) => {
7
+ ZFileSystemNodeType2["File"] = "file";
8
+ ZFileSystemNodeType2["Folder"] = "folder";
9
+ return ZFileSystemNodeType2;
10
+ })(ZFileSystemNodeType || {});
11
+ class ZFileSystemNodeBuilder {
12
+ constructor() {
13
+ this.file = this.type.bind(
14
+ this,
15
+ "file"
16
+ /* File */
17
+ );
18
+ this.folder = this.type.bind(
19
+ this,
20
+ "folder"
21
+ /* Folder */
22
+ );
23
+ this._node = {
24
+ path: "/",
25
+ type: "folder"
26
+ /* Folder */
27
+ };
28
+ }
29
+ created(at) {
30
+ this._node.created = at instanceof Date ? at.toJSON() : at;
31
+ return this;
32
+ }
33
+ updated(at) {
34
+ this._node.updated = at instanceof Date ? at.toJSON() : at;
35
+ return this;
36
+ }
37
+ size(bytes) {
38
+ this._node.size = typeof bytes == "number" ? BigInt(bytes) : bytes;
39
+ return this;
40
+ }
41
+ path(path) {
42
+ this._node.path = path;
43
+ return this;
44
+ }
45
+ type(type) {
46
+ this._node.type = type;
47
+ return this;
48
+ }
49
+ build() {
50
+ const clone = structuredClone(this._node);
51
+ return omitBy(clone, isUndefined);
52
+ }
53
+ }
54
+ const ZFileSystemToken = Symbol("file-system");
55
+ class ZFileSystemService {
56
+ async info(path) {
57
+ const [file] = await this.search(path);
58
+ return file;
59
+ }
60
+ async search(pattern, options) {
61
+ const paths = await glob(pattern, {
62
+ ...options,
63
+ stat: true,
64
+ withFileTypes: true
65
+ });
66
+ return paths.map((path) => {
67
+ let info = new ZFileSystemNodeBuilder().path(path.fullpath()).created(path.birthtime).updated(path.atime).size(path.size);
68
+ info = path.isFile() ? info.file() : info.folder().size(void 0);
69
+ return info.build();
70
+ });
71
+ }
72
+ }
73
+ async function walk(search, options) {
74
+ const start = (options == null ? void 0 : options.start) || cwd();
75
+ const stop = (options == null ? void 0 : options.stop) || "/";
76
+ const mode = options == null ? void 0 : options.mode;
77
+ const _test = async (path) => {
78
+ try {
79
+ await access(path, mode);
80
+ return true;
81
+ } catch {
82
+ return false;
83
+ }
84
+ };
85
+ if (isAbsolute(search)) {
86
+ return await _test(search) ? search : null;
87
+ }
88
+ let dir = start;
89
+ do {
90
+ const path = resolve(dir, search);
91
+ const exists = await _test(path);
92
+ if (exists) {
93
+ return path;
94
+ }
95
+ dir = dir === stop ? "" : dirname(dir);
96
+ } while (dir.length);
97
+ return null;
98
+ }
99
+ export {
100
+ ZFileSystemNodeBuilder,
101
+ ZFileSystemNodeType,
102
+ ZFileSystemService,
103
+ ZFileSystemToken,
104
+ walk
105
+ };
106
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/file-system/file-system-node.mts","../src/file-system/file-system-service.mts","../src/walk/walk.mts"],"sourcesContent":["import { isUndefined, omitBy } from \"lodash-es\";\n\n/**\n * Represents a type of a file system node.\n */\nexport enum ZFileSystemNodeType {\n File = \"file\",\n Folder = \"folder\",\n}\n\nexport interface IZFileSystemNode {\n /**\n * The absolute path of the node.\n */\n path: string;\n\n /**\n * The type of the node (file or folder).\n */\n type: ZFileSystemNodeType;\n\n /**\n * The creation date of the node.\n */\n created?: string;\n\n /**\n * The modification date of the node.\n */\n updated?: string;\n\n /**\n * The size of the node in bytes.\n */\n size?: bigint;\n}\n\n/**\n * Represents a builder for a file system node.\n */\nexport class ZFileSystemNodeBuilder {\n private _node: IZFileSystemNode;\n\n public constructor() {\n this._node = {\n path: \"/\",\n type: ZFileSystemNodeType.Folder,\n };\n }\n\n public created(at?: Date | string | undefined) {\n this._node.created = at instanceof Date ? at.toJSON() : at;\n return this;\n }\n\n public updated(at?: Date | string | undefined) {\n this._node.updated = at instanceof Date ? at.toJSON() : at;\n return this;\n }\n\n public size(bytes: bigint | number | undefined) {\n this._node.size = typeof bytes == \"number\" ? BigInt(bytes) : bytes;\n return this;\n }\n\n public path(path: string) {\n this._node.path = path;\n return this;\n }\n\n public type(type: ZFileSystemNodeType) {\n this._node.type = type;\n return this;\n }\n\n public file = this.type.bind(this, ZFileSystemNodeType.File);\n public folder = this.type.bind(this, ZFileSystemNodeType.Folder);\n\n public build() {\n const clone = structuredClone(this._node);\n return omitBy<IZFileSystemNode>(clone, isUndefined) as IZFileSystemNode;\n }\n}\n","import { glob } from \"glob\";\nimport {\n IZFileSystemNode,\n ZFileSystemNodeBuilder,\n} from \"./file-system-node.mjs\";\n\n/**\n * Represents options for searches.\n */\nexport interface IZFileSystemSearchOptions {\n /**\n * The directory to search in.\n *\n * If this is not set, then the cwd of the application is used.\n */\n cwd?: string;\n}\n\n/**\n * Represents a service to enumerate the file system.\n */\nexport interface IZFileSystemService {\n /**\n * Gets information about a node.\n *\n * @param path -\n * The path to get information for.\n *\n * @returns\n * Information about the node.\n */\n info(path: string): Promise<IZFileSystemNode>;\n\n /**\n * Reads all nodes that match a pattern.\n *\n * @param pattern -\n * The pattern string to match against.\n * @param options -\n * The options for the search.\n *\n * @returns\n * A list of path strings that match the given patterns.\n */\n search(\n pattern: string,\n options?: IZFileSystemSearchOptions,\n ): Promise<IZFileSystemNode[]>;\n}\n\n/**\n * A token that you can use with a framework like nest-js to describe\n * a file system service implementation.\n */\nexport const ZFileSystemToken = Symbol(\"file-system\");\n\n/**\n * Represents a node implementation of a file system service.\n */\nexport class ZFileSystemService implements IZFileSystemService {\n public async info(path: string): Promise<IZFileSystemNode> {\n const [file] = await this.search(path);\n return file;\n }\n\n public async search(\n pattern: string | string[],\n options?: IZFileSystemSearchOptions,\n ): Promise<IZFileSystemNode[]> {\n const paths = await glob(pattern, {\n ...options,\n stat: true,\n withFileTypes: true,\n });\n\n return paths.map((path) => {\n let info = new ZFileSystemNodeBuilder()\n .path(path.fullpath())\n .created(path.birthtime)\n .updated(path.atime)\n .size(path.size);\n\n // Note that for folders, node will return the size of the underlying OS metadata,\n // hence, why we 0 out the size on a folder here.\n info = path.isFile() ? info.file() : info.folder().size(undefined);\n\n return info.build();\n });\n }\n}\n","import { access } from \"node:fs/promises\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { cwd } from \"node:process\";\n\n/**\n * Options for where a directory walk starts.\n */\nexport interface ZWalkOptions {\n /**\n * The starting directory.\n *\n * The default is the current working directory\n *\n * @see process.cwd for more information.\n */\n start?: string;\n\n /**\n * The directory to stop at.\n *\n * The default is the root of the file system.\n */\n stop?: string;\n\n /**\n * The type of mode that the path must support in order\n * to be approved.\n *\n * The default is undefined and left up to the node api.\n */\n mode?: number;\n}\n\n/**\n * Walks up a directory tree to search for existence of a given path.\n *\n * @param search -\n * The path to search for. This is the candidate folder path that\n * may or may not exist somewhere from the start directory all the\n * way up the directory tree.\n * @param options -\n * The given options for the search.\n *\n * @returns\n * The fully qualified path to the first path that matches the search\n * starting at the options start or the current working directory if not\n * specified. Returns null if no such directory exists.\n *\n * @example\n *\n * ```ts\n * import { resolve } from 'path'\n *\n * // Find any folder named .vscode from the current working directory all the way\n * // up the directory tree. Returns null if no vscode directory can be found.\n * const vscode = walk('.vscode');\n *\n * // Walk the directory tree from the current script and find the .config folder.\n * const config = walk('.config', { start: __dirname });\n *\n * // Walk the directory tree from the current script and stop when we reach\n * // at most 2 directories up.\n * const start = __dirname;\n * const stop = resolve(__dirname, '../..');\n * const file = walk('path/to/file.json', { start, stop });\n * ```\n */\nexport async function walk(\n search: string,\n options?: ZWalkOptions,\n): Promise<string | null> {\n const start = options?.start || cwd();\n const stop = options?.stop || \"/\";\n const mode = options?.mode;\n\n const _test = async (path: string) => {\n try {\n await access(path, mode);\n return true;\n } catch {\n return false;\n }\n };\n\n if (isAbsolute(search)) {\n return (await _test(search)) ? search : null;\n }\n\n let dir = start;\n do {\n const path = resolve(dir, search);\n const exists = await _test(path);\n\n if (exists) {\n return path;\n }\n\n dir = dir === stop ? \"\" : dirname(dir);\n } while (dir.length);\n\n return null;\n}\n"],"names":["ZFileSystemNodeType"],"mappings":";;;;;AAKY,IAAA,wCAAAA,yBAAL;AACLA,uBAAA,MAAO,IAAA;AACPA,uBAAA,QAAS,IAAA;AAFCA,SAAAA;AAAA,GAAA,uBAAA,CAAA,CAAA;AAmCL,MAAM,uBAAuB;AAAA,EAG3B,cAAc;AAgCrB,SAAO,OAAO,KAAK,KAAK;AAAA,MAAK;AAAA,MAAM;AAAA;AAAA,IAAwB;AAC3D,SAAO,SAAS,KAAK,KAAK;AAAA,MAAK;AAAA,MAAM;AAAA;AAAA,IAA0B;AAhC7D,SAAK,QAAQ;AAAA,MACX,MAAM;AAAA,MACN,MAAM;AAAA;AAAA,IACR;AAAA,EAAA;AAAA,EAGK,QAAQ,IAAgC;AAC7C,SAAK,MAAM,UAAU,cAAc,OAAO,GAAG,WAAW;AACjD,WAAA;AAAA,EAAA;AAAA,EAGF,QAAQ,IAAgC;AAC7C,SAAK,MAAM,UAAU,cAAc,OAAO,GAAG,WAAW;AACjD,WAAA;AAAA,EAAA;AAAA,EAGF,KAAK,OAAoC;AAC9C,SAAK,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,KAAK,IAAI;AACtD,WAAA;AAAA,EAAA;AAAA,EAGF,KAAK,MAAc;AACxB,SAAK,MAAM,OAAO;AACX,WAAA;AAAA,EAAA;AAAA,EAGF,KAAK,MAA2B;AACrC,SAAK,MAAM,OAAO;AACX,WAAA;AAAA,EAAA;AAAA,EAMF,QAAQ;AACP,UAAA,QAAQ,gBAAgB,KAAK,KAAK;AACjC,WAAA,OAAyB,OAAO,WAAW;AAAA,EAAA;AAEtD;AC5Ba,MAAA,mBAAmB,OAAO,aAAa;AAK7C,MAAM,mBAAkD;AAAA,EAC7D,MAAa,KAAK,MAAyC;AACzD,UAAM,CAAC,IAAI,IAAI,MAAM,KAAK,OAAO,IAAI;AAC9B,WAAA;AAAA,EAAA;AAAA,EAGT,MAAa,OACX,SACA,SAC6B;AACvB,UAAA,QAAQ,MAAM,KAAK,SAAS;AAAA,MAChC,GAAG;AAAA,MACH,MAAM;AAAA,MACN,eAAe;AAAA,IAAA,CAChB;AAEM,WAAA,MAAM,IAAI,CAAC,SAAS;AACzB,UAAI,OAAO,IAAI,yBACZ,KAAK,KAAK,SAAU,CAAA,EACpB,QAAQ,KAAK,SAAS,EACtB,QAAQ,KAAK,KAAK,EAClB,KAAK,KAAK,IAAI;AAIV,aAAA,KAAK,OAAO,IAAI,KAAK,SAAS,KAAK,OAAA,EAAS,KAAK,MAAS;AAEjE,aAAO,KAAK,MAAM;AAAA,IAAA,CACnB;AAAA,EAAA;AAEL;ACtBsB,eAAA,KACpB,QACA,SACwB;AAClB,QAAA,SAAQ,mCAAS,UAAS,IAAI;AAC9B,QAAA,QAAO,mCAAS,SAAQ;AAC9B,QAAM,OAAO,mCAAS;AAEhB,QAAA,QAAQ,OAAO,SAAiB;AAChC,QAAA;AACI,YAAA,OAAO,MAAM,IAAI;AAChB,aAAA;AAAA,IAAA,QACD;AACC,aAAA;AAAA,IAAA;AAAA,EAEX;AAEI,MAAA,WAAW,MAAM,GAAG;AACtB,WAAQ,MAAM,MAAM,MAAM,IAAK,SAAS;AAAA,EAAA;AAG1C,MAAI,MAAM;AACP,KAAA;AACK,UAAA,OAAO,QAAQ,KAAK,MAAM;AAC1B,UAAA,SAAS,MAAM,MAAM,IAAI;AAE/B,QAAI,QAAQ;AACH,aAAA;AAAA,IAAA;AAGT,UAAM,QAAQ,OAAO,KAAK,QAAQ,GAAG;AAAA,WAC9B,IAAI;AAEN,SAAA;AACT;"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Options for where a directory walk starts.
3
+ */
4
+ export interface ZWalkOptions {
5
+ /**
6
+ * The starting directory.
7
+ *
8
+ * The default is the current working directory
9
+ *
10
+ * @see process.cwd for more information.
11
+ */
12
+ start?: string;
13
+ /**
14
+ * The directory to stop at.
15
+ *
16
+ * The default is the root of the file system.
17
+ */
18
+ stop?: string;
19
+ /**
20
+ * The type of mode that the path must support in order
21
+ * to be approved.
22
+ *
23
+ * The default is undefined and left up to the node api.
24
+ */
25
+ mode?: number;
26
+ }
27
+ /**
28
+ * Walks up a directory tree to search for existence of a given path.
29
+ *
30
+ * @param search -
31
+ * The path to search for. This is the candidate folder path that
32
+ * may or may not exist somewhere from the start directory all the
33
+ * way up the directory tree.
34
+ * @param options -
35
+ * The given options for the search.
36
+ *
37
+ * @returns
38
+ * The fully qualified path to the first path that matches the search
39
+ * starting at the options start or the current working directory if not
40
+ * specified. Returns null if no such directory exists.
41
+ *
42
+ * @example
43
+ *
44
+ * ```ts
45
+ * import { resolve } from 'path'
46
+ *
47
+ * // Find any folder named .vscode from the current working directory all the way
48
+ * // up the directory tree. Returns null if no vscode directory can be found.
49
+ * const vscode = walk('.vscode');
50
+ *
51
+ * // Walk the directory tree from the current script and find the .config folder.
52
+ * const config = walk('.config', { start: __dirname });
53
+ *
54
+ * // Walk the directory tree from the current script and stop when we reach
55
+ * // at most 2 directories up.
56
+ * const start = __dirname;
57
+ * const stop = resolve(__dirname, '../..');
58
+ * const file = walk('path/to/file.json', { start, stop });
59
+ * ```
60
+ */
61
+ export declare function walk(search: string, options?: ZWalkOptions): Promise<string | null>;
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@zthun/crumbtrail-fs",
3
+ "version": "1.1.0",
4
+ "description": "A better file system module for node",
5
+ "author": "Anthony Bonta",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/zthun/crumbtrail",
10
+ "directory": "packages/crumbtrail-fs"
11
+ },
12
+ "type": "module",
13
+ "types": "./dist/index.d.ts",
14
+ "main": "./dist/index.cjs",
15
+ "module": "./dist/index.js",
16
+ "exports": {
17
+ "types": "./dist/index.d.ts",
18
+ "require": "./dist/index.cjs",
19
+ "import": "./dist/index.js"
20
+ },
21
+ "engines": {
22
+ "node": ">=22.16.0"
23
+ },
24
+ "scripts": {
25
+ "build": "vite build"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "dependencies": {
31
+ "glob": "^11.0.2",
32
+ "lodash-es": "^4.17.21"
33
+ },
34
+ "devDependencies": {
35
+ "vite": "^6.3.5",
36
+ "vitest": "^3.1.4"
37
+ },
38
+ "files": [
39
+ "dist"
40
+ ],
41
+ "sideEffects": false,
42
+ "gitHead": "3e311966e8d62306da2cf5427f4db2cf1a027bf5"
43
+ }