feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,8 @@
/// <reference types="node" />
import { ImageOptions } from './Image.types';
export declare function createCacheKey(fileSource: string, properties: string[]): string;
export declare function createCacheKeyWithDirectoryAsync(projectRoot: string, type: string, icon: ImageOptions): Promise<string>;
export declare function ensureCacheDirectory(projectRoot: string, type: string, cacheKey: string): Promise<string>;
export declare function getImageFromCacheAsync(fileName: string, cacheKey: string): Promise<null | Buffer>;
export declare function cacheImageAsync(fileName: string, buffer: Buffer, cacheKey: string): Promise<void>;
export declare function clearUnusedCachesAsync(projectRoot: string, type: string): Promise<void>;

View File

@@ -0,0 +1,78 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.clearUnusedCachesAsync = exports.cacheImageAsync = exports.getImageFromCacheAsync = exports.ensureCacheDirectory = exports.createCacheKeyWithDirectoryAsync = exports.createCacheKey = void 0;
const crypto_1 = __importDefault(require("crypto"));
const fs_extra_1 = require("fs-extra");
const path_1 = require("path");
const CACHE_LOCATION = '.expo/web/cache/production/images';
const cacheKeys = {};
// Calculate SHA256 Checksum value of a file based on its contents
function calculateHash(filePath) {
const contents = filePath.startsWith('http') ? filePath : (0, fs_extra_1.readFileSync)(filePath);
return crypto_1.default.createHash('sha256').update(contents).digest('hex');
}
// Create a hash key for caching the images between builds
function createCacheKey(fileSource, properties) {
const hash = calculateHash(fileSource);
return [hash].concat(properties).filter(Boolean).join('-');
}
exports.createCacheKey = createCacheKey;
async function createCacheKeyWithDirectoryAsync(projectRoot, type, icon) {
const cacheKey = `${type}-${createCacheKey(icon.src, [icon.resizeMode, icon.backgroundColor])}`;
if (!(cacheKey in cacheKeys)) {
cacheKeys[cacheKey] = await ensureCacheDirectory(projectRoot, type, cacheKey);
}
return cacheKey;
}
exports.createCacheKeyWithDirectoryAsync = createCacheKeyWithDirectoryAsync;
async function ensureCacheDirectory(projectRoot, type, cacheKey) {
const cacheFolder = (0, path_1.join)(projectRoot, CACHE_LOCATION, type, cacheKey);
await (0, fs_extra_1.ensureDir)(cacheFolder);
return cacheFolder;
}
exports.ensureCacheDirectory = ensureCacheDirectory;
async function getImageFromCacheAsync(fileName, cacheKey) {
try {
return await (0, fs_extra_1.readFile)((0, path_1.resolve)(cacheKeys[cacheKey], fileName));
}
catch {
return null;
}
}
exports.getImageFromCacheAsync = getImageFromCacheAsync;
async function cacheImageAsync(fileName, buffer, cacheKey) {
try {
await (0, fs_extra_1.writeFile)((0, path_1.resolve)(cacheKeys[cacheKey], fileName), buffer);
}
catch (error) {
console.warn(`Error caching image: "${fileName}". ${error.message}`);
}
}
exports.cacheImageAsync = cacheImageAsync;
async function clearUnusedCachesAsync(projectRoot, type) {
// Clean up any old caches
const cacheFolder = (0, path_1.join)(projectRoot, CACHE_LOCATION, type);
await (0, fs_extra_1.ensureDir)(cacheFolder);
const currentCaches = (0, fs_extra_1.readdirSync)(cacheFolder);
if (!Array.isArray(currentCaches)) {
console.warn('Failed to read the icon cache');
return;
}
const deleteCachePromises = [];
for (const cache of currentCaches) {
// skip hidden folders
if (cache.startsWith('.')) {
continue;
}
// delete
if (!(cache in cacheKeys)) {
deleteCachePromises.push((0, fs_extra_1.remove)((0, path_1.join)(cacheFolder, cache)));
}
}
await Promise.all(deleteCachePromises);
}
exports.clearUnusedCachesAsync = clearUnusedCachesAsync;
//# sourceMappingURL=Cache.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Cache.js","sourceRoot":"","sources":["../src/Cache.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,2 @@
export declare function downloadOrUseCachedImage(url: string): Promise<string>;
export declare function downloadImage(url: string): Promise<string>;

View File

