@stacksjs/storage 0.70.88 → 0.70.91

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.
Files changed (71) hide show
  1. package/dist/adapters/bun.d.ts +31 -0
  2. package/dist/adapters/bun.js +226 -0
  3. package/dist/adapters/index.d.ts +6 -0
  4. package/dist/adapters/index.js +5 -0
  5. package/dist/adapters/local.d.ts +38 -0
  6. package/dist/adapters/local.js +225 -0
  7. package/dist/adapters/memory.d.ts +38 -0
  8. package/dist/adapters/memory.js +318 -0
  9. package/dist/adapters/s3.d.ts +53 -0
  10. package/dist/adapters/s3.js +471 -0
  11. package/dist/adapters/scoped.d.ts +68 -0
  12. package/dist/adapters/scoped.js +142 -0
  13. package/dist/copy.d.ts +3 -0
  14. package/dist/copy.js +30 -0
  15. package/dist/delete.d.ts +8 -0
  16. package/dist/delete.js +103 -0
  17. package/dist/drivers/aws.d.ts +4 -0
  18. package/dist/drivers/aws.js +94 -0
  19. package/dist/drivers/bun.d.ts +4 -0
  20. package/dist/drivers/bun.js +88 -0
  21. package/dist/drivers/index.d.ts +4 -0
  22. package/dist/drivers/index.js +4 -0
  23. package/dist/drivers/local.d.ts +4 -0
  24. package/dist/drivers/local.js +88 -0
  25. package/dist/drivers/memory.d.ts +4 -0
  26. package/dist/drivers/memory.js +67 -0
  27. package/dist/facade.d.ts +53 -0
  28. package/dist/facade.js +226 -0
  29. package/dist/files.d.ts +52 -0
  30. package/dist/files.js +126 -0
  31. package/dist/folders.d.ts +18 -0
  32. package/dist/folders.js +36 -0
  33. package/dist/fs.d.ts +4 -0
  34. package/dist/fs.js +7 -0
  35. package/dist/glob.d.ts +13 -0
  36. package/dist/glob.js +40 -0
  37. package/dist/hash.d.ts +5 -0
  38. package/dist/hash.js +33 -0
  39. package/dist/helpers.d.ts +7 -0
  40. package/dist/helpers.js +28 -0
  41. package/dist/image.d.ts +55 -0
  42. package/dist/image.js +29 -0
  43. package/dist/index.d.ts +60 -0
  44. package/dist/index.js +27 -0
  45. package/dist/mime-verify.d.ts +65 -0
  46. package/dist/mime-verify.js +47 -0
  47. package/dist/move.d.ts +6 -0
  48. package/dist/move.js +55 -0
  49. package/dist/path-sanitize.d.ts +92 -0
  50. package/dist/path-sanitize.js +84 -0
  51. package/dist/put-file.d.ts +53 -0
  52. package/dist/put-file.js +85 -0
  53. package/dist/s3-presigned-post.d.ts +52 -0
  54. package/dist/s3-presigned-post.js +68 -0
  55. package/dist/signed-url.d.ts +69 -0
  56. package/dist/signed-url.js +86 -0
  57. package/dist/static-serve.d.ts +37 -0
  58. package/dist/static-serve.js +110 -0
  59. package/dist/storage.d.ts +9 -0
  60. package/dist/storage.js +9 -0
  61. package/dist/types/filesystem.d.ts +131 -0
  62. package/dist/types/filesystem.js +40 -0
  63. package/dist/types.d.ts +229 -0
  64. package/dist/types.js +25 -0
  65. package/dist/uploaded-file.d.ts +38 -0
  66. package/dist/uploaded-file.js +114 -0
  67. package/dist/visibility.d.ts +3 -0
  68. package/dist/visibility.js +3 -0
  69. package/dist/zip.d.ts +16 -0
  70. package/dist/zip.js +41 -0
  71. package/package.json +6 -6
