fluent-file 0.1.2 → 0.2.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 CHANGED
@@ -1,68 +1,85 @@
1
1
  # Fluent File
2
2
  A fluent TypeScript library for working with files and folders
3
3
 
4
- ## Folder
5
- Provides folder fields:
4
+ ## FluentFolder
5
+
6
+ ### Fields:
6
7
  - `path`: the absolute path to this folder (`/tmp/example/someFolder`)
7
8
  - `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`)
9
+ - `info`: an object with both `path` and `name`
10
+
11
+ ### Methods:
12
+ - `stats()`
13
+ - `exists()`
14
+ - `ensureExists()`
15
+ - `ensureEmpty()`
16
+ - `remove()`
17
+ - `findFolders()`
18
+ - `findFiles()`
19
+
20
+ ### Transform methods:
21
+ - `file()`: create `FluentFile` by resolving the path from this `FluentFolder`
22
+ - `folder()`: create `FluentFolder` by resolving the path from this `FluentFolder`
10
23
 
11
- Provides folder methods:
12
- - exists()
13
- - ensureExists()
14
- - ensureEmpty()
15
- - delete()
16
24
 
17
- ## AFile
18
- Provides fields for any kind of file:
25
+ ## FluentFile
26
+
27
+ ### Fields:
19
28
  - `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`)