@@ -0,0 +1,54 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.downloadImage = exports.downloadOrUseCachedImage = void 0;
const fs_extra_1 = __importDefault(require("fs-extra"));
// @ts-ignore
const jimp_compact_1 = __importDefault(require("jimp-compact"));
const node_fetch_1 = __importDefault(require("node-fetch"));
const path_1 = __importDefault(require("path"));
const stream_1 = __importDefault(require("stream"));
const tempy_1 = __importDefault(require("tempy"));
const util_1 = __importDefault(require("util"));
// cache downloaded images into memory
const cacheDownloadedKeys = {};
function stripQueryParams(url) {
return url.split('?')[0].split('#')[0];
}
async function downloadOrUseCachedImage(url) {
if (url in cacheDownloadedKeys) {
return cacheDownloadedKeys[url];
}
if (url.startsWith('http')) {
cacheDownloadedKeys[url] = await downloadImage(url);
}
else {
cacheDownloadedKeys[url] = url;
}
return cacheDownloadedKeys[url];
}
exports.downloadOrUseCachedImage = downloadOrUseCachedImage;
async function downloadImage(url) {
const outputPath = tempy_1.default.directory();
const response = await (0, node_fetch_1.default)(url);
if (!response.ok) {
throw new Error(`It was not possible to download image from '${url}'`);
}
// Download to local file
const streamPipeline = util_1.default.promisify(stream_1.default.pipeline);
const localPath = path_1.default.join(outputPath, path_1.default.basename(stripQueryParams(url)));
await streamPipeline(response.body, fs_extra_1.default.createWriteStream(localPath));
// If an image URL doesn't have a name, get the mime type and move the file.
const img = await jimp_compact_1.default.read(localPath);
const mime = img.getMIME().split('/').pop();
if (!localPath.endsWith(mime)) {
const newPath = path_1.default.join(outputPath, `image.${mime}`);
await fs_extra_1.default.move(localPath, newPath);
return newPath;
}
return localPath;
}
exports.downloadImage = downloadImage;
//# sourceMappingURL=Download.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Download.js","sourceRoot":"","sources":["../src/Download.ts"],"names":[],"mappings":";;;;;;AAAA,wDAA0B;AAC1B,aAAa;AACb,gEAAgC;AAChC,4DAA+B;AAC/B,gDAAwB;AACxB,oDAA4B;AAC5B,kDAA8B;AAC9B,gDAAwB;AAExB,sCAAsC;AACtC,MAAM,mBAAmB,GAA2B,EAAE,CAAC;AAEvD,SAAS,gBAAgB,CAAC,GAAW;IACnC,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACzC,CAAC;AAEM,KAAK,UAAU,wBAAwB,CAAC,GAAW;IACxD,IAAI,GAAG,IAAI,mBAAmB,EAAE;QAC9B,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC;KACjC;IACD,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE;QAC1B,mBAAmB,CAAC,GAAG,CAAC,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;KACrD;SAAM;QACL,mBAAmB,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;KAChC;IACD,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC;AAVD,4DAUC;AAEM,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,MAAM,UAAU,GAAG,eAAS,CAAC,SAAS,EAAE,CAAC;IAEzC,MAAM,QAAQ,GAAG,MAAM,IAAA,oBAAK,EAAC,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE;QAChB,MAAM,IAAI,KAAK,CAAC,+CAA+C,GAAG,GAAG,CAAC,CAAC;KACxE;IAED,yBAAyB;IACzB,MAAM,cAAc,GAAG,cAAI,CAAC,SAAS,CAAC,gBAAM,CAAC,QAAQ,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9E,MAAM,cAAc,CAAC,QAAQ,CAAC,IAAI,EAAE,kBAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC,CAAC;IAErE,4EAA4E;IAC5E,MAAM,GAAG,GAAG,MAAM,sBAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC;IAC7C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;QAC7B,MAAM,OAAO,GAAG,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;QACvD,MAAM,kBAAE,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAClC,OAAO,OAAO,CAAC;KAChB;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAvBD,sCAuBC"}

View File

@@ -0,0 +1,2 @@
/// <reference types="node" />
export declare function generateAsync(buffers: Buffer[]): Promise<Buffer>;

View File

@@ -0,0 +1,98 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateAsync = void 0;
// Inspired by https://github.com/kevva/to-ico but reuses existing packages to keep bundle size small.
const parse_png_1 = __importDefault(require("parse-png"));
const constants = {
directorySize: 16,
bitmapSize: 40,
headerSize: 6,
colorMode: 0,
};
function createHeader(header) {
const buffer = Buffer.alloc(constants.headerSize);
buffer.writeUInt16LE(0, 0);
buffer.writeUInt16LE(1, 2);
buffer.writeUInt16LE(header, 4);
return buffer;
}
function createDirectory(data, offset) {
const buffer = Buffer.alloc(constants.directorySize);
const size = data.data.length + constants.bitmapSize;
const width = data.width === 256 ? 0 : data.width;
const height = data.height === 256 ? 0 : data.height;
const bpp = data.bpp * 8;
buffer.writeUInt8(width, 0);
buffer.writeUInt8(height, 1);
buffer.writeUInt8(0, 2);
buffer.writeUInt8(0, 3);
buffer.writeUInt16LE(1, 4);
buffer.writeUInt16LE(bpp, 6);
buffer.writeUInt32LE(size, 8);
buffer.writeUInt32LE(offset, 12);
return buffer;
}
function createBitmap(data, compression) {
const buffer = Buffer.alloc(constants.bitmapSize);
buffer.writeUInt32LE(constants.bitmapSize, 0);
buffer.writeInt32LE(data.width, 4);
buffer.writeInt32LE(data.height * 2, 8);
buffer.writeUInt16LE(1, 12);
buffer.writeUInt16LE(data.bpp * 8, 14);
buffer.writeUInt32LE(compression, 16);
buffer.writeUInt32LE(data.data.length, 20);
buffer.writeInt32LE(0, 24);
buffer.writeInt32LE(0, 28);
buffer.writeUInt32LE(0, 32);
buffer.writeUInt32LE(0, 36);
return buffer;
}
function createDIB(data, width, height, bpp) {
const cols = width * bpp;
const rows = height * cols;
const end = rows - cols;
const buffer = Buffer.alloc(data.length);
for (let row = 0; row < rows; row += cols) {
for (let col = 0; col < cols; col += bpp) {
let pos = row + col;
const r = data.readUInt8(pos);
const g = data.readUInt8(pos + 1);
const b = data.readUInt8(pos + 2);
const a = data.readUInt8(pos + 3);
pos = end - row + col;
buffer.writeUInt8(b, pos);
buffer.writeUInt8(g, pos + 1);
buffer.writeUInt8(r, pos + 2);
buffer.writeUInt8(a, pos + 3);
}
}
return buffer;
}
function generateFromPNGs(pngs) {
const header = createHeader(pngs.length);
const arr = [header];
let len = header.length;
let offset = constants.headerSize + constants.directorySize * pngs.length;
for (const png of pngs) {
const dir = createDirectory(png, offset);
arr.push(dir);
len += dir.length;
offset += png.data.length + constants.bitmapSize;
}
for (const png of pngs) {
const header = createBitmap(png, constants.colorMode);
const dib = createDIB(png.data, png.width, png.height, png.bpp);
arr.push(header, dib);
len += header.length + dib.length;
}
return Buffer.concat(arr, len);
}
async function generateAsync(buffers) {
const pngs = await Promise.all(buffers.map((x) => (0, parse_png_1.default)(x)));
return generateFromPNGs(pngs);
}
exports.generateAsync = generateAsync;
//# sourceMappingURL=Ico.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Ico.js","sourceRoot":"","sources":["../src/Ico.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,33 @@
/// <reference types="node" />
import { ImageOptions } from './Image.types';
export declare function getMimeType(srcPath: string): string | null;
export declare function generateImageAsync(options: {
projectRoot: string;
cacheType?: string;
}, imageOptions: ImageOptions): Promise<{
source: Buffer;
name: string;
}>;
export declare function generateFaviconAsync(pngImageBuffer: Buffer, sizes?: number[]): Promise<Buffer>;
/**
* Layers the provided foreground image over the provided background image.
*
* @param foregroundImageBuffer
* @param foregroundImageBuffer
* @param x pixel offset from the left edge, defaults to 0.
* @param y pixel offset from the top edge, defaults to 0.
*/
export declare function compositeImagesAsync({ foreground, background, x, y, }: {
foreground: Buffer;
background: Buffer;
x?: number;
y?: number;
}): Promise<Buffer>;
type PNGInfo = {
data: Buffer;
width: number;
height: number;
bpp: number;
};
export declare function getPngInfo(src: string): Promise<PNGInfo>;
export {};

