1 | "use strict";
|
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4 | return new (P || (P = Promise))(function (resolve, reject) {
|
5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9 | });
|
10 | };
|
11 | var __rest = (this && this.__rest) || function (s, e) {
|
12 | var t = {};
|
13 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
14 | t[p] = s[p];
|
15 | if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
16 | for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
17 | if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
18 | t[p[i]] = s[p[i]];
|
19 | }
|
20 | return t;
|
21 | };
|
22 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
23 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
24 | };
|
25 | Object.defineProperty(exports, "__esModule", { value: true });
|
26 | const image_utils_1 = require("@expo/image-utils");
|
27 | const fs_extra_1 = __importDefault(require("fs-extra"));
|
28 | const mime_1 = __importDefault(require("mime"));
|
29 | const node_fetch_1 = __importDefault(require("node-fetch"));
|
30 | const path_1 = __importDefault(require("path"));
|
31 | const stream_1 = __importDefault(require("stream"));
|
32 | const tempy_1 = __importDefault(require("tempy"));
|
33 | const util_1 = __importDefault(require("util"));
|
34 | const chalk_1 = __importDefault(require("chalk"));
|
35 | const crypto_1 = __importDefault(require("crypto"));
|
36 | const Errors_1 = require("./Errors");
|
37 | const utils_1 = require("./utils");
|
38 | const Apple_1 = require("./validators/Apple");
|
39 | const ImageComposite_1 = require("./ImageComposite");
|
40 | const supportedMimeTypes = ['image/png', 'image/jpeg', 'image/webp'];
|
41 | function sanitizeIcon(iconSnippet) {
|
42 | if (!iconSnippet.src) {
|
43 | throw new Errors_1.IconError('Unknown icon source.');
|
44 | }
|
45 | const sizes = utils_1.toArray(iconSnippet.size || iconSnippet.sizes);
|
46 | if (!sizes) {
|
47 | throw new Errors_1.IconError('Unknown icon sizes.');
|
48 | }
|
49 | return {
|
50 | src: iconSnippet.src,
|
51 | resizeMode: iconSnippet.resizeMode,
|
52 | sizes,
|
53 | media: iconSnippet.media,
|
54 | destination: iconSnippet.destination,
|
55 | ios: iconSnippet.ios,
|
56 | color: iconSnippet.color,
|
57 | };
|
58 | }
|
59 | function getBufferWithMimeAsync({ src, resizeMode, color }, mimeType, { width, height }) {
|
60 | return __awaiter(this, void 0, void 0, function* () {
|
61 | let imagePath;
|
62 | if (!supportedMimeTypes.includes(mimeType)) {
|
63 | imagePath = src;
|
64 | }
|
65 | else {
|
66 | const imageData = yield resize(src, mimeType, width, height, resizeMode, color);
|
67 | if (imageData instanceof Buffer) {
|
68 | return imageData;
|
69 | }
|
70 | else {
|
71 | imagePath = imageData;
|
72 | }
|
73 | }
|
74 | try {
|
75 | return yield fs_extra_1.default.readFile(imagePath);
|
76 | }
|
77 | catch (err) {
|
78 | throw new Errors_1.IconError(`It was not possible to read '${src}'.`);
|
79 | }
|
80 | });
|
81 | }
|
82 | function downloadImage(url) {
|
83 | return __awaiter(this, void 0, void 0, function* () {
|
84 | const outputPath = tempy_1.default.directory();
|
85 | const localPath = path_1.default.join(outputPath, path_1.default.basename(stripQueryParams(url)));
|
86 | const response = yield node_fetch_1.default(url);
|
87 | if (!response.ok) {
|
88 | throw new Errors_1.IconError(`It was not possible to download splash screen from '${url}'`);
|
89 | }
|
90 |
|
91 | const streamPipeline = util_1.default.promisify(stream_1.default.pipeline);
|
92 | yield streamPipeline(response.body, fs_extra_1.default.createWriteStream(localPath));
|
93 | return localPath;
|
94 | });
|
95 | }
|
96 | function ensureCacheDirectory(projectRoot, cacheKey) {
|
97 | return __awaiter(this, void 0, void 0, function* () {
|
98 | const cacheFolder = path_1.default.join(projectRoot, '.expo/web/cache/production/images', cacheKey);
|
99 | yield fs_extra_1.default.ensureDir(cacheFolder);
|
100 | return cacheFolder;
|
101 | });
|
102 | }
|
103 | function getImageFromCacheAsync(fileName, cacheKey) {
|
104 | return __awaiter(this, void 0, void 0, function* () {
|
105 | try {
|
106 | return yield fs_extra_1.default.readFile(path_1.default.resolve(cacheKeys[cacheKey], fileName));
|
107 | }
|
108 | catch (_) {
|
109 | return null;
|
110 | }
|
111 | });
|
112 | }
|
113 | function cacheImageAsync(fileName, buffer, cacheKey) {
|
114 | return __awaiter(this, void 0, void 0, function* () {
|
115 | try {
|
116 | yield fs_extra_1.default.writeFile(path_1.default.resolve(cacheKeys[cacheKey], fileName), buffer);
|
117 | }
|
118 | catch ({ message }) {
|
119 | console.warn(`error caching image: "${fileName}". ${message}`);
|
120 | }
|
121 | });
|
122 | }
|
123 | let hasWarned = false;
|
124 | function processImageAsync(size, icon, publicPath, cacheKey) {
|
125 | return __awaiter(this, void 0, void 0, function* () {
|
126 | const { width, height } = utils_1.toSize(size);
|
127 | if (width <= 0 || height <= 0) {
|
128 | throw Error(`Failed to process image with invalid size: { width: ${width}, height: ${height}}`);
|
129 | }
|
130 | const mimeType = mime_1.default.getType(icon.src);
|
131 | if (!mimeType) {
|
132 | throw new Error(`Invalid mimeType for image with source: ${icon.src}`);
|
133 | }
|
134 | const dimensions = `${width}x${height}`;
|
135 | const fileName = `icon_${dimensions}.${mime_1.default.getExtension(mimeType)}`;
|
136 | let imageBuffer = yield getImageFromCacheAsync(fileName, cacheKey);
|
137 | if (!imageBuffer) {
|
138 |
|
139 | if (!hasWarned && !(yield image_utils_1.isAvailableAsync())) {
|
140 | hasWarned = true;
|
141 |
|
142 | console.log('ff', cacheKey, fileName, dimensions);
|
143 | console.log();
|
144 | console.log(chalk_1.default.bgYellow.black(`PWA Images: Using node to generate images. This is much slower than using native packages.`));
|
145 | console.log(chalk_1.default.yellow(`- Optionally you can stop the process and try again after successfully running \`npm install -g sharp-cli\`.\n- If you are using \`expo-cli\` to build your project then you could use the \`--no-pwa\` flag to skip the PWA asset generation step entirely.`));
|
146 | }
|
147 | imageBuffer = yield getBufferWithMimeAsync(icon, mimeType, { width, height });
|
148 | yield cacheImageAsync(fileName, imageBuffer, cacheKey);
|
149 | }
|
150 | const iconOutputDir = icon.destination ? utils_1.joinURI(icon.destination, fileName) : fileName;
|
151 | const iconPublicUrl = utils_1.joinURI(publicPath, iconOutputDir);
|
152 | return {
|
153 | manifestIcon: {
|
154 | src: iconPublicUrl,
|
155 | sizes: dimensions,
|
156 | type: mimeType,
|
157 | },
|
158 | webpackAsset: {
|
159 | output: iconOutputDir,
|
160 | url: iconPublicUrl,
|
161 | source: imageBuffer,
|
162 | size: imageBuffer.length,
|
163 | ios: icon.ios
|
164 | ? { valid: icon.ios, media: icon.media, size: dimensions, href: iconPublicUrl }
|
165 | : false,
|
166 | resizeMode: icon.resizeMode,
|
167 | color: icon.color,
|
168 | },
|
169 | };
|
170 | });
|
171 | }
|
172 | function ensureValidMimeType(mimeType) {
|
173 | if (['input', 'jpeg', 'jpg', 'png', 'raw', 'tiff', 'webp'].includes(mimeType)) {
|
174 | return mimeType;
|
175 | }
|
176 | return 'png';
|
177 | }
|
178 | function resize(inputPath, mimeType, width, height, fit = 'contain', background) {
|
179 | return __awaiter(this, void 0, void 0, function* () {
|
180 | if (!(yield image_utils_1.isAvailableAsync())) {
|
181 | return yield ImageComposite_1.resize(inputPath, mimeType, width, height, fit, background);
|
182 | }
|
183 | const format = ensureValidMimeType(mimeType.split('/')[1]);
|
184 | const outputPath = tempy_1.default.directory();
|
185 | try {
|
186 | yield image_utils_1.sharpAsync({
|
187 | input: inputPath,
|
188 | output: outputPath,
|
189 | format,
|
190 | }, [
|
191 | {
|
192 | operation: 'flatten',
|
193 | background,
|
194 | },
|
195 | {
|
196 | operation: 'resize',
|
197 | width,
|
198 | height,
|
199 | fit,
|
200 | background,
|
201 | },
|
202 | ]);
|
203 | return path_1.default.join(outputPath, path_1.default.basename(inputPath));
|
204 | }
|
205 | catch ({ message }) {
|
206 | throw new Errors_1.IconError(`It was not possible to generate splash screen '${inputPath}'. ${message}`);
|
207 | }
|
208 | });
|
209 | }
|
210 | function retrieveIcons(manifest) {
|
211 |
|
212 | const { startupImages, icons } = manifest, config = __rest(manifest, ["startupImages", "icons"]);
|
213 | const parsedStartupImages = utils_1.toArray(startupImages);
|
214 | let parsedIcons = utils_1.toArray(icons);
|
215 | if (parsedStartupImages.length) {
|
216 |
|
217 | const startupImage = parsedStartupImages[0];
|
218 | parsedIcons = [...parsedIcons, ...Apple_1.fromStartupImage(startupImage)];
|
219 | }
|
220 | const response = parsedIcons.map(icon => sanitizeIcon(icon));
|
221 | return [response, config];
|
222 | }
|
223 | exports.retrieveIcons = retrieveIcons;
|
224 |
|
225 | function calculateHash(filePath) {
|
226 | const contents = filePath.startsWith('http') ? filePath : fs_extra_1.default.readFileSync(filePath);
|
227 | return crypto_1.default
|
228 | .createHash('sha256')
|
229 | .update(contents)
|
230 | .digest('hex');
|
231 | }
|
232 |
|
233 | function createCacheKey(icon) {
|
234 | const hash = calculateHash(icon.src);
|
235 | return [hash, icon.resizeMode, icon.color].filter(Boolean).join('-');
|
236 | }
|
237 | const cacheKeys = {};
|
238 | const cacheDownloadedKeys = {};
|
239 | function stripQueryParams(url) {
|
240 | return url.split('?')[0].split('#')[0];
|
241 | }
|
242 | function downloadOrUseCachedImage(url) {
|
243 | return __awaiter(this, void 0, void 0, function* () {
|
244 | if (url in cacheDownloadedKeys) {
|
245 | return cacheDownloadedKeys[url];
|
246 | }
|
247 | if (url.startsWith('http')) {
|
248 | cacheDownloadedKeys[url] = yield downloadImage(url);
|
249 | }
|
250 | else {
|
251 | cacheDownloadedKeys[url] = url;
|
252 | }
|
253 | return cacheDownloadedKeys[url];
|
254 | });
|
255 | }
|
256 | function parseIconsAsync(projectRoot, inputIcons, publicPath) {
|
257 | return __awaiter(this, void 0, void 0, function* () {
|
258 | if (!inputIcons.length) {
|
259 | return {};
|
260 | }
|
261 | const icons = [];
|
262 | const assets = [];
|
263 | let promises = [];
|
264 | for (const icon of inputIcons) {
|
265 | const cacheKey = createCacheKey(icon);
|
266 | icon.src = yield downloadOrUseCachedImage(icon.src);
|
267 | if (!(cacheKey in cacheKeys)) {
|
268 | cacheKeys[cacheKey] = yield ensureCacheDirectory(projectRoot, cacheKey);
|
269 | }
|
270 | const { sizes } = icon;
|
271 | promises = [
|
272 | ...promises,
|
273 | ...sizes.map((size) => __awaiter(this, void 0, void 0, function* () {
|
274 | const { manifestIcon, webpackAsset } = yield processImageAsync(size, icon, publicPath, cacheKey);
|
275 | icons.push(manifestIcon);
|
276 | assets.push(webpackAsset);
|
277 | })),
|
278 | ];
|
279 | }
|
280 | yield Promise.all(promises);
|
281 | yield clearUnusedCachesAsync(projectRoot);
|
282 | return {
|
283 | icons: sortByAttribute(icons, 'sizes'),
|
284 |
|
285 | assets: sortByAttribute(assets, 'output'),
|
286 | };
|
287 | });
|
288 | }
|
289 | exports.parseIconsAsync = parseIconsAsync;
|
290 | function sortByAttribute(arr, key) {
|
291 | return arr.filter(Boolean).sort((valueA, valueB) => {
|
292 | if (valueA[key] < valueB[key])
|
293 | return -1;
|
294 | else if (valueA[key] > valueB[key])
|
295 | return 1;
|
296 | return 0;
|
297 | });
|
298 | }
|
299 | function clearUnusedCachesAsync(projectRoot) {
|
300 | return __awaiter(this, void 0, void 0, function* () {
|
301 |
|
302 | const cacheFolder = path_1.default.join(projectRoot, '.expo/web/cache/production/images');
|
303 | const currentCaches = fs_extra_1.default.readdirSync(cacheFolder);
|
304 | if (!Array.isArray(currentCaches)) {
|
305 | console.warn('Failed to read the icon cache');
|
306 | return;
|
307 | }
|
308 | const deleteCachePromises = [];
|
309 | for (const cache of currentCaches) {
|
310 |
|
311 | if (cache.startsWith('.')) {
|
312 | continue;
|
313 | }
|
314 |
|
315 | if (!(cache in cacheKeys)) {
|
316 | deleteCachePromises.push(fs_extra_1.default.remove(path_1.default.join(cacheFolder, cache)));
|
317 | }
|
318 | }
|
319 | yield Promise.all(deleteCachePromises);
|
320 | });
|
321 | }
|
322 |
|
\ | No newline at end of file |