29
+ - `folderPath`: the absolute path to the folder this file is in (`/tmp/example/someFolder`)
30
+ - `info`: an object with `absolutePath`, `folderPath`, `name`, `basename` and `ext`
31
+
32
+ ### Getters/setters (call without arg to get, call with arg to set):
33
+ - `name`: the name of the file **with** the extension (`someFile.txt`)
34
+ - `basename`: the name of the file **without** the extension (`someFile`)
22
35
  - `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
36
 
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()
37
+ ### Schema methods:
38
+ - `schema()`: create new `FluentFile` instance with a [Standard Schema](https://standardschema.dev)
39
+
40
+ ### Info methods:
41
+ - `stats()` calls `node:fs/promises.stat()`
29
42
  - `exists()`
30
43
  - `ensureExists()`
44
+
45
+ ### Move/remove methods:
31
46
  - `copyTo()`
32
47
  - `moveTo()`
33
48
  - `linkTo()`
34
49
  - `symlinkTo()`
35
- - `delete()`
50
+ - `remove()`
51
+
52
+ ### Read methods:
36
53
  - `readText()`
37
- - `readTextLines()`
54
+ - `readBuffer()`
55
+ - `read()`: makes use of this file's `schema` to parse then validate the file contents
56
+ - `createReadStream()`
57
+
58
+ ### Write methods:
59
+ - `append()`
38
60
  - `writeText()`
61
+ - `writeBuffer()`
62
+ - `write()`: makes use of this file's `schema` to validate then stringify the file contents
63
+ - `createWriteStream()`
64
+
65
+ ### Image methods:
66
+
67
+ Uses [sharp](https://github.com/lovell/sharp) library
68
+
69
+ - `metadata()`
70
+ - `resize()`
71
+ - `toAVIF()`
72
+ - `phash()`: uses [sharp-phash](https://www.npmjs.com/package/sharp-phash) to create a [perceptual hash](https://en.wikipedia.org/wiki/Perceptual_hashing) of the image
73
+ - plus all other `sharp` methods
74
+
75
+ ### Video methods:
76
+
77
+ Uses [ffmpeg](https://www.ffmpeg.org/) commands
78
+
79
+ - `metadata()`: using [ffprobe](https://ffmpeg.org/ffprobe.html)
80
+ - `extractFrame()`
81
+
82
+ ### Transform methods:
83
+ - `file()`: create `FluentFile` by resolving the path from this `FluentFile`
84
+ - `folder()`: create `FluentFolder` by resolving the path from this `FluentFile`
39
85
 
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 CHANGED
@@ -1,270 +1,494 @@
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';
1
+ import { createReadStream, createWriteStream } from "node:fs";
2
+ import { inspect } from "node:util";
3
+ import * as fluent_command1 from "fluent-command";
4
+ import * as fluent_command0 from "fluent-command";
5
+ import { WriteFileOptions } from "fs-extra/esm";
6
+ import * as neverthrow0$1 from "neverthrow";
7
+ import * as neverthrow0 from "neverthrow";
8
+ import { ResultAsync } from "neverthrow";
9
+ import sharpLib, { AvifOptions, ResizeOptions, SharpOptions } from "sharp";
10
+ import * as zerde5 from "zerde";
11
+ import * as zerde0 from "zerde";
12
+ import { ParseOptions, Prettify, StringifyOptions } from "zerde";
13
+ import * as zod0 from "zod";
14
+ import z from "zod";
15
+ import * as fs0$1 from "fs";
16
+ import * as fs0 from "fs";
17
+ import { StandardSchemaV1 } from "@standard-schema/spec";
12
18
 
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);
19
+ //#region src/common/glob.d.ts
20
+ type FindFoldersGlobOptions = {
21
+ recursive?: boolean;
22
+ glob?: string;
23
+ };
24
+ type FindFilesGlobOptions = FindFoldersGlobOptions & {
25
+ exts?: Array<string>;
26
+ };
27
+ //#endregion
28
+ //#region src/common/node.d.ts
29
+ declare const FsFlags: {
30
+ readonly Append: "a";
31
+ readonly AppendOrFail: "ax";
32
+ readonly AppendAndRead: "a+";
33
+ readonly AppendAndReadOrFail: "ax+";
34
+ readonly Read: "r";
35
+ readonly ReadAndWrite: "r+";
36
+ readonly Write: "w";
37
+ readonly WriteOrFailIfExists: "wx";
38
+ };
39
+ type FsFlag = (typeof FsFlags)[keyof typeof FsFlags];
40
+ //#endregion
41
+ //#region src/file/file.errors.d.ts
42
+ type FileOperation = "Append" | "Chmod" | "Copy" | "Download" | "ImageConvert" | "ImageMetadata" | "ImagePhash" | "ImageResize" | "Link" | "Move" | "Read" | "Remove" | "Stat" | "SymLink" | "Write";
43
+ declare abstract class FileError extends Error {
44
+ readonly operation: FileOperation;
45
+ readonly path: string;
46
+ constructor(path: string, cause: unknown);
36
47
  }
37
- declare class FileReadError extends Error {
38
- constructor(path: string, cause: Error);
48
+ declare class FileDownloadError extends FileError {
49
+ readonly url: string;
50
+ constructor(path: string, url: string, cause: unknown);
39
51
  }
40
- declare class FileWriteError extends Error {
41
- constructor(path: string, cause: Error);
52
+ declare class FileAppendError extends FileError {}
53
+ declare class FileChmodError extends FileError {}
54
+ declare class FileImageConvertError extends FileError {}
55
+ declare class FileImageMetadataError extends FileError {}
56
+ declare class FileImagePhashError extends FileError {}
57
+ declare class FileImageResizeError extends FileError {}
58
+ declare class FileReadError extends FileError {}
59
+ declare class FileRemoveError extends FileError {}
60
+ declare class FileStatError extends FileError {}
61
+ declare class FileWriteError extends FileError {}
62
+ declare const DESTINATION_FALLBACK = "DESTINATION_FALLBACK";
63
+ declare abstract class FileRelocateError extends FileError {
64
+ readonly destinationPath: string;
65
+ constructor(path: string, destinationPath: string | null, cause: unknown);
42
66
  }
43
- declare class FileWasNotFileError extends Error {
44
- actualFileEntryType: FileEntryType;
45
- constructor(path: string, actualFileEntryType: FileEntryType, cause?: Error);
67
+ declare class FileCopyError extends FileRelocateError {}
68
+ declare class FileMoveError extends FileRelocateError {}
69
+ declare class FileLinkError extends FileRelocateError {}
70
+ declare class FileSymLinkError extends FileRelocateError {}
71
+ //#endregion
72
+ //#region src/folder/folder.errors.d.ts
73
+ declare class FolderError extends Error {
74
+ readonly operation: string;
75
+ readonly path: string;
76
+ constructor(operation: string, path: string, cause?: unknown);
46
77
  }
47
-
48
- declare class StringifyError extends Error {
49
- constructor(path: string, cause: Error);
78
+ declare class FolderGlobError extends FolderError {
79
+ constructor(path: string, cause?: unknown);
50
80
  }
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;
81
+ //#endregion
82
+ //#region src/folder/folder.d.ts
83
+ declare class FluentFolder {
84
+ readonly path: string;
85
+ readonly name: string;
86
+ constructor(...pathPieces: PathPieces);
87
+ get info(): {
88
+ path: string;
89
+ name: string;
90
+ };
91
+ folder: (...pathPieces: PathPieces) => FluentFolder;
92
+ file: (file: string, ...extraPathPieces: PathPieces) => FluentFile<string, string>;
93
+ relativePath: (relativeTo?: FluentFolder) => string;
94
+ toString: () => string;
95
+ toJSON: () => {
96
+ FluentFolder: {
97
+ path: string;
98
+ name: string;
68
99
  };
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
- };
100
+ };
101
+ [inspect.custom]: () => {
102
+ FluentFolder: {
103
+ path: string;
104
+ name: string;
78
105
  };
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 afile(file: AFile | Folder | string, ...extraPathPieces: Strings): AFile;
161
- declare function homeFile(file: AFile | Folder | string, ...extraPathPieces: Strings): AFile;
162
- declare function findFiles(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: (contents: z.infer<FileSchema>, stringifyOptions?: YamlStringifyOptions) => 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>;
106
+ };
107
+ stats: () => ResultAsync<fs0$1.Stats, FolderError>;
108
+ exists: () => Promise<boolean>;
109
+ ensureExists: () => Promise<void>;
110
+ ensureEmpty: () => Promise<void>;
111
+ remove: () => Promise<void>;
112
+ findFolders: (findFoldersOptions?: FindFoldersGlobOptions | undefined) => ResultAsync<FluentFolder[], FolderGlobError>;
113
+ findFiles: (findFilesOptions?: FindFilesGlobOptions | undefined) => ResultAsync<FluentFile<string, string>[], FolderGlobError>;
205
114
  }
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;
115
+ declare function folder(...pathPieces: PathPieces): FluentFolder;
116
+ //#endregion
117
+ //#region src/common/path.d.ts
118
+ type PathPieces = ReadonlyArray<string | number>;
119
+ type FileOutputOptions = {
120
+ newFolder?: FluentFolder;
121
+ newBaseName?: string;
213
122
  };
214
- declare enum PhashSimilarity {
215
- Exact = "Exact",
216
- Similar = "Similar",
217
- Unique = "Unique"
218
- }
219
- type PhashSimilarityResult = {
220
- case: PhashSimilarity;
221
- phash: string;
123
+ //#endregion
124
+ //#region src/file/image.d.ts
125
+ declare const MAX_DIFFERENCES = 6;
126
+ declare const IMAGE_EXTENSIONS: string[];
127
+ type ImageResizeOptions = FileOutputOptions & ResizeOptions;
128
+ type ToAVIFOptions = FileOutputOptions & ResizeOptions & AvifOptions;
129
+ type PhashSimilarity = "SAME" | "SIMILAR" | "DIFFERENT";
130
+ type PhashSimilarityInfo = {
131
+ readonly level: PhashSimilarity;
132
+ readonly diff: number;
222
133
  };
134
+ declare const binaryStringRegex: RegExp;
135
+ declare const phashStringSchema: z.ZodCodec<z.ZodString, z.core.$ZodBranded<z.ZodCustomStringFormat<"hex">, "PhashString">>;
136
+ type PhashString = z.output<typeof phashStringSchema>;
137
+ declare const phashThresholdSchema: z.core.$ZodBranded<z.ZodInt, "PhashThreshold">;
138
+ type PhashThreshold = z.infer<typeof phashThresholdSchema>;
139
+ declare const DEFAULT_PHASH_THRESHOLD: number & z.core.$brand<"PhashThreshold">;
140
+ /**
141
+ * Compares two phashes, and returns information about how similar the two phashes are, based on the (optional) threshold.
142
+ *
143
+ * @param {PhashString} phashA
144
+ * @param {PhashString} phashB
145
+ * @param {PhashThreshold} [threshold] - Must be an integer 0-64 (inclusive). Defaults to 6.
146
+ * If there are fewer than `threshold` differences, the images are similar
147
+ *
148
+ * @example Given a needle and a haystack:
149
+ ```ts
150
+ const phashNeedle = "1a...8f" // (hex-encoded string)
151
+ const phashHaystack = ["2b..3c", "4d..5e"] // (Array of hex-encoded strings)
152
+ ```
153
+ *
154
+ * Can be used for sorting by using the `diff` count of the return value
155
+ * @example
156
+ ```ts
157
+ const sorted = phashHaystack.sort((phashA, phashB) => {
158
+ const adiff = comparePhashes(phashNeedle, phashA).diff
159
+ const bdiff = comparePhashes(phashNeedle, phashB).diff
160
+ return adiff - bdiff
161
+ })
162
+ ```
223
163
 
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
-
164
+ * Can be used for filtering by checking the `level` of the return value
165
+ * @example
166
+ ```ts
167
+ // all items in haystack that are the same or similar to the needle
168
+ const matches = phashHaystack.filter(hay => comparePhashes(phashNeedle, hay).level !== "DIFFERENT")
169
+ ```
170
+ *
171
+ * @see `phashesMatch` For a simpler function that returns a boolean of whether the two phashes are similar
172
+ */
173
+ declare function comparePhashes(phashA: PhashString, phashB: PhashString, threshold?: PhashThreshold): PhashSimilarityInfo;
174
+ /**
175
+ * Checks if two phashes `match` based on the (optional) threshold
176
+ *
177
+ * @param {PhashString} phashA
178
+ * @param {PhashString} phashB
179
+ * @param {PhashThreshold} [threshold] - Must be an integer 0-64 (inclusive). Defaults to 6.
180
+ * If there are fewer than `threshold` differences, the images are similar
181
+ *
182
+ * @returns whether the two phashes are similar
183
+ */
184
+ declare function phashesMatch(phashA: PhashString, phashB: PhashString, threshold?: PhashThreshold): boolean;
185
+ //#endregion
186
+ //#region src/file/video.d.ts
231
187
  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;
188
+ declare function getVideoMetaData(path: string): neverthrow0$1.ResultAsync<{
189
+ millis: number;
190
+ seconds: number;
191
+ minutes: number;
192
+ hours: number;
193
+ hms: string;
194
+ nb_streams: number;
195
+ format_name: string;
196
+ format_long_name: string;
197
+ size: number;
198
+ bit_rate: number;
199
+ videoStream: {
200
+ millis: number;
201
+ seconds: number;
202
+ minutes: number;
203
+ hours: number;
204
+ hms: string;
205
+ codec_type: "video";
206
+ height: number;
207
+ width: number;
208
+ codec_name: string;
209
+ codec_long_name: string;
210
+ codec_tag_string: string;
211
+ nb_frames: number;
212
+ bit_rate: number;
213
+ };
214
+ audioStreams: {
215
+ millis: number;
216
+ seconds: number;
217
+ minutes: number;
218
+ hours: number;
219
+ hms: string;
220
+ codec_type: "audio";
221
+ channels: number;
222
+ channel_layout: string;
223
+ codec_name: string;
224
+ codec_long_name: string;
225
+ codec_tag_string: string;
226
+ nb_frames: number;
227
+ bit_rate: number;
228
+ }[];
229
+ }, zerde5.ParseError | zerde5.ValidationError | fluent_command1.FluentCommandError>;
230
+ type ExtractFrameOptions = FileOutputOptions & {
231
+ time?: number;
232
+ unit?: "s" | "ms" | "us";
233
+ ext?: "png" | "jpg";
234
+ };
235
+ //#endregion
236
+ //#region src/file/download.d.ts
237
+ type DownloadProgress = {
238
+ totalBytes: number;
239
+ downloadedBytes: number;
240
+ };
241
+ type DownloadFileOptions = Prettify<Partial<{
242
+ overwrite: boolean;
243
+ progressThrottle: number;
244
+ onProgress: (progressStats: DownloadProgress) => void;
245
+ }>>;
246
+ //#endregion
247
+ //#region src/file/file.d.ts
248
+ declare class FluentFile<Content = string, ParsedContent = Content> {
249
+ #private;
250
+ constructor(schema: StandardSchemaV1<Content, ParsedContent>, file: string | number, ...pathPieces: PathPieces);
251
+ toString: () => string;
252
+ toJSON: () => {
253
+ FluentFile: {
254
+ absolutePath: string;
255
+ folderPath: string;
256
+ name: string;
257
+ basename: string;
258
+ ext: string;
259
+ };
260
+ };
261
+ [inspect.custom]: () => {
262
+ FluentFile: {
263
+ absolutePath: string;
264
+ folderPath: string;
265
+ name: string;
266
+ basename: string;
267
+ ext: string;
268
+ };
269
+ };
270
+ path: () => string;
271
+ folderPath: () => string;
272
+ name(): string;
273
+ name(newName: string): this;
274
+ basename(): string;
275
+ basename(newBaseName: string): this;
276
+ ext(): string;
277
+ ext(newExtension: string): this;
278
+ schema: <NewSchema extends StandardSchemaV1>(newSchema: NewSchema) => FluentFile<StandardSchemaV1.InferInput<NewSchema>, StandardSchemaV1.InferOutput<NewSchema>>;
279
+ get info(): {
280
+ absolutePath: string;
281
+ folderPath: string;
282
+ name: string;
283
+ basename: string;
284
+ ext: string;
285
+ };
286
+ relativePath: (relativeTo?: FluentFolder) => string;
287
+ file: (file: string, ...extraPathPieces: PathPieces) => FluentFile<Content, ParsedContent>;
288
+ folder: (...extraPathPieces: PathPieces) => FluentFolder;
289
+ stats: () => ResultAsync<fs0.Stats, FileStatError>;
290
+ exists: () => Promise<boolean>;
291
+ ensureExists: () => Promise<void>;
292
+ chmod: (mode: string | number) => ResultAsync<void, FileChmodError>;
293
+ copyTo: (destination: FluentFolder | FluentFile<string, string>) => ResultAsync<void, FileCopyError>;
294
+ moveTo: (destination: FluentFolder | FluentFile<string, string>, dereferenceSymlinks?: boolean | undefined) => ResultAsync<void, FileMoveError>;
295
+ linkTo: (destination: FluentFolder | FluentFile<string, string>) => ResultAsync<void, FileLinkError>;
296
+ symlinkTo: (destination: FluentFolder | FluentFile<string, string>) => ResultAsync<void, FileSymLinkError>;
297
+ remove: () => ResultAsync<void, FileRemoveError>;
298
+ readText: (options?: FileReadOptions | undefined) => ResultAsync<string, FileReadError>;
299
+ readBuffer: (options?: Pick<FileReadOptions, "flag" | "signal"> | undefined) => ResultAsync<Buffer<ArrayBufferLike>, FileReadError>;
300
+ read: (options?: FileReadOptions) => ResultAsync<ParsedContent, FileReadError | zerde0.ParseError | zerde0.ValidationError>;
301
+ createReadStream: (readStreamOptions?: Parameters<typeof createReadStream>[1]) => fs0.ReadStream;
302
+ writeText: (textContent: string, writeOptions?: WriteFileOptions | undefined) => ResultAsync<void, FileWriteError>;
303
+ writeBuffer: (bufferContent: Buffer<ArrayBufferLike>, writeOptions?: WriteFileOptions | undefined) => ResultAsync<void, FileWriteError>;
304
+ write: (content: Content, options?: FileWriteOptions) => ResultAsync<void, FileWriteError | zerde0.ValidationError | zerde0.StringifyError>;
305
+ createWriteStream: (writeStreamOptions?: Parameters<typeof createWriteStream>[1]) => fs0.WriteStream;
306
+ append: (data: string | Buffer<ArrayBufferLike>, writeOptions?: WriteFileOptions | undefined) => ResultAsync<void, FileAppendError>;
307
+ image: (sharpOptions?: SharpOptions) => {
308
+ metadata: () => ResultAsync<sharpLib.Metadata, FileImageMetadataError>;
309
+ resize: (resizeOptions: ImageResizeOptions) => ResultAsync<FluentFile<string, string>, FileImageResizeError>;
310
+ toAVIF: (toAVIFOptions?: ToAVIFOptions | undefined) => ResultAsync<FluentFile<string, string>, FileImageConvertError>;
311
+ /**
312
+ * Calculate the perceptual hash of an image
313
+ * @returns A hex-encoded string
314
+ * @see Use `comparePhashes` or `phashesMatch` to compare the returned phash
315
+ */
316
+ phash: () => ResultAsync<string & zod0.$brand<"PhashString">, FileImagePhashError>;
317
+ removeAlpha(): sharpLib.Sharp;
318
+ ensureAlpha(alpha?: number): sharpLib.Sharp;
319
+ extractChannel(channel: 0 | 1 | 2 | 3 | "red" | "green" | "blue" | "alpha"): sharpLib.Sharp;
320
+ joinChannel(images: string | Buffer | ArrayLike<string | Buffer>, options?: SharpOptions): sharpLib.Sharp;
321
+ bandbool(boolOp: keyof sharpLib.BoolEnum): sharpLib.Sharp;
322
+ tint(tint: sharpLib.Colour | sharpLib.Color): sharpLib.Sharp;
323
+ greyscale(greyscale?: boolean): sharpLib.Sharp;
324
+ grayscale(grayscale?: boolean): sharpLib.Sharp;
325
+ pipelineColourspace(colourspace?: string): sharpLib.Sharp;
326
+ pipelineColorspace(colorspace?: string): sharpLib.Sharp;
327
+ toColourspace(colourspace?: string): sharpLib.Sharp;
328
+ toColorspace(colorspace: string): sharpLib.Sharp;
329
+ composite(images: sharpLib.OverlayOptions[]): sharpLib.Sharp;
330
+ clone(): sharpLib.Sharp;
331
+ keepMetadata(): sharpLib.Sharp;
332
+ stats(callback: (err: Error, stats: sharpLib.Stats) => void): sharpLib.Sharp;
333
+ stats(): Promise<sharpLib.Stats>;
334
+ rotate(angle?: number, options?: sharpLib.RotateOptions): sharpLib.Sharp;
335
+ autoOrient(): sharpLib.Sharp;
336
+ flip(flip?: boolean): sharpLib.Sharp;
337
+ flop(flop?: boolean): sharpLib.Sharp;
338
+ affine(matrix: [number, number, number, number] | sharpLib.Matrix2x2, options?: sharpLib.AffineOptions): sharpLib.Sharp;
339
+ sharpen(options?: sharpLib.SharpenOptions): sharpLib.Sharp;
340
+ sharpen(sigma?: number, flat?: number, jagged?: number): sharpLib.Sharp;
341
+ median(size?: number): sharpLib.Sharp;
342
+ blur(sigma?: number | boolean | sharpLib.BlurOptions): sharpLib.Sharp;
343
+ dilate(width?: number): sharpLib.Sharp;
344
+ erode(width?: number): sharpLib.Sharp;
345
+ flatten(flatten?: boolean | sharpLib.FlattenOptions): sharpLib.Sharp;
346
+ unflatten(): sharpLib.Sharp;
347
+ gamma(gamma?: number, gammaOut?: number): sharpLib.Sharp;
348
+ negate(negate?: boolean | sharpLib.NegateOptions): sharpLib.Sharp;
349
+ normalise(normalise?: sharpLib.NormaliseOptions): sharpLib.Sharp;
350
+ normalize(normalize?: sharpLib.NormaliseOptions): sharpLib.Sharp;
351
+ clahe(options: sharpLib.ClaheOptions): sharpLib.Sharp;
352
+ convolve(kernel: sharpLib.Kernel): sharpLib.Sharp;
353
+ threshold(threshold?: number, options?: sharpLib.ThresholdOptions): sharpLib.Sharp;
354
+ boolean(operand: string | Buffer, operator: keyof sharpLib.BoolEnum, options?: {
355
+ raw: sharpLib.Raw;
356
+ }): sharpLib.Sharp;
357
+ linear(a?: number | number[] | null, b?: number | number[]): sharpLib.Sharp;
358
+ recomb(inputMatrix: sharpLib.Matrix3x3 | sharpLib.Matrix4x4): sharpLib.Sharp;
359
+ modulate(options?: {
360
+ brightness?: number | undefined;
361
+ saturation?: number | undefined;
362
+ hue?: number | undefined;
363
+ lightness?: number | undefined;
364
+ }): sharpLib.Sharp;
365
+ toFile(fileOut: string, callback: (err: Error, info: sharpLib.OutputInfo) => void): sharpLib.Sharp;
366
+ toFile(fileOut: string): Promise<sharpLib.OutputInfo>;
367
+ toBuffer(callback: (err: Error, buffer: Buffer, info: sharpLib.OutputInfo) => void): sharpLib.Sharp;
368
+ toBuffer(options?: {
369
+ resolveWithObject: false;
370
+ }): Promise<Buffer>;
371
+ toBuffer(options: {
372
+ resolveWithObject: true;
373
+ }): Promise<{
374
+ data: Buffer;
375
+ info: sharpLib.OutputInfo;
376
+ }>;
377
+ keepExif(): sharpLib.Sharp;
378
+ withExif(exif: sharpLib.Exif): sharpLib.Sharp;
379
+ withExifMerge(exif: sharpLib.Exif): sharpLib.Sharp;
380
+ keepIccProfile(): sharpLib.Sharp;
381
+ withIccProfile(icc: string, options?: sharpLib.WithIccProfileOptions): sharpLib.Sharp;
382
+ keepXmp(): sharpLib.Sharp;
383
+ withXmp(xmp: string): sharpLib.Sharp;
384
+ withMetadata(withMetadata?: sharpLib.WriteableMetadata): sharpLib.Sharp;
385
+ jpeg(options?: sharpLib.JpegOptions): sharpLib.Sharp;
386
+ jp2(options?: sharpLib.Jp2Options): sharpLib.Sharp;
387
+ jxl(options?: sharpLib.JxlOptions): sharpLib.Sharp;
388
+ png(options?: sharpLib.PngOptions): sharpLib.Sharp;
389
+ webp(options?: sharpLib.WebpOptions): sharpLib.Sharp;
390
+ gif(options?: sharpLib.GifOptions): sharpLib.Sharp;
391
+ avif(options?: sharpLib.AvifOptions): sharpLib.Sharp;
392
+ heif(options?: sharpLib.HeifOptions): sharpLib.Sharp;
393
+ tiff(options?: sharpLib.TiffOptions): sharpLib.Sharp;
394
+ raw(options?: sharpLib.RawOptions): sharpLib.Sharp;
395
+ toFormat(format: keyof sharpLib.FormatEnum | sharpLib.AvailableFormatInfo, options?: sharpLib.OutputOptions | sharpLib.JpegOptions | sharpLib.PngOptions | sharpLib.WebpOptions | sharpLib.AvifOptions | sharpLib.HeifOptions | sharpLib.JxlOptions | sharpLib.GifOptions | sharpLib.Jp2Options | sharpLib.RawOptions | sharpLib.TiffOptions): sharpLib.Sharp;
396
+ tile(tile?: sharpLib.TileOptions): sharpLib.Sharp;
397
+ timeout(options: sharpLib.TimeoutOptions): sharpLib.Sharp;
398
+ extend(extend: number | sharpLib.ExtendOptions): sharpLib.Sharp;
399
+ extract(region: sharpLib.Region): sharpLib.Sharp;
400
+ trim(options?: sharpLib.TrimOptions): sharpLib.Sharp;
401
+ allowHalfOpen: boolean;
402
+ off<K>(eventName: string | symbol, listener: (...args: any[]) => void): sharpLib.Sharp;
403
+ removeAllListeners(eventName?: string | symbol | undefined): sharpLib.Sharp;
404
+ setMaxListeners(n: number): sharpLib.Sharp;
405
+ getMaxListeners(): number;
406
+ listeners<K>(eventName: string | symbol): Function[];
407
+ rawListeners<K>(eventName: string | symbol): Function[];
408
+ listenerCount<K>(eventName: string | symbol, listener?: Function | undefined): number;
409
+ eventNames(): (string | symbol)[];
410
+ readableAborted: boolean;
411
+ readable: boolean;
412
+ readableDidRead: boolean;
413
+ readableEncoding: BufferEncoding | null;
414
+ readableEnded: boolean;
415
+ readableFlowing: boolean | null;
416
+ readableHighWaterMark: number;
417
+ readableLength: number;
418
+ readableObjectMode: boolean;
419
+ destroyed: boolean;
420
+ closed: boolean;
421
+ errored: Error | null;
422
+ writable: boolean;
423
+ writableAborted: boolean;
424
+ writableEnded: boolean;
425
+ writableFinished: boolean;
426
+ writableHighWaterMark: number;
427
+ writableLength: number;
428
+ writableObjectMode: boolean;
429
+ writableCorked: number;
430
+ writableNeedDrain: boolean;
431
+ };
432
+ video: () => {
433
+ metadata: () => ResultAsync<{
434
+ millis: number;
435
+ seconds: number;
436
+ minutes: number;
437
+ hours: number;
438
+ hms: string;
439
+ nb_streams: number;
440
+ format_name: string;
441
+ format_long_name: string;
442
+ size: number;
443
+ bit_rate: number;
444
+ videoStream: {
238
445
  millis: number;
239
- minutes: number;
240
446
  seconds: number;
447
+ minutes: number;
448
+ hours: number;
449
+ hms: string;
450
+ codec_type: "video";
451
+ height: number;
241
452
  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;
453
+ codec_name: string;
454
+ codec_long_name: string;
455
+ codec_tag_string: string;
456
+ nb_frames: number;
457
+ bit_rate: number;
458
+ };
459
+ audioStreams: {
460
+ millis: number;
461
+ seconds: number;
462
+ minutes: number;
463
+ hours: number;
464
+ hms: string;
465
+ codec_type: "audio";
466
+ channels: number;
467
+ channel_layout: string;
468
+ codec_name: string;
469
+ codec_long_name: string;
470
+ codec_tag_string: string;
471
+ nb_frames: number;
472
+ bit_rate: number;
473
+ }[];
474
+ }, zerde0.ParseError | zerde0.ValidationError | fluent_command0.FluentCommandError>;
475
+ extractFrame: (extractFrameOptions?: ExtractFrameOptions) => Promise<neverthrow0.Result<FluentFile<string, string>, fluent_command0.FluentCommandError>>;
476
+ };
477
+ download: (url: string, downloadOptions?: DownloadFileOptions) => ResultAsync<"ALREADY_EXISTS" | "SUCCESS", FileDownloadError>;
268
478
  }
269
-
270
- export { AFile, Folder, GitFolder, IMAGE_EXTENSIONS, ImageFile, JsonFile, PhashSimilarity, type PhashSimilarityResult, type ToAvifOptions, VIDEO_EXTENSIONS, VideoFile, YAML_EXTENSIONS, YamlFile, afile, base2to36, base36to2, cwd, findFiles, findImageFiles, findJsonFiles, findVideoFiles, findYamlFiles, folder, homeFile, homeFolder, imageFile, jsonFile, phashCheck, videoFile, yamlFile };
479
+ type FileReadOptions = ParseOptions & Partial<{
480
+ encoding: BufferEncoding;
481
+ flag: FsFlag;
482
+ signal: AbortSignal;
483
+ }>;
484
+ type FileWriteOptions = StringifyOptions & Partial<{
485
+ encoding: BufferEncoding;
486
+ mode: number;
487
+ flag: FsFlag;
488
+ flush: boolean;
489
+ signal: AbortSignal;
490
+ }>;
491
+ declare function ffile(file: PathPieces[number], ...extraPathPieces: PathPieces): FluentFile<string, string>;
492
+ declare function homeFile(file: PathPieces[number], ...extraPathPieces: PathPieces): FluentFile<string, string>;
493
+ //#endregion
494
+ export { DEFAULT_PHASH_THRESHOLD, DESTINATION_FALLBACK, ExtractFrameOptions, FileAppendError, FileChmodError, FileCopyError, FileDownloadError, FileImageConvertError, FileImageMetadataError, FileImagePhashError, FileImageResizeError, FileLinkError, FileMoveError, FileOperation, FileReadError, FileReadOptions, FileRemoveError, FileStatError, FileSymLinkError, FileWriteError, FileWriteOptions, FindFilesGlobOptions, FindFoldersGlobOptions, FluentFile, FluentFolder, FsFlag, FsFlags, IMAGE_EXTENSIONS, ImageResizeOptions, MAX_DIFFERENCES, PhashSimilarity, PhashSimilarityInfo, PhashString, PhashThreshold, ToAVIFOptions, VIDEO_EXTENSIONS, binaryStringRegex, comparePhashes, ffile, folder, getVideoMetaData, homeFile, phashStringSchema, phashThresholdSchema, phashesMatch };
package/dist/index.js CHANGED
@@ -1,10 +1 @@
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(t).andThen(o=>oe(o,r)).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 AFile,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,g as afile,ct as base2to36,z as base36to2,N as cwd,Vt as findFiles,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};
1
+ import{constants as e,createReadStream as t,createWriteStream as n}from"node:fs";import{chmod as r,copyFile as i,opendir as a,readFile as o,stat as s}from"node:fs/promises";import{homedir as c,platform as l}from"node:os";import{inspect as u}from"node:util";import{fcmd as d}from"fluent-command";import{appendFile as f}from"fs-extra";import{emptyDir as ee,ensureDir as te,ensureFile as ne,ensureLink as re,ensureSymlink as ie,move as ae,outputFile as p,remove as m}from"fs-extra/esm";import{ResultAsync as h}from"neverthrow";import g,{basename as oe}from"pathe";import se from"sharp";import ce from"sharp-phash";import{parseString as le,zparse as _,zstringify as ue}from"zerde";import v from"zod";import y from"picomatch";import{pipeline as b}from"node:stream/promises";const de={Append:`a`,AppendOrFail:`ax`,AppendAndRead:`a+`,AppendAndReadOrFail:`ax+`,Read:`r`,ReadAndWrite:`r+`,Write:`w`,WriteOrFailIfExists:`wx`};function x(...e){let t=e.map((e,t)=>{if(typeof e==`number`)return e.toString();if(t===0&&e===`~`)return c();if(e.toUpperCase()!==e)return e;let n=``;if(e.startsWith(`$`)&&(n=e.slice(1)),l()===`win32`&&e.startsWith(`%`)&&e.endsWith(`%`)&&(n=e.slice(1,-1)),n!==``){let t=process.env[n];if(!t)throw Error(`Missing Environment variable ${e}`);return t}return e});return g.resolve(...t)}function S(e){let t=e.lastIndexOf(`.`);return t<0?e:e.slice(t+1)}function fe(e){return S(g.extname(e))}const pe=4,me=-5;var C=class extends Error{operation;path;constructor(e,t){super(),this.path=e,this.operation=this.constructor.name.slice(pe,me),this.message=`Error trying to ${this.operation} file: ${e}`,this.cause=t,this.name=this.constructor.name}},w=class extends C{url;constructor(e,t,n){super(e,n),this.url=t,this.message=`Error trying to download url: ${t} to file: ${e}`}},he=class extends C{},ge=class extends C{},T=class extends C{},E=class extends C{},D=class extends C{},O=class extends C{},k=class extends C{},A=class extends C{},j=class extends C{},M=class extends C{};const N=`DESTINATION_FALLBACK`;var P=class extends C{destinationPath;constructor(e,t,n){super(e,n),this.destinationPath=t??N}},F=class extends P{},I=class extends P{},L=class extends P{},R=class extends P{};const _e=6,ve=[`avif`,`gif`,`jpeg`,`jpg`,`png`,`svg`,`tiff`,`webp`],z=64,ye=16,B=/^[01]+$/,V=v.codec(v.string().regex(B).length(z),v.hex().brand(`PhashString`),{decode:e=>BigInt(`0b${e}`).toString(ye),encode:e=>BigInt(`0x${e}`).toString(2).padStart(z,`0`)}),H=v.int().nonnegative().max(z).brand(`PhashThreshold`),be=6,U=H.parse(be);function xe(e,t,n=U){if(e===t)return Object.freeze({level:`SAME`,diff:0});let r=V.encode(e),i=V.encode(t),a=0,o=z;for(;o--;)r[o]!==i[o]&&a++;let s=a<n?`SIMILAR`:`DIFFERENT`;return Object.freeze({level:s,diff:a})}function Se(e,t,n=U){if(e===t)return!0;let r=V.encode(e),i=V.encode(t),a=0,o=z;for(;o--;)if(r[o]!==i[o]&&a++,a>=n)return!1;return!0}const Ce=v.coerce.number().positive(),W=v.coerce.number().int(),we=W.nonnegative(),G=W.positive(),K=v.object({duration:Ce,bit_rate:G}),Te=1e3;function q(e){let t=Math.round(e*Te),n=Math.trunc(e%60),r=Math.trunc(e/60%60),i=Math.trunc(e/60/60%60),a=[i,r,n].filter((e,t)=>t>0||e>0).map((e,t)=>{let n=e.toString();return t>0&&(n=n.padStart(2,`0`)),n}).join(`:`);return{millis:t,seconds:n,minutes:r,hours:i,hms:a}}const J=v.object({...K.shape,codec_name:v.string(),codec_long_name:v.string(),codec_tag_string:v.string(),nb_frames:G}),Ee=v.object({...J.shape,codec_type:v.literal(`audio`),channels:G,channel_layout:v.string()}),De=v.object({...J.shape,codec_type:v.literal(`video`),height:G,width:G}),Oe=v.object({...K.shape,nb_streams:G,format_name:v.string(),format_long_name:v.string(),size:G}),ke=v.object({format:Oe,streams:v.array(v.union([Ee,De])).min(2)}).transform(({format:{duration:e,...t},streams:n},{addIssue:r})=>{let i=n.filter(e=>e.codec_type===`video`).map(({duration:e,...t})=>({...t,...q(e)})).at(0);if(!i)return r({code:`custom`,message:`Video was missing video stream`}),v.NEVER;let a=n.filter(e=>e.codec_type===`audio`).map(({duration:e,...t})=>({...t,...q(e)}));return a.length===0?(r({code:`custom`,message:`Video was missing audio stream`}),v.NEVER):{videoStream:i,audioStreams:a,...t,...q(e)}}),Ae=[`avi`,`flv`,`mkv`,`mov`,`mp4`,`mpeg`,`mpg`,`rm`,`rmvb`,`webm`,`wmv`];function Y(e){return d(`ffprobe`).opt(`hide_banner`).opt(`show_format`).opt(`show_streams`).opt({v:`error`,print_format:`json`}).args(e).read().andThen(e=>_(e.stdout,ke,`json`))}var X=class extends Error{operation;path;constructor(e,t,n){let r=`Error trying to ${e} folder: ${t}`;super(r,{cause:n}),this.path=t,this.operation=e,this.name=this.constructor.name}},je=class extends X{constructor(e,t){super(`glob`,e,t)}};function Me(){return Q(process.cwd())}var Z=class{path;name;constructor(...e){this.path=x(...e),this.name=oe(this.path),this.name||=this.path}get info(){return{path:this.path,name:this.name}}folder=(...e)=>Q(this.path,...e);file=(e,...t)=>$(this.path,e,...t);relativePath=(e=Me())=>{let t=this.path;return t.startsWith(e.path)&&(t=t.slice(e.path.length),t.startsWith(`/`))?t.slice(1):t};toString=()=>this.path;toJSON=()=>({FluentFolder:this.info});[u.custom]=()=>this.toJSON();stats=h.fromThrowable(async()=>{let e=await s(this.path);if(!e.isDirectory())throw new X(`stat`,this.path,`FolderWasNotFolder`);return e},e=>e instanceof X?e:new X(`stat`,this.path,e));exists=async()=>(await this.stats()).isOk();ensureExists=()=>te(this.path);ensureEmpty=()=>ee(this.path);remove=()=>m(this.path);findFolders=h.fromThrowable(async(e={})=>{let t=e.glob?y(e.glob,{cwd:this.path}):null,n=await a(this.path,{recursive:e.recursive??!0}),r=[];for await(let e of n){if(!e.isDirectory())continue;let n=g.resolve(e.parentPath,e.name);if(t&&!t(n))continue;r.push(n)}return r.sort().map(e=>Q(e))},e=>new je(this.path,e));findFiles=h.fromThrowable(async(e={})=>{let t=e.glob?y(e.glob,{cwd:this.path}):null,n=await a(this.path,{recursive:e.recursive??!0}),r=[],i=(e.exts??[]).map(S);for await(let e of n){if(!e.isFile())continue;let n=g.resolve(e.parentPath,e.name);if(i.length>0){let t=fe(e.name);if(!i.includes(t))continue}if(t&&!t(n))continue;r.push(n)}return r.sort().map(e=>$(e))},e=>new je(this.path,e))};function Q(...e){return new Z(...e)}const Ne=h.fromThrowable(async(e,t,n={})=>{try{let{overwrite:r=!1,progressThrottle:i=100,onProgress:a}=n;if(!r&&await t.exists())return`ALREADY_EXISTS`;await t.folder().ensureExists();let o=await fetch(e);if(!o.ok){let{status:t,statusText:n,redirected:r,type:i}=o,a={};throw o.headers.forEach((e,t)=>{a[t]=e}),Error(`Got ${o.status} ${o.statusText} for url: ${e}`,{cause:{status:t,statusText:n,headers:a,redirected:r,type:i}})}let s=G.parse(o.headers.get(`content-length`));if(!o.body)throw Error(`Request body was null for url: ${e}`,{cause:o});let c=o.body,l=t.createWriteStream();if(!a)return await b(c,l),`SUCCESS`;let u=0,d=null,f=e=>{u+=e,!d&&(a({downloadedBytes:u,totalBytes:s}),d=setTimeout(()=>{d=null},i))};return await b(c,async function*(e){for await(let t of e)f(t.length)},l),`SUCCESS`}catch(n){throw new w(t.path(),e,n)}},e=>e);var Pe=class a{#path;#folderPath;#name;#basename;#ext;#schema;#info;#ensureDestination=async e=>{let t=e instanceof a?e:e.file(this.#name);return await t.folder().ensureExists(),await t.remove(),t.path()};#prepareTarget=async e=>{let{newFolder:t=this.folder(),newBaseName:n=this.#basename,newExt:r=this.#ext}=e,i=t.file(`${n}.${r}`);return await i.remove(),await t.ensureExists(),i};constructor(e,t,...n){this.#path=x(t,...n),this.#schema=e;let r=Fe(this.#path);this.#folderPath=r.folderPath,this.#name=r.name,this.#basename=r.basename,this.#ext=r.ext,this.#info=r}toString=()=>this.#path;toJSON=()=>({FluentFile:this.info});[u.custom]=()=>this.toJSON();path=()=>this.#path;folderPath=()=>this.#folderPath;name(e){return e===void 0?this.#name:(this.#name=e,this.#path=`${this.#folderPath}/${e}`,this)}basename(e){return e===void 0?this.#basename:(this.#basename=e,this.#name=e+this.#ext,this.#path=`${this.#folderPath}/${this.#name}`,this)}ext(e){return e===void 0?this.#ext:(this.#ext=e,this.#name=this.#basename+e,this.#path=`${this.#folderPath}/${this.#name}`,this)}schema=e=>new a(e,this.#path);get info(){return this.#info}relativePath=(e=Me())=>{let t=this.#path;return t.startsWith(e.path)&&(t=t.slice(e.path.length),t.startsWith(`/`))?t.slice(1):t};file=(e,...t)=>new a(this.#schema,this.#folderPath,e,...t);folder=(...e)=>new Z(this.#folderPath,...e);stats=h.fromThrowable(async()=>{let e=await s(this.#path);if(!e.isFile())throw new j(this.#path,`FileWasNotFile`);return e},e=>e instanceof j?e:new j(this.#path,e));exists=async()=>(await this.stats()).isOk();ensureExists=()=>ne(this.#path);chmod=h.fromThrowable(async e=>{await r(this.#path,e)},e=>new ge(this.#path,e));copyTo=h.fromThrowable(async t=>{let n=null;try{n=await this.#ensureDestination(t),await i(this.#path,n,e.COPYFILE_FICLONE)}catch(e){throw new F(this.#path,n,e)}},e=>e);moveTo=h.fromThrowable(async(e,t)=>{let n=null;try{n=await this.#ensureDestination(e),await ae(this.#path,n,{overwrite:!0,dereference:t})}catch(e){throw new I(this.#path,n,e)}},e=>e);linkTo=h.fromThrowable(async e=>{let t=null;try{t=await this.#ensureDestination(e),await re(this.#path,t)}catch(e){throw new L(this.#path,t,e)}},e=>e);symlinkTo=h.fromThrowable(async e=>{let t=null;try{t=await this.#ensureDestination(e),await ie(this.#path,t)}catch(e){throw new R(this.#path,t,e)}},e=>e);remove=h.fromThrowable(()=>m(this.#path),e=>new A(this.#path,e));readText=h.fromThrowable((e={})=>o(this.#path,{encoding:`utf8`,...e}),e=>new k(this.#path,e));readBuffer=h.fromThrowable((e={})=>o(this.#path,e),e=>new k(this.#path,e));read=(e={})=>this.readText(e).andThen(t=>_(t,this.#schema,{format:this.#path,...e}));createReadStream=e=>t(this.#path,e);writeText=h.fromThrowable((e,t={})=>p(this.#path,e,t),e=>new M(this.#path,e));writeBuffer=h.fromThrowable((e,t={})=>p(this.#path,e,t),e=>new M(this.#path,e));write=(e,t={})=>ue(e,this.#schema,t).andThen(e=>this.writeText(e,t));createWriteStream=e=>n(this.#path,e);append=h.fromThrowable((e,t={})=>f(this.#path,e,t),e=>new he(this.#path,e));image=(e={})=>{let t=se(this.#path,e),n=h.fromThrowable(()=>t.metadata(),e=>new E(this.#path,e)),r=h.fromThrowable(async e=>{let n=await this.#prepareTarget(e);return await t.resize(e).toFile(n.path()),n},e=>new O(this.#path,e)),i=h.fromThrowable(async(e={})=>{let n=await this.#prepareTarget({...e,newExt:`avif`});return await t.resize(e).avif(e).toFile(n.path()),n},e=>new T(this.#path,e)),a=h.fromThrowable(async()=>{let t=await ce(this.#path,e);return V.decode(t)},e=>new D(this.#path,e));return{...t,metadata:n,resize:r,toAVIF:i,phash:a}};video=()=>({metadata:()=>Y(this.#path),extractFrame:async(e={})=>{let{time:t=1,unit:n=`s`,ext:r=`png`,...i}=e,a=n===`s`?t:`${t}${n}`,o=await this.#prepareTarget({newExt:r,...i});return d(`ffmpeg`).opt(`hide_banner`).opt({ss:a,i:this.#path,"frames:v":1,y:``}).args(o.path()).read().map(()=>o)}});download=(e,t={})=>Ne(e,this,t)};function Fe(e){let t=g.basename(e),n=g.dirname(e),r=``,i=``,a=t.lastIndexOf(`.`);return a<=0?(r=t,i=``):(r=t.slice(0,a),i=t.slice(a+1)),{absolutePath:e,folderPath:n,name:t,basename:r,ext:i}}function $(e,...t){return new Pe(le(),e,...t)}function Ie(e,...t){return $(c(),e,...t)}export{U as DEFAULT_PHASH_THRESHOLD,N as DESTINATION_FALLBACK,he as FileAppendError,ge as FileChmodError,F as FileCopyError,w as FileDownloadError,T as FileImageConvertError,E as FileImageMetadataError,D as FileImagePhashError,O as FileImageResizeError,L as FileLinkError,I as FileMoveError,k as FileReadError,A as FileRemoveError,j as FileStatError,R as FileSymLinkError,M as FileWriteError,Pe as FluentFile,Z as FluentFolder,de as FsFlags,ve as IMAGE_EXTENSIONS,_e as MAX_DIFFERENCES,Ae as VIDEO_EXTENSIONS,B as binaryStringRegex,xe as comparePhashes,$ as ffile,Q as folder,Y as getVideoMetaData,Ie as homeFile,V as phashStringSchema,H as phashThresholdSchema,Se as phashesMatch};
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "fluent-file",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "A fluent TypeScript library for working with files and folders",
5
5
  "keywords": [
6
6
  "files",
7
7
  "folders",
8
8
  "fluent"
9
9
  ],
10
- "repository": "github:dannywexler/fluent-file",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/dannywexler/fluent-file.git"
13
+ },
11
14
  "author": "Danny Wexler",