View File

@@ -0,0 +1,216 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPngInfo = exports.compositeImagesAsync = exports.generateFaviconAsync = exports.generateImageAsync = exports.getMimeType = void 0;
const chalk_1 = __importDefault(require("chalk"));
const fs_1 = __importDefault(require("fs"));
const parse_png_1 = __importDefault(require("parse-png"));
const path_1 = __importDefault(require("path"));
const Cache = __importStar(require("./Cache"));
const Download = __importStar(require("./Download"));
const Ico = __importStar(require("./Ico"));
const env_1 = require("./env");
const Jimp = __importStar(require("./jimp"));
const Sharp = __importStar(require("./sharp"));
let hasWarned = false;
async function resizeImagesAsync(buffer, sizes) {
const sharp = await getSharpAsync();
if (!sharp) {
return Jimp.resizeBufferAsync(buffer, sizes);
}
return Sharp.resizeBufferAsync(buffer, sizes);
}
async function resizeAsync(imageOptions) {
const sharp = await getSharpAsync();
const { width, height, backgroundColor, resizeMode } = imageOptions;
if (!sharp) {
const inputOptions = { input: imageOptions.src, quality: 100 };
const jimp = await Jimp.resize(inputOptions, {
width,
height,
fit: resizeMode,
background: backgroundColor,
});
if (imageOptions.removeTransparency) {
jimp.colorType(2);
}
if (imageOptions.borderRadius) {
// TODO: support setting border radius with Jimp. Currently only support making the image a circle
await Jimp.circleAsync(jimp);
}
// Convert to png buffer
return jimp.getBufferAsync('image/png');
}
try {
let sharpBuffer = sharp(imageOptions.src)
.ensureAlpha()
.resize(width, height, { fit: resizeMode, background: 'transparent' });
// Skip an extra step if the background is explicitly transparent.
if (backgroundColor && backgroundColor !== 'transparent') {
// Add the background color to the image
sharpBuffer = sharpBuffer.composite([
{
// create a background color
input: {
create: {
width,
height,
// allow alpha colors
channels: imageOptions.removeTransparency ? 3 : 4,
background: backgroundColor,
},
},
// dest-over makes the first image (input) appear on top of the created image (background color)
blend: 'dest-over',
},
]);
}
else if (imageOptions.removeTransparency) {
sharpBuffer.flatten();
}
if (imageOptions.borderRadius) {
const mask = Buffer.from(`<svg><rect x="0" y="0" width="${width}" height="${height}"
rx="${imageOptions.borderRadius}" ry="${imageOptions.borderRadius}"
fill="${backgroundColor && backgroundColor !== 'transparent' ? backgroundColor : 'none'}" /></svg>`);
sharpBuffer.composite([{ input: mask, blend: 'dest-in' }]);
}
return await sharpBuffer.png().toBuffer();
}
catch (error) {
throw new Error(`It was not possible to generate splash screen '${imageOptions.src}'. ${error.message}`);
}
}
async function getSharpAsync() {
let sharp;
if (await Sharp.isAvailableAsync())
sharp = await Sharp.findSharpInstanceAsync();
return sharp;
}
function getDimensionsId(imageOptions) {
return imageOptions.width === imageOptions.height
? `${imageOptions.width}`
: `${imageOptions.width}x${imageOptions.height}`;
}
async function maybeWarnAboutInstallingSharpAsync() {
// Putting the warning here will prevent the warning from showing if all images were reused from the cache
if (env_1.env.EXPO_IMAGE_UTILS_DEBUG && !hasWarned && !(await Sharp.isAvailableAsync())) {
hasWarned = true;
console.warn(chalk_1.default.yellow(`Using node to generate images. This is much slower than using native packages.\n\u203A Optionally you can stop the process and try again after successfully running \`npm install -g sharp-cli\`.\n`));
}
}
const types = {
png: 'image/png',
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
jpe: 'image/jpeg',
webp: 'image/webp',
gif: 'image/gif',
};
const inverseMimeTypes = {
'image/png': 'png',
'image/jpeg': 'jpg',
'image/webp': 'webp',
'image/gif': 'gif',
};
function getMimeType(srcPath) {
if (typeof srcPath !== 'string')
return null;
try {
// If the path is a URL, use the pathname
const url = new URL(srcPath);
srcPath = url.pathname;
}
catch { }
const ext = path_1.default.extname(srcPath).replace(/^\./, '');
return types[ext] ?? null;
}
exports.getMimeType = getMimeType;
async function ensureImageOptionsAsync(imageOptions) {
const icon = {
...imageOptions,
src: await Download.downloadOrUseCachedImage(imageOptions.src),
};
// Default to contain
if (!icon.resizeMode) {
icon.resizeMode = 'contain';
}
const mimeType = getMimeType(icon.src);
if (!mimeType) {
throw new Error(`Invalid mimeType for image with source: ${icon.src}`);
}
if (!icon.name) {
icon.name = `icon_${getDimensionsId(imageOptions)}.${inverseMimeTypes[mimeType]}`;
}
return icon;
}
async function generateImageAsync(options, imageOptions) {
const icon = await ensureImageOptionsAsync(imageOptions);
if (!options.cacheType) {
await maybeWarnAboutInstallingSharpAsync();
return { name: icon.name, source: await resizeAsync(icon) };
}
const cacheKey = await Cache.createCacheKeyWithDirectoryAsync(options.projectRoot, options.cacheType, icon);
const name = icon.name;
let source = await Cache.getImageFromCacheAsync(name, cacheKey);
if (!source) {
await maybeWarnAboutInstallingSharpAsync();
source = await resizeAsync(icon);
await Cache.cacheImageAsync(name, source, cacheKey);
}
return { name, source };
}
exports.generateImageAsync = generateImageAsync;
async function generateFaviconAsync(pngImageBuffer, sizes = [16, 32, 48]) {
const buffers = await resizeImagesAsync(pngImageBuffer, sizes);
return await Ico.generateAsync(buffers);
}
exports.generateFaviconAsync = generateFaviconAsync;
/**
* Layers the provided foreground image over the provided background image.
*
* @param foregroundImageBuffer
* @param foregroundImageBuffer
* @param x pixel offset from the left edge, defaults to 0.
* @param y pixel offset from the top edge, defaults to 0.
*/
async function compositeImagesAsync({ foreground, background, x = 0, y = 0, }) {
const sharp = await getSharpAsync();
if (!sharp) {
const image = (await Jimp.getJimpImageAsync(background)).composite(await Jimp.getJimpImageAsync(foreground), x, y);
return await image.getBufferAsync(image.getMIME());
}
return await sharp(background)
.composite([{ input: foreground, left: x, top: y }])
.toBuffer();
}
exports.compositeImagesAsync = compositeImagesAsync;
async function getPngInfo(src) {
return await (0, parse_png_1.default)(fs_1.default.readFileSync(src));
}
exports.getPngInfo = getPngInfo;
//# sourceMappingURL=Image.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,13 @@
export type ResizeMode = 'contain' | 'cover' | 'fill' | 'inside' | 'outside';
export type ImageFormat = 'input' | 'jpeg' | 'jpg' | 'png' | 'raw' | 'tiff' | 'webp';
export type ImageOptions = {
src: string;
name?: string;
resizeMode: ResizeMode;
backgroundColor: string;
removeTransparency?: boolean;
width: number;
height: number;
padding?: number;
borderRadius?: number;
};

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=Image.types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Image.types.js","sourceRoot":"","sources":["../src/Image.types.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,8 @@
declare class Env {
/** Enable image utils related debugging messages */
get EXPO_IMAGE_UTILS_DEBUG(): boolean;
/** Disable all Sharp related functionality. */
get EXPO_IMAGE_UTILS_NO_SHARP(): boolean;
}
export declare const env: Env;
export {};

