From 72a7fe406410f149e277fd5b1255a078f4b50d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96nder=20Ceylan?= Date: Wed, 11 Sep 2019 15:11:24 +0200 Subject: [PATCH] feat(main): refactored main and added index.d.ts Refactored main.js file to return saved images array, html content of meta tags and manifest json icons array. Added interfaces and docs via index.d.ts and updated readme. fix #5 --- .eslintrc | 1 + .npmignore | 2 + README.md | 27 +++- __snapshots__/cli.test.js.snap | 7 + cli.js | 72 +-------- cli.test.js | 10 +- config/constants.js | 66 ++++++++ helpers/file.js | 8 +- helpers/flags.js | 13 ++ helpers/logger.js | 21 ++- helpers/url.js | 3 +- index.d.ts | 286 +++++++++++++++++++++++++++++++++ main.js | 13 +- package.json | 1 + puppets.js | 18 +-- 15 files changed, 452 insertions(+), 96 deletions(-) create mode 100644 index.d.ts diff --git a/.eslintrc b/.eslintrc index 3187d18f..418663d8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -18,5 +18,6 @@ "ecmaVersion": 2018 }, "rules": { + "no-prototype-builtins": "off" } } diff --git a/.npmignore b/.npmignore index e1a60b2b..5aeff52d 100644 --- a/.npmignore +++ b/.npmignore @@ -10,4 +10,6 @@ __snapshots__/* .prettierrc .travis.yml .releaserc +.idea +.github *.test.js diff --git a/README.md b/README.md index 169cf916..b023b3c2 100644 --- a/README.md +++ b/README.md @@ -66,15 +66,16 @@ $ pwa-asset-generator --help -b --background Page background to use when image source is provided: css value [default: transparent] -o --opaque Making screenshots to be saved with a background color [default: true] -p --padding Padding to use when image source provided: css value [default: "10%"] - -s --scrape Scraping Apple Human Interface Guidelines to fetch splash screen specs [default: true] - -m --manifest Web app manifest file path to automatically update manifest file with the generated images - -i --index Index html file path to automatically put splash screen meta tags in + -s --scrape Scraping Apple Human Interface guidelines to fetch splash screen specs [default: true] + -m --manifest Web app manifest file path to automatically update manifest file with the generated icons + -i --index Index html file path to automatically put splash screen and icon meta tags in -t --type Image type: png|jpeg [default: png] -q --quality Image quality: 0...100 (Only for JPEG) [default: 100] -h --splash-only Only generate splash screens [default: false] -c --icon-only Only generate icons [default: false] -l --landscape-only Only generate landscape splash screens [default: false] -r --portrait-only Only generate portrait splash screens [default: false] + -g --log Logs the steps of the library process [default: true] Examples $ pwa-asset-generator logo.html . @@ -95,6 +96,26 @@ $ pwa-asset-generator --help --icon-only --landscape-only --portrait-only + --log=false +``` + +### Module + +```javascript +const pwaAssetGenerator = require('pwa-asset-generator'); + +(async () => { + const { savedImages, htmlContent, manifestJsonContent } = await pwaAssetGenerator.generateImages( + 'https://raw.githubusercontent.com/onderceylan/pwa-asset-generator/HEAD/static/logo.png', + './temp', + { + scrape: false, + background: "linear-gradient(to right, #fa709a 0%, #fee140 100%)", + splashOnly: true, + portraitOnly: true, + log: false + }); +})(); ``` ## Troubleshooting diff --git a/__snapshots__/cli.test.js.snap b/__snapshots__/cli.test.js.snap index 54157b32..c7a588a0 100644 --- a/__snapshots__/cli.test.js.snap +++ b/__snapshots__/cli.test.js.snap @@ -16,6 +16,7 @@ exports[`generates icons and splash screens when both only flags exist 1`] = ` ] + @@ -82,6 +83,7 @@ exports[`generates icons and splash screens when both only flags exist 1`] = ` + " `; @@ -101,12 +103,14 @@ exports[`generates icons only 1`] = ` ] + + " `; @@ -144,6 +148,7 @@ exports[`generates landscape splash screens only 1`] = ` + " `; @@ -181,6 +186,7 @@ exports[`generates portrait splash screens only 1`] = ` + " `; @@ -248,5 +254,6 @@ exports[`generates splash screens only 1`] = ` + " `; diff --git a/cli.js b/cli.js index 4dbb70bb..d08d7f7d 100755 --- a/cli.js +++ b/cli.js @@ -3,8 +3,8 @@ const meow = require('meow'); const preLogger = require('./helpers/logger'); const main = require('./main'); +const { FLAGS: flags } = require('./config/constants'); -const logger = preLogger('cli'); const cli = meow( ` $ pwa-asset-generator --help @@ -18,15 +18,16 @@ $ pwa-asset-generator --help -b --background Page background to use when image source is provided: css value [default: transparent] -o --opaque Making screenshots to be saved with a background color [default: true] -p --padding Padding to use when image source provided: css value [default: "10%"] - -s --scrape Scraping Apple Human Interface Guidelines to fetch splash screen specs [default: true] - -m --manifest Web app manifest file path to automatically update manifest file with the generated images - -i --index Index html file path to automatically put splash screen meta tags in + -s --scrape Scraping Apple Human Interface guidelines to fetch splash screen specs [default: true] + -m --manifest Web app manifest file path to automatically update manifest file with the generated icons + -i --index Index html file path to automatically put splash screen and icon meta tags in -t --type Image type: png|jpeg [default: png] -q --quality Image quality: 0...100 (Only for JPEG) [default: 100] -h --splash-only Only generate splash screens [default: false] -c --icon-only Only generate icons [default: false] -l --landscape-only Only generate landscape splash screens [default: false] -r --portrait-only Only generate portrait splash screens [default: false] + -g --log Logs the steps of the library process [default: true] Examples $ pwa-asset-generator logo.html . @@ -47,70 +48,13 @@ $ pwa-asset-generator --help --icon-only --landscape-only --portrait-only + --log=false `, { - flags: { - background: { - type: 'string', - alias: 'b', - default: 'transparent', - }, - manifest: { - type: 'string', - alias: 'm', - }, - index: { - type: 'string', - alias: 'i', - }, - opaque: { - type: 'boolean', - alias: 'o', - default: true, - }, - scrape: { - type: 'boolean', - alias: 's', - default: true, - }, - padding: { - type: 'string', - alias: 'p', - default: '10%', - }, - type: { - type: 'string', - alias: 't', - default: 'png', - }, - quality: { - type: 'number', - alias: 'q', - default: 100, - }, - splashOnly: { - type: 'boolean', - alias: 'h', - default: false, - }, - iconOnly: { - type: 'boolean', - alias: 'c', - default: false, - }, - landscapeOnly: { - type: 'boolean', - alias: 'l', - default: false, - }, - portraitOnly: { - type: 'boolean', - alias: 'r', - default: false, - }, - }, + flags, }, ); +const logger = preLogger('cli', cli.flags); (async () => { try { diff --git a/cli.test.js b/cli.test.js index 96c4f508..50ae51a4 100644 --- a/cli.test.js +++ b/cli.test.js @@ -29,7 +29,7 @@ test('generates icons only', async () => { const { stdout } = await execa( './cli.js', ['./static/logo.png', './temp', '-s=false', '--icon-only'], - { env: { PAG_NO_TRACE: '1' } }, + { env: { PAG_TEST_MODE: '1' } }, ); expect(stdout).toMatchSnapshot(); @@ -41,7 +41,7 @@ test('generates splash screens only', async () => { const { stdout } = await execa( './cli.js', ['./static/logo.png', './temp', '-s=false', '--splash-only'], - { env: { PAG_NO_TRACE: '1' } }, + { env: { PAG_TEST_MODE: '1' } }, ); expect(stdout).toMatchSnapshot(); @@ -59,7 +59,7 @@ test('generates portrait splash screens only', async () => { '--splash-only', '--portrait-only', ], - { env: { PAG_NO_TRACE: '1' } }, + { env: { PAG_TEST_MODE: '1' } }, ); expect(stdout).toMatchSnapshot(); @@ -77,7 +77,7 @@ test('generates landscape splash screens only', async () => { '--splash-only', '--landscape-only', ], - { env: { PAG_NO_TRACE: '1' } }, + { env: { PAG_TEST_MODE: '1' } }, ); expect(stdout).toMatchSnapshot(); @@ -89,7 +89,7 @@ test('generates icons and splash screens when both only flags exist', async () = const { stdout } = await execa( './cli.js', ['./static/logo.png', './temp', '-s=false', '--splash-only', '--icon-only'], - { env: { PAG_NO_TRACE: '1' } }, + { env: { PAG_TEST_MODE: '1' } }, ); expect(stdout).toMatchSnapshot(); diff --git a/config/constants.js b/config/constants.js index 42ccbcd9..a5d8a7f8 100644 --- a/config/constants.js +++ b/config/constants.js @@ -1,4 +1,70 @@ module.exports = { + FLAGS: { + background: { + type: 'string', + alias: 'b', + default: 'transparent', + }, + manifest: { + type: 'string', + alias: 'm', + }, + index: { + type: 'string', + alias: 'i', + }, + opaque: { + type: 'boolean', + alias: 'o', + default: true, + }, + scrape: { + type: 'boolean', + alias: 's', + default: true, + }, + padding: { + type: 'string', + alias: 'p', + default: '10%', + }, + type: { + type: 'string', + alias: 't', + default: 'png', + }, + quality: { + type: 'number', + alias: 'q', + default: 100, + }, + splashOnly: { + type: 'boolean', + alias: 'h', + default: false, + }, + iconOnly: { + type: 'boolean', + alias: 'c', + default: false, + }, + landscapeOnly: { + type: 'boolean', + alias: 'l', + default: false, + }, + portraitOnly: { + type: 'boolean', + alias: 'r', + default: false, + }, + log: { + type: 'boolean', + alias: 'g', + default: true, + }, + }, + PUPPETEER_LAUNCH_ARGS: [ '--log-level=3', // Fatal only '--no-default-browser-check', diff --git a/helpers/file.js b/helpers/file.js index 35dba969..35efe348 100644 --- a/helpers/file.js +++ b/helpers/file.js @@ -32,7 +32,13 @@ const isHtmlFile = file => { }; const getAppDir = () => { - return path.dirname(require.main.filename); + let appPath; + try { + appPath = require.resolve('pwa-asset-generator'); + } catch (e) { + appPath = require.main.filename; + } + return path.dirname(appPath); }; const getShellHtmlFilePath = () => { diff --git a/helpers/flags.js b/helpers/flags.js index ee6b545f..ca3718ba 100644 --- a/helpers/flags.js +++ b/helpers/flags.js @@ -1,3 +1,5 @@ +const constants = require('../config/constants'); + const normalizeOnlyFlagPairs = (flag1Key, flag2Key, opts, logger) => { const stripOnly = key => key.replace('Only', ''); if (opts[flag1Key] && opts[flag2Key]) { @@ -23,7 +25,18 @@ const normalizeOutput = output => { return output; }; +const getDefaultOptions = () => { + const { FLAGS: flags } = constants; + + return Object.keys(flags) + .filter(flagKey => flags[flagKey].hasOwnProperty('default')) + .reduce((acc, curr) => { + return { ...acc, [curr]: flags[curr].default }; + }, {}); +}; + module.exports = { normalizeOnlyFlagPairs, normalizeOutput, + getDefaultOptions, }; diff --git a/helpers/logger.js b/helpers/logger.js index d3eced84..00300e04 100644 --- a/helpers/logger.js +++ b/helpers/logger.js @@ -1,8 +1,11 @@ const chalk = require('chalk'); -const noTrace = !!+process.env.PAG_NO_TRACE; +const testMode = !!+process.env.PAG_TEST_MODE; + +const logger = (prefix, options) => { + const isLogEnabled = + options && options.hasOwnProperty('log') ? options.log : true; -const logger = prefix => { const getTime = () => { return chalk.inverse(new Date().toLocaleTimeString()); }; @@ -12,18 +15,23 @@ const logger = prefix => { }; /* eslint-disable no-console */ + const raw = (...args) => { + if (!isLogEnabled) return; + console.log(...args); + }; + const log = (...args) => { - if (noTrace) return; + if (testMode || !isLogEnabled) return; console.log(getTime(), getPrefix(), ...args); }; const warn = (...args) => { - if (noTrace) return; + if (testMode || !isLogEnabled) return; console.warn(getTime(), getPrefix(), chalk.yellow(...args), '🤔'); }; const trace = (...args) => { - if (noTrace) return; + if (testMode || !isLogEnabled) return; console.trace(getTime(), getPrefix(), ...args); }; @@ -32,12 +40,13 @@ const logger = prefix => { }; const success = (...args) => { - if (noTrace) return; + if (testMode || !isLogEnabled) return; console.log(getTime(), getPrefix(), chalk.green(...args), '🙌'); }; /* eslint-enable no-console */ return { + raw, log, warn, trace, diff --git a/helpers/url.js b/helpers/url.js index a61b1e5f..11a3849f 100644 --- a/helpers/url.js +++ b/helpers/url.js @@ -25,12 +25,13 @@ const isUrlExists = source => { }; const getAddress = async (source, options) => { - const logger = preLogger(getAddress.name); + const logger = preLogger(getAddress.name, options); const useShell = async (isSourceUrl = false) => { try { await file.saveHtmlShell(source, options, isSourceUrl); } catch (e) { + logger.error(e); throw Error('Failed saving html shell'); } diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 00000000..cf419d7b --- /dev/null +++ b/index.d.ts @@ -0,0 +1,286 @@ +declare namespace PwaAssetGenerator { + + interface Options { + /** + Page background to use when image source is provided + Same as css background property + + @default transparent + */ + readonly background?: string; + + /** + Making screenshots to be saved with a background color + Uses white background when background option is not provided + + @default true + */ + readonly opaque?: boolean; + + /** + Padding to use when image source provided + Same as css padding property + + @default "10%" + */ + readonly padding?: string; + + /** + Scraping Apple Human Interface guidelines to fetch splash screen specs + + @default true + */ + readonly scrape?: boolean; + + /** + Web app manifest file path to automatically update manifest file with the generated icons + */ + readonly manifest?: string; + + /** + Index html file path to automatically put splash screen and icon meta tags in + */ + readonly index?: string; + + /** + Image type + + @default png + */ + readonly type?: 'png' | 'jpeg'; + + /** + Image quality: 0...100 + Enabled only for jpeg image type + + @default 100 + */ + readonly quality?: number; + + /** + Only generate splash screens + + @default false + */ + readonly splashOnly?: boolean; + + /** + Only generate icons + + @default false + */ + readonly iconOnly?: boolean; + + /** + Only generate landscape splash screens + Disabled when iconOnly option is provided + + @default false + */ + readonly landscapeOnly?: boolean; + + /** + Only generate portrait splash screens + Disabled when iconOnly option is provided + + @default false + */ + readonly portraitOnly?: boolean; + } + + interface SavedImage { + /** + Name of the saved image file, without file extension + */ + name: string; + + /** + Image width in pixels + */ + width: number; + + /** + Image height in pixels + */ + height: number; + + /** + Device scale factor used for generating HTML meta tags for iOS splash screens + Defaults to null for icons + + @default null + */ + scaleFactor: number | null; + + /** + Saved image path + Path is relative to execution folder + */ + path: string; + + /** + Device orientation used for generating HTML meta tags for iOS splash screens + Defaults to null for icons + + @default null + */ + orientation: 'landscape' | 'portrait' | null; + } + + interface ManifestJsonIcon { + /** + A URL from which a user agent can fetch the image's data + + @tutorial https://www.w3.org/TR/appmanifest/#dom-imageresource-src + @example //icons.example.com/lowres + */ + src: string; + + /** + A string consisting of an unordered set of unique space-separated tokens + + @tutorial https://www.w3.org/TR/appmanifest/#sizes-member + @example 192x192 + */ + sizes?: string; + + /** + Media type of the image + The purpose of this member is to allow a user agent to ignore images of media types it does not support + + @tutorial https://www.w3.org/TR/appmanifest/#dom-imageresource-type + @example image/png + */ + type?: string; + + /** + When an ImageResource is used as an icon, a developer can hint that + the image is intended to serve some special purpose in the context of the host OS + + @tutorial https://www.w3.org/TR/appmanifest/#dfn-icon-purposes + @default any + */ + purpose?: 'badge' | 'maskable' | 'any'; + + /** + The platform member represents the platform to which a containing object applies + + @tutorial https://github.com/w3c/manifest/wiki/Platforms + */ + platform?: 'chrome_web_store' | 'play' | 'itunes' | 'windows'; + } + + interface Result { + /** + Saved images array that keeps both splash screens and icons, with image properties + + @example + ```javascript + [{ + name: 'apple-splash-1136-640', + width: 1136, + height: 640, + scaleFactor: 2, + path: 'temp/apple-splash-1136-640.png', + orientation: 'landscape' + }, + { + name: 'apple-icon-180', + width: 180, + height: 180, + scaleFactor: null, + path: 'temp/apple-icon-180.png', + orientation: null + }] + ``` + */ + savedImages: SavedImage[]; + + /** + Meta tags to be added to index.html file + + @example + ```html + + + + + ``` + */ + htmlContent: string; + + /** + Icons to be added to manifest.json's icons property + + @example + ```json + [{ + "src": "assets/pwa/manifest-icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "assets/pwa/manifest-icon-512.png", + "sizes": "512x512", + "type": "image/png" + }] + ``` + */ + manifestJsonContent: ManifestJsonIcon[]; + } + + /** + Logger function to print out steps of the lib + + @param prefix - Shows the origin of the log, e.g. function name + @param options - Option flags of the library in an object + */ + interface Logger { + (prefix: string, options?: Options): { + raw(): string; + log(): string; + warn(): string; + trace(): string; + error(): string; + success(): string; + } + } +} + +declare const pwaAssetGenerator: { + /** + Generates PWA assets based on a source input and saves generated images in the output folder provided + + @param source - A local image file, a local HTML file, a remote image or remote HTML file path + @param outputFolderPath - The path of the folder to save the images in + @param options - Option flags of the library in an object, keeps default values + @param logger - An optional logger function to log the output + @returns A promise of result object that resolves when all images are generated and file updates are finalized + + @example + ```javascript + import pwaAssetGenerator = require('pwa-asset-generator'); + + (async () => { + const { savedImages, htmlContent, manifestJsonContent } = await pwaAssetGenerator.generateImages( + 'https://raw.githubusercontent.com/onderceylan/pwa-asset-generator/HEAD/static/logo.png', + './temp', + { + scrape: false, + background: "linear-gradient(to right, #fa709a 0%, #fee140 100%)", + splashOnly: true, + portraitOnly: true, + log: false + }); + })(); + ``` + */ + generateImages( + source: string, + outputFolderPath: string, + options?: PwaAssetGenerator.Options, + logger?: PwaAssetGenerator.Logger, + ): Promise; +}; + +export = pwaAssetGenerator; diff --git a/main.js b/main.js index 612d4add..ee575d41 100644 --- a/main.js +++ b/main.js @@ -3,14 +3,15 @@ const puppets = require('./puppets'); const flags = require('./helpers/flags'); const preLogger = require('./helpers/logger'); -const generateImages = async (source, _output, _options, loggerInjection) => { - const logger = loggerInjection || preLogger(generateImages.name); +const generateImages = async (source, _output, _options, loggerFn) => { + const logger = loggerFn || preLogger(generateImages.name, _options); if (!source) { throw Error('Please specify a URL or file path as a source'); } const options = { + ...flags.getDefaultOptions(), ..._options, ...flags.normalizeOnlyFlagPairs('splashOnly', 'iconOnly', _options, logger), ...flags.normalizeOnlyFlagPairs( @@ -43,9 +44,7 @@ const generateImages = async (source, _output, _options, loggerInjection) => { logger.success( 'Below is the icons content for your manifest.json file. You can copy/paste it manually', ); - process.stdout.write( - `\n${JSON.stringify(manifestJsonContent, null, 2)}\n\n`, - ); + logger.raw(`\n${JSON.stringify(manifestJsonContent, null, 2)}\n\n`); } } @@ -61,10 +60,10 @@ const generateImages = async (source, _output, _options, loggerInjection) => { logger.success( 'Below is the iOS meta tags content for your index.html file. You can copy/paste it manually', ); - process.stdout.write(`\n${htmlContent}\n`); + logger.raw(`\n${htmlContent}\n`); } - return savedImages; + return { savedImages, htmlContent, manifestJsonContent }; }; module.exports = { diff --git a/package.json b/package.json index fb4c2820..9f036f5c 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.1.7", "description": "PWA asset generator based on Puppeteer. Automatically generates icons and splash screens guided by Web App Manifest specs and Apple Human Interface guidelines. Updates manifest.json and index.html files with the generated images.", "main": "main.js", + "types": "index.d.ts", "bin": { "pwa-asset-generator": "cli.js" }, diff --git a/puppets.js b/puppets.js index 1467d449..92e57179 100644 --- a/puppets.js +++ b/puppets.js @@ -6,8 +6,8 @@ const file = require('./helpers/file'); const images = require('./helpers/images'); const preLogger = require('./helpers/logger'); -const getAppleSplashScreenData = async browser => { - const logger = preLogger(getAppleSplashScreenData.name); +const getAppleSplashScreenData = async (browser, options) => { + const logger = preLogger(getAppleSplashScreenData.name, options); const page = await browser.newPage(); await page.setUserAgent(constants.EMULATED_USER_AGENT); logger.log( @@ -86,8 +86,8 @@ const getAppleSplashScreenData = async browser => { return splashScreenData; }; -const getDeviceScaleFactorData = async browser => { - const logger = preLogger(getDeviceScaleFactorData.name); +const getDeviceScaleFactorData = async (browser, options) => { + const logger = preLogger(getDeviceScaleFactorData.name, options); const page = await browser.newPage(); await page.setUserAgent(constants.EMULATED_USER_AGENT); logger.log( @@ -156,7 +156,7 @@ const getDeviceScaleFactorData = async browser => { }; const getSplashScreenMetaData = async options => { - const logger = preLogger(getSplashScreenMetaData.name); + const logger = preLogger(getSplashScreenMetaData.name, options); if (!options.scrape) { logger.log( @@ -178,8 +178,8 @@ const getSplashScreenMetaData = async options => { let splashScreenUniformMetaData; try { - const splashScreenData = await getAppleSplashScreenData(browser); - const scaleFactorData = await getDeviceScaleFactorData(browser); + const splashScreenData = await getAppleSplashScreenData(browser, options); + const scaleFactorData = await getDeviceScaleFactorData(browser, options); splashScreenUniformMetaData = images.getSplashScreenScaleFactorUnionData( splashScreenData, scaleFactorData, @@ -199,7 +199,7 @@ const getSplashScreenMetaData = async options => { }; const saveImages = async (imageList, source, output, options) => { - const logger = preLogger(saveImages.name); + const logger = preLogger(saveImages.name, options); logger.log('Initialising puppeteer to take screenshots', '🤖'); const address = await url.getAddress(source, options); @@ -243,7 +243,7 @@ const saveImages = async (imageList, source, output, options) => { }; const generateImages = async (source, output, options) => { - const logger = preLogger(generateImages.name); + const logger = preLogger(generateImages.name, options); const splashScreenMetaData = await getSplashScreenMetaData(options); const allImages = [ ...(!options.iconOnly