@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.
- package/dist/adapters/bun.d.ts +31 -0
- package/dist/adapters/bun.js +226 -0
- package/dist/adapters/index.d.ts +6 -0
- package/dist/adapters/index.js +5 -0
- package/dist/adapters/local.d.ts +38 -0
- package/dist/adapters/local.js +225 -0
- package/dist/adapters/memory.d.ts +38 -0
- package/dist/adapters/memory.js +318 -0
- package/dist/adapters/s3.d.ts +53 -0
- package/dist/adapters/s3.js +471 -0
- package/dist/adapters/scoped.d.ts +68 -0
- package/dist/adapters/scoped.js +142 -0
- package/dist/copy.d.ts +3 -0
- package/dist/copy.js +30 -0
- package/dist/delete.d.ts +8 -0
- package/dist/delete.js +103 -0
- package/dist/drivers/aws.d.ts +4 -0
- package/dist/drivers/aws.js +94 -0
- package/dist/drivers/bun.d.ts +4 -0
- package/dist/drivers/bun.js +88 -0
- package/dist/drivers/index.d.ts +4 -0
- package/dist/drivers/index.js +4 -0
- package/dist/drivers/local.d.ts +4 -0
- package/dist/drivers/local.js +88 -0
- package/dist/drivers/memory.d.ts +4 -0
- package/dist/drivers/memory.js +67 -0
- package/dist/facade.d.ts +53 -0
- package/dist/facade.js +226 -0
- package/dist/files.d.ts +52 -0
- package/dist/files.js +126 -0
- package/dist/folders.d.ts +18 -0
- package/dist/folders.js +36 -0
- package/dist/fs.d.ts +4 -0
- package/dist/fs.js +7 -0
- package/dist/glob.d.ts +13 -0
- package/dist/glob.js +40 -0
- package/dist/hash.d.ts +5 -0
- package/dist/hash.js +33 -0
- package/dist/helpers.d.ts +7 -0
- package/dist/helpers.js +28 -0
- package/dist/image.d.ts +55 -0
- package/dist/image.js +29 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +27 -0
- package/dist/mime-verify.d.ts +65 -0
- package/dist/mime-verify.js +47 -0
- package/dist/move.d.ts +6 -0
- package/dist/move.js +55 -0
- package/dist/path-sanitize.d.ts +92 -0
- package/dist/path-sanitize.js +84 -0
- package/dist/put-file.d.ts +53 -0
- package/dist/put-file.js +85 -0
- package/dist/s3-presigned-post.d.ts +52 -0
- package/dist/s3-presigned-post.js +68 -0
- package/dist/signed-url.d.ts +69 -0
- package/dist/signed-url.js +86 -0
- package/dist/static-serve.d.ts +37 -0
- package/dist/static-serve.js +110 -0
- package/dist/storage.d.ts +9 -0
- package/dist/storage.js +9 -0
- package/dist/types/filesystem.d.ts +131 -0
- package/dist/types/filesystem.js +40 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.js +25 -0
- package/dist/uploaded-file.d.ts +38 -0
- package/dist/uploaded-file.js +114 -0
- package/dist/visibility.d.ts +3 -0
- package/dist/visibility.js +3 -0
- package/dist/zip.d.ts +16 -0
- package/dist/zip.js +41 -0
- 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 };
|
package/dist/files.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/folders.js
ADDED
|
@@ -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
|
+
}
|
package/dist/helpers.js
ADDED
|
@@ -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
|
+
};
|
package/dist/image.d.ts
ADDED
|
@@ -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
|
+
}
|