View File

@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.env = void 0;
const getenv_1 = require("getenv");
class Env {
/** Enable image utils related debugging messages */
get EXPO_IMAGE_UTILS_DEBUG() {
return (0, getenv_1.boolish)('EXPO_IMAGE_UTILS_DEBUG', false);
}
/** Disable all Sharp related functionality. */
get EXPO_IMAGE_UTILS_NO_SHARP() {
return (0, getenv_1.boolish)('EXPO_IMAGE_UTILS_NO_SHARP', false);
}
}
exports.env = new Env();
//# sourceMappingURL=env.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":";;;AAAA,mCAAiC;AAEjC,MAAM,GAAG;IACP,oDAAoD;IACpD,IAAI,sBAAsB;QACxB,OAAO,IAAA,gBAAO,EAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;IAED,+CAA+C;IAC/C,IAAI,yBAAyB;QAC3B,OAAO,IAAA,gBAAO,EAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;IACrD,CAAC;CACF;AAEY,QAAA,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC"}

View File

@@ -0,0 +1,10 @@
/// <reference types="node" />
import * as Cache from './Cache';
import { compositeImagesAsync, generateFaviconAsync, generateImageAsync, getPngInfo } from './Image';
import { ImageFormat, ImageOptions, ResizeMode } from './Image.types';
import { jimpAsync, createSquareAsync } from './jimp';
import { findSharpInstanceAsync, isAvailableAsync, sharpAsync } from './sharp';
import { SharpCommandOptions, SharpGlobalOptions } from './sharp.types';
export declare function imageAsync(options: SharpGlobalOptions, commands?: SharpCommandOptions[]): Promise<Buffer | string[]>;
export { jimpAsync, createSquareAsync, findSharpInstanceAsync, isAvailableAsync, sharpAsync, generateImageAsync, generateFaviconAsync, Cache, compositeImagesAsync, getPngInfo, };
export { SharpGlobalOptions, SharpCommandOptions, ResizeMode, ImageFormat, ImageOptions };

