@deenruv/asset-server-plugin 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.
Files changed (37) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +103 -0
  3. package/lib/index.d.ts +4 -0
  4. package/lib/index.js +21 -0
  5. package/lib/index.js.map +1 -0
  6. package/lib/src/common.d.ts +4 -0
  7. package/lib/src/common.js +42 -0
  8. package/lib/src/common.js.map +1 -0
  9. package/lib/src/constants.d.ts +2 -0
  10. package/lib/src/constants.js +6 -0
  11. package/lib/src/constants.js.map +1 -0
  12. package/lib/src/default-asset-storage-strategy-factory.d.ts +6 -0
  13. package/lib/src/default-asset-storage-strategy-factory.js +24 -0
  14. package/lib/src/default-asset-storage-strategy-factory.js.map +1 -0
  15. package/lib/src/file-icon.png +0 -0
  16. package/lib/src/hashed-asset-naming-strategy.d.ts +21 -0
  17. package/lib/src/hashed-asset-naming-strategy.js +39 -0
  18. package/lib/src/hashed-asset-naming-strategy.js.map +1 -0
  19. package/lib/src/local-asset-storage-strategy.d.ts +28 -0
  20. package/lib/src/local-asset-storage-strategy.js +68 -0
  21. package/lib/src/local-asset-storage-strategy.js.map +1 -0
  22. package/lib/src/plugin.d.ts +167 -0
  23. package/lib/src/plugin.js +394 -0
  24. package/lib/src/plugin.js.map +1 -0
  25. package/lib/src/s3-asset-storage-strategy.d.ts +159 -0
  26. package/lib/src/s3-asset-storage-strategy.js +289 -0
  27. package/lib/src/s3-asset-storage-strategy.js.map +1 -0
  28. package/lib/src/sharp-asset-preview-strategy.d.ts +99 -0
  29. package/lib/src/sharp-asset-preview-strategy.js +121 -0
  30. package/lib/src/sharp-asset-preview-strategy.js.map +1 -0
  31. package/lib/src/transform-image.d.ts +25 -0
  32. package/lib/src/transform-image.js +145 -0
  33. package/lib/src/transform-image.js.map +1 -0
  34. package/lib/src/types.d.ts +137 -0
  35. package/lib/src/types.js +3 -0
  36. package/lib/src/types.js.map +1 -0
  37. package/package.json +47 -0
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
26
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
27
+ var m = o[Symbol.asyncIterator], i;
28
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
29
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
30
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
31
+ };
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.S3AssetStorageStrategy = exports.configureS3AssetStorage = void 0;
34
+ const core_1 = require("@deenruv/core");
35
+ const path = __importStar(require("node:path"));
36
+ const node_stream_1 = require("node:stream");
37
+ const common_1 = require("./common");
38
+ const constants_1 = require("./constants");
39
+ /**
40
+ * @description
41
+ * Returns a configured instance of the {@link S3AssetStorageStrategy} which can then be passed to the {@link AssetServerOptions}
42
+ * `storageStrategyFactory` property.
43
+ *
44
+ * Before using this strategy, make sure you have the `@aws-sdk/client-s3` and `@aws-sdk/lib-storage` package installed:
45
+ *
46
+ * ```sh
47
+ * npm install \@aws-sdk/client-s3 \@aws-sdk/lib-storage
48
+ * ```
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { AssetServerPlugin, configureS3AssetStorage } from '\@deenruv/asset-server-plugin';
53
+ * import { DefaultAssetNamingStrategy } from '\@deenruv/core';
54
+ * import { fromEnv } from '\@aws-sdk/credential-providers';
55
+ *
56
+ * // ...
57
+ *
58
+ * plugins: [
59
+ * AssetServerPlugin.init({
60
+ * route: 'assets',
61
+ * assetUploadDir: path.join(__dirname, 'assets'),
62
+ * namingStrategy: new DefaultAssetNamingStrategy(),
63
+ * storageStrategyFactory: configureS3AssetStorage({
64
+ * bucket: 'my-s3-bucket',
65
+ * credentials: fromEnv(), // or any other credential provider
66
+ * nativeS3Configuration: {
67
+ * region: process.env.AWS_REGION,
68
+ * },
69
+ * }),
70
+ * }),
71
+ * ```
72
+ *
73
+ * ## Usage with MinIO
74
+ *
75
+ * Reference: [How to use AWS SDK for Javascript with MinIO Server](https://docs.min.io/docs/how-to-use-aws-sdk-for-javascript-with-minio-server.html)
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * import { AssetServerPlugin, configureS3AssetStorage } from '\@deenruv/asset-server-plugin';
80
+ * import { DefaultAssetNamingStrategy } from '\@deenruv/core';
81
+ *
82
+ * // ...
83
+ *
84
+ * plugins: [
85
+ * AssetServerPlugin.init({
86
+ * route: 'assets',
87
+ * assetUploadDir: path.join(__dirname, 'assets'),
88
+ * namingStrategy: new DefaultAssetNamingStrategy(),
89
+ * storageStrategyFactory: configureS3AssetStorage({
90
+ * bucket: 'my-minio-bucket',
91
+ * credentials: {
92
+ * accessKeyId: process.env.MINIO_ACCESS_KEY_ID,
93
+ * secretAccessKey: process.env.MINIO_SECRET_ACCESS_KEY,
94
+ * },
95
+ * nativeS3Configuration: {
96
+ * endpoint: process.env.MINIO_ENDPOINT ?? 'http://localhost:9000',
97
+ * forcePathStyle: true,
98
+ * signatureVersion: 'v4',
99
+ * // The `region` is required by the AWS SDK even when using MinIO,
100
+ * // so we just use a dummy value here.
101
+ * region: 'eu-west-1',
102
+ * },
103
+ * }),
104
+ * }),
105
+ * ```
106
+ * @docsCategory core plugins/AssetServerPlugin
107
+ * @docsPage S3AssetStorageStrategy
108
+ */
109
+ function configureS3AssetStorage(s3Config) {
110
+ return (options) => {
111
+ const prefixFn = (0, common_1.getAssetUrlPrefixFn)(options);
112
+ const toAbsoluteUrlFn = (request, identifier) => {
113
+ if (!identifier) {
114
+ return "";
115
+ }
116
+ const prefix = prefixFn(request, identifier);
117
+ return identifier.startsWith(prefix)
118
+ ? identifier
119
+ : `${prefix}${identifier}`;
120
+ };
121
+ return new S3AssetStorageStrategy(s3Config, toAbsoluteUrlFn);
122
+ };
123
+ }
124
+ exports.configureS3AssetStorage = configureS3AssetStorage;
125
+ /**
126
+ * @description
127
+ * An {@link AssetStorageStrategy} which uses [Amazon S3](https://aws.amazon.com/s3/) object storage service.
128
+ * To us this strategy you must first have access to an AWS account.
129
+ * See their [getting started guide](https://aws.amazon.com/s3/getting-started/) for how to get set up.
130
+ *
131
+ * Before using this strategy, make sure you have the `@aws-sdk/client-s3` and `@aws-sdk/lib-storage` package installed:
132
+ *
133
+ * ```sh
134
+ * npm install \@aws-sdk/client-s3 \@aws-sdk/lib-storage
135
+ * ```
136
+ *
137
+ * **Note:** Rather than instantiating this manually, use the {@link configureS3AssetStorage} function.
138
+ *
139
+ * ## Use with S3-compatible services (MinIO)
140
+ * This strategy will also work with any S3-compatible object storage solutions, such as [MinIO](https://min.io/).
141
+ * See the {@link configureS3AssetStorage} for an example with MinIO.
142
+ *
143
+ * @docsCategory asset-server-plugin
144
+ * @docsPage S3AssetStorageStrategy
145
+ * @docsWeight 0
146
+ */
147
+ class S3AssetStorageStrategy {
148
+ constructor(s3Config, toAbsoluteUrl) {
149
+ this.s3Config = s3Config;
150
+ this.toAbsoluteUrl = toAbsoluteUrl;
151
+ }
152
+ async init() {
153
+ try {
154
+ this.AWS = await import("@aws-sdk/client-s3");
155
+ }
156
+ catch (err) {
157
+ core_1.Logger.error('Could not find the "@aws-sdk/client-s3" package. Make sure it is installed', constants_1.loggerCtx, err.stack);
158
+ }
159
+ try {
160
+ this.libStorage = await import("@aws-sdk/lib-storage");
161
+ }
162
+ catch (err) {
163
+ core_1.Logger.error('Could not find the "@aws-sdk/lib-storage" package. Make sure it is installed', constants_1.loggerCtx, err.stack);
164
+ }
165
+ const config = Object.assign(Object.assign({}, this.s3Config.nativeS3Configuration), { credentials: await this.getCredentials() });
166
+ this.s3Client = new this.AWS.S3Client(config);
167
+ await this.ensureBucket();
168
+ }
169
+ async writeFileFromBuffer(fileName, data) {
170
+ return this.writeFile(fileName, data);
171
+ }
172
+ async writeFileFromStream(fileName, data) {
173
+ return this.writeFile(fileName, data);
174
+ }
175
+ async readFileToBuffer(identifier) {
176
+ var _a, e_1, _b, _c;
177
+ const body = await this.readFile(identifier);
178
+ if (!body) {
179
+ core_1.Logger.error(`Got undefined Body for ${identifier}`, constants_1.loggerCtx);
180
+ return Buffer.from("");
181
+ }
182
+ const chunks = [];
183
+ try {
184
+ for (var _d = true, body_1 = __asyncValues(body), body_1_1; body_1_1 = await body_1.next(), _a = body_1_1.done, !_a; _d = true) {
185
+ _c = body_1_1.value;
186
+ _d = false;
187
+ const chunk = _c;
188
+ chunks.push(chunk);
189
+ }
190
+ }
191
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
192
+ finally {
193
+ try {
194
+ if (!_d && !_a && (_b = body_1.return)) await _b.call(body_1);
195
+ }
196
+ finally { if (e_1) throw e_1.error; }
197
+ }
198
+ return Buffer.concat(chunks);
199
+ }
200
+ async readFileToStream(identifier) {
201
+ const body = await this.readFile(identifier);
202
+ if (!body) {
203
+ return new node_stream_1.Readable({
204
+ read() {
205
+ this.push(null);
206
+ },
207
+ });
208
+ }
209
+ return body;
210
+ }
211
+ async readFile(identifier) {
212
+ const { GetObjectCommand } = this.AWS;
213
+ const result = await this.s3Client.send(new GetObjectCommand(this.getObjectParams(identifier)));
214
+ return result.Body;
215
+ }
216
+ async writeFile(fileName, data) {
217
+ const { Upload } = this.libStorage;
218
+ const upload = new Upload({
219
+ client: this.s3Client,
220
+ params: Object.assign(Object.assign({}, this.s3Config.nativeS3UploadConfiguration), { Bucket: this.s3Config.bucket, Key: fileName, Body: data }),
221
+ });
222
+ return upload.done().then((result) => {
223
+ if (!("Key" in result) || !result.Key) {
224
+ core_1.Logger.error(`Got undefined Key for ${fileName}`, constants_1.loggerCtx);
225
+ throw new Error(`Got undefined Key for ${fileName}`);
226
+ }
227
+ return result.Key;
228
+ });
229
+ }
230
+ async deleteFile(identifier) {
231
+ const { DeleteObjectCommand } = this.AWS;
232
+ await this.s3Client.send(new DeleteObjectCommand(this.getObjectParams(identifier)));
233
+ }
234
+ async fileExists(fileName) {
235
+ const { HeadObjectCommand } = this.AWS;
236
+ try {
237
+ await this.s3Client.send(new HeadObjectCommand(this.getObjectParams(fileName)));
238
+ return true;
239
+ }
240
+ catch (err) {
241
+ return false;
242
+ }
243
+ }
244
+ getObjectParams(identifier) {
245
+ return {
246
+ Bucket: this.s3Config.bucket,
247
+ Key: path.join(identifier.replace(/^\//, "")),
248
+ };
249
+ }
250
+ async ensureBucket(bucket = this.s3Config.bucket) {
251
+ const { HeadBucketCommand, CreateBucketCommand } = this.AWS;
252
+ try {
253
+ await this.s3Client.send(new HeadBucketCommand({ Bucket: bucket }));
254
+ core_1.Logger.verbose(`Found S3 bucket "${bucket}"`, constants_1.loggerCtx);
255
+ return;
256
+ }
257
+ catch (err) {
258
+ core_1.Logger.verbose(`Could not find bucket "${bucket}: ${JSON.stringify(err.message)}". Attempting to create...`);
259
+ }
260
+ try {
261
+ await this.s3Client.send(new CreateBucketCommand({ Bucket: bucket, ACL: "private" }));
262
+ core_1.Logger.verbose(`Created S3 bucket "${bucket}"`, constants_1.loggerCtx);
263
+ }
264
+ catch (err) {
265
+ core_1.Logger.error(`Could not find nor create the S3 bucket "${bucket}: ${JSON.stringify(err.message)}"`, constants_1.loggerCtx, err.stack);
266
+ }
267
+ }
268
+ async getCredentials() {
269
+ if (this.s3Config.credentials == null) {
270
+ return undefined;
271
+ }
272
+ if (this.isCredentialsProfile(this.s3Config.credentials)) {
273
+ core_1.Logger.warn('The "profile" property of the "s3Config.credentials" is deprecated. ' +
274
+ 'Please use the "fromIni()" function from the "@aws-sdk/credential-provider-ini" or "@aws-sdk/credential-providers" package instead.', constants_1.loggerCtx);
275
+ return (await import("@aws-sdk/credential-provider-ini")).fromIni({
276
+ profile: this.s3Config.credentials.profile,
277
+ });
278
+ }
279
+ return this.s3Config.credentials;
280
+ }
281
+ isCredentialsProfile(credentials) {
282
+ return (credentials !== null &&
283
+ typeof credentials === "object" &&
284
+ "profile" in credentials &&
285
+ Object.keys(credentials).length === 1);
286
+ }
287
+ }
288
+ exports.S3AssetStorageStrategy = S3AssetStorageStrategy;
289
+ //# sourceMappingURL=s3-asset-storage-strategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3-asset-storage-strategy.js","sourceRoot":"","sources":["../../src/s3-asset-storage-strategy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { AssetPreviewStrategy, RequestContext } from "@deenruv/core";
4
+ import sharp from "sharp";
5
+ /**
6
+ * @description
7
+ * This {@link AssetPreviewStrategy} uses the [Sharp library](https://sharp.pixelplumbing.com/) to generate
8
+ * preview images of uploaded binary files. For non-image binaries, a generic "file" icon with the mime type
9
+ * overlay will be generated.
10
+ *
11
+ * @docsCategory core plugins/AssetServerPlugin
12
+ * @docsPage SharpAssetPreviewStrategy
13
+ */
14
+ interface SharpAssetPreviewConfig {
15
+ /**
16
+ * @description
17
+ * The max height in pixels of a generated preview image.
18
+ *
19
+ * @default 1600
20
+ */
21
+ maxHeight?: number;
22
+ /**
23
+ * @description
24
+ * The max width in pixels of a generated preview image.
25
+ *
26
+ * @default 1600
27
+ */
28
+ maxWidth?: number;
29
+ /**
30
+ * @description
31
+ * Set Sharp's options for encoding jpeg files: https://sharp.pixelplumbing.com/api-output#jpeg
32
+ *
33
+ * @since 1.7.0
34
+ */
35
+ jpegOptions?: sharp.JpegOptions;
36
+ /**
37
+ * @description
38
+ * Set Sharp's options for encoding png files: https://sharp.pixelplumbing.com/api-output#png
39
+ *
40
+ * @since 1.7.0
41
+ */
42
+ pngOptions?: sharp.PngOptions;
43
+ /**
44
+ * @description
45
+ * Set Sharp's options for encoding webp files: https://sharp.pixelplumbing.com/api-output#webp
46
+ *
47
+ * @since 1.7.0
48
+ */
49
+ webpOptions?: sharp.WebpOptions;
50
+ /**
51
+ * @description
52
+ * Set Sharp's options for encoding gif files: https://sharp.pixelplumbing.com/api-output#gif
53
+ *
54
+ * @since 1.7.0
55
+ */
56
+ gifOptions?: sharp.GifOptions;
57
+ /**
58
+ * @description
59
+ * Set Sharp's options for encoding avif files: https://sharp.pixelplumbing.com/api-output#avif
60
+ *
61
+ * @since 1.7.0
62
+ */
63
+ avifOptions?: sharp.AvifOptions;
64
+ }
65
+ /**
66
+ * @description
67
+ * This {@link AssetPreviewStrategy} uses the [Sharp library](https://sharp.pixelplumbing.com/) to generate
68
+ * preview images of uploaded binary files. For non-image binaries, a generic "file" icon with the mime type
69
+ * overlay will be generated.
70
+ *
71
+ * By default, this strategy will produce previews up to maximum dimensions of 1600 x 1600 pixels. The created
72
+ * preview images will match the input format - so a source file in jpeg format will output a jpeg preview,
73
+ * a webp source file will output a webp preview, and so on.
74
+ *
75
+ * The settings for the outputs will default to Sharp's defaults (https://sharp.pixelplumbing.com/api-output).
76
+ * However, it is possible to pass your own configurations to control the output of each format:
77
+ *
78
+ * ```ts
79
+ * AssetServerPlugin.init({
80
+ * previewStrategy: new SharpAssetPreviewStrategy({
81
+ * jpegOptions: { quality: 95 },
82
+ * webpOptions: { quality: 95 },
83
+ * }),
84
+ * }),
85
+ * ```
86
+ *
87
+ * @docsCategory core plugins/AssetServerPlugin
88
+ * @docsPage SharpAssetPreviewStrategy
89
+ * @docsWeight 0
90
+ */
91
+ export declare class SharpAssetPreviewStrategy implements AssetPreviewStrategy {
92
+ private readonly defaultConfig;
93
+ private readonly config;
94
+ constructor(config?: SharpAssetPreviewConfig);
95
+ generatePreviewImage(ctx: RequestContext, mimeType: string, data: Buffer): Promise<Buffer>;
96
+ private generateMimeTypeOverlay;
97
+ private generateBinaryFilePreview;
98
+ }
99
+ export {};
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SharpAssetPreviewStrategy = void 0;
7
+ const generated_types_1 = require("@deenruv/common/lib/generated-types");
8
+ const core_1 = require("@deenruv/core");
9
+ const path_1 = __importDefault(require("path"));
10
+ const sharp_1 = __importDefault(require("sharp"));
11
+ const constants_1 = require("./constants");
12
+ /**
13
+ * @description
14
+ * This {@link AssetPreviewStrategy} uses the [Sharp library](https://sharp.pixelplumbing.com/) to generate
15
+ * preview images of uploaded binary files. For non-image binaries, a generic "file" icon with the mime type
16
+ * overlay will be generated.
17
+ *
18
+ * By default, this strategy will produce previews up to maximum dimensions of 1600 x 1600 pixels. The created
19
+ * preview images will match the input format - so a source file in jpeg format will output a jpeg preview,
20
+ * a webp source file will output a webp preview, and so on.
21
+ *
22
+ * The settings for the outputs will default to Sharp's defaults (https://sharp.pixelplumbing.com/api-output).
23
+ * However, it is possible to pass your own configurations to control the output of each format:
24
+ *
25
+ * ```ts
26
+ * AssetServerPlugin.init({
27
+ * previewStrategy: new SharpAssetPreviewStrategy({
28
+ * jpegOptions: { quality: 95 },
29
+ * webpOptions: { quality: 95 },
30
+ * }),
31
+ * }),
32
+ * ```
33
+ *
34
+ * @docsCategory core plugins/AssetServerPlugin
35
+ * @docsPage SharpAssetPreviewStrategy
36
+ * @docsWeight 0
37
+ */
38
+ class SharpAssetPreviewStrategy {
39
+ constructor(config) {
40
+ this.defaultConfig = {
41
+ maxHeight: 1600,
42
+ maxWidth: 1600,
43
+ jpegOptions: {},
44
+ pngOptions: {},
45
+ webpOptions: {},
46
+ gifOptions: {},
47
+ avifOptions: {},
48
+ };
49
+ this.config = Object.assign(Object.assign({}, this.defaultConfig), (config !== null && config !== void 0 ? config : {}));
50
+ }
51
+ async generatePreviewImage(ctx, mimeType, data) {
52
+ const assetType = (0, core_1.getAssetType)(mimeType);
53
+ const { maxWidth, maxHeight } = this.config;
54
+ if (assetType === generated_types_1.AssetType.IMAGE) {
55
+ try {
56
+ const image = (0, sharp_1.default)(data, { failOn: "truncated" }).rotate();
57
+ const metadata = await image.metadata();
58
+ const width = metadata.width || 0;
59
+ const height = metadata.height || 0;
60
+ if (maxWidth < width || maxHeight < height) {
61
+ image.resize(maxWidth, maxHeight, { fit: "inside" });
62
+ }
63
+ if (mimeType === "image/svg+xml") {
64
+ // Convert the SVG to a raster for the preview
65
+ return image.toBuffer();
66
+ }
67
+ else {
68
+ switch (metadata.format) {
69
+ case "jpeg":
70
+ case "jpg":
71
+ return image.jpeg(this.config.jpegOptions).toBuffer();
72
+ case "png":
73
+ return image.png(this.config.pngOptions).toBuffer();
74
+ case "webp":
75
+ return image.webp(this.config.webpOptions).toBuffer();
76
+ case "gif":
77
+ return image.gif(this.config.jpegOptions).toBuffer();
78
+ case "avif":
79
+ return image.avif(this.config.avifOptions).toBuffer();
80
+ default:
81
+ return image.toBuffer();
82
+ }
83
+ }
84
+ }
85
+ catch (err) {
86
+ core_1.Logger.error(`An error occurred when generating preview for image with mimeType ${mimeType}: ${JSON.stringify(err.message)}`, constants_1.loggerCtx);
87
+ return this.generateBinaryFilePreview(mimeType);
88
+ }
89
+ }
90
+ else {
91
+ return this.generateBinaryFilePreview(mimeType);
92
+ }
93
+ }
94
+ generateMimeTypeOverlay(mimeType) {
95
+ return Buffer.from(`
96
+ <svg xmlns="http://www.w3.org/2000/svg" height="150" width="800">
97
+ <style>
98
+ text {
99
+ font-size: 64px;
100
+ font-family: Arial, sans-serif;
101
+ fill: #666;
102
+ }
103
+ </style>
104
+
105
+ <text x="400" y="110" text-anchor="middle" width="800">${mimeType}</text>
106
+ </svg>`);
107
+ }
108
+ generateBinaryFilePreview(mimeType) {
109
+ return (0, sharp_1.default)(path_1.default.join(__dirname, "file-icon.png"))
110
+ .resize(800, 800, { fit: "outside" })
111
+ .composite([
112
+ {
113
+ input: this.generateMimeTypeOverlay(mimeType),
114
+ gravity: sharp_1.default.gravity.center,
115
+ },
116
+ ])
117
+ .toBuffer();
118
+ }
119
+ }
120
+ exports.SharpAssetPreviewStrategy = SharpAssetPreviewStrategy;
121
+ //# sourceMappingURL=sharp-asset-preview-strategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharp-asset-preview-strategy.js","sourceRoot":"","sources":["../../src/sharp-asset-preview-strategy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import sharp, { Region } from "sharp";
4
+ import { ImageTransformPreset } from "./types";
5
+ export type Dimensions = {
6
+ w: number;
7
+ h: number;
8
+ };
9
+ export type Point = {
10
+ x: number;
11
+ y: number;
12
+ };
13
+ /**
14
+ * Applies transforms to the given image according to the query params passed.
15
+ */
16
+ export declare function transformImage(originalImage: Buffer, queryParams: Record<string, string>, presets: ImageTransformPreset[]): Promise<sharp.Sharp>;
17
+ /**
18
+ * Resize an image but keep it centered on the focal point.
19
+ * Based on the method outlined in https://github.com/lovell/sharp/issues/1198#issuecomment-384591756
20
+ */
21
+ export declare function resizeToFocalPoint(original: Dimensions, target: Dimensions, focalPoint: Point): {
22
+ width: number;
23
+ height: number;
24
+ region: Region;
25
+ };