fluent-file 0.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/README.md +68 -0
- package/dist/index.d.ts +270 -0
- package/dist/index.js +10 -0
- package/package.json +115 -0
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Fluent File
|
|
2
|
+
A fluent TypeScript library for working with files and folders
|
|
3
|
+
|
|
4
|
+
## Folder
|
|
5
|
+
Provides folder fields:
|
|
6
|
+
- `path`: the absolute path to this folder (`/tmp/example/someFolder`)
|
|
7
|
+
- `name`: the last piece of the absolute path (`someFolder`)
|
|
8
|
+
- `parentName`: the 2nd to last piece of the absolute path (`example`)
|
|
9
|
+
- `parentPath`: the absolute path to the parent of this folder (`/tmp/example`)
|
|
10
|
+
|
|
11
|
+
Provides folder methods:
|
|
12
|
+
- exists()
|
|
13
|
+
- ensureExists()
|
|
14
|
+
- ensureEmpty()
|
|
15
|
+
- delete()
|
|
16
|
+
|
|
17
|
+
## AFile
|
|
18
|
+
Provides fields for any kind of file:
|
|
19
|
+
- `path`: the absolute path to this file (`/tmp/example/someFolder/someFile.txt`)
|
|
20
|
+
- `name`: the name of the file **without** the extension (`someFile`)
|
|
21
|
+
- `fullName`: the name of the file **with** the extension (`someFile.txt`)
|
|
22
|
+
- `ext`: the file extension **without** the leading dot. (`txt`)
|
|
23
|
+
- `parentName`: the 2nd to last piece of the absolute path to this file (`someFolder`)
|
|
24
|
+
- `parentPath`: the absolute path to the parent of this file (`/tmp/example/someFolder`)
|
|
25
|
+
|
|
26
|
+
Provides file methods for any kind of file:
|
|
27
|
+
- `getParentFolder()` returns the [Folder](#folder) instance this file is in
|
|
28
|
+
- `getStats()` calls node:fs/promises.stat()
|
|
29
|
+
- `exists()`
|
|
30
|
+
- `ensureExists()`
|
|
31
|
+
- `copyTo()`
|
|
32
|
+
- `moveTo()`
|
|
33
|
+
- `linkTo()`
|
|
34
|
+
- `symlinkTo()`
|
|
35
|
+
- `delete()`
|
|
36
|
+
- `readText()`
|
|
37
|
+
- `readTextLines()`
|
|
38
|
+
- `writeText()`
|
|
39
|
+
|
|
40
|
+
## JsonFile
|
|
41
|
+
Extends [AFile](#afile)
|
|
42
|
+
- the `constructor()` takes a [zod](https://zod.dev) schema
|
|
43
|
+
- adds a `read()` method that reads the file as text, JSON parses it, then validates it using the zod schema
|
|
44
|
+
- adds a `write(contents)` method that validates the contents using the zod schema, JSON stringifies it, then writes the text
|
|
45
|
+
|
|
46
|
+
## YamlFile
|
|
47
|
+
Extends [JsonFile](#jsonfile)
|
|
48
|
+
- overrides `read()` to use `YAML.parse` instead of `JSON.parse`, but still validates using the zod schema
|
|
49
|
+
- overrides `write(contents)` to use `YAML.stringify` instead of `JSON.stringify`, but still validates using the zod schema
|
|
50
|
+
|
|
51
|
+
## ImageFile
|
|
52
|
+
Extends [AFile](#afile)
|
|
53
|
+
- adds a [sharp](https://github.com/lovell/sharp) property
|
|
54
|
+
- adds a `convertToAvif()` method
|
|
55
|
+
- adds a `getPhash()` method using [sharp-phash](https://www.npmjs.com/package/sharp-phash) to create a [perceptual hash](https://en.wikipedia.org/wiki/Perceptual_hashing) of the image
|
|
56
|
+
|
|
57
|
+
## VideoFile
|
|
58
|
+
Extends [AFile](#afile)
|
|
59
|
+
- adds a [ffmpeg](http://www.ffmpeg.org/) property, provided by [fluent-ffmpeg](https://www.npmjs.com/package/fluent-ffmpeg)
|
|
60
|
+
- adds a `getMetaData()` method
|
|
61
|
+
- adds a `getThumbnail()` method
|
|
62
|
+
|
|
63
|
+
## GitFolder
|
|
64
|
+
Extends [Folder](#folder)
|
|
65
|
+
- the `constructor()` takes the git owner, repo, and baseUrl (defaults to "https://github.com")
|
|
66
|
+
- adds a [git](https://git-scm.com/) property, provided by [simple-git](https://www.npmjs.com/package/simple-git)
|
|
67
|
+
- adds a `clone()` method, using the owner, repo and baseUrl provided in the constructor
|
|
68
|
+
- adds a `pull()` method, that will clone the repo if it does not already exist
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import * as neverthrow from 'neverthrow';
|
|
2
|
+
import { ResultAsync } from 'neverthrow';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import { inspect } from 'node:util';
|
|
5
|
+
import { Options } from 'globby';
|
|
6
|
+
import { ZodError, ZodTypeAny, z } from 'zod';
|
|
7
|
+
import { JSONError } from 'parse-json';
|
|
8
|
+
import { YAMLParseError, ParseOptions, DocumentOptions, SchemaOptions, ToJSOptions, CreateNodeOptions, ToStringOptions } from 'yaml';
|
|
9
|
+
import { AvifOptions, Sharp } from 'sharp';
|
|
10
|
+
import * as simple_git from 'simple-git';
|
|
11
|
+
import { SimpleGitProgressEvent } from 'simple-git';
|
|
12
|
+
|
|
13
|
+
declare class NodeError extends Error {
|
|
14
|
+
readonly code: string;
|
|
15
|
+
readonly dest: string | undefined;
|
|
16
|
+
readonly info: Record<string, unknown> | undefined;
|
|
17
|
+
readonly path: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
declare enum FileEntryType {
|
|
20
|
+
BlockDevice = "BlockDevice",
|
|
21
|
+
CharacterDevice = "CharacterDevice",
|
|
22
|
+
Directory = "Directory",
|
|
23
|
+
FIFO = "FIFO",
|
|
24
|
+
File = "File",
|
|
25
|
+
Socket = "Socket",
|
|
26
|
+
SymbolicLink = "SymbolicLink"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare class FolderWasNotFolderError extends Error {
|
|
30
|
+
actualFileEntryType: FileEntryType;
|
|
31
|
+
constructor(path: string, actualFileEntryType: FileEntryType, cause?: Error);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
declare class FileNotFoundError extends Error {
|
|
35
|
+
constructor(path: string, cause: Error);
|
|
36
|
+
}
|
|
37
|
+
declare class FileReadError extends Error {
|
|
38
|
+
constructor(path: string, cause: Error);
|
|
39
|
+
}
|
|
40
|
+
declare class FileWriteError extends Error {
|
|
41
|
+
constructor(path: string, cause: Error);
|
|
42
|
+
}
|
|
43
|
+
declare class FileWasNotFileError extends Error {
|
|
44
|
+
actualFileEntryType: FileEntryType;
|
|
45
|
+
constructor(path: string, actualFileEntryType: FileEntryType, cause?: Error);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
declare class StringifyError extends Error {
|
|
49
|
+
constructor(path: string, cause: Error);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type Strings = Array<string>;
|
|
53
|
+
type UnknownObject<Key extends string = string> = Record<Key, unknown>;
|
|
54
|
+
type Spacing = string | number | undefined;
|
|
55
|
+
|
|
56
|
+
declare class Folder {
|
|
57
|
+
#private;
|
|
58
|
+
constructor(folder: string | Folder, ...extraPathPieces: Strings);
|
|
59
|
+
get path(): string;
|
|
60
|
+
get name(): string;
|
|
61
|
+
get parentName(): string;
|
|
62
|
+
get parentPath(): string;
|
|
63
|
+
get info(): {
|
|
64
|
+
path: string;
|
|
65
|
+
name: string;
|
|
66
|
+
parentName: string;
|
|
67
|
+
parentPath: string;
|
|
68
|
+
};
|
|
69
|
+
relativePath: (relativeTo?: string) => string;
|
|
70
|
+
toString: () => string;
|
|
71
|
+
toJSON: () => {
|
|
72
|
+
Folder: {
|
|
73
|
+
path: string;
|
|
74
|
+
name: string;
|
|
75
|
+
parentName: string;
|
|
76
|
+
parentPath: string;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
[inspect.custom]: () => {
|
|
80
|
+
Folder: {
|
|
81
|
+
path: string;
|
|
82
|
+
name: string;
|
|
83
|
+
parentName: string;
|
|
84
|
+
parentPath: string;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
getStats: () => neverthrow.ResultAsync<fs.Stats, NodeError | FolderWasNotFolderError>;
|
|
88
|
+
exists: () => Promise<boolean>;
|
|
89
|
+
ensureExists: () => Promise<void>;
|
|
90
|
+
ensureEmpty: () => Promise<void>;
|
|
91
|
+
delete: () => Promise<void>;
|
|
92
|
+
getParentFolder: () => Folder;
|
|
93
|
+
subFolder: (folder: string | Folder, ...extraPathPieces: Strings) => Folder;
|
|
94
|
+
childFolders: (globPattern?: AnyGlob) => neverthrow.ResultAsync<Folder[], NodeError | FolderWasNotFolderError>;
|
|
95
|
+
findFolders: (globPattern: AnyGlob) => neverthrow.ResultAsync<Folder[], NodeError | FolderWasNotFolderError>;
|
|
96
|
+
}
|
|
97
|
+
declare function folder(folder?: string | Folder): Folder;
|
|
98
|
+
declare function folder(folder: string | Folder, ...extraPathPieces: Strings): Folder;
|
|
99
|
+
declare function homeFolder(...extraPathPieces: Strings): Folder;
|
|
100
|
+
declare function cwd(): string;
|
|
101
|
+
|
|
102
|
+
type AnyGlob = string | Strings | GlobOptions;
|
|
103
|
+
type GlobOptions = Omit<Options, "cwd"> & Partial<{
|
|
104
|
+
patterns: Strings;
|
|
105
|
+
extensions: Strings;
|
|
106
|
+
}>;
|
|
107
|
+
|
|
108
|
+
declare class AFile {
|
|
109
|
+
#private;
|
|
110
|
+
constructor(file: AFile | Folder | string, ...extraPathPieces: Strings);
|
|
111
|
+
get path(): string;
|
|
112
|
+
get fullName(): string;
|
|
113
|
+
get name(): string;
|
|
114
|
+
get ext(): string;
|
|
115
|
+
get parentName(): string;
|
|
116
|
+
get parentPath(): string;
|
|
117
|
+
get info(): {
|
|
118
|
+
path: string;
|
|
119
|
+
fullName: string;
|
|
120
|
+
name: string;
|
|
121
|
+
ext: string;
|
|
122
|
+
parentName: string;
|
|
123
|
+
parentPath: string;
|
|
124
|
+
};
|
|
125
|
+
relativePath: (relativeTo?: string) => string;
|
|
126
|
+
toString: () => string;
|
|
127
|
+
toJSON: () => {
|
|
128
|
+
File: {
|
|
129
|
+
path: string;
|
|
130
|
+
fullName: string;
|
|
131
|
+
name: string;
|
|
132
|
+
ext: string;
|
|
133
|
+
parentName: string;
|
|
134
|
+
parentPath: string;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
[inspect.custom]: () => {
|
|
138
|
+
File: {
|
|
139
|
+
path: string;
|
|
140
|
+
fullName: string;
|
|
141
|
+
name: string;
|
|
142
|
+
ext: string;
|
|
143
|
+
parentName: string;
|
|
144
|
+
parentPath: string;
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
getParentFolder: () => Folder;
|
|
148
|
+
getStats: () => neverthrow.ResultAsync<fs.Stats, NodeError | FileWasNotFileError>;
|
|
149
|
+
exists: () => Promise<boolean>;
|
|
150
|
+
ensureExists: () => Promise<void>;
|
|
151
|
+
copyTo: (destination: AFile | Folder) => Promise<void>;
|
|
152
|
+
moveTo: (destination: AFile | Folder) => Promise<void>;
|
|
153
|
+
linkTo: (destination: AFile | Folder) => Promise<void>;
|
|
154
|
+
symlinkTo: (destination: AFile | Folder) => Promise<void>;
|
|
155
|
+
delete: () => Promise<void>;
|
|
156
|
+
readText: () => neverthrow.ResultAsync<string, FileNotFoundError | FileReadError | FileWasNotFileError>;
|
|
157
|
+
readTextLines: () => neverthrow.ResultAsync<string[], FileNotFoundError | FileReadError | FileWasNotFileError>;
|
|
158
|
+
writeText: (content: string) => neverthrow.ResultAsync<void, FileWriteError | FileWasNotFileError>;
|
|
159
|
+
}
|
|
160
|
+
declare function file(file: AFile | Folder | string, ...extraPathPieces: Strings): AFile;
|
|
161
|
+
declare function homeFile(file: AFile | Folder | string, ...extraPathPieces: Strings): AFile;
|
|
162
|
+
declare function findAnyFiles(inFolder?: Folder, anyGlob?: AnyGlob): neverthrow.ResultAsync<AFile[], NodeError | FolderWasNotFolderError>;
|
|
163
|
+
|
|
164
|
+
declare class ZodParseError extends Error {
|
|
165
|
+
nestedErrors: UnknownObject;
|
|
166
|
+
constructor({ cause, nestedErrors, }: {
|
|
167
|
+
cause: ZodError;
|
|
168
|
+
nestedErrors: UnknownObject;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
declare class JsonFile<FileSchema extends ZodTypeAny> extends AFile {
|
|
173
|
+
protected readonly fileSchema: FileSchema;
|
|
174
|
+
constructor(fileSchema: FileSchema, filePath: AFile | Folder | string, ...extraPathPieces: Strings);
|
|
175
|
+
protected validateUnknown: (unknownContents: unknown) => neverthrow.Result<z.TypeOf<FileSchema>, ZodParseError>;
|
|
176
|
+
read: () => neverthrow.ResultAsync<z.TypeOf<FileSchema>, FileNotFoundError | FileReadError | FileWasNotFileError | ZodParseError | JSONError>;
|
|
177
|
+
write: (contents: z.infer<FileSchema>, spacing?: Spacing) => neverthrow.ResultAsync<void, Error | FileWriteError | FileWasNotFileError | ZodParseError>;
|
|
178
|
+
}
|
|
179
|
+
declare function jsonFile<FileSchema extends ZodTypeAny>(fileSchema: FileSchema, filePath: AFile | Folder | string, ...extraPathPieces: Strings): JsonFile<FileSchema>;
|
|
180
|
+
declare function findJsonFiles<FileSchema extends ZodTypeAny>(fileSchema: FileSchema, inFolder?: Folder, anyGlob?: AnyGlob): neverthrow.ResultAsync<JsonFile<FileSchema>[], NodeError | FolderWasNotFolderError>;
|
|
181
|
+
|
|
182
|
+
type YamlParseOptions = ParseOptions & DocumentOptions & SchemaOptions & ToJSOptions;
|
|
183
|
+
type YamlStringifyOptions = (DocumentOptions & SchemaOptions & CreateNodeOptions & ToStringOptions) | Spacing;
|
|
184
|
+
|
|
185
|
+
declare const YAML_EXTENSIONS: string[];
|
|
186
|
+
declare class YamlFile<FileSchema extends ZodTypeAny> extends JsonFile<FileSchema> {
|
|
187
|
+
read: (parseOptions?: YamlParseOptions) => neverthrow.ResultAsync<z.TypeOf<FileSchema>, FileNotFoundError | FileReadError | FileWasNotFileError | ZodParseError | YAMLParseError>;
|
|
188
|
+
write: (stringifyOptions: YamlStringifyOptions, contents: z.infer<FileSchema>) => neverthrow.ResultAsync<void, FileWriteError | FileWasNotFileError | StringifyError | ZodParseError>;
|
|
189
|
+
}
|
|
190
|
+
declare function yamlFile<FileSchema extends ZodTypeAny>(fileSchema: FileSchema, filePath: AFile | Folder | string, ...extraPathPieces: Strings): YamlFile<FileSchema>;
|
|
191
|
+
declare function findYamlFiles<FileSchema extends ZodTypeAny>(fileSchema: FileSchema, inFolder?: Folder, anyGlob?: AnyGlob): neverthrow.ResultAsync<YamlFile<FileSchema>[], NodeError | FolderWasNotFolderError>;
|
|
192
|
+
|
|
193
|
+
declare const IMAGE_EXTENSIONS: string[];
|
|
194
|
+
type ToAvifOptions = {
|
|
195
|
+
newFolder?: Folder;
|
|
196
|
+
newName?: string;
|
|
197
|
+
height?: number;
|
|
198
|
+
width?: number;
|
|
199
|
+
} & Pick<AvifOptions, "effort" | "quality">;
|
|
200
|
+
declare class ImageFile extends AFile {
|
|
201
|
+
readonly sharp: Sharp;
|
|
202
|
+
constructor(file: AFile | Folder | string, ...extraPathPieces: Strings);
|
|
203
|
+
convertToAvif: ({ newFolder, newName, height, width, effort, quality, }?: ToAvifOptions) => Promise<ImageFile>;
|
|
204
|
+
getPhash: () => Promise<string>;
|
|
205
|
+
}
|
|
206
|
+
declare function imageFile(file: AFile | Folder | string, ...extraPathPieces: Strings): ImageFile;
|
|
207
|
+
declare function findImageFiles(inFolder?: Folder, anyGlob?: AnyGlob): neverthrow.ResultAsync<ImageFile[], NodeError | FolderWasNotFolderError>;
|
|
208
|
+
declare function base2to36(base2: string): string;
|
|
209
|
+
declare function base36to2(base36: string): string;
|
|
210
|
+
declare function phashCheck(needle: string, haystack: Strings): {
|
|
211
|
+
case: PhashSimilarity;
|
|
212
|
+
phash: string;
|
|
213
|
+
};
|
|
214
|
+
declare enum PhashSimilarity {
|
|
215
|
+
Exact = "Exact",
|
|
216
|
+
Similar = "Similar",
|
|
217
|
+
Unique = "Unique"
|
|
218
|
+
}
|
|
219
|
+
type PhashSimilarityResult = {
|
|
220
|
+
case: PhashSimilarity;
|
|
221
|
+
phash: string;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
declare class VideoMetaDataError extends Error {
|
|
225
|
+
constructor(path: string, cause: Error);
|
|
226
|
+
}
|
|
227
|
+
declare class VideoThumbNailError extends Error {
|
|
228
|
+
constructor(path: string, cause: Error);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
declare const VIDEO_EXTENSIONS: string[];
|
|
232
|
+
declare class VideoFile extends AFile {
|
|
233
|
+
getMetaData: () => ResultAsync<{
|
|
234
|
+
bitRate: number;
|
|
235
|
+
bytes: number;
|
|
236
|
+
height: number;
|
|
237
|
+
hours: number;
|
|
238
|
+
millis: number;
|
|
239
|
+
minutes: number;
|
|
240
|
+
seconds: number;
|
|
241
|
+
width: number;
|
|
242
|
+
}, NodeError | FileWasNotFileError | ZodParseError | VideoMetaDataError>;
|
|
243
|
+
getThumbnail: (timestamp: string | number, destination: ImageFile) => ResultAsync<ImageFile, VideoThumbNailError>;
|
|
244
|
+
}
|
|
245
|
+
declare function videoFile(file: AFile | Folder | string, ...extraPathPieces: Strings): VideoFile;
|
|
246
|
+
declare function findVideoFiles(inFolder?: Folder, anyGlob?: AnyGlob): ResultAsync<VideoFile[], NodeError | FolderWasNotFolderError>;
|
|
247
|
+
|
|
248
|
+
type GitFolderOptions = {
|
|
249
|
+
owner: string;
|
|
250
|
+
repo: string;
|
|
251
|
+
onProgress?: ProgressHandler;
|
|
252
|
+
baseUrl?: string;
|
|
253
|
+
};
|
|
254
|
+
type ProgressHandler = (progressEvent: SimpleGitProgressEvent) => void;
|
|
255
|
+
declare class GitFolder extends Folder {
|
|
256
|
+
readonly owner: string;
|
|
257
|
+
readonly repo: string;
|
|
258
|
+
readonly baseUrl: string;
|
|
259
|
+
readonly shortUrl: string;
|
|
260
|
+
readonly fullUrl: string;
|
|
261
|
+
readonly progress: ProgressHandler;
|
|
262
|
+
constructor({ owner, repo, onProgress, baseUrl }: GitFolderOptions, folder: string | Folder, ...extraPathPieces: Strings);
|
|
263
|
+
get git(): simple_git.SimpleGit;
|
|
264
|
+
isaRepo: () => Promise<boolean>;
|
|
265
|
+
clone: () => Promise<void>;
|
|
266
|
+
pull: () => Promise<simple_git.PullResult>;
|
|
267
|
+
getReadMe: () => AFile;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export { AFile as AnyFile, Folder, GitFolder, IMAGE_EXTENSIONS, ImageFile, JsonFile, PhashSimilarity, type PhashSimilarityResult, type ToAvifOptions, VIDEO_EXTENSIONS, VideoFile, YAML_EXTENSIONS, YamlFile, base2to36, base36to2, cwd, file, findAnyFiles, findImageFiles, findJsonFiles, findVideoFiles, findYamlFiles, folder, homeFile, homeFolder, imageFile, jsonFile, phashCheck, videoFile, yamlFile };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import{constants as kt}from"node:fs";import{copyFile as Gt}from"node:fs/promises";import{homedir as $t}from"node:os";import{inspect as Ut}from"node:util";import{stat as V}from"node:fs/promises";var J=Object.keys;function F(e){if(!(e instanceof Error&&"code"in e&&typeof e.code=="string"))throw new Error("was not a Node error",{cause:e})}var T=(s=>(s.BlockDevice="BlockDevice",s.CharacterDevice="CharacterDevice",s.Directory="Directory",s.FIFO="FIFO",s.File="File",s.Socket="Socket",s.SymbolicLink="SymbolicLink",s))(T||{});function U(e){for(let t of J(T))if(e[`is${t}`]())return T[t];return"File"}var A=class extends Error{constructor(t,r){super(t,{cause:r}),this.name=this.constructor.name}},O=class extends Error{constructor(t,r){super(t,{cause:r}),this.name=this.constructor.name}},P=class extends Error{constructor(t,r){super(t,{cause:r}),this.name=this.constructor.name}},u=class extends Error{actualFileEntryType;constructor(t,r,o){let i=`${t}
|
|
2
|
+
was actually a ${r} instead of a Folder`;o?super(i,{cause:o}):super(i),this.name=this.constructor.name,this.actualFileEntryType=r}};var E=class extends Error{actualFileEntryType;constructor(t,r,o){let i=`${t}
|
|
3
|
+
was actually a ${r} instead of a Folder`;o?super(i,{cause:o}):super(i),this.name=this.constructor.name,this.actualFileEntryType=r}};import{ResultAsync as W}from"neverthrow";function w(e){return W.fromThrowable(async()=>{let t=await V(e.path);if(t.isDirectory())return t;throw new E(e.path,U(t))},t=>(t instanceof E||F(t),t))()}function _(e){return W.fromThrowable(async()=>{let t=await V(e.path);if(t.isFile())return t;throw new u(e.path,U(t))},t=>(t instanceof u||F(t),t))()}import{homedir as wt}from"node:os";import{inspect as bt}from"node:util";import{emptyDir as Tt,ensureDir as At,remove as Ot}from"fs-extra/esm";import{basename as X,dirname as Pt,resolve as K}from"pathe";var h=class e{#t;#e;#r;#o;constructor(t,...r){let o=t instanceof e?t.path:t;this.#t=K(o,...r),this.#e=X(this.#t),this.#e||(this.#e=this.#t),this.#o=Pt(this.path),this.#r=X(this.#o),this.#r||(this.#r=this.#e)}get path(){return this.#t}get name(){return this.#e}get parentName(){return this.#r}get parentPath(){return this.#o}get info(){return{path:this.#t,name:this.#e,parentName:this.#r,parentPath:this.#o}}relativePath=(t=N())=>this.#t.startsWith(t)?this.#t.slice(t.length+1):this.#t;toString=()=>this.#t;toJSON=()=>({Folder:this.info});[bt.custom]=()=>this.toJSON();getStats=()=>w(this);exists=async()=>(await this.getStats()).isOk();ensureExists=()=>At(this.#t);ensureEmpty=()=>Tt(this.#t);delete=()=>Ot(this.#t);getParentFolder=()=>new e(this.#o);subFolder=(t,...r)=>{let o=t instanceof e?t.path:t;return new e(this.#t,o,...r)};childFolders=t=>M(this,t,1);findFolders=t=>M(this,t)};function l(e,...t){return e===void 0?new h(""):new h(e,...t)}function Nt(...e){return new h(wt(),...e)}function N(){return K(process.cwd())}import{globby as It}from"globby";var j="**",vt={absolute:!0,gitignore:!0};async function q(e,t,r,o){let i={cwd:t.path,...vt,...r},a=j;if(typeof o=="string"||Array.isArray(o))a=o;else{let{patterns:n,extensions:s,expandDirectories:d,...m}=o;n&&(a=n),i={expandDirectories:s?{extensions:s}:!0,...m,...i}}return(await It(a,i)).sort().map(n=>e(n))}function f(e,t,r=j,o=Number.POSITIVE_INFINITY){return w(t).map(()=>q(e,t,{deep:o},r))}function M(e,t=j,r=Number.POSITIVE_INFINITY){return w(e).map(()=>q(l,e,{onlyDirectories:!0,deep:r},t))}import{readFile as Rt}from"node:fs/promises";import{outputFile as Dt}from"fs-extra/esm";import{ResultAsync as H}from"neverthrow";var B=/\n|\r|\r\n/;function Q(e){return H.fromThrowable(()=>Rt(e,"utf8"),t=>(F(t),t.code==="ENOENT"?new A(e,t):t.code==="EISDIR"?new u(e,"Directory",t):new O(e,t)))()}function tt(e,t){return H.fromThrowable(()=>Dt(e,t),r=>(F(r),r.code==="EISDIR"?new u(e,"Directory",r):new P(e,r)))()}import{ensureFile as Mt,ensureLink as jt,ensureSymlink as Ct,move as Yt,remove as zt}from"fs-extra/esm";import{basename as et,dirname as Lt,resolve as Zt}from"pathe";var c=class e{#t;#e;#r;#o;#i;#s;constructor(t,...r){let o=rt(t);this.#t=Zt(o,...r),this.#e=et(this.#t);let i=this.#e.lastIndexOf(".");i<=0?(this.#r=this.#e,this.#o=""):(this.#r=this.#e.slice(0,i),this.#o=this.#e.slice(i+1)),this.#s=Lt(this.path),this.#i=et(this.#s),this.#i||(this.#i=this.#e)}get path(){return this.#t}get fullName(){return this.#e}get name(){return this.#r}get ext(){return this.#o}get parentName(){return this.#i}get parentPath(){return this.#s}get info(){return{path:this.#t,fullName:this.#e,name:this.#r,ext:this.#o,parentName:this.#i,parentPath:this.#s}}relativePath=(t=N())=>this.#t.startsWith(t)?this.#t.slice(t.length+1):this.#t;toString=()=>this.#t;toJSON=()=>({File:this.info});[Ut.custom]=()=>this.toJSON();getParentFolder=()=>new h(this.#s);getStats=()=>_(this);exists=async()=>(await this.getStats()).isOk();ensureExists=()=>Mt(this.#t);copyTo=async t=>{let r=t instanceof e?t:g(t,this.#e);await r.getParentFolder().ensureExists(),await r.delete(),await Gt(this.#t,r.path,kt.COPYFILE_FICLONE)};moveTo=async t=>{let r=t instanceof e?t:g(t,this.#e);await r.getParentFolder().ensureExists(),await r.delete(),await Yt(this.#t,r.path)};linkTo=async t=>{let r=t instanceof e?t:g(t,this.#e);await r.getParentFolder().ensureExists(),await r.delete(),await jt(this.#t,r.path)};symlinkTo=async t=>{let r=t instanceof e?t:g(t,this.#e);await r.getParentFolder().ensureExists(),await r.delete(),await Ct(this.#t,r.path)};delete=()=>zt(this.#t);readText=()=>Q(this.path);readTextLines=()=>this.readText().map(t=>t.split(B));writeText=t=>tt(this.path,t)};function g(e,...t){return new c(e,...t)}function Jt(e,...t){return new c($t(),rt(e),...t)}function rt(e){return e instanceof c||e instanceof h?e.path:e}function Vt(e=l(),t){return f(g,e,t)}import{inspect as Wt}from"node:util";import{fromThrowable as _t}from"neverthrow";var C=class extends Error{nestedErrors;constructor({cause:t,nestedErrors:r}){super(`ZodParseError: ${Wt(r)}`,{cause:t}),this.nestedErrors=r}};function I(e,t){return _t(()=>e.parse(t),r=>{let o=r;return new C({cause:o,nestedErrors:o.format(i=>({...i,actual:Xt(t,i.path)}))})})()}function Xt(e,t){let r=e[t[0]];for(let o of t)r=r[o];return r}import{fromThrowable as ot}from"neverthrow";import Kt from"parse-json";import{configure as qt}from"safe-stable-stringify";var Ht=ot(e=>Kt(e),e=>e),Bt=qt({circularValue:Error,strict:!0}),Qt=ot((e,t)=>{let r=Bt(e,null,t);if(r===void 0)throw new Error("Undefined returned from stringify!!!");if(r.trim().length===0)throw new Error("Empty string returned from stringify!!!");return r},e=>e),x=class extends c{fileSchema;constructor(t,r,...o){super(r,...o),this.fileSchema=t}validateUnknown=t=>I(this.fileSchema,t);read=()=>this.readText().andThen(Ht).andThen(this.validateUnknown);write=(t,r=2)=>this.validateUnknown(t).andThen(o=>Qt(o,r)).asyncAndThen(this.writeText)};function it(e,t,...r){return new x(e,t,...r)}function te(e,t=l(),r="*.json"){return f(o=>it(e,o),t,r)}import{fromThrowable as st}from"neverthrow";import{parse as ee,stringify as Y}from"yaml";var nt=["yaml","yml"],re=st((e,t)=>ee(e,t),e=>e),oe=st((e,t=2)=>typeof t=="string"?Y(e,{indent:Number.parseInt(t),sortMapEntries:!0}):typeof t=="number"?Y(e,{indent:t,sortMapEntries:!0}):Y(e,{sortMapEntries:!0,...t}),e=>e),v=class extends x{read=t=>this.readText().andThen(r=>re(r,t)).andThen(r=>this.validateUnknown(r));write=(t,r)=>this.validateUnknown(r).andThen(o=>oe(o,t)).asyncAndThen(this.writeText)};function at(e,t,...r){return new v(e,t,...r)}function ie(e,t=l(),r={extensions:nt}){return f(o=>at(e,o),t,r)}import se from"sharp";import ne from"sharp-phash";var pt=6,lt=".avif",mt=["avif","gif","jpeg","jpg","png","svg","tiff","webp"],R=class extends c{sharp;constructor(t,...r){super(t,...r),this.sharp=se(this.path)}convertToAvif=async({newFolder:t,newName:r,height:o,width:i,effort:a=9,quality:n}={})=>{let s=t||this.getParentFolder(),d=r??this.name;d.endsWith(lt)||(d+=lt);let m=L(s,d);return await m.exists()||(await m.getParentFolder().ensureExists(),await this.sharp.resize({fit:"contain",height:o,width:i,withoutEnlargement:!0}).avif({effort:a,quality:n}).toFile(m.path)),m};getPhash=async()=>{let t=await ne(this.path);return ct(t)}};function L(e,...t){return new R(e,...t)}function ae(e=l(),t={extensions:mt}){return f(L,e,t)}function ct(e){return Number.parseInt(e,2).toString(36)}function z(e){return Number.parseInt(e,36).toString(2)}function pe(e,t){if(t.includes(e))return{case:"Exact",phash:e};let o=z(e);for(let i of t){let a=z(i),n=0,s=o.length;for(;s--&&n<=pt;)n+=Math.abs(o.charCodeAt(s)-a.charCodeAt(s));if(n<pt)return{case:"Similar",phash:i}}return{case:"Unique",phash:e}}var ht=(o=>(o.Exact="Exact",o.Similar="Similar",o.Unique="Unique",o))(ht||{});import p from"zod";var Pr=p.object({codec_type:p.literal("data")}),le=p.object({codec_type:p.literal("audio")}),me=p.object({codec_type:p.literal("video"),height:p.number().int().positive(),width:p.number().int().positive()}),ft=p.object({format:p.object({bit_rate:p.coerce.number(),duration:p.coerce.number(),size:p.coerce.number()}),streams:p.array(p.union([le,me]))}).transform(({format:{bit_rate:e,duration:t,size:r},streams:o},{addIssue:i})=>{for(let a of o)if(a.codec_type==="video"){let{height:n,width:s}=a,d=Math.round(t*1e3),m=Math.trunc(t%60),b=Math.trunc(t/60%60),$=Math.trunc(t/60/60%60);return{bitRate:e,bytes:r,height:n,hours:$,millis:d,minutes:b,seconds:m,width:s}}return i({code:p.ZodIssueCode.custom,message:"Video was missing video stream"}),p.NEVER});import ut from"fluent-ffmpeg";import{ResultAsync as dt}from"neverthrow";var D=class extends Error{constructor(t,r){super(t,{cause:r}),this.name=this.constructor.name}},k=class extends Error{constructor(t,r){super(t,{cause:r}),this.name=this.constructor.name}};var gt=["avi","flv","mkv","mov","mp4","mpeg","mpg","rm","rmvb","webm","wmv"],ce=e=>new Promise((t,r)=>ut.ffprobe(e,(o,i)=>{o&&r(o),t(i)})),he=(e,t,r,o)=>new Promise((i,a)=>ut(e).on("error",n=>a(n)).on("end",()=>i()).screenshot({filename:r,folder:t,timestamps:typeof o=="string"?[o]:[o]})),G=class extends c{getMetaData=()=>this.getStats().andThen(()=>dt.fromThrowable(()=>ce(this.path),t=>new D(this.path,t))().andThen(t=>I(ft,t)));getThumbnail=(t,r)=>dt.fromThrowable(async()=>{if(await r.exists())return r;let i=r.getParentFolder();return await i.ensureExists(),await he(this.path,i.path,`${r.name}.png`,t),r},o=>new k(this.path,o))()};function yt(e,...t){return new G(e,...t)}function fe(e=l(),t={extensions:gt}){return f(yt,e,t)}import y from"log-update";import{CheckRepoActions as de,simpleGit as Ft}from"simple-git";var ue=e=>e.padEnd(11),S="";function ge({method:e,stage:t,progress:r,processed:o,total:i}){let a=e.toUpperCase(),n=ue(t);if(n!==S){S&&(y(a,S,"completed"),y.done()),S=n;return}let s=i.toString().length,d=process.stdout.columns,m=Math.max(1,Math.ceil(d*r/100)),b=d-m,$="=".repeat(m),xt="_".repeat(b),St=[a,n,o.toString().padStart(s),"of",i.toString(),`(${r.toString().padStart(2)}%)`].join(" "),Et=`${$}${xt}`;y(`${St}
|
|
4
|
+
${Et}`)}var Z=class extends h{owner;repo;baseUrl;shortUrl;fullUrl;progress;constructor({owner:t,repo:r,onProgress:o,baseUrl:i},a,...n){let s=[...n,t,r];super(a,...s),this.owner=t,this.repo=r,this.baseUrl=i??"https://github.com",this.shortUrl=`${t}/${r}`,this.fullUrl=`${this.baseUrl}/${this.shortUrl}`,this.progress=o??ge}get git(){return Ft({baseDir:this.path,progress:this.progress})}isaRepo=async()=>await Ft(this.path).checkIsRepo(de.IS_REPO_ROOT);clone=async()=>{if(console.log(`Cloning
|
|
5
|
+
`,this.fullUrl,`
|
|
6
|
+
into:
|
|
7
|
+
`,this.relativePath()),await this.ensureExists(),await this.isaRepo()){console.log(this.fullUrl,"is already cloned");return}await this.git.clone(this.fullUrl,this.path),y("CLONE",S,"completed"),y.done(),console.log(`Cloned
|
|
8
|
+
`,this.fullUrl,`
|
|
9
|
+
into:
|
|
10
|
+
`,this.relativePath())};pull=async()=>{console.log("Pulling",this.fullUrl),await this.ensureExists(),await this.isaRepo()||(console.log(this.fullUrl,"does not exist yet, cloning..."),await this.clone());let r=await this.git.pull();return y("PULL",S,"completed"),y.done(),console.log("Pulled",this.fullUrl),r};getReadMe=()=>g(this.path,"README.md")};export{c as AnyFile,h as Folder,Z as GitFolder,mt as IMAGE_EXTENSIONS,R as ImageFile,x as JsonFile,ht as PhashSimilarity,gt as VIDEO_EXTENSIONS,G as VideoFile,nt as YAML_EXTENSIONS,v as YamlFile,ct as base2to36,z as base36to2,N as cwd,g as file,Vt as findAnyFiles,ae as findImageFiles,te as findJsonFiles,fe as findVideoFiles,ie as findYamlFiles,l as folder,Jt as homeFile,Nt as homeFolder,L as imageFile,it as jsonFile,pe as phashCheck,yt as videoFile,at as yamlFile};
|
package/package.json
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fluent-file",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A fluent TypeScript library for working with files and folders",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"files",
|
|
7
|
+
"folders",
|
|
8
|
+
"fluent"
|
|
9
|
+
],
|
|
10
|
+
"repository": "github:dannywexler/fluent-file",
|
|
11
|
+
"author": "Danny Wexler",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"overrides": {
|
|
27
|
+
"esbuild": "0.24.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"fluent-ffmpeg": "^2.1.3",
|
|
31
|
+
"fs-extra": "^11.2.0",
|
|
32
|
+
"globby": "^14.0.2",
|
|
33
|
+
"log-update": "^6.1.0",
|
|
34
|
+
"neverthrow": "^8.1.1",
|
|
35
|
+
"parse-json": "^8.1.0",
|
|
36
|
+
"pathe": "^1.1.2",
|
|
37
|
+
"safe-stable-stringify": "^2.5.0",
|
|
38
|
+
"sharp": "^0.33.5",
|
|
39
|
+
"sharp-phash": "^2.2.0",
|
|
40
|
+
"simple-git": "^3.27.0",
|
|
41
|
+
"yaml": "^2.6.1",
|
|
42
|
+
"zod": "^3.24.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@biomejs/biome": "^1.9.4",
|
|
46
|
+
"@types/download": "^8.0.5",
|
|
47
|
+
"@types/fluent-ffmpeg": "^2.1.27",
|
|
48
|
+
"@types/fs-extra": "^11.0.4",
|
|
49
|
+
"@types/node": "^22.10.2",
|
|
50
|
+
"download": "^8.0.0",
|
|
51
|
+
"tsup": "^8.3.5",
|
|
52
|
+
"typescript": "^5.7.2",
|
|
53
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
54
|
+
"vitest": "^2.1.8",
|
|
55
|
+
"wireit": "^0.14.9"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "wireit",
|
|
59
|
+
"test": "wireit",
|
|
60
|
+
"test:watch": "vitest"
|
|
61
|
+
},
|
|
62
|
+
"wireit": {
|
|
63
|
+
"lint": {
|
|
64
|
+
"command": "biome check --error-on-warnings --colors=force --write src",
|
|
65
|
+
"files": [
|
|
66
|
+
"src"
|
|
67
|
+
],
|
|
68
|
+
"output": []
|
|
69
|
+
},
|
|
70
|
+
"check": {
|
|
71
|
+
"command": "tsc --build --pretty",
|
|
72
|
+
"dependencies": [
|
|
73
|
+
"lint"
|
|
74
|
+
],
|
|
75
|
+
"files": [
|
|
76
|
+
"src/**/*.ts",
|
|
77
|
+
"tsconfig.json"
|
|
78
|
+
],
|
|
79
|
+
"output": [
|
|
80
|
+
"tsconfig.tsbuildinfo"
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"test": {
|
|
84
|
+
"command": "vitest run",
|
|
85
|
+
"dependencies": [
|
|
86
|
+
"check"
|
|
87
|
+
],
|
|
88
|
+
"files": [
|
|
89
|
+
"src",
|
|
90
|
+
"test"
|
|
91
|
+
],
|
|
92
|
+
"output": []
|
|
93
|
+
},
|
|
94
|
+
"build": {
|
|
95
|
+
"command": "tsup",
|
|
96
|
+
"dependencies": [
|
|
97
|
+
"test"
|
|
98
|
+
],
|
|
99
|
+
"files": [
|
|
100
|
+
"src",
|
|
101
|
+
"tsconfig.json",
|
|
102
|
+
"tsconfig.tsbuildinfo",
|
|
103
|
+
"tsup.config.ts"
|
|
104
|
+
],
|
|
105
|
+
"output": [
|
|
106
|
+
"dist"
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"trustedDependencies": [
|
|
111
|
+
"@biomejs/biome",
|
|
112
|
+
"esbuild",
|
|
113
|
+
"sharp"
|
|
114
|
+
]
|
|
115
|
+
}
|