View File

@@ -0,0 +1,48 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPngInfo = exports.compositeImagesAsync = exports.Cache = exports.generateFaviconAsync = exports.generateImageAsync = exports.sharpAsync = exports.isAvailableAsync = exports.findSharpInstanceAsync = exports.createSquareAsync = exports.jimpAsync = exports.imageAsync = void 0;
const Cache = __importStar(require("./Cache"));
exports.Cache = Cache;
const Image_1 = require("./Image");
Object.defineProperty(exports, "compositeImagesAsync", { enumerable: true, get: function () { return Image_1.compositeImagesAsync; } });
Object.defineProperty(exports, "generateFaviconAsync", { enumerable: true, get: function () { return Image_1.generateFaviconAsync; } });
Object.defineProperty(exports, "generateImageAsync", { enumerable: true, get: function () { return Image_1.generateImageAsync; } });
Object.defineProperty(exports, "getPngInfo", { enumerable: true, get: function () { return Image_1.getPngInfo; } });
const jimp_1 = require("./jimp");
Object.defineProperty(exports, "jimpAsync", { enumerable: true, get: function () { return jimp_1.jimpAsync; } });
Object.defineProperty(exports, "createSquareAsync", { enumerable: true, get: function () { return jimp_1.createSquareAsync; } });
const sharp_1 = require("./sharp");
Object.defineProperty(exports, "findSharpInstanceAsync", { enumerable: true, get: function () { return sharp_1.findSharpInstanceAsync; } });
Object.defineProperty(exports, "isAvailableAsync", { enumerable: true, get: function () { return sharp_1.isAvailableAsync; } });
Object.defineProperty(exports, "sharpAsync", { enumerable: true, get: function () { return sharp_1.sharpAsync; } });
async function imageAsync(options, commands = []) {
if (await (0, sharp_1.isAvailableAsync)()) {
return (0, sharp_1.sharpAsync)(options, commands);
}
return (0, jimp_1.jimpAsync)({ ...options, format: (0, jimp_1.convertFormat)(options.format), originalInput: options.input }, commands);
}
exports.imageAsync = imageAsync;
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AAiC/B,sBAAK;AAhCP,mCAKiB;AA4Bf,qGAhCA,4BAAoB,OAgCA;AAFpB,qGA7BA,4BAAoB,OA6BA;AADpB,mGA3BA,0BAAkB,OA2BA;AAIlB,2FA9BA,kBAAU,OA8BA;AA3BZ,iCAAqE;AAkBnE,0FAlBsB,gBAAS,OAkBtB;AACT,kGAnBiC,wBAAiB,OAmBjC;AAlBnB,mCAA+E;AAmB7E,uGAnBO,8BAAsB,OAmBP;AACtB,iGApB+B,wBAAgB,OAoB/B;AAChB,2FArBiD,kBAAU,OAqBjD;AAlBL,KAAK,UAAU,UAAU,CAC9B,OAA2B,EAC3B,WAAkC,EAAE;IAEpC,IAAI,MAAM,IAAA,wBAAgB,GAAE,EAAE;QAC5B,OAAO,IAAA,kBAAU,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;KACtC;IACD,OAAO,IAAA,gBAAS,EACd,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,IAAA,oBAAa,EAAC,OAAO,CAAC,MAAM,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,KAAK,EAAE,EACnF,QAAQ,CACT,CAAC;AACJ,CAAC;AAXD,gCAWC"}

View File

@@ -0,0 +1,23 @@
/// <reference types="node" />
import Jimp from 'jimp-compact';
import { ResizeOptions, SharpCommandOptions, SharpGlobalOptions } from './sharp.types';
type JimpGlobalOptions = Omit<SharpGlobalOptions, 'input'> & {
input: string | Buffer | Jimp;
originalInput: string;
};
export declare function resizeBufferAsync(buffer: Buffer, sizes: number[]): Promise<Buffer[]>;
export declare function convertFormat(format?: string): string | undefined;
export declare function jimpAsync(options: JimpGlobalOptions, commands?: SharpCommandOptions[]): Promise<Buffer>;
export declare function isFolderAsync(path: string): Promise<boolean>;
export declare function circleAsync(jimp: Jimp): Promise<Jimp>;
/**
* Create a square image of a given size and color. Defaults to a white PNG.
*/
export declare function createSquareAsync({ size, color, mime, }: {
size: number;
color?: string;
mime?: any;
}): Promise<Buffer>;
export declare function getJimpImageAsync(input: string | Buffer | Jimp): Promise<Jimp>;
export declare function resize({ input, quality }: JimpGlobalOptions, { background, position, fit, width, height }: Omit<ResizeOptions, 'operation'>): Promise<Jimp>;
export {};

View File

