@strapi/provider-upload-aws-s3 0.0.0-fee711c4e2d9a317dcad4559ef10c87071da6272 → 0.0.0-next.13502afca039cc21e1caa9553f13e4816419f200
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 +63 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +112 -0
- package/dist/index.js.map +1 -0
- package/dist/utils.d.ts +15 -0
- package/dist/utils.js +53 -0
- package/dist/utils.js.map +1 -0
- package/package.json +21 -8
- package/lib/index.js +0 -72
package/README.md
CHANGED
|
@@ -25,12 +25,57 @@ npm install @strapi/provider-upload-aws-s3 --save
|
|
|
25
25
|
|
|
26
26
|
- `provider` defines the name of the provider
|
|
27
27
|
- `providerOptions` is passed down during the construction of the provider. (ex: `new AWS.S3(config)`). [Complete list of options](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property)
|
|
28
|
+
- `providerOptions.params` is passed directly to the parameters to each method respectively.
|
|
29
|
+
- `ACL` is the access control list for the object. Defaults to `public-read`.
|
|
30
|
+
- `signedUrlExpires` is the number of seconds before a signed URL expires. (See [how signed URLs work](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-urls.html)). Defaults to 15 minutes and URLs are only signed when ACL is set to `private`.
|
|
31
|
+
- `Bucket` is the name of the bucket to upload to.
|
|
28
32
|
- `actionOptions` is passed directly to the parameters to each method respectively. You can find the complete list of [upload/ uploadStream options](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property) and [delete options](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#deleteObject-property)
|
|
29
33
|
|
|
30
34
|
See the [documentation about using a provider](https://docs.strapi.io/developer-docs/latest/plugins/upload.html#using-a-provider) for information on installing and using a provider. To understand how environment variables are used in Strapi, please refer to the [documentation about environment variables](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/configurations/optional/environment.html#environment-variables).
|
|
31
35
|
|
|
36
|
+
If you're using the bucket as a CDN and deliver the content on a custom domain, you can get use of the `baseUrl` and `rootPath` properties to configure how your assets' urls will be saved inside Strapi.
|
|
37
|
+
|
|
32
38
|
### Provider Configuration
|
|
33
39
|
|
|
40
|
+
`./config/plugins.js` or `./config/plugins.ts` for TypeScript projects:
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
module.exports = ({ env }) => ({
|
|
44
|
+
// ...
|
|
45
|
+
upload: {
|
|
46
|
+
config: {
|
|
47
|
+
provider: 'aws-s3',
|
|
48
|
+
providerOptions: {
|
|
49
|
+
baseUrl: env('CDN_URL'),
|
|
50
|
+
rootPath: env('CDN_ROOT_PATH'),
|
|
51
|
+
s3Options: {
|
|
52
|
+
accessKeyId: env('AWS_ACCESS_KEY_ID'),
|
|
53
|
+
secretAccessKey: env('AWS_ACCESS_SECRET'),
|
|
54
|
+
region: env('AWS_REGION'),
|
|
55
|
+
params: {
|
|
56
|
+
ACL: env('AWS_ACL', 'public-read'),
|
|
57
|
+
signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 15 * 60),
|
|
58
|
+
Bucket: env('AWS_BUCKET'),
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
actionOptions: {
|
|
63
|
+
upload: {},
|
|
64
|
+
uploadStream: {},
|
|
65
|
+
delete: {},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
// ...
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Configuration for a private S3 bucket
|
|
74
|
+
|
|
75
|
+
If your bucket is configured to be private, you will need to set the `ACL` option to `private` in the `params` object. This will ensure that the signed URL is generated with the correct permissions.
|
|
76
|
+
|
|
77
|
+
You can also define the expiration time of the signed URL by setting the `signedUrlExpires` option in the `params` object. The default value is 7 days.
|
|
78
|
+
|
|
34
79
|
`./config/plugins.js`
|
|
35
80
|
|
|
36
81
|
```js
|
|
@@ -44,6 +89,8 @@ module.exports = ({ env }) => ({
|
|
|
44
89
|
secretAccessKey: env('AWS_ACCESS_SECRET'),
|
|
45
90
|
region: env('AWS_REGION'),
|
|
46
91
|
params: {
|
|
92
|
+
ACL: 'private', // <== set ACL to private
|
|
93
|
+
signedUrlExpires: env('AWS_SIGNED_URL_EXPIRES', 60 * 60 * 24 * 7),
|
|
47
94
|
Bucket: env('AWS_BUCKET'),
|
|
48
95
|
},
|
|
49
96
|
},
|
|
@@ -124,6 +171,22 @@ module.exports = [
|
|
|
124
171
|
|
|
125
172
|
If you use dots in your bucket name, the url of the ressource is in directory style (`s3.yourRegion.amazonaws.com/your.bucket.name/image.jpg`) instead of `yourBucketName.s3.yourRegion.amazonaws.com/image.jpg`. Then only add `s3.yourRegion.amazonaws.com` to img-src and media-src directives.
|
|
126
173
|
|
|
174
|
+
## Bucket CORS Configuration
|
|
175
|
+
|
|
176
|
+
If you are planning on uploading content like GIFs and videos to your S3 bucket, you will want to edit its CORS configuration so that thumbnails are properly shown in Strapi. To do so, open your Bucket on the AWS console and locate the _Cross-origin resource sharing (CORS)_ field under the _Permissions_ tab, then amend the policies by writing your own JSON configuration, or copying and pasting the following one:
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
[
|
|
180
|
+
{
|
|
181
|
+
"AllowedHeaders": ["*"],
|
|
182
|
+
"AllowedMethods": ["GET"],
|
|
183
|
+
"AllowedOrigins": ["YOUR STRAPI URL"],
|
|
184
|
+
"ExposeHeaders": [],
|
|
185
|
+
"MaxAgeSeconds": 3000
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
```
|
|
189
|
+
|
|
127
190
|
## Required AWS Policy Actions
|
|
128
191
|
|
|
129
192
|
These are the minimum amount of permissions needed for this provider to work.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import type { ReadStream } from 'node:fs';
|
|
4
|
+
import AWS from 'aws-sdk';
|
|
5
|
+
interface File {
|
|
6
|
+
name: string;
|
|
7
|
+
alternativeText?: string;
|
|
8
|
+
caption?: string;
|
|
9
|
+
width?: number;
|
|
10
|
+
height?: number;
|
|
11
|
+
formats?: Record<string, unknown>;
|
|
12
|
+
hash: string;
|
|
13
|
+
ext?: string;
|
|
14
|
+
mime: string;
|
|
15
|
+
size: number;
|
|
16
|
+
url: string;
|
|
17
|
+
previewUrl?: string;
|
|
18
|
+
path?: string;
|
|
19
|
+
provider?: string;
|
|
20
|
+
provider_metadata?: Record<string, unknown>;
|
|
21
|
+
stream?: ReadStream;
|
|
22
|
+
buffer?: Buffer;
|
|
23
|
+
}
|
|
24
|
+
interface InitOptions extends Partial<AWS.S3.ClientConfiguration> {
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
rootPath?: string;
|
|
27
|
+
s3Options: AWS.S3.ClientConfiguration & {
|
|
28
|
+
params: {
|
|
29
|
+
Bucket: string;
|
|
30
|
+
ACL?: string;
|
|
31
|
+
signedUrlExpires?: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
declare const _default: {
|
|
36
|
+
init({ baseUrl, rootPath, s3Options, ...legacyS3Options }: InitOptions): {
|
|
37
|
+
isPrivate(): boolean;
|
|
38
|
+
getSignedUrl(file: File): Promise<{
|
|
39
|
+
url: string;
|
|
40
|
+
}>;
|
|
41
|
+
uploadStream(file: File, customParams?: {}): Promise<void>;
|
|
42
|
+
upload(file: File, customParams?: {}): Promise<void>;
|
|
43
|
+
delete(file: File, customParams?: {}): Promise<void>;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export = _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
const fp_1 = require("lodash/fp");
|
|
6
|
+
const aws_sdk_1 = __importDefault(require("aws-sdk"));
|
|
7
|
+
const utils_1 = require("./utils");
|
|
8
|
+
// TODO V5: Migrate to aws-sdk v3
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
10
|
+
require('aws-sdk/lib/maintenance_mode_message').suppress = true;
|
|
11
|
+
function hasUrlProtocol(url) {
|
|
12
|
+
// Regex to test protocol like "http://", "https://"
|
|
13
|
+
return /^\w*:\/\//.test(url);
|
|
14
|
+
}
|
|
15
|
+
module.exports = {
|
|
16
|
+
init({ baseUrl, rootPath, s3Options, ...legacyS3Options }) {
|
|
17
|
+
if (Object.keys(legacyS3Options).length > 0) {
|
|
18
|
+
process.emitWarning("S3 configuration options passed at root level of the plugin's providerOptions is deprecated and will be removed in a future release. Please wrap them inside the 's3Options:{}' property.");
|
|
19
|
+
}
|
|
20
|
+
const config = { ...s3Options, ...legacyS3Options };
|
|
21
|
+
const S3 = new aws_sdk_1.default.S3({
|
|
22
|
+
apiVersion: '2006-03-01',
|
|
23
|
+
...config,
|
|
24
|
+
});
|
|
25
|
+
const filePrefix = rootPath ? `${rootPath.replace(/\/+$/, '')}/` : '';
|
|
26
|
+
const getFileKey = (file) => {
|
|
27
|
+
const path = file.path ? `${file.path}/` : '';
|
|
28
|
+
return `${filePrefix}${path}${file.hash}${file.ext}`;
|
|
29
|
+
};
|
|
30
|
+
const ACL = (0, fp_1.getOr)('public-read', ['params', 'ACL'], config);
|
|
31
|
+
const upload = (file, customParams = {}) => new Promise((resolve, reject) => {
|
|
32
|
+
const fileKey = getFileKey(file);
|
|
33
|
+
if (!file.stream && !file.buffer) {
|
|
34
|
+
reject(new Error('Missing file stream or buffer'));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const params = {
|
|
38
|
+
Key: fileKey,
|
|
39
|
+
Bucket: config.params.Bucket,
|
|
40
|
+
Body: file.stream || file.buffer,
|
|
41
|
+
ACL,
|
|
42
|
+
ContentType: file.mime,
|
|
43
|
+
...customParams,
|
|
44
|
+
};
|
|
45
|
+
const onUploaded = (err, data) => {
|
|
46
|
+
if (err) {
|
|
47
|
+
return reject(err);
|
|
48
|
+
}
|
|
49
|
+
// set the bucket file url
|
|
50
|
+
if (baseUrl) {
|
|
51
|
+
// Construct the url with the baseUrl
|
|
52
|
+
file.url = `${baseUrl}/${fileKey}`;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Add the protocol if it is missing
|
|
56
|
+
// Some providers like DigitalOcean Spaces return the url without the protocol
|
|
57
|
+
file.url = hasUrlProtocol(data.Location) ? data.Location : `https://${data.Location}`;
|
|
58
|
+
}
|
|
59
|
+
resolve();
|
|
60
|
+
};
|
|
61
|
+
S3.upload(params, onUploaded);
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
isPrivate() {
|
|
65
|
+
return ACL === 'private';
|
|
66
|
+
},
|
|
67
|
+
async getSignedUrl(file) {
|
|
68
|
+
// Do not sign the url if it does not come from the same bucket.
|
|
69
|
+
const { bucket } = (0, utils_1.getBucketFromUrl)(file.url);
|
|
70
|
+
if (bucket !== config.params.Bucket) {
|
|
71
|
+
return { url: file.url };
|
|
72
|
+
}
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const fileKey = getFileKey(file);
|
|
75
|
+
S3.getSignedUrl('getObject', {
|
|
76
|
+
Bucket: config.params.Bucket,
|
|
77
|
+
Key: fileKey,
|
|
78
|
+
Expires: (0, fp_1.getOr)(15 * 60, ['params', 'signedUrlExpires'], config), // 15 minutes
|
|
79
|
+
}, (err, url) => {
|
|
80
|
+
if (err) {
|
|
81
|
+
return reject(err);
|
|
82
|
+
}
|
|
83
|
+
resolve({ url });
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
uploadStream(file, customParams = {}) {
|
|
88
|
+
return upload(file, customParams);
|
|
89
|
+
},
|
|
90
|
+
upload(file, customParams = {}) {
|
|
91
|
+
return upload(file, customParams);
|
|
92
|
+
},
|
|
93
|
+
delete(file, customParams = {}) {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
// delete file on S3 bucket
|
|
96
|
+
const fileKey = getFileKey(file);
|
|
97
|
+
S3.deleteObject({
|
|
98
|
+
Key: fileKey,
|
|
99
|
+
Bucket: config.params.Bucket,
|
|
100
|
+
...customParams,
|
|
101
|
+
}, (err) => {
|
|
102
|
+
if (err) {
|
|
103
|
+
return reject(err);
|
|
104
|
+
}
|
|
105
|
+
resolve();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface BucketInfo {
|
|
2
|
+
bucket?: string | null;
|
|
3
|
+
err?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Parse the bucket name from a URL.
|
|
7
|
+
* See all URL formats in https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html
|
|
8
|
+
*
|
|
9
|
+
* @param {string} fileUrl - the URL to parse
|
|
10
|
+
* @returns {object} result
|
|
11
|
+
* @returns {string} result.bucket - the bucket name
|
|
12
|
+
* @returns {string} result.err - if any
|
|
13
|
+
*/
|
|
14
|
+
export declare function getBucketFromUrl(fileUrl: string): BucketInfo;
|
|
15
|
+
export {};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getBucketFromUrl = void 0;
|
|
4
|
+
const ENDPOINT_PATTERN = /^(.+\.)?s3[.-]([a-z0-9-]+)\./;
|
|
5
|
+
/**
|
|
6
|
+
* Parse the bucket name from a URL.
|
|
7
|
+
* See all URL formats in https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-bucket-intro.html
|
|
8
|
+
*
|
|
9
|
+
* @param {string} fileUrl - the URL to parse
|
|
10
|
+
* @returns {object} result
|
|
11
|
+
* @returns {string} result.bucket - the bucket name
|
|
12
|
+
* @returns {string} result.err - if any
|
|
13
|
+
*/
|
|
14
|
+
function getBucketFromUrl(fileUrl) {
|
|
15
|
+
const uri = new URL(fileUrl);
|
|
16
|
+
// S3://<bucket-name>/<key>
|
|
17
|
+
if (uri.protocol === 's3:') {
|
|
18
|
+
const bucket = uri.host;
|
|
19
|
+
if (!bucket) {
|
|
20
|
+
return { err: `Invalid S3 URI: no bucket: ${uri}` };
|
|
21
|
+
}
|
|
22
|
+
return { bucket };
|
|
23
|
+
}
|
|
24
|
+
if (!uri.host) {
|
|
25
|
+
return { err: `Invalid S3 URI: no hostname: ${uri}` };
|
|
26
|
+
}
|
|
27
|
+
const matches = uri.host.match(ENDPOINT_PATTERN);
|
|
28
|
+
if (!matches) {
|
|
29
|
+
return { err: `Invalid S3 URI: hostname does not appear to be a valid S3 endpoint: ${uri}` };
|
|
30
|
+
}
|
|
31
|
+
const prefix = matches[1];
|
|
32
|
+
// https://s3.amazonaws.com/<bucket-name>
|
|
33
|
+
if (!prefix) {
|
|
34
|
+
if (uri.pathname === '/') {
|
|
35
|
+
return { bucket: null };
|
|
36
|
+
}
|
|
37
|
+
const index = uri.pathname.indexOf('/', 1);
|
|
38
|
+
// https://s3.amazonaws.com/<bucket-name>
|
|
39
|
+
if (index === -1) {
|
|
40
|
+
return { bucket: uri.pathname.substring(1) };
|
|
41
|
+
}
|
|
42
|
+
// https://s3.amazonaws.com/<bucket-name>/
|
|
43
|
+
if (index === uri.pathname.length - 1) {
|
|
44
|
+
return { bucket: uri.pathname.substring(1, index) };
|
|
45
|
+
}
|
|
46
|
+
// https://s3.amazonaws.com/<bucket-name>/key
|
|
47
|
+
return { bucket: uri.pathname.substring(1, index) };
|
|
48
|
+
}
|
|
49
|
+
// https://<bucket-name>.s3.amazonaws.com/
|
|
50
|
+
return { bucket: prefix.substring(0, prefix.length - 1) };
|
|
51
|
+
}
|
|
52
|
+
exports.getBucketFromUrl = getBucketFromUrl;
|
|
53
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAAA,MAAM,gBAAgB,GAAG,8BAA8B,CAAC;AAOxD;;;;;;;;GAQG;AACH,SAAgB,gBAAgB,CAAC,OAAe;IAC9C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAE7B,2BAA2B;IAC3B,IAAI,GAAG,CAAC,QAAQ,KAAK,KAAK,EAAE;QAC1B,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC;QAExB,IAAI,CAAC,MAAM,EAAE;YACX,OAAO,EAAE,GAAG,EAAE,8BAA8B,GAAG,EAAE,EAAE,CAAC;SACrD;QACD,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB;IAED,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;QACb,OAAO,EAAE,GAAG,EAAE,gCAAgC,GAAG,EAAE,EAAE,CAAC;KACvD;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACjD,IAAI,CAAC,OAAO,EAAE;QACZ,OAAO,EAAE,GAAG,EAAE,uEAAuE,GAAG,EAAE,EAAE,CAAC;KAC9F;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1B,yCAAyC;IACzC,IAAI,CAAC,MAAM,EAAE;QACX,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,EAAE;YACxB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;SACzB;QAED,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAE3C,yCAAyC;QACzC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE;YAChB,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9C;QAED,0CAA0C;QAC1C,IAAI,KAAK,KAAK,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;YACrC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;SACrD;QAED,6CAA6C;QAC7C,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;KACrD;IAED,0CAA0C;IAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;AAC5D,CAAC;AA/CD,4CA+CC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/provider-upload-aws-s3",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-next.13502afca039cc21e1caa9553f13e4816419f200",
|
|
4
4
|
"description": "AWS S3 provider for strapi upload",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"upload",
|
|
@@ -29,20 +29,33 @@
|
|
|
29
29
|
"url": "https://strapi.io"
|
|
30
30
|
}
|
|
31
31
|
],
|
|
32
|
-
"main": "./
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
"main": "./dist/index.js",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"files": [
|
|
35
|
+
"./dist"
|
|
36
|
+
],
|
|
36
37
|
"scripts": {
|
|
37
|
-
"
|
|
38
|
+
"build": "run -T tsc",
|
|
39
|
+
"build:ts": "run -T tsc",
|
|
40
|
+
"watch": "run -T tsc -w --preserveWatchOutput",
|
|
41
|
+
"clean": "run -T rimraf ./dist",
|
|
42
|
+
"prepublishOnly": "yarn clean && yarn build",
|
|
43
|
+
"test:unit": "run -T jest",
|
|
44
|
+
"test:unit:watch": "run -T jest --watch",
|
|
45
|
+
"lint": "run -T eslint ."
|
|
38
46
|
},
|
|
39
47
|
"dependencies": {
|
|
40
|
-
"aws-sdk": "2.
|
|
48
|
+
"aws-sdk": "2.1372.0",
|
|
41
49
|
"lodash": "4.17.21"
|
|
42
50
|
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/jest": "29.2.0",
|
|
53
|
+
"eslint-config-custom": "0.0.0-next.13502afca039cc21e1caa9553f13e4816419f200",
|
|
54
|
+
"tsconfig": "0.0.0-next.13502afca039cc21e1caa9553f13e4816419f200"
|
|
55
|
+
},
|
|
43
56
|
"engines": {
|
|
44
57
|
"node": ">=14.19.1 <=18.x.x",
|
|
45
58
|
"npm": ">=6.0.0"
|
|
46
59
|
},
|
|
47
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "13502afca039cc21e1caa9553f13e4816419f200"
|
|
48
61
|
}
|
package/lib/index.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Module dependencies
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/* eslint-disable no-unused-vars */
|
|
8
|
-
// Public node modules.
|
|
9
|
-
const _ = require('lodash');
|
|
10
|
-
const AWS = require('aws-sdk');
|
|
11
|
-
|
|
12
|
-
module.exports = {
|
|
13
|
-
init(config) {
|
|
14
|
-
const S3 = new AWS.S3({
|
|
15
|
-
apiVersion: '2006-03-01',
|
|
16
|
-
...config,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const upload = (file, customParams = {}) =>
|
|
20
|
-
new Promise((resolve, reject) => {
|
|
21
|
-
// upload file on S3 bucket
|
|
22
|
-
const path = file.path ? `${file.path}/` : '';
|
|
23
|
-
S3.upload(
|
|
24
|
-
{
|
|
25
|
-
Key: `${path}${file.hash}${file.ext}`,
|
|
26
|
-
Body: file.stream || Buffer.from(file.buffer, 'binary'),
|
|
27
|
-
ACL: 'public-read',
|
|
28
|
-
ContentType: file.mime,
|
|
29
|
-
...customParams,
|
|
30
|
-
},
|
|
31
|
-
(err, data) => {
|
|
32
|
-
if (err) {
|
|
33
|
-
return reject(err);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// set the bucket file url
|
|
37
|
-
file.url = data.Location;
|
|
38
|
-
|
|
39
|
-
resolve();
|
|
40
|
-
}
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
uploadStream(file, customParams = {}) {
|
|
46
|
-
return upload(file, customParams);
|
|
47
|
-
},
|
|
48
|
-
upload(file, customParams = {}) {
|
|
49
|
-
return upload(file, customParams);
|
|
50
|
-
},
|
|
51
|
-
delete(file, customParams = {}) {
|
|
52
|
-
return new Promise((resolve, reject) => {
|
|
53
|
-
// delete file on S3 bucket
|
|
54
|
-
const path = file.path ? `${file.path}/` : '';
|
|
55
|
-
S3.deleteObject(
|
|
56
|
-
{
|
|
57
|
-
Key: `${path}${file.hash}${file.ext}`,
|
|
58
|
-
...customParams,
|
|
59
|
-
},
|
|
60
|
-
(err, data) => {
|
|
61
|
-
if (err) {
|
|
62
|
-
return reject(err);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
resolve();
|
|
66
|
-
}
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
},
|
|
72
|
-
};
|