build: refresh release artifacts after image variants and upload compression
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+240
-12
@@ -93325,6 +93325,165 @@ var require_upload_path = __commonJS({
|
||||
}
|
||||
});
|
||||
|
||||
// dist/modules/admin-uploads/image-variants.service.js
|
||||
var require_image_variants_service = __commonJS({
|
||||
"dist/modules/admin-uploads/image-variants.service.js"(exports2) {
|
||||
"use strict";
|
||||
var __decorate4 = exports2 && exports2.__decorate || function(decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var ImageVariantService_1;
|
||||
Object.defineProperty(exports2, "__esModule", { value: true });
|
||||
exports2.ImageVariantService = exports2.ORIGINAL_CEILING_BYTES = exports2.ORIGINAL_TARGET_BYTES = exports2.IMAGE_WIDTHS = void 0;
|
||||
var common_12 = require_common();
|
||||
var promises_1 = require("node:fs/promises");
|
||||
var node_path_12 = require("node:path");
|
||||
var upload_path_1 = require_upload_path();
|
||||
exports2.IMAGE_WIDTHS = [200, 400, 600, 800, 1200, 1600];
|
||||
var WARM_WIDTHS = [600, 1600];
|
||||
var RESIZABLE = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".webp", ".avif"]);
|
||||
var RESIZABLE_MIME = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp", "image/avif"]);
|
||||
var WEBP_QUALITY = 78;
|
||||
exports2.ORIGINAL_TARGET_BYTES = 500 * 1024;
|
||||
exports2.ORIGINAL_CEILING_BYTES = 1024 * 1024;
|
||||
var ORIGINAL_ATTEMPTS = [
|
||||
{ side: 2e3, quality: 82 },
|
||||
{ side: 2e3, quality: 72 },
|
||||
{ side: 1600, quality: 72 },
|
||||
{ side: 1600, quality: 62 },
|
||||
{ side: 1280, quality: 60 }
|
||||
];
|
||||
var ImageVariantService = ImageVariantService_1 = class ImageVariantService {
|
||||
logger = new common_12.Logger(ImageVariantService_1.name);
|
||||
cacheDir = (0, node_path_12.join)((0, upload_path_1.getUploadDir)(), ".variants");
|
||||
inFlight = /* @__PURE__ */ new Map();
|
||||
sharp;
|
||||
static snapWidth(raw) {
|
||||
const n = Number(raw);
|
||||
if (!Number.isFinite(n) || n <= 0)
|
||||
return 800;
|
||||
return exports2.IMAGE_WIDTHS.find((w) => w >= n) ?? exports2.IMAGE_WIDTHS[exports2.IMAGE_WIDTHS.length - 1];
|
||||
}
|
||||
static isResizable(filename) {
|
||||
return RESIZABLE.has((0, node_path_12.extname)(filename).toLowerCase());
|
||||
}
|
||||
async resolve(filename, width) {
|
||||
const original = (0, upload_path_1.resolveStoredUpload)(filename);
|
||||
if (!original)
|
||||
return null;
|
||||
const fallback = {
|
||||
path: original,
|
||||
type: (0, upload_path_1.mimeForUpload)(original) ?? "application/octet-stream",
|
||||
optimized: false
|
||||
};
|
||||
if (!ImageVariantService_1.isResizable(original))
|
||||
return fallback;
|
||||
const variant = await this.variant(original, width);
|
||||
if (!variant)
|
||||
return fallback;
|
||||
const [v, o] = await Promise.all([(0, promises_1.stat)(variant), (0, promises_1.stat)(original)]);
|
||||
if (v.size >= o.size)
|
||||
return fallback;
|
||||
return { path: variant, type: "image/webp", optimized: true };
|
||||
}
|
||||
/**
|
||||
* Shrinks an uploaded raster image before it is written to disk, so an 8 MB
|
||||
* phone photo is stored as a WebP of roughly ORIGINAL_TARGET_BYTES. Returns
|
||||
* null — store the upload as-is — when the file is already small, is not a
|
||||
* resizable format, sharp is missing, or re-encoding would not make it smaller.
|
||||
*/
|
||||
async compressUpload(buffer, mimeType, filename) {
|
||||
if (buffer.length <= exports2.ORIGINAL_TARGET_BYTES)
|
||||
return null;
|
||||
if (!RESIZABLE_MIME.has(mimeType))
|
||||
return null;
|
||||
const sharp = this.loadSharp();
|
||||
if (!sharp)
|
||||
return null;
|
||||
let smallest = null;
|
||||
try {
|
||||
for (const { side, quality } of ORIGINAL_ATTEMPTS) {
|
||||
const out = await sharp(buffer, { failOn: "none" }).rotate().resize({ width: side, height: side, fit: "inside", withoutEnlargement: true }).webp({ quality }).toBuffer();
|
||||
if (!smallest || out.length < smallest.length)
|
||||
smallest = out;
|
||||
if (out.length <= exports2.ORIGINAL_TARGET_BYTES)
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn(`compress failed for ${filename}: ${err.message}`);
|
||||
return null;
|
||||
}
|
||||
if (!smallest || smallest.length >= buffer.length)
|
||||
return null;
|
||||
if (smallest.length > exports2.ORIGINAL_CEILING_BYTES) {
|
||||
this.logger.warn(`${filename} is still ${smallest.length} bytes after compression`);
|
||||
}
|
||||
return { buffer: smallest, ext: ".webp", mimeType: "image/webp" };
|
||||
}
|
||||
/** Fire-and-forget pre-render for a freshly stored upload. */
|
||||
warm(filename) {
|
||||
if (!ImageVariantService_1.isResizable(filename))
|
||||
return;
|
||||
for (const width of WARM_WIDTHS)
|
||||
void this.resolve(filename, width);
|
||||
}
|
||||
async variant(original, width) {
|
||||
const target = (0, node_path_12.join)(this.cacheDir, String(width), `${(0, node_path_12.basename)(original)}.webp`);
|
||||
if (await isFresh(target, original))
|
||||
return target;
|
||||
const pending = this.inFlight.get(target);
|
||||
if (pending)
|
||||
return pending;
|
||||
const job = this.render(original, target, width).finally(() => this.inFlight.delete(target));
|
||||
this.inFlight.set(target, job);
|
||||
return job;
|
||||
}
|
||||
async render(original, target, width) {
|
||||
const sharp = this.loadSharp();
|
||||
if (!sharp)
|
||||
return null;
|
||||
const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
|
||||
try {
|
||||
await (0, promises_1.mkdir)((0, node_path_12.dirname)(target), { recursive: true });
|
||||
await sharp(original, { failOn: "none" }).rotate().resize({ width, withoutEnlargement: true }).webp({ quality: WEBP_QUALITY }).toFile(tmp);
|
||||
await (0, promises_1.rename)(tmp, target);
|
||||
return target;
|
||||
} catch (err) {
|
||||
this.logger.warn(`resize failed for ${(0, node_path_12.basename)(original)} @${width}: ${err.message}`);
|
||||
await (0, promises_1.unlink)(tmp).catch(() => void 0);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
loadSharp() {
|
||||
if (this.sharp !== void 0)
|
||||
return this.sharp;
|
||||
try {
|
||||
this.sharp = require("sharp");
|
||||
} catch {
|
||||
this.sharp = null;
|
||||
this.logger.warn("sharp is not installed; images are served without resizing");
|
||||
}
|
||||
return this.sharp;
|
||||
}
|
||||
};
|
||||
exports2.ImageVariantService = ImageVariantService;
|
||||
exports2.ImageVariantService = ImageVariantService = ImageVariantService_1 = __decorate4([
|
||||
(0, common_12.Injectable)()
|
||||
], ImageVariantService);
|
||||
async function isFresh(target, original) {
|
||||
try {
|
||||
const [t, o] = await Promise.all([(0, promises_1.stat)(target), (0, promises_1.stat)(original)]);
|
||||
return t.mtimeMs >= o.mtimeMs;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// dist/modules/admin-uploads/admin-uploads.service.js
|
||||
var require_admin_uploads_service = __commonJS({
|
||||
"dist/modules/admin-uploads/admin-uploads.service.js"(exports2) {
|
||||
@@ -93346,12 +93505,14 @@ var require_admin_uploads_service = __commonJS({
|
||||
var promises_1 = require("node:fs/promises");
|
||||
var node_path_12 = require("node:path");
|
||||
var prisma_service_1 = require_prisma_service();
|
||||
var image_variants_service_1 = require_image_variants_service();
|
||||
var upload_path_1 = require_upload_path();
|
||||
var AdminUploadsService = class AdminUploadsService {
|
||||
static {
|
||||
AdminUploadsService_1 = this;
|
||||
}
|
||||
prisma;
|
||||
variants;
|
||||
/** Matches legacy's flat public/storage directory. */
|
||||
dir = (0, upload_path_1.getUploadDir)();
|
||||
/** Mirrors legacy's `type` field: 1 = image, 2 = any other document. */
|
||||
@@ -93359,28 +93520,32 @@ var require_admin_uploads_service = __commonJS({
|
||||
static ALLOWED = /^(image\/(jpeg|png|gif|webp|svg\+xml)|application\/pdf)$/;
|
||||
/** 8 MB. Legacy relied on PHP's upload_max_filesize; this makes it explicit. */
|
||||
static MAX_BYTES = 8 * 1024 * 1024;
|
||||
constructor(prisma) {
|
||||
constructor(prisma, variants) {
|
||||
this.prisma = prisma;
|
||||
this.variants = variants;
|
||||
}
|
||||
async store(file2, purpose, actorAdminUserId) {
|
||||
if (!file2)
|
||||
throw new common_12.BadRequestException("\u0641\u0627\u06CC\u0644\u06CC \u0627\u0631\u0633\u0627\u0644 \u0646\u0634\u062F\u0647 \u0627\u0633\u062A");
|
||||
throw new common_12.BadRequestException("\u0641\u0627\u06CC\u0644\u06CC \u0627\u0646\u062A\u062E\u0627\u0628 \u0646\u0634\u062F\u0647 \u0627\u0633\u062A");
|
||||
if (file2.size > AdminUploadsService_1.MAX_BYTES) {
|
||||
throw new common_12.BadRequestException("\u062D\u062C\u0645 \u0641\u0627\u06CC\u0644 \u0628\u06CC\u0634 \u0627\u0632 \u062D\u062F \u0645\u062C\u0627\u0632 \u0627\u0633\u062A (\u06F8 \u0645\u06AF\u0627\u0628\u0627\u06CC\u062A)");
|
||||
throw new common_12.BadRequestException("\u062D\u062C\u0645 \u0641\u0627\u06CC\u0644 \u0628\u06CC\u0634\u062A\u0631 \u0627\u0632 \u06F8 \u0645\u06AF\u0627\u0628\u0627\u06CC\u062A \u0627\u0633\u062A\u061B \u0641\u0627\u06CC\u0644 \u06A9\u0648\u0686\u06A9\u200C\u062A\u0631\u06CC \u0627\u0646\u062A\u062E\u0627\u0628 \u06A9\u0646\u06CC\u062F");
|
||||
}
|
||||
if (!AdminUploadsService_1.ALLOWED.test(file2.mimetype)) {
|
||||
throw new common_12.BadRequestException("\u0646\u0648\u0639 \u0641\u0627\u06CC\u0644 \u0645\u062C\u0627\u0632 \u0646\u06CC\u0633\u062A");
|
||||
throw new common_12.BadRequestException("\u0627\u06CC\u0646 \u0646\u0648\u0639 \u0641\u0627\u06CC\u0644 \u067E\u0634\u062A\u06CC\u0628\u0627\u0646\u06CC \u0646\u0645\u06CC\u200C\u0634\u0648\u062F\u061B \u0641\u0642\u0637 JPG\u060C PNG\u060C WebP\u060C GIF\u060C SVG \u06CC\u0627 PDF");
|
||||
}
|
||||
const ext = (0, node_path_12.extname)(file2.originalname).toLowerCase();
|
||||
const compressed = await this.variants.compressUpload(file2.buffer, file2.mimetype, file2.originalname);
|
||||
const buffer = compressed?.buffer ?? file2.buffer;
|
||||
const ext = compressed?.ext ?? (0, node_path_12.extname)(file2.originalname).toLowerCase();
|
||||
const stored = (0, node_crypto_1.createHash)("md5").update(`${Date.now()}${file2.originalname}${(0, node_crypto_1.randomUUID)()}`).digest("hex") + ext;
|
||||
await (0, promises_1.mkdir)(this.dir, { recursive: true });
|
||||
await (0, promises_1.writeFile)((0, node_path_12.join)(this.dir, stored), file2.buffer);
|
||||
await (0, promises_1.writeFile)((0, node_path_12.join)(this.dir, stored), buffer);
|
||||
this.variants.warm(stored);
|
||||
return this.prisma.uploadedFile.create({
|
||||
data: {
|
||||
url: `/uploads/${stored}`,
|
||||
originalName: file2.originalname,
|
||||
mimeType: file2.mimetype,
|
||||
sizeBytes: file2.size,
|
||||
mimeType: compressed?.mimeType ?? file2.mimetype,
|
||||
sizeBytes: buffer.length,
|
||||
purpose: purpose ?? null,
|
||||
uploadedByAdminId: actorAdminUserId
|
||||
}
|
||||
@@ -93400,7 +93565,10 @@ var require_admin_uploads_service = __commonJS({
|
||||
exports2.AdminUploadsService = AdminUploadsService;
|
||||
exports2.AdminUploadsService = AdminUploadsService = AdminUploadsService_1 = __decorate4([
|
||||
(0, common_12.Injectable)(),
|
||||
__metadata3("design:paramtypes", [prisma_service_1.PrismaService])
|
||||
__metadata3("design:paramtypes", [
|
||||
prisma_service_1.PrismaService,
|
||||
image_variants_service_1.ImageVariantService
|
||||
])
|
||||
], AdminUploadsService);
|
||||
}
|
||||
});
|
||||
@@ -93468,6 +93636,64 @@ var require_admin_uploads_controller = __commonJS({
|
||||
}
|
||||
});
|
||||
|
||||
// dist/modules/admin-uploads/public-images.controller.js
|
||||
var require_public_images_controller = __commonJS({
|
||||
"dist/modules/admin-uploads/public-images.controller.js"(exports2) {
|
||||
"use strict";
|
||||
var __decorate4 = exports2 && exports2.__decorate || function(decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata3 = exports2 && exports2.__metadata || function(k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
var __param3 = exports2 && exports2.__param || function(paramIndex, decorator) {
|
||||
return function(target, key) {
|
||||
decorator(target, key, paramIndex);
|
||||
};
|
||||
};
|
||||
Object.defineProperty(exports2, "__esModule", { value: true });
|
||||
exports2.PublicImagesController = void 0;
|
||||
var common_12 = require_common();
|
||||
var node_fs_1 = require("node:fs");
|
||||
var guards_1 = require_guards2();
|
||||
var image_variants_service_1 = require_image_variants_service();
|
||||
var PublicImagesController = class PublicImagesController {
|
||||
variants;
|
||||
constructor(variants) {
|
||||
this.variants = variants;
|
||||
}
|
||||
async serve(filename, w, res) {
|
||||
const image = await this.variants.resolve(filename, image_variants_service_1.ImageVariantService.snapWidth(w));
|
||||
if (!image)
|
||||
throw new common_12.NotFoundException("\u0641\u0627\u06CC\u0644 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
|
||||
res.setHeader("Cache-Control", image.optimized ? "public, max-age=31536000, immutable" : "public, max-age=86400");
|
||||
return new common_12.StreamableFile((0, node_fs_1.createReadStream)(image.path), {
|
||||
type: image.type,
|
||||
disposition: "inline"
|
||||
});
|
||||
}
|
||||
};
|
||||
exports2.PublicImagesController = PublicImagesController;
|
||||
__decorate4([
|
||||
(0, common_12.Get)(":filename"),
|
||||
__param3(0, (0, common_12.Param)("filename")),
|
||||
__param3(1, (0, common_12.Query)("w")),
|
||||
__param3(2, (0, common_12.Res)({ passthrough: true })),
|
||||
__metadata3("design:type", Function),
|
||||
__metadata3("design:paramtypes", [String, Object, Object]),
|
||||
__metadata3("design:returntype", Promise)
|
||||
], PublicImagesController.prototype, "serve", null);
|
||||
exports2.PublicImagesController = PublicImagesController = __decorate4([
|
||||
(0, guards_1.Public)(),
|
||||
(0, common_12.Controller)("img"),
|
||||
__metadata3("design:paramtypes", [image_variants_service_1.ImageVariantService])
|
||||
], PublicImagesController);
|
||||
}
|
||||
});
|
||||
|
||||
// dist/modules/admin-uploads/public-uploads.controller.js
|
||||
var require_public_uploads_controller = __commonJS({
|
||||
"dist/modules/admin-uploads/public-uploads.controller.js"(exports2) {
|
||||
@@ -93534,17 +93760,19 @@ var require_admin_uploads_module = __commonJS({
|
||||
var common_12 = require_common();
|
||||
var prisma_module_1 = require_prisma_module();
|
||||
var admin_uploads_controller_1 = require_admin_uploads_controller();
|
||||
var public_images_controller_1 = require_public_images_controller();
|
||||
var public_uploads_controller_1 = require_public_uploads_controller();
|
||||
var admin_uploads_service_1 = require_admin_uploads_service();
|
||||
var image_variants_service_1 = require_image_variants_service();
|
||||
var AdminUploadsModule = class AdminUploadsModule {
|
||||
};
|
||||
exports2.AdminUploadsModule = AdminUploadsModule;
|
||||
exports2.AdminUploadsModule = AdminUploadsModule = __decorate4([
|
||||
(0, common_12.Module)({
|
||||
imports: [prisma_module_1.PrismaModule],
|
||||
controllers: [admin_uploads_controller_1.AdminUploadsController, public_uploads_controller_1.PublicUploadsController],
|
||||
providers: [admin_uploads_service_1.AdminUploadsService],
|
||||
exports: [admin_uploads_service_1.AdminUploadsService]
|
||||
controllers: [admin_uploads_controller_1.AdminUploadsController, public_uploads_controller_1.PublicUploadsController, public_images_controller_1.PublicImagesController],
|
||||
providers: [admin_uploads_service_1.AdminUploadsService, image_variants_service_1.ImageVariantService],
|
||||
exports: [admin_uploads_service_1.AdminUploadsService, image_variants_service_1.ImageVariantService]
|
||||
})
|
||||
], AdminUploadsModule);
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user