@@ -0,0 +1,217 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resize = exports.getJimpImageAsync = exports.createSquareAsync = exports.circleAsync = exports.isFolderAsync = exports.jimpAsync = exports.convertFormat = exports.resizeBufferAsync = void 0;
const fs_extra_1 = __importDefault(require("fs-extra"));
// @ts-ignore
const jimp_compact_1 = __importDefault(require("jimp-compact"));
const path = __importStar(require("path"));
async function resizeBufferAsync(buffer, sizes) {
return Promise.all(sizes.map(async (size) => {
// Parse the buffer each time to prevent mutable copies.
// Parse the buffer each time to prevent mutable copies.
const jimpImage = await jimp_compact_1.default.read(buffer);
const mime = jimpImage.getMIME();
return jimpImage.resize(size, size).getBufferAsync(mime);
}));
}
exports.resizeBufferAsync = resizeBufferAsync;
function convertFormat(format) {
if (typeof format === 'undefined')
return format;
const input = format?.toLowerCase();
switch (input) {
case 'png':
case 'webp':
case 'jpeg':
return `image/${input}`;
case 'jpg':
return `image/jpeg`;
}
return undefined;
}
exports.convertFormat = convertFormat;
async function jimpAsync(options, commands = []) {
if (commands.length) {
const command = commands.shift();
if (command) {
let input;
if (command.operation === 'resize') {
input = await resize(options, command);
}
else if (command.operation === 'flatten') {
input = await flatten(options, command);
}
else {
throw new Error(`The operation: '${command.operation}' is not supported with Jimp`);
}
// @ts-ignore
return jimpAsync({ ...options, input }, commands);
}
}
const image = await getJimpImageAsync(options.input);
const mime = typeof options.format === 'string' ? options.format : image.getMIME();
const imgBuffer = await image.getBufferAsync(mime);
if (typeof options.output === 'string') {
if (await isFolderAsync(options.output)) {
await fs_extra_1.default.writeFile(path.join(options.output, path.basename(options.originalInput)), imgBuffer);
}
else {
await fs_extra_1.default.writeFile(options.output, imgBuffer);
}
}
return imgBuffer;
}
exports.jimpAsync = jimpAsync;
async function isFolderAsync(path) {
try {
return (await fs_extra_1.default.stat(path)).isDirectory();
}
catch {
return false;
}
}
exports.isFolderAsync = isFolderAsync;
function circleAsync(jimp) {
const radius = Math.min(jimp.bitmap.width, jimp.bitmap.height) / 2;
const center = {
x: jimp.bitmap.width / 2,
y: jimp.bitmap.height / 2,
};
return new Promise((resolve) => {
jimp.scanQuiet(0, 0, jimp.bitmap.width, jimp.bitmap.height, (x, y, idx) => {
const curR = Math.sqrt(Math.pow(x - center.x, 2) + Math.pow(y - center.y, 2));
if (radius - curR <= 0.0) {
jimp.bitmap.data[idx + 3] = 0;
}
else if (radius - curR < 1.0) {
jimp.bitmap.data[idx + 3] = 255 * (radius - curR);
}
resolve(jimp);
});
});
}
exports.circleAsync = circleAsync;
/**
* Create a square image of a given size and color. Defaults to a white PNG.
*/
async function createSquareAsync({ size, color = '#FFFFFF', mime = jimp_compact_1.default.MIME_PNG, }) {
const image = await new jimp_compact_1.default(size, size, color);
// Convert Jimp image to a Buffer
return await image.getBufferAsync(mime);
}
exports.createSquareAsync = createSquareAsync;
async function getJimpImageAsync(input) {
// @ts-ignore: Jimp types are broken
if (typeof input === 'string' || input instanceof Buffer)
return await jimp_compact_1.default.read(input);
return input;
}
exports.getJimpImageAsync = getJimpImageAsync;
async function resize({ input, quality = 100 }, { background, position, fit, width, height }) {
let initialImage = await getJimpImageAsync(input);
if (width && !height) {
height = jimp_compact_1.default.AUTO;
}
else if (!width && height) {
width = jimp_compact_1.default.AUTO;
}
else if (!width && !height) {
width = initialImage.bitmap.width;
height = initialImage.bitmap.height;
}
const jimpPosition = convertPosition(position);
const jimpQuality = typeof quality !== 'number' ? 100 : quality;
if (fit === 'cover') {
initialImage = initialImage.cover(width, height, jimpPosition);
}
else if (fit === 'contain') {
initialImage = initialImage.contain(width, height, jimpPosition);
}
else {
throw new Error(`Unsupported fit: ${fit}. Please choose either 'cover', or 'contain' when using Jimp`);
}
if (background) {
initialImage = initialImage.composite(new jimp_compact_1.default(width, height, background), 0, 0, {
mode: jimp_compact_1.default.BLEND_DESTINATION_OVER,
opacitySource: 1,
opacityDest: 1,
});
}
return await initialImage.quality(jimpQuality);
}
exports.resize = resize;
async function flatten({ input, quality = 100 }, { background }) {
const initialImage = await getJimpImageAsync(input);
const jimpQuality = typeof quality !== 'number' ? 100 : quality;
return initialImage.quality(jimpQuality).background(jimp_compact_1.default.cssColorToHex(background));
}
/**
* Convert sharp position to Jimp position.
*
* @param position
*/
function convertPosition(position) {
if (!position)
return convertPosition('center');
switch (position) {
case 'center':
case 'centre':
return jimp_compact_1.default.VERTICAL_ALIGN_MIDDLE | jimp_compact_1.default.HORIZONTAL_ALIGN_CENTER;
case 'north':
case 'top':
return jimp_compact_1.default.VERTICAL_ALIGN_TOP | jimp_compact_1.default.HORIZONTAL_ALIGN_CENTER;
case 'east':
case 'right':
return jimp_compact_1.default.VERTICAL_ALIGN_MIDDLE | jimp_compact_1.default.HORIZONTAL_ALIGN_RIGHT;
case 'south':
case 'bottom':
return jimp_compact_1.default.VERTICAL_ALIGN_BOTTOM | jimp_compact_1.default.HORIZONTAL_ALIGN_CENTER;
case 'west':
case 'left':
return jimp_compact_1.default.VERTICAL_ALIGN_MIDDLE | jimp_compact_1.default.HORIZONTAL_ALIGN_LEFT;
case 'northeast':
case 'right top':
return jimp_compact_1.default.VERTICAL_ALIGN_TOP | jimp_compact_1.default.HORIZONTAL_ALIGN_RIGHT;
case 'southeast':
case 'right bottom':
return jimp_compact_1.default.VERTICAL_ALIGN_BOTTOM | jimp_compact_1.default.HORIZONTAL_ALIGN_RIGHT;
case 'southwest':
case 'left bottom':
return jimp_compact_1.default.VERTICAL_ALIGN_BOTTOM | jimp_compact_1.default.HORIZONTAL_ALIGN_LEFT;
case 'northwest':
case 'left top':
return jimp_compact_1.default.VERTICAL_ALIGN_TOP | jimp_compact_1.default.HORIZONTAL_ALIGN_LEFT;
case 'entropy':
case 'attention':
throw new Error(`Position: '${position}' is not supported`);
default:
throw new Error(`Unknown position: '${position}'`);
}
}
//# sourceMappingURL=jimp.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
/// <reference types="node" />
import { SharpCommandOptions, SharpGlobalOptions } from './sharp.types';
export declare function resizeBufferAsync(buffer: Buffer, sizes: number[]): Promise<Buffer[]>;
/**
* Returns `true` if a global sharp instance can be found.
* This functionality can be overridden with `process.env.EXPO_IMAGE_UTILS_NO_SHARP=1`.
*/
export declare function isAvailableAsync(): Promise<boolean>;
export declare function sharpAsync(options: SharpGlobalOptions, commands?: SharpCommandOptions[]): Promise<string[]>;
/**
* Returns the instance of `sharp` installed by the global `sharp-cli` package.
* This method will throw errors if the `sharp` instance cannot be found, these errors can be circumvented by ensuring `isAvailableAsync()` resolves to `true`.
*/
export declare function findSharpInstanceAsync(): Promise<any | null>;

