strapi-provider-upload-s3-mini 1.0.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 ADDED
@@ -0,0 +1,62 @@
1
+ # Strapi Provider Upload S3mini
2
+
3
+ A lightweight S3-compatible provider for Strapi uploads, powered by the `s3mini` client. This provider is designed to work with AWS S3, DigitalOcean Spaces, Cloudflare R2, and other S3-compatible storage services.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install strapi-provider-upload-s3-mini
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ In your Strapi project, edit or create `config/plugins.js` (or `config/plugins.ts`):
14
+
15
+ ```javascript
16
+ module.exports = ({ env }) => ({
17
+ upload: {
18
+ config: {
19
+ provider: 'strapi-provider-upload-s3-mini',
20
+ providerOptions: {
21
+ key: env('S3_ACCESS_KEY'),
22
+ secret: env('S3_SECRET_KEY'),
23
+ endpoint: env('S3_ENDPOINT'), // e.g. https://nyc3.digitaloceanspaces.com
24
+ region: env('S3_REGION'),
25
+ bucket: env('S3_BUCKET'),
26
+ directory: env('S3_DIRECTORY'), // Optional
27
+ cdnEndpoint: env('S3_CDN'), // Optional
28
+ },
29
+ },
30
+ },
31
+ });
32
+ ```
33
+
34
+ ### Parameters
35
+
36
+ | Name | Description | Example |
37
+ | --- | --- | --- |
38
+ | `key` | S3 Access Key | `L3U...` |
39
+ | `secret` | S3 Secret Key | `x8k...` |
40
+ | `endpoint` | S3 Endpoint | `https://nyc3.digitaloceanspaces.com` |
41
+ | `region` | S3 Region | `nyc3` |
42
+ | `bucket` | S3 Bucket Name | `my-bucket` |
43
+ | `directory` | Optional directory in the bucket | `uploads` |
44
+ | `cdnEndpoint` | Optional CDN endpoint | `https://cdn.example.com` |
45
+
46
+ ## Development
47
+
48
+ ### Running Tests
49
+
50
+ ```bash
51
+ npm test
52
+ ```
53
+
54
+ ### Building
55
+
56
+ ```bash
57
+ npm run build
58
+ ```
59
+
60
+ ## License
61
+
62
+ MIT
@@ -0,0 +1,45 @@
1
+ import type { Readable } from "node:stream";
2
+ interface StrapiFile {
3
+ name: string;
4
+ alternativeText?: string;
5
+ caption?: string;
6
+ width?: number;
7
+ height?: number;
8
+ formats?: any;
9
+ hash: string;
10
+ ext: string;
11
+ mime: string;
12
+ size: number;
13
+ url: string;
14
+ previewUrl?: string;
15
+ path?: string;
16
+ getStream: () => Readable;
17
+ stream?: Readable;
18
+ buffer?: Buffer;
19
+ }
20
+ export interface InitOptions {
21
+ key: string;
22
+ secret: string;
23
+ endpoint: string;
24
+ region: string;
25
+ bucket: string;
26
+ directory?: string;
27
+ cdnEndpoint?: string;
28
+ s3Options?: {
29
+ credentials?: {
30
+ accessKeyId: string;
31
+ secretAccessKey: string;
32
+ };
33
+ };
34
+ [key: string]: any;
35
+ }
36
+ declare const _default: {
37
+ init(config: InitOptions): {
38
+ upload(file: StrapiFile): Promise<void>;
39
+ uploadStream(file: StrapiFile): Promise<void>;
40
+ delete(file: StrapiFile): Promise<void>;
41
+ check(file: StrapiFile): Promise<import("s3mini").ExistResponseCode>;
42
+ };
43
+ };
44
+ export default _default;
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,QAAQ,CAAC;IAC1B,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE;QACV,WAAW,CAAC,EAAE;YACZ,WAAW,EAAE,MAAM,CAAC;YACpB,eAAe,EAAE,MAAM,CAAC;SACzB,CAAC;KACH,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;;iBAac,WAAW;qBA+DP,UAAU;2BAGJ,UAAU;qBAGV,UAAU;oBAQX,UAAU;;;AA9ElC,wBAwFE"}
package/dist/index.js ADDED
@@ -0,0 +1,96 @@
1
+ import { S3mini } from "s3mini";
2
+ const streamToBuffer = (stream) => {
3
+ return new Promise((resolve, reject) => {
4
+ const chunks = [];
5
+ stream.on("data", (chunk) => chunks.push(chunk));
6
+ stream.on("error", reject);
7
+ stream.on("end", () => resolve(Buffer.concat(chunks)));
8
+ });
9
+ };
10
+ export default {
11
+ init(config) {
12
+ const key = config.key;
13
+ const secret = config.secret;
14
+ const { endpoint, region, bucket, directory, cdnEndpoint } = config;
15
+ // Ensure endpoint includes the bucket name as a subdomain if not already present
16
+ // s3mini often works better with this format for DigitalOcean/Cloudflare
17
+ let finalEndpoint = endpoint;
18
+ try {
19
+ if (bucket && !endpoint.includes(bucket)) {
20
+ const url = new URL(endpoint);
21
+ // Avoid double bucket prefix if it's already there or if it's a generic s3 endpoint
22
+ if (!url.host.startsWith(`${bucket}.`)) {
23
+ finalEndpoint = `${url.protocol}//${bucket}.${url.host}${url.pathname}`;
24
+ }
25
+ }
26
+ }
27
+ catch (e) {
28
+ // fallback to original endpoint if URL parsing fails
29
+ }
30
+ const s3 = new S3mini({
31
+ accessKeyId: key,
32
+ secretAccessKey: secret,
33
+ endpoint: finalEndpoint,
34
+ region,
35
+ });
36
+ const filePrefix = directory ? `${directory.replace(/\/+$/, '')}/` : '';
37
+ const getFileKey = (file) => {
38
+ const path = file.path ? `${file.path}/` : '';
39
+ return `${filePrefix}${path}${file.hash}${file.ext}`;
40
+ };
41
+ const getUrl = (key) => {
42
+ if (cdnEndpoint) {
43
+ return `${cdnEndpoint.replace(/\/$/, "")}/${key}`;
44
+ }
45
+ const host = endpoint.replace(/^https?:\/\//, "");
46
+ return `https://${bucket}.${host}/${key}`;
47
+ };
48
+ const upload = async (file) => {
49
+ const key = getFileKey(file);
50
+ let content;
51
+ if (file.buffer) {
52
+ content = file.buffer;
53
+ }
54
+ else if (file.stream) {
55
+ content = await streamToBuffer(file.stream);
56
+ }
57
+ else if (file.getStream) {
58
+ content = await streamToBuffer(file.getStream());
59
+ }
60
+ else {
61
+ throw new Error("File buffer or stream is missing");
62
+ }
63
+ await s3.putObject(key, content, file.mime, undefined, {
64
+ "x-amz-acl": "public-read",
65
+ });
66
+ file.url = getUrl(key);
67
+ };
68
+ return {
69
+ upload(file) {
70
+ return upload(file);
71
+ },
72
+ uploadStream(file) {
73
+ return upload(file);
74
+ },
75
+ async delete(file) {
76
+ const key = getFileKey(file);
77
+ try {
78
+ await s3.deleteObject(key);
79
+ }
80
+ catch (error) {
81
+ // Strapi usually expects delete to be silent if file not found
82
+ }
83
+ },
84
+ async check(file) {
85
+ const key = getFileKey(file);
86
+ try {
87
+ return await s3.objectExists(key);
88
+ }
89
+ catch (error) {
90
+ return false;
91
+ }
92
+ },
93
+ };
94
+ },
95
+ };
96
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"root":["../src/index.ts"],"version":"5.9.3"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "strapi-provider-upload-s3-mini",
3
+ "version": "1.0.0",
4
+ "description": "S3mini provider for Strapi uploads",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc --build tsconfig.build.json",
14
+ "watch": "tsc -w",
15
+ "test": "vitest run",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "strapi",
20
+ "upload",
21
+ "provider",
22
+ "s3",
23
+ "do",
24
+ "spaces"
25
+ ],
26
+ "author": "",
27
+ "license": "MIT",
28
+ "devDependencies": {
29
+ "@types/node": "^20.19.30",
30
+ "typescript": "^5.0.0",
31
+ "vitest": "^4.0.17"
32
+ },
33
+ "dependencies": {
34
+ "s3mini": "^0.9.1"
35
+ }
36
+ }