@uxf/resizer 2.3.3 → 10.0.0-beta.80

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 CHANGED
@@ -50,6 +50,7 @@ You can use configuration file `.resizer-config.json` or environment variable `U
50
50
  ```
51
51
 
52
52
  ### Supported route parameters
53
+
53
54
  | Parameter name | Type | Default sharp value |
54
55
  |----------------|------------------------------------|----------------------------------------|
55
56
  | width | `number` or `x` (auto) | `undefined` |
@@ -61,6 +62,7 @@ You can use configuration file `.resizer-config.json` or environment variable `U
61
62
  | trim | `number` or `nt` (not trim) | |
62
63
  | quality | `number` (min: 1, max: 100) or `x` | default of sharp (avif: 50, other: 80) |
63
64
 
65
+
64
66
  #### Fit
65
67
  - `cv`: cover,
66
68
  - `f`: fill,
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -23,96 +23,61 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  __setModuleDefault(result, mod);
24
24
  return result;
25
25
  };
26
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
27
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
28
- return new (P || (P = Promise))(function (resolve, reject) {
29
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
30
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
31
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
32
- step((generator = generator.apply(thisArg, _arguments || [])).next());
33
- });
34
- };
35
- var __generator = (this && this.__generator) || function (thisArg, body) {
36
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
37
- return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
38
- function verb(n) { return function (v) { return step([n, v]); }; }
39
- function step(op) {
40
- if (f) throw new TypeError("Generator is already executing.");
41
- while (_) try {
42
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
43
- if (y = 0, t) op = [op[0] & 2, t.value];
44
- switch (op[0]) {
45
- case 0: case 1: t = op; break;
46
- case 4: _.label++; return { value: op[1], done: false };
47
- case 5: _.label++; y = op[1]; op = [0]; continue;
48
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
49
- default:
50
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
51
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
52
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
53
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
54
- if (t[2]) _.ops.pop();
55
- _.trys.pop(); continue;
56
- }
57
- op = body.call(thisArg, _);
58
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
59
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
60
- }
61
- };
62
26
  var __importDefault = (this && this.__importDefault) || function (mod) {
63
27
  return (mod && mod.__esModule) ? mod : { "default": mod };
64
28
  };
65
29
  Object.defineProperty(exports, "__esModule", { value: true });
66
- var process_1 = require("process");
67
- var yargs_1 = __importDefault(require("yargs"));
68
- var index_1 = require("../index");
69
- var express_1 = __importDefault(require("express"));
70
- var fs = __importStar(require("fs"));
71
- var uxfResizer = function () { return __awaiter(void 0, void 0, void 0, function () {
72
- var help, configFilename, configString, config_1;
30
+ const express_1 = __importDefault(require("express"));
31
+ const fs = __importStar(require("fs"));
32
+ const process_1 = require("process");
33
+ const yargs_1 = __importDefault(require("yargs"));
34
+ const index_1 = require("../index");
35
+ const uxfResizer = async () => {
73
36
  var _a;
74
- return __generator(this, function (_b) {
75
- yargs_1.default.command("$0", "UXF Resizer", function (yargs) {
76
- yargs.demandCommand(0, 0).usage("\nUsage:\n uxf-resizer\n \nEnvironment variables:\n UXF_RESIZER_GENERATE_PATH - required");
77
- })
78
- .option("h", { alias: "help", group: "Options" })
79
- .strict(false)
80
- .exitProcess(false);
81
- try {
82
- help = yargs_1.default.parse(process_1.argv.slice(2)).help;
83
- if (help) {
84
- return [2 /*return*/, 0];
85
- }
86
- configFilename = process.cwd() + "/.resizer-config.json";
87
- configString = (_a = process_1.env.UXF_RESIZER_CONFIG) !== null && _a !== void 0 ? _a : (fs.existsSync(configFilename) ? fs.readFileSync(configFilename).toString() : "[]");
88
- config_1 = JSON.parse(configString);
89
- if (!Array.isArray(config_1) || config_1.length === 0) {
90
- throw new Error("Invalid or empty configuration");
91
- }
92
- return [2 /*return*/, new Promise(function (resolve, reject) {
93
- try {
94
- var app_1 = (0, express_1.default)().use((0, index_1.resizerMiddleware)(config_1));
95
- var listenedApp_1 = app_1.listen(3000, function () {
96
- // eslint-disable-next-line no-console
97
- console.log("\nImage resizer app listening on port 3000");
98
- app_1.on("close", function () {
99
- listenedApp_1.close();
100
- resolve(true);
101
- });
102
- });
103
- }
104
- catch (e) {
105
- reject(e);
106
- }
107
- })];
37
+ yargs_1.default.command("$0", "UXF Resizer", (yargs) => {
38
+ yargs.demandCommand(0, 0).usage(`
39
+ Usage:
40
+ uxf-resizer
41
+
42
+ Environment variables:
43
+ UXF_RESIZER_GENERATE_PATH - required`);
44
+ })
45
+ .option("h", { alias: "help", group: "Options" })
46
+ .strict(false)
47
+ .exitProcess(false);
48
+ try {
49
+ const { help } = yargs_1.default.parse(process_1.argv.slice(2)); // TODO
50
+ if (help) {
51
+ return 0;
108
52
  }
109
- catch (e) {
110
- // eslint-disable-next-line no-console
111
- console.error(e);
112
- return [2 /*return*/, 1];
53
+ const configFilename = process.cwd() + "/.resizer-config.json";
54
+ const configString = (_a = process_1.env.UXF_RESIZER_CONFIG) !== null && _a !== void 0 ? _a : (fs.existsSync(configFilename) ? fs.readFileSync(configFilename).toString() : "[]");
55
+ const config = JSON.parse(configString);
56
+ if (!Array.isArray(config) || config.length === 0) {
57
+ throw new Error("Invalid or empty configuration");
113
58
  }
114
- return [2 /*return*/];
115
- });
116
- }); };
59
+ return new Promise((resolve, reject) => {
60
+ try {
61
+ const app = (0, express_1.default)().use((0, index_1.resizerMiddleware)(config));
62
+ const listenedApp = app.listen(3000, () => {
63
+ // eslint-disable-next-line no-console
64
+ console.log("\nImage resizer app listening on port 3000");
65
+ app.on("close", () => {
66
+ listenedApp.close();
67
+ resolve(true);
68
+ });
69
+ });
70
+ }
71
+ catch (e) {
72
+ reject(e);
73
+ }
74
+ });
75
+ }
76
+ catch (e) {
77
+ // eslint-disable-next-line no-console
78
+ console.error(e);
79
+ return 1;
80
+ }
81
+ };
117
82
  // eslint-disable-next-line no-console
118
83
  uxfResizer().catch(console.error);
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ import express from "express";
3
+ import * as fs from "fs";
4
+ import { argv, env } from "process";
5
+ import cli from "yargs";
6
+ import { Config, resizerMiddleware } from "../index";
7
+
8
+ const uxfResizer = async () => {
9
+ cli.command("$0", "UXF Resizer", (yargs) => {
10
+ yargs.demandCommand(0, 0).usage(`
11
+ Usage:
12
+ uxf-resizer
13
+
14
+ Environment variables:
15
+ UXF_RESIZER_GENERATE_PATH - required`);
16
+ })
17
+ .option("h", { alias: "help", group: "Options" })
18
+ .strict(false)
19
+ .exitProcess(false);
20
+
21
+ try {
22
+ const { help } = cli.parse(argv.slice(2)) as any; // TODO
23
+
24
+ if (help) {
25
+ return 0;
26
+ }
27
+
28
+ const configFilename = process.cwd() + "/.resizer-config.json";
29
+
30
+ const configString =
31
+ env.UXF_RESIZER_CONFIG ??
32
+ (fs.existsSync(configFilename) ? fs.readFileSync(configFilename).toString() : "[]");
33
+
34
+ const config: Config = JSON.parse(configString);
35
+
36
+ if (!Array.isArray(config) || config.length === 0) {
37
+ throw new Error("Invalid or empty configuration");
38
+ }
39
+
40
+ return new Promise((resolve, reject) => {
41
+ try {
42
+ const app = express().use(resizerMiddleware(config));
43
+
44
+ const listenedApp = app.listen(3000, () => {
45
+ // eslint-disable-next-line no-console
46
+ console.log("\nImage resizer app listening on port 3000");
47
+
48
+ app.on("close", () => {
49
+ listenedApp.close();
50
+ resolve(true);
51
+ });
52
+ });
53
+ } catch (e) {
54
+ reject(e);
55
+ }
56
+ });
57
+ } catch (e) {
58
+ // eslint-disable-next-line no-console
59
+ console.error(e);
60
+ return 1;
61
+ }
62
+ };
63
+
64
+ // eslint-disable-next-line no-console
65
+ uxfResizer().catch(console.error);
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export type Config = Array<{
2
+ route: string;
3
+ source: string;
4
+ }>;
5
+ export declare const resizerMiddleware: (config: Config) => import("express-serve-static-core").Router;
package/index.js CHANGED
@@ -22,62 +22,26 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
- return new (P || (P = Promise))(function (resolve, reject) {
28
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
- step((generator = generator.apply(thisArg, _arguments || [])).next());
32
- });
33
- };
34
- var __generator = (this && this.__generator) || function (thisArg, body) {
35
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
36
- return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
37
- function verb(n) { return function (v) { return step([n, v]); }; }
38
- function step(op) {
39
- if (f) throw new TypeError("Generator is already executing.");
40
- while (_) try {
41
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
42
- if (y = 0, t) op = [op[0] & 2, t.value];
43
- switch (op[0]) {
44
- case 0: case 1: t = op; break;
45
- case 4: _.label++; return { value: op[1], done: false };
46
- case 5: _.label++; y = op[1]; op = [0]; continue;
47
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
48
- default:
49
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
50
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
51
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
52
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
53
- if (t[2]) _.ops.pop();
54
- _.trys.pop(); continue;
55
- }
56
- op = body.call(thisArg, _);
57
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
58
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
59
- }
60
- };
61
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
62
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
63
27
  };
64
28
  Object.defineProperty(exports, "__esModule", { value: true });
65
29
  exports.resizerMiddleware = void 0;
66
- var express_1 = require("express");
67
- var node_fetch_1 = __importDefault(require("node-fetch"));
68
- var fs_1 = require("fs");
69
- var path_1 = require("path");
70
- var sharp_1 = __importDefault(require("sharp"));
71
- var path_to_regexp_1 = require("path-to-regexp");
72
- var fs = __importStar(require("fs"));
73
- var FIT_OPTIONS = {
30
+ const express_1 = require("express");
31
+ const fs = __importStar(require("fs"));
32
+ const fs_1 = require("fs");
33
+ const node_fetch_1 = __importDefault(require("node-fetch"));
34
+ const path_1 = require("path");
35
+ const path_to_regexp_1 = require("path-to-regexp");
36
+ const sharp_1 = __importDefault(require("sharp"));
37
+ const FIT_OPTIONS = {
74
38
  cv: "cover",
75
39
  f: "fill",
76
40
  cn: "contain",
77
41
  in: "inside",
78
42
  out: "outside",
79
43
  };
80
- var POSITION_OPTIONS = {
44
+ const POSITION_OPTIONS = {
81
45
  a: "attention",
82
46
  b: "bottom",
83
47
  c: "centre",
@@ -90,40 +54,14 @@ var POSITION_OPTIONS = {
90
54
  rt: "right top",
91
55
  t: "top",
92
56
  };
93
- var getQuality = function (_a) {
94
- var quality = _a.quality;
95
- return (quality && quality !== "x" ? Number(quality) : undefined);
96
- };
97
- var getWidth = function (_a) {
98
- var width = _a.width;
99
- return (width && width !== "x" ? Number(width) : undefined);
100
- };
101
- var getHeight = function (_a) {
102
- var height = _a.height;
103
- return (height && height !== "x" ? Number(height) : undefined);
104
- };
105
- var getBackground = function (_a) {
106
- var background = _a.background;
107
- return background === undefined ? undefined : background === "t" ? "transparent" : "#".concat(background);
108
- };
109
- var getFit = function (_a) {
110
- var _b;
111
- var fit = _a.fit;
112
- return (fit ? (_b = FIT_OPTIONS[fit]) !== null && _b !== void 0 ? _b : "cover" : "cover");
113
- };
114
- var getPosition = function (_a) {
115
- var _b;
116
- var position = _a.position;
117
- return (position ? (_b = POSITION_OPTIONS[position]) !== null && _b !== void 0 ? _b : "centre" : undefined);
118
- };
119
- var getWithoutEnlargement = function (_a) {
120
- var extension = _a.extension;
121
- return extension !== "svg";
122
- };
123
- var getTrim = function (_a) {
124
- var trim = _a.trim;
125
- return (trim === undefined || trim === "nt" ? undefined : Number(trim));
126
- };
57
+ const getQuality = ({ quality }) => (quality && quality !== "x" ? Number(quality) : undefined);
58
+ const getWidth = ({ width }) => (width && width !== "x" ? Number(width) : undefined);
59
+ const getHeight = ({ height }) => (height && height !== "x" ? Number(height) : undefined);
60
+ const getBackground = ({ background }) => background === undefined ? undefined : background === "t" ? "transparent" : `#${background}`;
61
+ const getFit = ({ fit }) => { var _a; return (fit ? (_a = FIT_OPTIONS[fit]) !== null && _a !== void 0 ? _a : "cover" : "cover"); };
62
+ const getPosition = ({ position }) => { var _a; return (position ? (_a = POSITION_OPTIONS[position]) !== null && _a !== void 0 ? _a : "centre" : undefined); };
63
+ const getWithoutEnlargement = ({ extension }) => extension !== "svg";
64
+ const getTrim = ({ trim }) => (trim === undefined || trim === "nt" ? undefined : Number(trim));
127
65
  function log(message) {
128
66
  // eslint-disable-next-line no-console
129
67
  console.log(message);
@@ -135,107 +73,88 @@ function log(message) {
135
73
  * pro zpětné složení pomocí pathToRegexp::compile() se používá: /generated/:restPath+ => restPath musí být string[]
136
74
  * Tato funkce projde celý objekt a pokud v hodnotě atributu najde "/" tak tento atribut převede na pole
137
75
  */
138
- var repairParams = function (params) {
139
- var obj = {};
140
- Object.keys(params).forEach(function (key) {
141
- var value = params[key];
76
+ const repairParams = (params) => {
77
+ const obj = {};
78
+ Object.keys(params).forEach((key) => {
79
+ const value = params[key];
142
80
  obj[key] = value.includes("/") ? value.split("/") : value;
143
81
  });
144
82
  return obj;
145
83
  };
146
- var resizerMiddleware = function (config) {
147
- var middleware = (0, express_1.Router)();
148
- config.forEach(function (c, i) {
149
- log("Config #".concat(i + 1, ":"));
150
- log(" Route: ".concat(c.route));
151
- log(" Source: ".concat(c.source));
152
- middleware.get(c.route, function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
153
- var url, domain, repairedParams, sourceFilename, generatedFilename, generatedDirectory, params, sourceFile, _a, pipeline, trim, quality;
154
- var _b, _c;
155
- return __generator(this, function (_d) {
156
- switch (_d.label) {
157
- case 0:
158
- url = c.source.startsWith("http") ? new URL(c.source) : null;
159
- domain = url === null || url === void 0 ? void 0 : url.origin;
160
- repairedParams = repairParams(req.params);
161
- sourceFilename = (0, path_to_regexp_1.compile)((_b = url === null || url === void 0 ? void 0 : url.pathname) !== null && _b !== void 0 ? _b : c.source, {})(repairedParams);
162
- generatedFilename = ((_c = process.env.UXF_RESIZER_GENERATE_PATH) !== null && _c !== void 0 ? _c : process.cwd()) + req.path;
163
- if ((0, fs_1.existsSync)(generatedFilename)) {
164
- return [2 /*return*/, res.sendFile(generatedFilename)];
165
- }
166
- log("----------------------------------------");
167
- log(repairedParams);
168
- log("Source filename: ".concat(sourceFilename));
169
- log("Generated filename: ".concat(generatedFilename));
170
- generatedDirectory = (0, path_1.dirname)(generatedFilename);
171
- if (!(0, fs_1.existsSync)(generatedDirectory)) {
172
- (0, fs_1.mkdirSync)(generatedDirectory, { recursive: true });
173
- }
174
- params = req.params;
175
- if (!domain) return [3 /*break*/, 3];
176
- return [4 /*yield*/, (0, node_fetch_1.default)(domain + sourceFilename)];
177
- case 1: return [4 /*yield*/, (_d.sent()).buffer()];
178
- case 2:
179
- _a = _d.sent();
180
- return [3 /*break*/, 4];
181
- case 3:
182
- _a = sourceFilename;
183
- _d.label = 4;
184
- case 4:
185
- sourceFile = _a;
186
- if (params.toFormat === "svg") {
187
- fs.writeFileSync(generatedFilename, sourceFile);
188
- return [2 /*return*/, res.sendFile(generatedFilename)];
189
- }
190
- return [4 /*yield*/, (0, sharp_1.default)(sourceFile)
191
- .rotate()
192
- .resize({
193
- width: getWidth(params),
194
- height: getHeight(params),
195
- background: getBackground(params),
196
- fastShrinkOnLoad: false,
197
- fit: getFit(params),
198
- position: getPosition(params),
199
- withoutEnlargement: getWithoutEnlargement(params),
200
- })];
201
- case 5:
202
- pipeline = _d.sent();
203
- trim = getTrim(params);
204
- if (trim !== undefined) {
205
- pipeline.trim(trim);
206
- }
207
- quality = getQuality(params);
208
- switch (params.toFormat) {
209
- case "webp":
210
- pipeline.webp({ quality: quality });
211
- res.setHeader("Content-Type", "image/webp");
212
- break;
213
- case "png":
214
- pipeline.png({ progressive: true, quality: quality });
215
- res.setHeader("Content-Type", "image/png");
216
- break;
217
- case "avif":
218
- pipeline.avif({ quality: quality });
219
- res.setHeader("Content-Type", "image/avif");
220
- break;
221
- default:
222
- pipeline.jpeg({ progressive: true, quality: quality });
223
- res.setHeader("Content-Type", "image/jpeg");
224
- break;
225
- }
226
- pipeline
227
- .withMetadata({ density: 72 })
228
- .toColorspace("srgb")
229
- .toFile(generatedFilename)
230
- .then(function () { return res.sendFile(generatedFilename); })
231
- .catch(function (e) {
232
- log(e);
233
- return res.sendStatus(404);
234
- });
235
- return [2 /*return*/];
236
- }
84
+ const resizerMiddleware = (config) => {
85
+ const middleware = (0, express_1.Router)();
86
+ config.forEach((c, i) => {
87
+ log(`Config #${i + 1}:`);
88
+ log(` Route: ${c.route}`);
89
+ log(` Source: ${c.source}`);
90
+ middleware.get(c.route, async (req, res) => {
91
+ var _a, _b;
92
+ const url = c.source.startsWith("http") ? new URL(c.source) : null;
93
+ const domain = url === null || url === void 0 ? void 0 : url.origin;
94
+ const repairedParams = repairParams(req.params);
95
+ const sourceFilename = (0, path_to_regexp_1.compile)((_a = url === null || url === void 0 ? void 0 : url.pathname) !== null && _a !== void 0 ? _a : c.source, {})(repairedParams);
96
+ const generatedFilename = ((_b = process.env.UXF_RESIZER_GENERATE_PATH) !== null && _b !== void 0 ? _b : process.cwd()) + req.path;
97
+ if ((0, fs_1.existsSync)(generatedFilename)) {
98
+ return res.sendFile(generatedFilename);
99
+ }
100
+ log("----------------------------------------");
101
+ log(repairedParams);
102
+ log(`Source filename: ${sourceFilename}`);
103
+ log(`Generated filename: ${generatedFilename}`);
104
+ const generatedDirectory = (0, path_1.dirname)(generatedFilename);
105
+ if (!(0, fs_1.existsSync)(generatedDirectory)) {
106
+ (0, fs_1.mkdirSync)(generatedDirectory, { recursive: true });
107
+ }
108
+ const params = req.params;
109
+ const sourceFile = domain ? await (await (0, node_fetch_1.default)(domain + sourceFilename)).buffer() : sourceFilename;
110
+ if (params.toFormat === "svg") {
111
+ fs.writeFileSync(generatedFilename, sourceFile);
112
+ return res.sendFile(generatedFilename);
113
+ }
114
+ const pipeline = await (0, sharp_1.default)(sourceFile)
115
+ .rotate()
116
+ .resize({
117
+ width: getWidth(params),
118
+ height: getHeight(params),
119
+ background: getBackground(params),
120
+ fastShrinkOnLoad: false,
121
+ fit: getFit(params),
122
+ position: getPosition(params),
123
+ withoutEnlargement: getWithoutEnlargement(params),
124
+ });
125
+ const trim = getTrim(params);
126
+ if (trim !== undefined) {
127
+ pipeline.trim(trim);
128
+ }
129
+ const quality = getQuality(params);
130
+ switch (params.toFormat) {
131
+ case "webp":
132
+ pipeline.webp({ quality });
133
+ res.setHeader("Content-Type", "image/webp");
134
+ break;
135
+ case "png":
136
+ pipeline.png({ progressive: true, quality });
137
+ res.setHeader("Content-Type", "image/png");
138
+ break;
139
+ case "avif":
140
+ pipeline.avif({ quality });
141
+ res.setHeader("Content-Type", "image/avif");
142
+ break;
143
+ default:
144
+ pipeline.jpeg({ progressive: true, quality });
145
+ res.setHeader("Content-Type", "image/jpeg");
146
+ break;
147
+ }
148
+ pipeline
149
+ .withMetadata({ density: 72 })
150
+ .toColorspace("srgb")
151
+ .toFile(generatedFilename)
152
+ .then(() => res.sendFile(generatedFilename))
153
+ .catch((e) => {
154
+ log(e);
155
+ return res.sendStatus(404);
237
156
  });
238
- }); });
157
+ });
239
158
  });
240
159
  return middleware;
241
160
  };
package/index.ts ADDED
@@ -0,0 +1,156 @@
1
+ import { Router } from "express";
2
+ import * as fs from "fs";
3
+ import { existsSync, mkdirSync } from "fs";
4
+ import fetch from "node-fetch";
5
+ import { dirname } from "path";
6
+ import { compile } from "path-to-regexp";
7
+ import sharp, { FitEnum, ResizeOptions } from "sharp";
8
+
9
+ const FIT_OPTIONS: Record<string, ResizeOptions["fit"]> = {
10
+ cv: "cover",
11
+ f: "fill",
12
+ cn: "contain",
13
+ in: "inside",
14
+ out: "outside",
15
+ } as const;
16
+
17
+ const POSITION_OPTIONS: Record<string, ResizeOptions["position"]> = {
18
+ a: "attention",
19
+ b: "bottom",
20
+ c: "centre",
21
+ e: "entropy",
22
+ l: "left",
23
+ lb: "left bottom",
24
+ lt: "left top",
25
+ r: "right",
26
+ rb: "right bottom",
27
+ rt: "right top",
28
+ t: "top",
29
+ };
30
+
31
+ export type Config = Array<{
32
+ route: string;
33
+ source: string;
34
+ }>;
35
+
36
+ const getQuality = ({ quality }: any) => (quality && quality !== "x" ? Number(quality) : undefined);
37
+ const getWidth = ({ width }: any) => (width && width !== "x" ? Number(width) : undefined);
38
+ const getHeight = ({ height }: any) => (height && height !== "x" ? Number(height) : undefined);
39
+ const getBackground = ({ background }: any) =>
40
+ background === undefined ? undefined : background === "t" ? "transparent" : `#${background}`;
41
+ const getFit = ({ fit }: any): keyof FitEnum => (fit ? FIT_OPTIONS[fit] ?? "cover" : "cover");
42
+ const getPosition = ({ position }: any) => (position ? POSITION_OPTIONS[position] ?? "centre" : undefined);
43
+ const getWithoutEnlargement = ({ extension }: any) => extension !== "svg";
44
+ const getTrim = ({ trim }: any) => (trim === undefined || trim === "nt" ? undefined : Number(trim));
45
+
46
+ function log(message: any) {
47
+ // eslint-disable-next-line no-console
48
+ console.log(message);
49
+ }
50
+
51
+ /**
52
+ * TODO @vejvis HACK
53
+ *
54
+ * pro odchycení celé cesty i s lomítkama je potřeba udělat hack: /generated/:restPath(*) => restPath bude string
55
+ * pro zpětné složení pomocí pathToRegexp::compile() se používá: /generated/:restPath+ => restPath musí být string[]
56
+ * Tato funkce projde celý objekt a pokud v hodnotě atributu najde "/" tak tento atribut převede na pole
57
+ */
58
+ const repairParams = (params: any) => {
59
+ const obj: any = {};
60
+ Object.keys(params).forEach((key) => {
61
+ const value: string = params[key];
62
+ obj[key] = value.includes("/") ? value.split("/") : value;
63
+ });
64
+ return obj;
65
+ };
66
+
67
+ export const resizerMiddleware = (config: Config) => {
68
+ const middleware = Router();
69
+
70
+ config.forEach((c, i) => {
71
+ log(`Config #${i + 1}:`);
72
+ log(` Route: ${c.route}`);
73
+ log(` Source: ${c.source}`);
74
+
75
+ middleware.get(c.route, async (req, res) => {
76
+ const url = c.source.startsWith("http") ? new URL(c.source) : null;
77
+ const domain = url?.origin;
78
+ const repairedParams = repairParams(req.params);
79
+ const sourceFilename = compile(url?.pathname ?? c.source, {})(repairedParams);
80
+ const generatedFilename = (process.env.UXF_RESIZER_GENERATE_PATH ?? process.cwd()) + req.path;
81
+
82
+ if (existsSync(generatedFilename)) {
83
+ return res.sendFile(generatedFilename);
84
+ }
85
+
86
+ log("----------------------------------------");
87
+ log(repairedParams);
88
+ log(`Source filename: ${sourceFilename}`);
89
+ log(`Generated filename: ${generatedFilename}`);
90
+
91
+ const generatedDirectory = dirname(generatedFilename);
92
+ if (!existsSync(generatedDirectory)) {
93
+ mkdirSync(generatedDirectory, { recursive: true });
94
+ }
95
+
96
+ const params = req.params;
97
+
98
+ const sourceFile = domain ? await (await fetch(domain + sourceFilename)).buffer() : sourceFilename;
99
+
100
+ if (params.toFormat === "svg") {
101
+ fs.writeFileSync(generatedFilename, sourceFile);
102
+ return res.sendFile(generatedFilename);
103
+ }
104
+
105
+ const pipeline = await sharp(sourceFile)
106
+ .rotate()
107
+ .resize({
108
+ width: getWidth(params),
109
+ height: getHeight(params),
110
+ background: getBackground(params),
111
+ fastShrinkOnLoad: false,
112
+ fit: getFit(params),
113
+ position: getPosition(params),
114
+ withoutEnlargement: getWithoutEnlargement(params),
115
+ });
116
+
117
+ const trim = getTrim(params);
118
+ if (trim !== undefined) {
119
+ pipeline.trim(trim);
120
+ }
121
+
122
+ const quality = getQuality(params);
123
+
124
+ switch (params.toFormat) {
125
+ case "webp":
126
+ pipeline.webp({ quality });
127
+ res.setHeader("Content-Type", "image/webp");
128
+ break;
129
+ case "png":
130
+ pipeline.png({ progressive: true, quality });
131
+ res.setHeader("Content-Type", "image/png");
132
+ break;
133
+ case "avif":
134
+ pipeline.avif({ quality });
135
+ res.setHeader("Content-Type", "image/avif");
136
+ break;
137
+ default:
138
+ pipeline.jpeg({ progressive: true, quality });
139
+ res.setHeader("Content-Type", "image/jpeg");
140
+ break;
141
+ }
142
+
143
+ pipeline
144
+ .withMetadata({ density: 72 })
145
+ .toColorspace("srgb")
146
+ .toFile(generatedFilename)
147
+ .then(() => res.sendFile(generatedFilename))
148
+ .catch((e) => {
149
+ log(e);
150
+ return res.sendStatus(404);
151
+ });
152
+ });
153
+ });
154
+
155
+ return middleware;
156
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uxf/resizer",
3
- "version": "2.3.3",
3
+ "version": "10.0.0-beta.80",
4
4
  "main": "index.js",
5
5
  "description": "UXF Resizer",
6
6
  "author": "UXFans <dev@uxf.cz>",
@@ -16,42 +16,21 @@
16
16
  },
17
17
  "license": "MIT",
18
18
  "dependencies": {
19
- "express": "^4.17.1",
20
- "fs": "^0.0.1-security",
21
- "node-fetch": "^2.6.7",
22
- "path-to-regexp": "^6.2.0",
23
- "process": "^0.11.10",
24
- "sharp": "^0.30.2",
25
- "yargs": "^17.2.1"
19
+ "express": "4.18.2",
20
+ "node-fetch": "3.3.2",
21
+ "path-to-regexp": "6.2.1",
22
+ "process": "0.11.10",
23
+ "sharp": "0.32.6",
24
+ "yargs": "17.7.2"
26
25
  },
27
26
  "devDependencies": {
28
- "@types/express": "^4.17.13",
29
- "@types/jest": "^27.0.1",
30
- "@types/node-fetch": "^2.5.12",
31
- "@types/path-to-regexp": "^1.7.0",
32
- "@types/sharp": "^0.29.5",
33
- "@types/supertest": "^2.0.11",
34
- "@typescript-eslint/eslint-plugin": "^5.11.0",
35
- "@uxf/eslint-config": "^1.2.3",
36
- "concurrently": "^7.0.0",
37
- "crypto-js": "^4.1.1",
38
- "eslint": "^8.8.0",
39
- "eslint-config-prettier": "^8.3.0",
40
- "eslint-plugin-prettier": "^4.0.0",
41
- "jest": "^27.0.6",
42
- "jest-junit": "^13.0.0",
43
- "prettier": "^2.3.2",
44
- "supertest": "^6.1.5",
45
- "ts-jest": "^27.0.5",
46
- "ts-node": "^10.2.0",
47
- "typescript": "^4.3.5"
27
+ "@types/express": "4.17.18",
28
+ "concurrently": "8.2.1",
29
+ "crypto-js": "4.1.1"
48
30
  },
49
31
  "scripts": {
50
- "lint": "./node_modules/.bin/eslint -c .eslintrc.js \"./**/*.ts*\"",
51
- "build": "./node_modules/.bin/tsc -p tsconfig.build.json",
52
- "start": "NODE_ENV=production node ./bin/uxf-resizer.js",
53
- "typecheck": "./node_modules/.bin/tsc --noEmit --skipLibCheck",
54
- "dev": "./node_modules/.bin/ts-node ./index.ts",
55
- "test": "concurrently \"yarn run lint\" \"yarn run typecheck\" \"yarn prettier ./**/*.ts --check\""
32
+ "build": "tsc -P tsconfig.json",
33
+ "dev": "ts-node ./bin/uxf-resizer.ts",
34
+ "typecheck": "tsc --noEmit --skipLibCheck"
56
35
  }
57
- }
36
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+ "outDir": "./"
6
+ }
7
+ }
package/.gitlab-ci.yml DELETED
@@ -1,32 +0,0 @@
1
- stages:
2
- - test
3
- - release
4
-
5
- test:
6
- image: node:18-buster-slim
7
- stage: test
8
- tags:
9
- - pluto-docker
10
- script:
11
- - npm install
12
- - npm run test
13
- except:
14
- - master
15
- - beta
16
-
17
- release:
18
- image: node:18-buster-slim
19
- stage: release
20
- tags:
21
- - pluto-docker
22
- before_script:
23
- - apt-get update && apt-get install -y --no-install-recommends git-core ca-certificates
24
- - npm install -g semantic-release @semantic-release/gitlab
25
- script:
26
- - npm install
27
- - npm run test
28
- - npm run build
29
- - semantic-release --debug
30
- only:
31
- - master
32
- - beta
package/.releaserc.yml DELETED
@@ -1,6 +0,0 @@
1
- repositoryUrl: "git+https://gitlab.uxf.cz/uxf/npm-packages/resizer.git"
2
- plugins:
3
- - "@semantic-release/commit-analyzer"
4
- - "@semantic-release/release-notes-generator"
5
- - "@semantic-release/gitlab"
6
- - "@semantic-release/npm"