package/dist/facade.js ADDED
@@ -0,0 +1,226 @@
1
+ import { resolve } from "node:path";
2
+ import process from "node:process";
3
+ import { filesystems, app as appConfig } from "@stacksjs/config";
4
+ import { createLocalStorage } from "./adapters/local";
5
+ import { S3StorageAdapter } from "./adapters/s3";
6
+ import { parseDiskPath } from "./path-sanitize";
7
+ import { putUploadedFile } from "./put-file";
8
+ function buildConfig() {
9
+ const cwd = process.cwd(), rootDir = filesystems.root || cwd, s3Config = filesystems.s3, appUrl = appConfig?.url || "", config = {
10
+ default: filesystems.driver || "local",
11
+ disks: {
12
+ local: {
13
+ driver: "local",
14
+ root: resolve(rootDir, "storage/app"),
15
+ visibility: filesystems.defaultVisibility || "private"
16
+ },
17
+ public: {
18
+ driver: "local",
19
+ root: resolve(rootDir, "public"),
20
+ url: appUrl ? `${appUrl}/storage` : "/storage",
21
+ visibility: "public"
22
+ }
23
+ }
24
+ };
25
+ if (s3Config?.bucket)
26
+ config.disks.s3 = {
27
+ driver: "s3",
28
+ bucket: s3Config.bucket,
29
+ region: s3Config.region || "us-east-1",
30
+ prefix: s3Config.prefix,
31
+ endpoint: s3Config.endpoint,
32
+ url: filesystems.publicUrl?.domain,
33
+ usePathStyleEndpoint: !!s3Config.endpoint,
34
+ visibility: filesystems.defaultVisibility || "private",
35
+ credentials: s3Config.credentials ? { key: s3Config.credentials.accessKeyId, secret: s3Config.credentials.secretAccessKey } : void 0
36
+ };
37
+ return config;
38
+ }
39
+
40
+ class StorageManager {
41
+ _config = null;
42
+ disks = new Map;
43
+ customConfig = null;
44
+ get config() {
45
+ if (!this._config) {
46
+ const builtConfig = buildConfig();
47
+ this._config = this.customConfig ? {
48
+ default: this.customConfig.default || builtConfig.default,
49
+ disks: { ...builtConfig.disks, ...this.customConfig.disks }
50
+ } : builtConfig;
51
+ }
52
+ return this._config;
53
+ }
54
+ init(config) {
55
+ this.customConfig = config;
56
+ this._config = null;
57
+ this.disks.clear();
58
+ return this;
59
+ }
60
+ disk(name) {
61
+ const diskName = name || this.config.default;
62
+ if (this.disks.has(diskName))
63
+ return this.disks.get(diskName);
64
+ const diskConfig = this.config.disks[diskName];
65
+ if (!diskConfig) {
66
+ const available = Object.keys(this.config.disks).join(", ");
67
+ throw Error(`Disk [${diskName}] is not configured. Available: ${available}`);
68
+ }
69
+ const adapter = this.createAdapter(diskName, diskConfig);
70
+ this.disks.set(diskName, adapter);
71
+ return adapter;
72
+ }
73
+ createAdapter(name, config) {
74
+ switch (config.driver) {
75
+ case "local":
76
+ return this.createLocalAdapter(config);
77
+ case "s3":
78
+ return this.createS3Adapter(name, config);
79
+ default:
80
+ throw Error(`Unsupported driver: ${config.driver}`);
81
+ }
82
+ }
83
+ createLocalAdapter(config) {
84
+ return createLocalStorage({ root: config.root });
85
+ }
86
+ createS3Adapter(_name, config) {
87
+ return new S3StorageAdapter(null, {
88
+ bucket: config.bucket,
89
+ region: config.region,
90
+ prefix: config.prefix
91
+ });
92
+ }
93
+ async put(pathOrFile, contentsOrOpts) {
94
+ if (typeof pathOrFile === "string")
95
+ return this.disk().write(pathOrFile, contentsOrOpts);
96
+ return putUploadedFile(this, pathOrFile, contentsOrOpts ?? {});
97
+ }
98
+ async stat(path) {
99
+ return this.disk().stat(path);
100
+ }
101
+ async getStream(path, options) {
102
+ const adapter = this.disk();
103
+ if (typeof adapter.getStream !== "function")
104
+ throw Error(`[storage] disk '${this.config.default}' does not support getStream \u2014 adapter is missing the optional method`);
105
+ return adapter.getStream(path, options);
106
+ }
107
+ async putStream(path, stream, options) {
108
+ const adapter = this.disk();
109
+ if (typeof adapter.putStream !== "function")
110
+ throw Error(`[storage] disk '${this.config.default}' does not support putStream \u2014 adapter is missing the optional method`);
111
+ return adapter.putStream(path, stream, options);
112
+ }
113
+ async copyAcross(source, dest) {
114
+ const src = parseDiskPath(source), dst = parseDiskPath(dest);
115
+ if (src.disk === dst.disk) {
116
+ const adapter = this.disk(src.disk);
117
+ await adapter.copyFile(src.path, dst.path);
118
+ return adapter.stat(dst.path).then((entry) => ({
119
+ path: dst.path,
120
+ size: entry.size,
121
+ contentType: entry.mimeType,
122
+ lastModified: entry.lastModified
123
+ }));
124
+ }
125
+ const contents = await this.disk(src.disk).read(src.path);
126
+ return this.disk(dst.disk).write(dst.path, contents);
127
+ }
128
+ async moveAcross(source, dest) {
129
+ const src = parseDiskPath(source), result = await this.copyAcross(source, dest);
130
+ await this.disk(src.disk).deleteFile(src.path);
131
+ return result;
132
+ }
133
+ async get(path) {
134
+ return this.disk().readToString(path);
135
+ }
136
+ async exists(path) {
137
+ return this.disk().fileExists(path);
138
+ }
139
+ async missing(path) {
140
+ return !await this.exists(path);
141
+ }
142
+ async delete(path) {
143
+ return this.disk().deleteFile(path);
144
+ }
145
+ async copy(from, to) {
146
+ return this.disk().copyFile(from, to);
147
+ }
148
+ async move(from, to) {
149
+ return this.disk().moveFile(from, to);
150
+ }
151
+ async url(path) {
152
+ return this.disk().publicUrl(path);
153
+ }
154
+ async signedUrl(path, options) {
155
+ const adapter = this.disk();
156
+ if (typeof adapter.signedUrl !== "function")
157
+ throw Error(`[storage] disk '${this.config.default}' does not support signedUrl`);
158
+ return adapter.signedUrl(path, options);
159
+ }
160
+ async presignedUploadUrl(options) {
161
+ const adapter = this.disk();
162
+ if (typeof adapter.presignedUploadUrl !== "function")
163
+ throw Error(`[storage] disk '${this.config.default}' does not support presignedUploadUrl \u2014 only S3-style adapters do. Use \`Storage.put(file, opts)\` for local/proxied uploads.`);
164
+ return adapter.presignedUploadUrl(options);
165
+ }
166
+ async presignedUploadPolicy(options) {
167
+ const adapter = this.disk();
168
+ if (typeof adapter.presignedUploadPolicy !== "function")
169
+ throw Error(`[storage] disk '${this.config.default}' does not support presignedUploadPolicy \u2014 S3-only. Use \`presignedUploadUrl\` for the PUT-form, or \`Storage.put(file, opts)\` for server-proxied uploads.`);
170
+ return adapter.presignedUploadPolicy(options);
171
+ }
172
+ async size(path) {
173
+ return this.disk().fileSize(path);
174
+ }
175
+ async lastModified(path) {
176
+ return this.disk().lastModified(path);
177
+ }
178
+ async mimeType(path) {
179
+ return this.disk().mimeType(path);
180
+ }
181
+ async checksum(path, algorithm) {
182
+ return this.disk().checksum(path, { algorithm });
183
+ }
184
+ async makeDirectory(path) {
185
+ return this.disk().createDirectory(path);
186
+ }
187
+ async deleteDirectory(path) {
188
+ return this.disk().deleteDirectory(path);
189
+ }
190
+ files(path = "") {
191
+ return this.disk().list(path);
192
+ }
193
+ allFiles(path = "") {
194
+ return this.disk().list(path, { deep: !0 });
195
+ }
196
+ configure(name, config) {
197
+ const currentConfig = this.config;
198
+ currentConfig.disks[name] = config;
199
+ this.disks.delete(name);
200
+ return this;
201
+ }
202
+ setDefaultDisk(name) {
203
+ if (!this.config.disks[name])
204
+ throw Error(`Disk [${name}] is not configured`);
205
+ this.config.default = name;
206
+ return this;
207
+ }
208
+ getDiskConfig(name) {
209
+ return this.config.disks[name || this.config.default];
210
+ }
211
+ getConfiguredDisks() {
212
+ return Object.keys(this.config.disks);
213
+ }
214
+ getDefaultDisk() {
215
+ return this.config.default;
216
+ }
217
+ reset() {
218
+ this._config = null;
219
+ this.customConfig = null;
220
+ this.disks.clear();
221
+ return this;
222
+ }
223
+ }
224
+ export const Storage = new StorageManager;
225
+
226
+ export { StorageManager };
@@ -0,0 +1,52 @@
1
+ import type { JsonFile, PackageJson, TextFile } from '@stacksjs/types';
2
+ /**
3
+ * Reads a JSON file and returns the parsed data.
4
+ */
5
+ export declare function readJsonFile(name: string, cwd?: string): Promise<JsonFile>;
6
+ /**
7
+ * Reads a package.json file and returns the parsed data.
8
+ */
9
+ export declare function readPackageJson(name: string, cwd?: string): Promise<PackageJson>;
10
+ /**
11
+ * Writes the given text to the specified file.
12
+ */
13
+ export declare function writeFile(path: string, data: any): Promise<number>;
14
+ /**
15
+ * Writes the given data to the specified JSON file.
16
+ */
17
+ export declare function writeJsonFile(file: JsonFile): Promise<number>;
18
+ /**
19
+ * Reads a text file and returns its contents.
20
+ */
21
+ export declare function readTextFile(name: string, cwd?: string): Promise<TextFile>;
22
+ /**
23
+ * Writes the given text to the specified file.
24
+ */
25
+ export declare function writeTextFile(file: TextFile): Promise<number>;
26
+ export declare function doesExist(path: string): boolean;
27
+ export declare function doesNotExist(path: string): boolean;
28
+ /**
29
+ * Determine whether a folder has any files in it.
30
+ */
31
+ export declare function hasFiles(folder: string): boolean;
32
+ export declare function hasComponents(): boolean;
33
+ export declare function hasFunctions(): boolean;
34
+ export declare function deleteFiles(dir: string, exclude?: string[]): void;
35
+ export declare function getFiles(dir: string, exclude?: string[]): string[];
36
+ export declare function put(path: string, contents: string): void;
37
+ export declare function get(path: string): Promise<string>;
38
+ export declare const files: Files;
39
+ export declare interface Files {
40
+ readJsonFile: typeof readJsonFile
41
+ readPackageJson: typeof readPackageJson
42
+ readTextFile: typeof readTextFile
43
+ writeJsonFile: typeof writeJsonFile
44
+ writeTextFile: typeof writeTextFile
45
+ hasFiles: typeof hasFiles
46
+ hasComponents: typeof hasComponents
47
+ hasFunctions: typeof hasFunctions
48
+ deleteFiles: typeof deleteFiles
49
+ getFiles: typeof getFiles
50
+ put: typeof put
51
+ get: typeof get
52
+ }
package/dist/files.js ADDED
@@ -0,0 +1,126 @@
1
+ import { contains } from "@stacksjs/arrays";
2
+ import { log } from "@stacksjs/logging";
3
+ import { dirname, join, path as p } from "@stacksjs/path";
4
+ import { detectIndent, detectNewline } from "@stacksjs/strings";
5
+ import { createFolder, isFolder } from "./folders";
6
+ import { existsSync, fs } from "./fs";
7
+ export async function readJsonFile(name, cwd) {
8
+ const file = await readTextFile(name, cwd);
9
+ let data;
10
+ try {
11
+ data = JSON.parse(file.data);
12
+ } catch (error) {
13
+ throw Error(`Failed to parse JSON file "${name}": ${error.message}`);
14
+ }
15
+ const indent = detectIndent(file.data).indent, newline = detectNewline(file.data);
16
+ return { ...file, data, indent, newline };
17
+ }
18
+ export async function readPackageJson(name, cwd) {
19
+ return (await readJsonFile(name, cwd)).data;
20
+ }
21
+ export async function writeFile(path, data) {
22
+ if (typeof path === "string") {
23
+ const dirPath = dirname(path);
24
+ if (!await existsSync(dirPath))
25
+ await createFolder(dirPath);
26
+ return await Bun.write(Bun.file(path), data);
27
+ }
28
+ return await Bun.write(path, data);
29
+ }
30
+ export async function writeJsonFile(file) {
31
+ let json = JSON.stringify(file.data, void 0, file.indent);
32
+ if (file.newline)
33
+ json += file.newline;
34
+ return writeTextFile({ ...file, data: json });
35
+ }
36
+ export function readTextFile(name, cwd) {
37
+ return new Promise((resolve, reject) => {
38
+ let filePath;
39
+ if (cwd)
40
+ filePath = join(cwd, name);
41
+ else
42
+ filePath = name;
43
+ fs.readFile(filePath, "utf8", (err, text) => {
44
+ if (err)
45
+ reject(err);
46
+ else
47
+ resolve({
48
+ path: filePath,
49
+ data: text
50
+ });
51
+ });
52
+ });
53
+ }
54
+ export async function writeTextFile(file) {
55
+ return await Bun.write(file.path, file.data);
56
+ }
57
+ function isFile(path) {
58
+ return fs.existsSync(path);
59
+ }
60
+ export function doesExist(path) {
61
+ return isFile(path) || isFolder(path);
62
+ }
63
+ export function doesNotExist(path) {
64
+ return !isFile(path) && !isFolder(path);
65
+ }
66
+ export function hasFiles(folder) {
67
+ try {
68
+ return fs.readdirSync(folder).length > 0;
69
+ } catch (err) {
70
+ log.debug(`Error reading folder: ${folder}`, err);
71
+ return !1;
72
+ }
73
+ }
74
+ export function hasComponents() {
75
+ return hasFiles(p.componentsPath());
76
+ }
77
+ export function hasFunctions() {
78
+ return hasFiles(p.functionsPath());
79
+ }
80
+ export function deleteFiles(dir, exclude = []) {
81
+ if (fs.existsSync(dir))
82
+ fs.readdirSync(dir).forEach((file) => {
83
+ const p = join(dir, file);
84
+ if (fs.statSync(p).isDirectory())
85
+ if (fs.readdirSync(p).length === 0)
86
+ fs.rmSync(p, { recursive: !0, force: !0 });
87
+ else
88
+ deleteFiles(p, exclude);
89
+ else if (!contains(p, exclude))
90
+ fs.rmSync(p);
91
+ });
92
+ }
93
+ export function getFiles(dir, exclude = []) {
94
+ let results = [];
95
+ fs.readdirSync(dir).forEach((file) => {
96
+ file = join(dir, file);
97
+ if (fs.statSync(file).isDirectory())
98
+ results = results.concat(getFiles(file, exclude));
99
+ else if (!contains(file, exclude))
100
+ results.push(file);
101
+ });
102
+ return results;
103
+ }
104
+ export function put(path, contents) {
105
+ const dirPath = dirname(path);
106
+ if (!fs.existsSync(dirPath))
107
+ fs.mkdirSync(dirPath, { recursive: !0 });
108
+ fs.writeFileSync(path, contents, "utf-8");
109
+ }
110
+ export async function get(path) {
111
+ return Bun.file(path).text();
112
+ }
113
+ export const files = {
114
+ readJsonFile,
115
+ readPackageJson,
116
+ readTextFile,
117
+ writeJsonFile,
118
+ writeTextFile,
119
+ hasFiles,
120
+ hasComponents,
121
+ hasFunctions,
122
+ deleteFiles,
123
+ getFiles,
124
+ put,
125
+ get
126
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Determine whether a path is a folder.
3
+ */
4
+ export declare function isFolder(path: string): boolean;
5
+ // export function isDirectory(path: string): boolean {
6
+ // return isFolder(path)
7
+ // }
8
+ export declare function isDir(path: string): boolean;
9
+ export declare function doesFolderExist(path: string): boolean;
10
+ export declare function createFolder(dir: string): Promise<void>;
11
+ export declare function getFolders(dir: string): string[];
12
+ export declare const folders: Folders;
13
+ export declare interface Folders {
14
+ isFolder: (path: string) => boolean
15
+ doesFolderExist: (path: string) => boolean
16
+ createFolder: (dir: string) => Promise<void>
17
+ getFolders: (dir: string) => string[]
18
+ }
@@ -0,0 +1,36 @@
1
+ import { join } from "@stacksjs/path";
2
+ import { fs } from "./fs";
3
+ export function isFolder(path) {
4
+ try {
5
+ return fs.statSync(path).isDirectory();
6
+ } catch {
7
+ return !1;
8
+ }
9
+ }
10
+ export function isDir(path) {
11
+ return isFolder(path);
12
+ }
13
+ export function doesFolderExist(path) {
14
+ return fs.existsSync(path);
15
+ }
16
+ export function createFolder(dir) {
17
+ return new Promise((resolve, reject) => {
18
+ try {
19
+ fs.mkdirSync(dir, { recursive: !0 });
20
+ resolve();
21
+ } catch (err) {
22
+ reject(err);
23
+ }
24
+ });
25
+ }
26
+ export function getFolders(dir) {
27
+ return fs.readdirSync(dir).filter((file) => {
28
+ return fs.statSync(join(dir, file)).isDirectory();
29
+ });
30
+ }
31
+ export const folders = {
32
+ isFolder,
33
+ doesFolderExist,
34
+ createFolder,
35
+ getFolders
36
+ };
package/dist/fs.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { existsSync, watch as fsWatch, mkdirSync, readFileSync, watchFile, writeFileSync } from 'node:fs';
2
+ import * as fs from 'node:fs';
3
+ export declare function exists(path: string): boolean;
4
+ export { existsSync, fsWatch, mkdirSync, readFileSync, watchFile, writeFileSync, fs };
package/dist/fs.js ADDED
@@ -0,0 +1,7 @@
1
+ import * as fs from "node:fs";
2
+ import { existsSync, watch as fsWatch, mkdirSync, readFileSync, watchFile, writeFileSync } from "node:fs";
3
+ export function exists(path) {
4
+ return existsSync(path);
5
+ }
6
+
7
+ export { existsSync, fsWatch, mkdirSync, readFileSync, watchFile, writeFileSync, fs };
package/dist/glob.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ export declare function globSync(patterns: string | string[], options?: Omit<GlobOptions, 'patterns'>): string[];
2
+ export declare function glob(patterns: string | string[], options?: Omit<GlobOptions, 'patterns'>): Promise<string[]>;
3
+ export declare interface GlobOptions {
4
+ absolute?: boolean
5
+ cwd?: string
6
+ patterns?: string[]
7
+ ignore?: string[]
8
+ dot?: boolean
9
+ deep?: number
10
+ expandDirectories?: boolean
11
+ onlyDirectories?: boolean
12
+ onlyFiles?: boolean
13
+ }
package/dist/glob.js ADDED
@@ -0,0 +1,40 @@
1
+ import { Glob as BunGlob } from "bun";
2
+ function isEnoent(err) {
3
+ return !!err && typeof err === "object" && err.code === "ENOENT";
4
+ }
5
+ export function globSync(patterns, options) {
6
+ const patternArray = typeof patterns === "string" ? [patterns] : patterns, results = [];
7
+ for (const pattern of patternArray)
8
+ try {
9
+ const matches = new BunGlob(pattern).scanSync({
10
+ cwd: options?.cwd,
11
+ absolute: options?.absolute,
12
+ dot: options?.dot,
13
+ onlyFiles: options?.onlyFiles
14
+ });
15
+ for (const match of matches)
16
+ results.push(match);
17
+ } catch (err) {
18
+ if (!isEnoent(err))
19
+ throw err;
20
+ }
21
+ return results;
22
+ }
23
+ export async function glob(patterns, options) {
24
+ const patternArray = typeof patterns === "string" ? [patterns] : patterns, results = [];
25
+ for (const pattern of patternArray)
26
+ try {
27
+ const matches = new BunGlob(pattern).scan({
28
+ cwd: options?.cwd,
29
+ absolute: options?.absolute,
30
+ dot: options?.dot,
31
+ onlyFiles: options?.onlyFiles
32
+ });
33
+ for await (const match of matches)
34
+ results.push(match);
35
+ } catch (err) {
36
+ if (!isEnoent(err))
37
+ throw err;
38
+ }
39
+ return results;
40
+ }
package/dist/hash.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { Hash } from 'node:crypto';
2
+ export declare function hashFileOrDirectory(path: string, hash: Hash): void;
3
+ export declare function hashDirectory(directory: string): string;
4
+ export declare function hashPath(path: string): string;
5
+ export declare function hashPaths(paths: string | string[]): string;
package/dist/hash.js ADDED
@@ -0,0 +1,33 @@
1
+ import { createHash } from "node:crypto";
2
+ import { path as p } from "@stacksjs/path";
3
+ import { fs } from "./fs";
4
+ export function hashFileOrDirectory(path, hash) {
5
+ if (!fs.existsSync(path)) {
6
+ console.error(`Path does not exist: ${path}`);
7
+ return;
8
+ }
9
+ if (fs.statSync(path).isDirectory()) {
10
+ const files = fs.readdirSync(path);
11
+ for (const file of files) {
12
+ const filePath = p.join(path, file);
13
+ hashFileOrDirectory(filePath, hash);
14
+ }
15
+ } else
16
+ hash.update(fs.readFileSync(path));
17
+ }
18
+ export function hashDirectory(directory) {
19
+ const hash = createHash("sha256");
20
+ hashFileOrDirectory(directory, hash);
21
+ return hash.digest("hex");
22
+ }
23
+ export function hashPath(path) {
24
+ const hash = createHash("sha256");
25
+ hashFileOrDirectory(path, hash);
26
+ return hash.digest("hex");
27
+ }
28
+ export function hashPaths(paths) {
29
+ const hash = createHash("sha256"), pathsArray = Array.isArray(paths) ? paths : [paths];
30
+ for (const path of pathsArray)
31
+ hashFileOrDirectory(path, hash);
32
+ return hash.digest("hex");
33
+ }
@@ -0,0 +1,7 @@
1
+ export declare function updateConfigFile(filePath: string, newConfig: Record<string, unknown>): Promise<void>;
2
+ export declare const _dirname: string;
3
+ export declare const helpers: Helpers;
4
+ declare interface Helpers {
5
+ _dirname: string
6
+ updateConfigFile: (filePath: string, newConfig: Record<string, unknown>) => Promise<void>
7
+ }
@@ -0,0 +1,28 @@
1
+ var __dirname = "";
2
+ import { fileURLToPath } from "node:url";
3
+ import { dirname } from "@stacksjs/path";
4
+ import { fs } from "./fs";
5
+ export const _dirname = typeof __dirname < "u" ? __dirname : dirname(fileURLToPath(import.meta.url));
6
+ export function updateConfigFile(filePath, newConfig) {
7
+ return new Promise((resolve, reject) => {
8
+ let config;
9
+ try {
10
+ config = JSON.parse(fs.readFileSync(filePath, "utf8"));
11
+ } catch (error) {
12
+ reject(Error(`Failed to parse config file "${filePath}": ${error.message}`));
13
+ return;
14
+ }
15
+ for (const key in newConfig)
16
+ config[key] = newConfig[key];
17
+ try {
18
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2));
19
+ resolve();
20
+ } catch (error) {
21
+ reject(error);
22
+ }
23
+ });
24
+ }
25
+ export const helpers = {
26
+ _dirname,
27
+ updateConfigFile
28
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Wrap a Sharp pipeline so it composes with
3
+ * `Storage.put(file, { transform })`. The pipeline function receives a
4
+ * fresh Sharp instance preloaded with the file's bytes; its return
5
+ * value is the final pipeline chain (sharp's methods are chainable so
6
+ * "return img.resize(...)" is the normal shape).
7
+ *
8
+ * The wrapper handles the buffer normalisation + final `.toBuffer()`
9
+ * call so callers don't have to.
10
+ */
11
+ export declare function transform(pipeline: (img: SharpInstance) => SharpInstance | Promise<SharpInstance>): (input: Uint8Array | Buffer | ArrayBuffer) => Promise<Buffer>;
12
+ /**
13
+ * Common preset for square avatars — resizes + crops to fit, encodes
14
+ * as WebP at quality 85. Covers the by-far most common
15
+ * `Storage.put(file, { transform: ... })` callsite.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { avatar } from '@stacksjs/storage/image'
20
+ *
21
+ * await Storage.put(file, {
22
+ * dir: 'avatars',
23
+ * transform: avatar(512),
24
+ * })
25
+ * ```
26
+ */
27
+ export declare function avatar(size?: number, quality?: number): (input: Uint8Array | Buffer | ArrayBuffer) => Promise<Buffer>;
28
+ /**
29
+ * Generic resize preset. `fit` defaults to `'inside'` (preserve aspect
30
+ * ratio, don't crop) which is the most common non-avatar case.
31
+ */
32
+ export declare function resize(width: number, height: number, fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside'): (input: Uint8Array | Buffer | ArrayBuffer) => Promise<Buffer>;
33
+ /**
34
+ * Strip EXIF + colour profile + other embedded metadata. Useful when
35
+ * accepting user uploads where you don't want to leak GPS coordinates
36
+ * embedded in phone photos. Pairs well with resize/format presets via
37
+ * the explicit `transform()` form when needed.
38
+ *
39
+ * sharp strips metadata by default unless `withMetadata()` is called;
40
+ * this preset just makes the intent explicit.
41
+ */
42
+ export declare function stripMetadata(): (input: Uint8Array | Buffer | ArrayBuffer) => Promise<Buffer>;
43
+ declare interface SharpInstance {
44
+ toBuffer(): Promise<Buffer>
45
+ resize: (...args: unknown[]) => SharpInstance
46
+ jpeg: (...args: unknown[]) => SharpInstance
47
+ png: (...args: unknown[]) => SharpInstance
48
+ webp: (...args: unknown[]) => SharpInstance
49
+ avif: (...args: unknown[]) => SharpInstance
50
+ gif: (...args: unknown[]) => SharpInstance
51
+ rotate: (...args: unknown[]) => SharpInstance
52
+ blur: (...args: unknown[]) => SharpInstance
53
+ withMetadata: (...args: unknown[]) => SharpInstance
54
+ [k: string]: unknown
55
+ }