12
15
  "license": "MIT",
13
16
  "type": "module",
@@ -23,93 +26,37 @@
23
26
  "files": [
24
27
  "dist"
25
28
  ],
26
- "overrides": {
27
- "esbuild": "0.24.0"
28
- },
29
29
  "dependencies": {
30
- "fluent-ffmpeg": "^2.1.3",
30
+ "@standard-schema/spec": "^1.0.0",
31
+ "fluent-command": "^0.1.4",
31
32
  "fs-extra": "^11.2.0",
32
- "globby": "^14.0.2",
33
- "log-update": "^6.1.0",
34
33
  "neverthrow": "^8.1.1",
35
- "parse-json": "^8.1.0",
36
34
  "pathe": "^1.1.2",
37
- "safe-stable-stringify": "^2.5.0",
38
- "sharp": "^0.33.5",
35
+ "picomatch": "^4.0.3",
36
+ "sharp": "^0.34.5",
39
37
  "sharp-phash": "^2.2.0",
40
- "simple-git": "^3.27.0",
41
- "yaml": "^2.6.1",
42
- "zod": "^3.24.1"
38
+ "zerde": "^0.1.4",
39
+ "zod": "^4.1.13"
43
40
  },
44
41
  "devDependencies": {
45
- "@biomejs/biome": "^1.9.4",
42
+ "@biomejs/biome": "2.2.6",
46
43
  "@types/download": "^8.0.5",
47
44
  "@types/fluent-ffmpeg": "^2.1.27",
48
45
  "@types/fs-extra": "^11.0.4",
49
46
  "@types/node": "^22.10.2",
50
- "download": "^8.0.0",
51
- "tsup": "^8.3.5",
52
- "typescript": "^5.7.2",
47
+ "@types/picomatch": "^4.0.2",
48
+ "lefthook": "^1.13.6",
49
+ "tsdown": "^0.12.9",
50
+ "typescript": "^5.9.3",
53
51
  "vite-tsconfig-paths": "^5.1.4",
54
- "vitest": "^2.1.8",
55
- "wireit": "^0.14.9"
52
+ "vitest": "^3.2.4"
56
53
  },
57
54
  "scripts": {
58
- "build": "wireit",
59
- "test": "wireit",
55
+ "build": "lefthook run build",
60
56
  "test:watch": "vitest"
61
57
  },
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
58
  "trustedDependencies": [
111
59
  "@biomejs/biome",
112
- "esbuild",
113
60
  "sharp"
114
61
  ]
115
62
  }