View File

@@ -0,0 +1,211 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.findSharpInstanceAsync = exports.sharpAsync = exports.isAvailableAsync = exports.resizeBufferAsync = void 0;
const spawn_async_1 = __importDefault(require("@expo/spawn-async"));
const path_1 = __importDefault(require("path"));
const resolve_from_1 = __importDefault(require("resolve-from"));
const semver_1 = __importDefault(require("semver"));
const env_1 = require("./env");
const SHARP_HELP_PATTERN = /\n\nSpecify --help for available options/g;
const SHARP_REQUIRED_VERSION = '^2.1.0';
async function resizeBufferAsync(buffer, sizes) {
const sharp = await findSharpInstanceAsync();
const metadata = await sharp(buffer).metadata();
// Create buffer for each size
const resizedBuffers = await Promise.all(sizes.map((dimension) => {
const density = (dimension / Math.max(metadata.width, metadata.height)) * metadata.density;
return sharp(buffer, {
density: isNaN(density) ? undefined : Math.ceil(density),
})
.resize(dimension, dimension, { fit: 'contain', background: 'transparent' })
.toBuffer();
}));
return resizedBuffers;
}
exports.resizeBufferAsync = resizeBufferAsync;
/**
* Returns `true` if a global sharp instance can be found.
* This functionality can be overridden with `process.env.EXPO_IMAGE_UTILS_NO_SHARP=1`.
*/
async function isAvailableAsync() {
if (env_1.env.EXPO_IMAGE_UTILS_NO_SHARP) {
return false;
}
try {
return !!(await findSharpBinAsync());
}
catch {
return false;
}
}
exports.isAvailableAsync = isAvailableAsync;
async function sharpAsync(options, commands = []) {
const bin = await findSharpBinAsync();
try {
const { stdout } = await (0, spawn_async_1.default)(bin, [
...getOptions(options),
...getCommandOptions(commands),
]);
const outputFilePaths = stdout.trim().split('\n');
return outputFilePaths;
}
catch (error) {
if (error.stderr) {
throw new Error('\nProcessing images using sharp-cli failed: ' +
error.message +
'\nOutput: ' +
error.stderr.replace(SHARP_HELP_PATTERN, ''));
}
else {
throw error;
}
}
}
exports.sharpAsync = sharpAsync;
function getOptions(options) {
const args = [];
for (const [key, value] of Object.entries(options)) {
if (value != null && value !== false) {
if (typeof value === 'boolean') {
args.push(`--${key}`);
}
else if (typeof value === 'number') {
args.push(`--${key}`, value.toFixed());
}
else {
args.push(`--${key}`, value);
}
}
}
return args;
}
function getCommandOptions(commands) {
const args = [];
for (const command of commands) {
if (command.operation === 'resize') {
const { operation, width, ...namedOptions } = command;
args.push(operation, width.toFixed(), ...getOptions(namedOptions));
}
else {
const { operation, ...namedOptions } = command;
args.push(operation, ...getOptions(namedOptions));
}
args.push('--');
}
return args;
}
let _sharpBin = null;
let _sharpInstance = null;
async function findSharpBinAsync() {
if (_sharpBin) {
return _sharpBin;
}
try {
const sharpCliPackage = require('sharp-cli/package.json');
const libVipsVersion = require('sharp').versions.vips;
if (sharpCliPackage &&
semver_1.default.satisfies(sharpCliPackage.version, SHARP_REQUIRED_VERSION) &&
typeof sharpCliPackage.bin.sharp === 'string' &&
typeof libVipsVersion === 'string') {
_sharpBin = require.resolve(`sharp-cli/${sharpCliPackage.bin.sharp}`);
return _sharpBin;
}
}
catch {
// fall back to global sharp-cli
}
let installedCliVersion;
try {
installedCliVersion = (await (0, spawn_async_1.default)('sharp', ['--version'])).stdout.toString().trim();
}
catch {
throw notFoundError(SHARP_REQUIRED_VERSION);
}
if (!semver_1.default.satisfies(installedCliVersion, SHARP_REQUIRED_VERSION)) {
showVersionMismatchWarning(SHARP_REQUIRED_VERSION, installedCliVersion);
}
_sharpBin = 'sharp';
return _sharpBin;
}
/**
* Returns the instance of `sharp` installed by the global `sharp-cli` package.
* This method will throw errors if the `sharp` instance cannot be found, these errors can be circumvented by ensuring `isAvailableAsync()` resolves to `true`.
*/
async function findSharpInstanceAsync() {
if (env_1.env.EXPO_IMAGE_UTILS_NO_SHARP) {
throw new Error('Global instance of sharp-cli cannot be retrieved because sharp-cli has been disabled with the environment variable `EXPO_IMAGE_UTILS_NO_SHARP`');
}
if (_sharpInstance) {
return _sharpInstance;
}
// Ensure sharp-cli version is correct
await findSharpBinAsync();
// Attempt to use local sharp package
try {
const sharp = require('sharp');
_sharpInstance = sharp;
return sharp;
}
catch { }
// Attempt to resolve the sharp instance used by the global CLI
let sharpCliPath;
if (process.platform !== 'win32') {
try {
sharpCliPath = (await (0, spawn_async_1.default)('which', ['sharp'])).stdout.toString().trim();
}
catch { }
}
else {
// On Windows systems, nested dependencies aren't linked to the paths within `require.resolve.paths`.
// Yarn installs these modules in a different folder, let's add yarn to the other attempts.
// See: https://github.com/expo/expo-cli/issues/2708
let yarnGlobalPath = '';
try {
yarnGlobalPath = path_1.default.join((await (0, spawn_async_1.default)('yarn', ['global', 'dir'])).stdout.toString().trim(), 'node_modules');
}
catch { }
try {
sharpCliPath = require.resolve('sharp-cli/package.json', {
paths: (require.resolve.paths('sharp-cli') || []).concat(yarnGlobalPath),
});
}
catch { }
}
// resolve sharp from the sharp-cli package
const sharpPath = resolve_from_1.default.silent(sharpCliPath || '', 'sharp');
if (sharpPath) {
try {
// attempt to require the global sharp package
_sharpInstance = require(sharpPath);
}
catch { }
}
if (!_sharpInstance) {
throw new Error(`Failed to find the instance of sharp used by the global sharp-cli package.`);
}
return _sharpInstance;
}
exports.findSharpInstanceAsync = findSharpInstanceAsync;
function notFoundError(requiredCliVersion) {
return new Error(`This command requires version ${requiredCliVersion} of \`sharp-cli\`. \n` +
`You can install it using \`npm install -g sharp-cli@${requiredCliVersion}\`. \n` +
'\n' +
'For prerequisites, see: https://sharp.dimens.io/en/stable/install/#prerequisites');
}
let versionMismatchWarningShown = false;
function showVersionMismatchWarning(requiredCliVersion, installedCliVersion) {
if (versionMismatchWarningShown) {
return;
}
console.warn(`Warning: This command requires version ${requiredCliVersion} of \`sharp-cli\`. \n` +
`Currently installed version: "${installedCliVersion}" \n` +
`Required version: "${requiredCliVersion}" \n` +
`You can install it using \`npm install -g sharp-cli@${requiredCliVersion}\`. \n` +
'\n' +
'For prerequisites, see: https://sharp.dimens.io/en/stable/install/#prerequisites');
versionMismatchWarningShown = true;
}
//# sourceMappingURL=sharp.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,35 @@
import { ImageFormat, ResizeMode } from './Image.types';
export type SharpGlobalOptions = {
compressionLevel?: '';
format?: ImageFormat;
input: string;
limitInputPixels?: number;
output: string;
progressive?: boolean;
quality?: number;
withMetadata?: boolean;
[key: string]: string | number | boolean | undefined | null;
};
export type SharpCommandOptions = RemoveAlphaOptions | ResizeOptions | FlattenOptions;
export type FlattenOptions = {
operation: 'flatten';
background: string;
};
export type RemoveAlphaOptions = {
operation: 'removeAlpha';
};
export type Position = 'center' | 'centre' | 'north' | 'east' | 'south' | 'west' | 'northeast' | 'southeast' | 'southwest' | 'northwest' | 'top' | 'right' | 'bottom' | 'left' | 'right top' | 'right bottom' | 'left bottom' | 'left top' | 'entropy' | 'attention';
export type ResizeOptions = {
operation: 'resize';
background?: string;
fastShrinkOnLoad?: boolean;
fit?: ResizeMode;
height?: number;
kernel?: 'nearest' | 'cubic' | 'mitchell' | 'lanczos2' | 'lanczos3';
position?: Position;
width: number;
withoutEnlargement?: boolean;
};
export type Options = object | {
[key: string]: boolean | number | string | undefined;
};

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=sharp.types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"sharp.types.js","sourceRoot":"","sources":["../src/sharp.types.ts"],"names":[],"mappings":""}