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 supportedMimeTypes = ['image/png', 'image/jpeg', 'image/webp'];
|
40 | function sanitizeIcon(iconSnippet) {
|
41 | if (!iconSnippet.src) {
|
42 | throw new Errors_1.IconError('Unknown icon source.');
|
43 | }
|
44 | const sizes = utils_1.toArray(iconSnippet.size || iconSnippet.sizes);
|
45 | if (!sizes) {
|
46 | throw new Errors_1.IconError('Unknown icon sizes.');
|
47 | }
|
48 | return {
|
49 | src: iconSnippet.src,
|
50 | resizeMode: iconSnippet.resizeMode,
|
51 | sizes,
|
52 | media: iconSnippet.media,
|
53 | destination: iconSnippet.destination,
|
54 | ios: iconSnippet.ios,
|
55 | color: iconSnippet.color,
|
56 | };
|
57 | }
|
58 | function getBufferWithMimeAsync({ src, resizeMode, color }, mimeType, { width, height }) {
|
59 | return __awaiter(this, void 0, void 0, function* () {
|
60 | let imagePath;
|
61 | if (!supportedMimeTypes.includes(mimeType)) {
|
62 | imagePath = src;
|
63 | }
|
64 | else {
|
65 | let localSrc = src;
|
66 |
|
67 | if (src.startsWith('http')) {
|
68 | localSrc = yield downloadImage(src);
|
69 | }
|
70 | imagePath = yield resize(localSrc, mimeType, width, height, resizeMode, color);
|
71 | }
|
72 | try {
|
73 | return yield fs_extra_1.default.readFile(imagePath);
|
74 | }
|
75 | catch (err) {
|
76 | throw new Errors_1.IconError(`It was not possible to read '${src}'.`);
|
77 | }
|
78 | });
|
79 | }
|
80 | function downloadImage(url) {
|
81 | return __awaiter(this, void 0, void 0, function* () {
|
82 | const outputPath = tempy_1.default.directory();
|
83 | const localPath = path_1.default.join(outputPath, path_1.default.basename(url));
|
84 | const response = yield node_fetch_1.default(url);
|
85 | if (!response.ok) {
|
86 | throw new Errors_1.IconError(`It was not possible to download splash screen from '${url}'`);
|
87 | }
|
88 |
|
89 | const streamPipeline = util_1.default.promisify(stream_1.default.pipeline);
|
90 | yield streamPipeline(response.body, fs_extra_1.default.createWriteStream(localPath));
|
91 | return localPath;
|
92 | });
|
93 | }
|
94 | function ensureCacheDirectory(projectRoot, cacheKey) {
|
95 | return __awaiter(this, void 0, void 0, function* () {
|
96 | const cacheFolder = path_1.default.join(projectRoot, '.expo/web/cache/production/images', cacheKey);
|
97 | yield fs_extra_1.default.ensureDir(cacheFolder);
|
98 | return cacheFolder;
|
99 | });
|
100 | }
|
101 | function getImageFromCacheAsync(fileName, cacheKey) {
|
102 | return __awaiter(this, void 0, void 0, function* () {
|
103 | try {
|
104 | return yield fs_extra_1.default.readFile(path_1.default.resolve(cacheKeys[cacheKey], fileName));
|
105 | }
|
106 | catch (_) {
|
107 | return null;
|
108 | }
|
109 | });
|
110 | }
|
111 | function cacheImageAsync(fileName, buffer, cacheKey) {
|
112 | return __awaiter(this, void 0, void 0, function* () {
|
113 | try {
|
114 | yield fs_extra_1.default.writeFile(path_1.default.resolve(cacheKeys[cacheKey], fileName), buffer);
|
115 | }
|
116 | catch ({ message }) {
|
117 | console.warn(`error caching image: "${fileName}". ${message}`);
|
118 | }
|
119 | });
|
120 | }
|
121 | function processImageAsync(size, icon, publicPath, cacheKey) {
|
122 | return __awaiter(this, void 0, void 0, function* () {
|
123 | const { width, height } = utils_1.toSize(size);
|
124 | if (width <= 0 || height <= 0) {
|
125 | throw Error(`Failed to process image with invalid size: { width: ${width}, height: ${height}}`);
|
126 | }
|
127 | const mimeType = mime_1.default.getType(icon.src);
|
128 | if (!mimeType) {
|
129 | throw new Error(`Invalid mimeType for image with source: ${icon.src}`);
|
130 | }
|
131 | const dimensions = `${width}x${height}`;
|
132 | const fileName = `icon_${dimensions}.${mime_1.default.getExtension(mimeType)}`;
|
133 | let imageBuffer = yield getImageFromCacheAsync(fileName, cacheKey);
|
134 | if (!imageBuffer) {
|
135 | imageBuffer = yield getBufferWithMimeAsync(icon, mimeType, { width, height });
|
136 | yield cacheImageAsync(fileName, imageBuffer, cacheKey);
|
137 | }
|
138 | const iconOutputDir = icon.destination ? utils_1.joinURI(icon.destination, fileName) : fileName;
|
139 | const iconPublicUrl = utils_1.joinURI(publicPath, iconOutputDir);
|
140 | return {
|
141 | manifestIcon: {
|
142 | src: iconPublicUrl,
|
143 | sizes: dimensions,
|
144 | type: mimeType,
|
145 | },
|
146 | webpackAsset: {
|
147 | output: iconOutputDir,
|
148 | url: iconPublicUrl,
|
149 | source: imageBuffer,
|
150 | size: imageBuffer.length,
|
151 | ios: icon.ios
|
152 | ? { valid: icon.ios, media: icon.media, size: dimensions, href: iconPublicUrl }
|
153 | : false,
|
154 | resizeMode: icon.resizeMode,
|
155 | color: icon.color,
|
156 | },
|
157 | };
|
158 | });
|
159 | }
|
160 | function ensureValidMimeType(mimeType) {
|
161 | if (['input', 'jpeg', 'jpg', 'png', 'raw', 'tiff', 'webp'].includes(mimeType)) {
|
162 | return mimeType;
|
163 | }
|
164 | return 'png';
|
165 | }
|
166 | function resize(inputPath, mimeType, width, height, fit = 'contain', background) {
|
167 | return __awaiter(this, void 0, void 0, function* () {
|
168 | const format = ensureValidMimeType(mimeType.split('/')[1]);
|
169 | try {
|
170 | const outputPath = tempy_1.default.directory();
|
171 | yield image_utils_1.sharpAsync({
|
172 | input: inputPath,
|
173 | output: outputPath,
|
174 | format,
|
175 | }, [
|
176 | {
|
177 | operation: 'flatten',
|
178 | background,
|
179 | },
|
180 | {
|
181 | operation: 'resize',
|
182 | width,
|
183 | height,
|
184 | fit,
|
185 | background,
|
186 | },
|
187 | ]);
|
188 | return path_1.default.join(outputPath, path_1.default.basename(inputPath));
|
189 | }
|
190 | catch ({ message }) {
|
191 | throw new Errors_1.IconError(`It was not possible to generate splash screen '${inputPath}'. ${message}`);
|
192 | }
|
193 | });
|
194 | }
|
195 | function retrieveIcons(manifest) {
|
196 |
|
197 | const { startupImages, icons } = manifest, config = __rest(manifest, ["startupImages", "icons"]);
|
198 | const parsedStartupImages = utils_1.toArray(startupImages);
|
199 | let parsedIcons = utils_1.toArray(icons);
|
200 | if (parsedStartupImages.length) {
|
201 |
|
202 | const startupImage = parsedStartupImages[0];
|
203 | parsedIcons = [...parsedIcons, ...Apple_1.fromStartupImage(startupImage)];
|
204 | }
|
205 | const response = parsedIcons.map(icon => sanitizeIcon(icon));
|
206 | return [response, config];
|
207 | }
|
208 | exports.retrieveIcons = retrieveIcons;
|
209 |
|
210 | function calculateHash(filePath) {
|
211 | const contents = fs_extra_1.default.readFileSync(filePath);
|
212 | return crypto_1.default
|
213 | .createHash('sha256')
|
214 | .update(contents)
|
215 | .digest('hex');
|
216 | }
|
217 |
|
218 | function createCacheKey(icon) {
|
219 | const hash = calculateHash(icon.src);
|
220 | return [hash, icon.resizeMode, icon.color].filter(Boolean).join('-');
|
221 | }
|
222 | const cacheKeys = {};
|
223 | function parseIconsAsync(projectRoot, inputIcons, publicPath) {
|
224 | return __awaiter(this, void 0, void 0, function* () {
|
225 | if (!inputIcons.length) {
|
226 | return {};
|
227 | }
|
228 | if (!(yield image_utils_1.isAvailableAsync())) {
|
229 |
|
230 | console.log();
|
231 | console.log(chalk_1.default.bgRed.black(`PWA Error: It was not possible to generate the images for your progressive web app (splash screens and icons) because the host computer does not have \`sharp\` installed, and \`image-utils\` was unable to install it automatically.`));
|
232 | console.log(chalk_1.default.red(`- You may stop the process and try again after successfully running \`npm install -g sharp\`.\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.`));
|
233 | return {};
|
234 | }
|
235 | const icons = [];
|
236 | const assets = [];
|
237 | let promises = [];
|
238 | for (const icon of inputIcons) {
|
239 | const cacheKey = createCacheKey(icon);
|
240 | if (!(cacheKey in cacheKeys)) {
|
241 | cacheKeys[cacheKey] = yield ensureCacheDirectory(projectRoot, cacheKey);
|
242 | }
|
243 | const { sizes } = icon;
|
244 | promises = [
|
245 | ...promises,
|
246 | ...sizes.map((size) => __awaiter(this, void 0, void 0, function* () {
|
247 | const { manifestIcon, webpackAsset } = yield processImageAsync(size, icon, publicPath, cacheKey);
|
248 | icons.push(manifestIcon);
|
249 | assets.push(webpackAsset);
|
250 | })),
|
251 | ];
|
252 | }
|
253 | yield Promise.all(promises);
|
254 | yield clearUnusedCachesAsync(projectRoot);
|
255 | return {
|
256 | icons: sortByAttribute(icons, 'sizes'),
|
257 |
|
258 | assets: sortByAttribute(assets, 'output'),
|
259 | };
|
260 | });
|
261 | }
|
262 | exports.parseIconsAsync = parseIconsAsync;
|
263 | function sortByAttribute(arr, key) {
|
264 | return arr.filter(Boolean).sort((valueA, valueB) => {
|
265 | if (valueA[key] < valueB[key])
|
266 | return -1;
|
267 | else if (valueA[key] > valueB[key])
|
268 | return 1;
|
269 | return 0;
|
270 | });
|
271 | }
|
272 | function clearUnusedCachesAsync(projectRoot) {
|
273 | return __awaiter(this, void 0, void 0, function* () {
|
274 |
|
275 | const cacheFolder = path_1.default.join(projectRoot, '.expo/web/cache/production/images');
|
276 | const currentCaches = yield fs_extra_1.default.readdir(cacheFolder);
|
277 | const deleteCachePromises = [];
|
278 | for (const cache of currentCaches) {
|
279 |
|
280 | if (cache.startsWith('.')) {
|
281 | continue;
|
282 | }
|
283 |
|
284 | if (!(cache in cacheKeys)) {
|
285 | deleteCachePromises.push(fs_extra_1.default.remove(path_1.default.join(cacheFolder, cache)));
|
286 | }
|
287 | }
|
288 | yield Promise.all(deleteCachePromises);
|
289 | });
|
290 | }
|
291 |
|
\ | No newline at end of file |