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 +62 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +36 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|