feat: well known files

This commit is contained in:
Gregor Kleen 2020-01-11 22:02:12 +01:00
parent 6b51cc5e53
commit 068632b117
24 changed files with 830 additions and 1642 deletions

5
.gitignore vendored
View File

@ -35,5 +35,6 @@ test.log
/.stack-work.lock
/.npmrc
/config/webpack.yml
static/wp-*/
/config/favicon.json
/static
/well-known
/**/tmp-*

View File

@ -33,7 +33,7 @@ npm install:
- n stable
- npm install -g npm
- hash -r
- apt-get install openssh-client -y
- apt-get -y install openssh-client exiftool
- install -v -m 0700 -d ~/.ssh
- install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts
- install -v -T -m 0400 ${SSH_DEPLOY_KEY} ~/.ssh/deploy && echo "IdentityFile ~/.ssh/deploy" >> ~/.ssh/config;

77
config/favicon.json Normal file
View File

@ -0,0 +1,77 @@
{
"masterPicture": "assets/favicon.svg",
"design": {
"desktop_browser": {},
"ios": {
"picture_aspect": "background_and_margin",
"margin": "5%",
"background_color": "#ffffff",
"startup_image": {
"background_color": "#ffffff"
},
"app_name": "Uni2work",
"assets": {
"ios6_and_prior_icons": false,
"ios7_and_later_icons": true,
"precomposed_icons": true,
"declare_only_default_icon": true
}
},
"windows": {
"picture_aspect": "white_silhouette",
"background_color": "#0a9342",
"app_name": "Uni2work"
},
"firefox_app": {
"picture_aspect": "circle",
"keep_picture_in_circle": false,
"circle_inner_margin": "5%",
"background_color": "#ffffff",
"overlay": false,
"manifest": {
"app_name": "Uni2work",
"app_description": {
"_i18n": true,
"de-de-formal": "Ein webbasiertes Lehrverwaltungssystem der LMU München",
"en-eu": "A web based teaching management system at LMU Munich"
},
"developer_name": "Uni2work-Team",
"developer_url": "https://uni2work.ifi.lmu.de/info",
"display": "browser",
"start_url": "/"
}
},
"android_chrome": {
"picture_aspect": "shadow",
"manifest": {
"name": "Uni2work",
"display": "browser",
"orientation": "portrait",
"start_url": "/"
},
"assets": {
"legacy_icon": true,
"low_resolution_icons": false
}
},
"safari_pinned_tab": {
"picture_aspect": "silhouette",
"theme_color": "#0a9342"
},
"coast": {
"picture_aspect": "background_and_margin",
"background_color": "#ffffff",
"margin": "10%"
},
"open_graph": {
"picture_aspect": "background_and_margin",
"background_color": "#ffffff",
"margin": "10%",
"ratio": "square"
}
},
"settings": {
"html_code_file": true
},
"versioning": false
}

View File

@ -43,6 +43,8 @@ application/java-vm class
application/javascript js
application/json json
application/jsonml+json jsonml
application/manifest+json webmanifest
application/x-web-app-manifest+json webapp
application/lost+xml lostxml
application/mac-binhex40 hqx
application/mac-compactpro cpt

View File

@ -4,8 +4,10 @@
# See https://github.com/yesodweb/yesod/wiki/Configuration#parsing-numeric-values-as-strings
static-dir: "_env:STATIC_DIR:static"
well-known-dir: "_env:WELL_KNOWN_DIR:well-known"
well-known-link-file: "html_code.html"
webpack-manifest: "_env:WEBPACK_MANIFEST:config/webpack.yml"
favicon-stats: "_env:FAVICON_STATS:config/favicon.json"
host: "_env:HOST:*4" # any IPv4 host
port: "_env:PORT:3000"
ip-from-header: "_env:IP_FROM_HEADER:false"

View File

@ -1208,8 +1208,7 @@ BreadcrumbTerm: Semester
BreadcrumbSchool: Institut
BreadcrumbUser: Benutzer
BreadcrumbStatic: Statische Resource
BreadcrumbFavicon: Favicon
BreadcrumbRobots: robots.txt
BreadcrumbWellKnown: Benannte statische Resource
BreadcrumbMetrics: Metriken
BreadcrumbLecturerInvite: Einladung zum Kursverwalter
BreadcrumbExamOfficeUserInvite: Einladung bzgl. Prüfungsleistungen

View File

@ -1207,8 +1207,7 @@ BreadcrumbTerm: Semester
BreadcrumbSchool: Department
BreadcrumbUser: User
BreadcrumbStatic: Static resource
BreadcrumbFavicon: Favicon
BreadcrumbRobots: robots.txt
BreadcrumbWellKnown: Named static resource
BreadcrumbMetrics: Metrics
BreadcrumbLecturerInvite: Invitation to be a course administrator
BreadcrumbExamOfficeUserInvite: Invitation regarding exam achievements

1964
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -60,7 +60,6 @@
"@commitlint/cli": "^8.2.0",
"@commitlint/config-conventional": "^8.2.0",
"@fortawesome/fontawesome-pro": "^5.12.0",
"app-manifest-webpack-plugin": "^1.2.0",
"autoprefixer": "^9.7.3",
"babel-core": "^6.26.3",
"babel-eslint": "^10.0.3",
@ -73,6 +72,7 @@
"css-loader": "^2.1.1",
"eslint": "^5.16.0",
"file-loader": "^5.0.2",
"glob": "^7.1.6",
"html-webpack-plugin": "^3.2.0",
"husky": "^2.7.0",
"jasmine-core": "^3.5.0",
@ -92,6 +92,7 @@
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0",
"real-favicon-webpack-plugin": "^0.2.3",
"remove-files-webpack-plugin": "^1.1.3",
"resolve-url-loader": "^3.1.1",
"sass": "^1.23.7",
@ -100,6 +101,7 @@
"standard-version": "^6.0.1",
"style-loader": "^0.23.1",
"terser-webpack-plugin": "^2.2.3",
"tmp": "^0.1.0",
"typeface-roboto": "0.0.75",
"typeface-source-sans-pro": "0.0.75",
"webpack": "^4.41.2",

5
routes
View File

@ -39,8 +39,6 @@
/static StaticR EmbeddedStatic appStatic !free
/auth AuthR Auth getAuth !free
/favicon.ico FaviconR GET !free
/robots.txt RobotsR GET !free
/metrics MetricsR GET
/ HomeR GET !free
@ -204,6 +202,7 @@
/msgs MessageListR GET POST
/msg/#{CryptoUUIDSystemMessage} MessageR GET POST !timeANDreadANDauthentication
!/#UUID CryptoUUIDDispatchR GET !free -- just redirect
-- !/*{CI FilePath} CryptoFileNameDispatchR GET !free -- Disabled until preliminary check for valid cID exists
!/*WellKnownFileName WellKnownR GET !free

View File

@ -19,7 +19,7 @@ let
'';
override = oldAttrs: {
nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ nodejs-12_x postgresql openldap google-chrome ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install weeder ]);
nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ nodejs-12_x postgresql openldap google-chrome exiftool ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install weeder ]);
shellHook = ''
export PROMPT_INFO="${oldAttrs.name}"

View File

@ -101,7 +101,6 @@ import Data.List (cycle)
-- Import all relevant handler modules here.
-- (HPack takes care to add new modules to our cabal file nowadays.)
import Handler.Common
import Handler.Home
import Handler.Info
import Handler.Help

View File

@ -1575,8 +1575,8 @@ siteLayout' headingOverride widget = do
pc <- widgetToPageContent $ do
webpackLinks_main StaticR
faviconLinks
toWidget $(juliusFile "templates/i18n.julius")
wellKnownHtmlLinks
$(widgetFile "default-layout")
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
@ -1627,8 +1627,7 @@ i18nCrumb msg mbR = do
instance YesodBreadcrumbs UniWorX where
breadcrumb (AuthR _) = i18nCrumb MsgMenuLogin $ Just HomeR
breadcrumb (StaticR _) = i18nCrumb MsgBreadcrumbStatic Nothing
breadcrumb FaviconR = i18nCrumb MsgBreadcrumbFavicon Nothing
breadcrumb RobotsR = i18nCrumb MsgBreadcrumbRobots Nothing
breadcrumb (WellKnownR _) = i18nCrumb MsgBreadcrumbWellKnown Nothing
breadcrumb MetricsR = i18nCrumb MsgBreadcrumbMetrics Nothing
breadcrumb HomeR = i18nCrumb MsgMenuHome Nothing

View File

@ -1,29 +0,0 @@
-- | Common handler functions.
module Handler.Common
( getFaviconR
, getRobotsR
) where
import Data.FileEmbed (embedFile)
import Import hiding (embedFile)
-- These handlers embed files in the executable at compile time to avoid a
-- runtime dependency, and for efficiency.
getFaviconR :: Handler TypedContent
getFaviconR = do
let content = $(embedFile "static/favicon.ico")
setEtagHashable content
return $ TypedContent "image/x-icon"
$ toContent content
getRobotsR :: Handler TypedContent
getRobotsR = do
let content = $(embedFile "static/robots.txt")
setEtagHashable content
return $ TypedContent typePlain
$ toContent content

View File

@ -12,8 +12,9 @@ import Utils.Tokens as Import
import Utils.Frontend.Modal as Import
import Utils.Lens as Import
import Settings as Import
import Settings.StaticFiles as Import
import Settings as Import
import Settings.StaticFiles as Import
import Settings.WellKnownFiles as Import
import CryptoID as Import
import Audit as Import

View File

@ -78,7 +78,8 @@ data AppSettings = AppSettings
{ appStaticDir :: FilePath
-- ^ Directory from which to serve static files.
, appWebpackEntrypoints :: FilePath
, appFaviconStats :: FilePath
, appWellKnownDir :: FilePath
, appWellKnownLinkFile :: FilePath
, appDatabaseConf :: PostgresConf
-- ^ Configuration settings for accessing the database.
, appAutoDbMigrate :: Bool
@ -370,8 +371,9 @@ instance FromJSON AppSettings where
False
#endif
appStaticDir <- o .: "static-dir"
appWellKnownDir <- o .: "well-known-dir"
appWellKnownLinkFile <- o .: "well-known-link-file"
appWebpackEntrypoints <- o .: "webpack-manifest"
appFaviconStats <- o .: "favicon-stats"
appDatabaseConf <- o .: "database"
appAutoDbMigrate <- o .: "auto-db-migrate"
let nonEmptyHost LdapConf{..} = case ldapHost of

View File

@ -1,14 +1,19 @@
{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
-- Listing only files directly used by consumers of this module
-- prevents rebuilds if files change, that are not directly used (like
-- webpack bundles)
module Settings.StaticFiles
( module Settings.StaticFiles
( img_lmu_sigillum_svg
, webpackLinks_main
, embeddedStatic
, module Yesod.EmbeddedStatic
) where
import ClassyPrelude.Yesod
import Settings (appStaticDir, appWebpackEntrypoints, appFaviconStats, compileTimeAppSettings)
import Settings (appStaticDir, appWebpackEntrypoints, compileTimeAppSettings)
import Settings.StaticFiles.Generator
import Settings.StaticFiles.Webpack
import Settings.StaticFiles.Favicon
import Yesod.EmbeddedStatic
-- This generates easy references to files in the static directory at compile time,
@ -28,6 +33,3 @@ import Yesod.EmbeddedStatic
mkEmbeddedStatic DEV_BOOL "embeddedStatic" . pure . staticGenerator $ appStaticDir compileTimeAppSettings
mkWebpackEntrypoints (appWebpackEntrypoints compileTimeAppSettings) (pure staticGenerator) $ appStaticDir compileTimeAppSettings
faviconLinks :: MonadWidget m => m ()
faviconLinks = $(mkFaviconLinks $ appFaviconStats compileTimeAppSettings)

View File

@ -1,38 +0,0 @@
module Settings.StaticFiles.Favicon
( mkFaviconLinks
) where
import ClassyPrelude.Yesod
import Language.Haskell.TH
import Language.Haskell.TH.Syntax hiding (Lift(..))
import qualified Language.Haskell.TH.Syntax as TH (Lift(..))
import qualified Data.Aeson as JSON
import qualified Data.Map as Map
import Text.Blaze.Html (preEscapedToHtml)
mkFaviconLinks :: FilePath -- ^ Path to JSON-manifest
-> ExpQ
mkFaviconLinks manifest = do
addDependentFile manifest
htmlFragments <- decodeManifest manifest
doE $ map (\frag -> noBindS [e|toWidgetHead $ preEscapedToHtml $(TH.lift frag)|]) htmlFragments
where
decodeManifest :: FilePath -> Q [Text]
decodeManifest manifest' = do
res <- liftIO $ JSON.eitherDecodeFileStrict' manifest'
case res of
Left err -> fail err
Right res'
| Just frags' <- res' Map.!? ("html" :: Text)
, JSON.Success frags <- JSON.fromJSON frags'
-> return frags
| otherwise
-> fail "Could not parse favicon stats"

View File

@ -0,0 +1,11 @@
module Settings.WellKnownFiles
( WellKnownFileName
, getWellKnownR
, wellKnownHtmlLinks
) where
import Settings.WellKnownFiles.TH
import Settings (appWellKnownDir, appWellKnownLinkFile, compileTimeAppSettings)
mkWellKnown "de-de-formal" (appWellKnownDir compileTimeAppSettings) (appWellKnownLinkFile compileTimeAppSettings)

View File

@ -0,0 +1,192 @@
module Settings.WellKnownFiles.TH
( mkWellKnown
) where
import ClassyPrelude.Yesod
import Utils
import Language.Haskell.TH
import Language.Haskell.TH.Syntax hiding (Lift(..))
import qualified Language.Haskell.TH.Syntax as TH (Lift(..))
import System.Directory.Tree
import qualified Data.ByteString as BS
import Utils.Lens.TH
import Control.Lens
import Data.Set.Lens
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import Data.Char as Char (isAlphaNum, toUpper)
import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.List.NonEmpty (NonEmpty(..))
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import qualified Data.HashSet as HashSet
import System.FilePath ((</>), splitDirectories, makeRelative)
import Settings.Mime
import Text.Blaze.Html (preEscapedToHtml)
nWellKnownFileName :: Name
nWellKnownFileName = mkName "WellKnownFileName"
nwellKnownFileNames :: Name
nwellKnownFileNames = mkName "wellKnownFileNames"
ngetWellKnownR :: Name
ngetWellKnownR = mkName "getWellKnownR"
nwellKnownHtmlLinks :: Name
nwellKnownHtmlLinks = mkName "wellKnownHtmlLinks"
mkWellKnown :: Lang -- ^ Default language
-> FilePath -- ^ Base directory
-> FilePath -- ^ Link file (@html_code.html@)
-> DecsQ
mkWellKnown defLang wellKnownBase wellKnownLinks = do
inputFiles <- fmap dirTree . liftIO $ readDirectoryWith (\f -> (f, ) <$> BS.readFile f) wellKnownBase
mapM_ qAddDependentFile $ inputFiles ^.. folded . _1
-- languageFiles :: Map Lang [(FilePath, ByteString)]
languageFiles <- if
| Dir{contents} <- inputFiles
-> return . Map.fromList $ do
(language, lContents) <- contents ^.. folded . $(multifocusL 2) _name id
Dir{} <- pure lContents
let
lContents' :: [(FilePath, ByteString)]
lContents' = flip mapMaybe (flattenDir lContents) $ \pFile -> do
File{..} <- pure pFile
guard $ name /= wellKnownLinks
return $ file & _1 %~ makeRelative (wellKnownBase </> language)
return (Text.pack language, lContents')
| otherwise
-> fail "wellKnownBase is not a directory"
fLanguages <- if
| defLang `Set.member` Map.keysSet languageFiles
, let languages' = Set.delete defLang $ Map.keysSet languageFiles
-> return $ defLang :| Set.toList languages'
| otherwise
-> fail "default language is missing in wellKnownBase"
-- languageLinks :: Map Lang ByteString
languageLinks <- if
| Dir{contents} <- inputFiles
-> return . Map.fromList $ do
(language, lContents) <- contents ^.. folded . $(multifocusL 2) _name id
Dir{} <- pure lContents
let
lContents' :: [ByteString]
lContents' = flip mapMaybe (flattenDir lContents) $ \pFile -> do
File{..} <- pure pFile
guard $ name == wellKnownLinks
return $ file ^. _2
c <- lContents'
return (Text.pack language, c)
| otherwise
-> fail "wellKnownBase is not a directory"
lLanguages <- if
| defLang `Set.member` Map.keysSet languageLinks
, let languages' = Set.delete defLang $ Map.keysSet languageLinks
-> return $ defLang :| Set.toList languages'
| otherwise
-> fail "default language is missing in wellKnownBase"
fVar <- newName "f"
hVar <- newName "h"
lVar <- newName "l"
let fileNames = setOf (folded . folded . _1) languageFiles
fileContents = Map.fromListWith (<>) $ do
(lang, fs) <- Map.toList languageFiles
(fName, fContent) <- fs
return ((fContent, mimeLookup $ Text.pack fName), Set.singleton (lang, fName))
wellKnownFileName = dataD
(cxt [])
nWellKnownFileName
[]
Nothing
[ normalC (mkName $ fNameManip fName) []
| fName <- Set.toList fileNames
]
(pure $ derivClause Nothing [[t|Eq|], [t|Ord|], [t|Bounded|], [t|Enum|], [t|Read|], [t|Show|], [t|Generic|], [t|Typeable|]])
wellKnownFileNameMapSig = sigD
nwellKnownFileNames
[t|HashMap [Text] $(conT nWellKnownFileName)|]
wellKnownFileNameMap = funD
nwellKnownFileNames
[ clause [] (normalB $ [e|HashMap.fromList|] `appE` listE [ [e|($(TH.lift . map Text.pack $ splitDirectories fName), $(conE . mkName $ fNameManip fName))|] | fName <- Set.toList fileNames ]) []
]
wellKnownFileNamePathMultiPiece = instanceD
(cxt [])
(conT ''PathMultiPiece `appT` conT nWellKnownFileName)
[ funD 'toPathMultiPiece
[ clause [conP (mkName $ fNameManip fName) []] (normalB . TH.lift . map Text.pack $ splitDirectories fName) []
| fName <- Set.toList fileNames
]
, funD 'fromPathMultiPiece $
[ clause [] (normalB [e|flip HashMap.lookup $(varE nwellKnownFileNames)|]) []
]
]
wellKnownFileNameHashable = instanceD
(cxt [])
(conT ''Hashable `appT` conT nWellKnownFileName)
[]
getWellKnownRSig = sigD
ngetWellKnownR
[t|forall m. MonadHandler m => $(conT nWellKnownFileName) -> m TypedContent|]
getWellKnownR = funD
ngetWellKnownR
[ clause [varP fVar] (normalB [e|$(varE hVar) =<< selectLanguage fLanguages|])
[ funD hVar $
[ clause [varP lVar] (guardedB
[ (,) <$> normalG [e|HashSet.member ($(varE lVar), $(varE fVar)) $ HashSet.fromList $(listE [ tupE [TH.lift l, conE . mkName $ fNameManip fName] | (l, fName) <- Set.toList xs ])|]
<*> [e|TypedContent mime (toContent fContent) <$ setEtag $(TH.lift $ hashToText (mime, fContent))|]
]) []
| ((fContent, mime), xs) <- Map.toList fileContents
] ++ pure (clause [wildP] (normalB [e|notFound|]) [])
]
]
wellKnownHtmlLinksSig = sigD
nwellKnownHtmlLinks
[t|forall m. MonadWidget m => m ()|]
wellKnownHtmlLinks = funD
nwellKnownHtmlLinks
[ clause [] (normalB [e|toWidgetHead . preEscapedToHtml . $(varE hVar) =<< selectLanguage lLanguages|])
[ sigD hVar [t|Text -> Text|]
, funD hVar $
[ clause [varP lVar] (guardedB
[ (,) <$> normalG [|$(varE lVar) == lang|]
<*> TH.lift (Text.filter (`notElem` ['\r', '\n']) $ Text.decodeUtf8 c)
]) []
| (lang, c) <- Map.toList languageLinks
] ++ pure (clause [wildP] (normalB [e|mempty|]) [])
]
]
sequence
[ wellKnownFileName, wellKnownFileNameMapSig, wellKnownFileNameMap, wellKnownFileNamePathMultiPiece, wellKnownFileNameHashable
, getWellKnownRSig, getWellKnownR
, wellKnownHtmlLinksSig, wellKnownHtmlLinks
]
where
fNameManip = Text.unpack . mconcat . over (traverse . _head) Char.toUpper . filter (not . null) . Text.split (not . isAlphaNum) . Text.pack

View File

@ -913,11 +913,11 @@ cachedHereBinary = do
[e| \k -> cachedByBinary (loc, k) |]
hashToText :: Hashable a => a -> Text
hashToText = decodeUtf8 . Base64.encode . toStrict . Binary.encode . hash
hashToText = Text.dropWhileEnd (== '=') . decodeUtf8 . Base64.encode . toStrict . Binary.encode . hash
setEtagHashable, setWeakEtagHashable :: (MonadHandler m, Hashable a) => a -> m ()
setEtagHashable = setEtag . hashToText
setWeakEtagHashable = setEtag . hashToText
setWeakEtagHashable = setWeakEtag . hashToText
setLastModified :: MonadHandler m => UTCTime -> m ()
setLastModified lastModified = do

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

View File

@ -1,5 +1,11 @@
const webpack = require('webpack');
const path = require('path');
const tmp = require('tmp');
tmp.setGracefulCleanup();
const fs = require('fs');
const glob = require('glob');
const { execSync } = require('child_process');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
@ -9,8 +15,8 @@ const TerserPlugin = require('terser-webpack-plugin');
const yaml = require('js-yaml');
const HashOutput = require('webpack-plugin-hash-output');
const postcssPresetEnv = require('postcss-preset-env');
const AppManifestWebpackPlugin = require('app-manifest-webpack-plugin');
const RemovePlugin = require('remove-files-webpack-plugin');
const RealFaviconPlugin = require('real-favicon-webpack-plugin');
const webpackVersion = require('webpack/package.json').version.split('.').slice(0, 2).join('.');
const packageVersion = require('./package.json').version;
@ -116,7 +122,9 @@ module.exports = {
serialize: yaml.safeDump
}),
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: path.resolve(__dirname, 'static', 'wp-*')
cleanOnceBeforeBuildPatterns: [ path.resolve(__dirname, 'static'),
path.resolve(__dirname, 'well-known'),
]
}),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new CopyPlugin([
@ -125,38 +133,64 @@ module.exports = {
new webpack.DefinePlugin({
VERSION: JSON.stringify(packageVersion)
}),
new AppManifestWebpackPlugin({
logo: path.resolve(__dirname, 'assets/favicon.svg'),
output: '/[hash]-icons/',
prefix: `/static/res/wp-${webpackVersion}/`,
inject: false,
emitStats: true,
statsFilename: path.resolve(__dirname, 'config/favicon.json'),
persistentCache: false,
config: {
background: '#fff',
icons: {
android: false,
appleIcon: false,
appleStartup: false,
coast: false,
favicons: true,
firefox: false,
windows: false,
yandex: false
...(() => {
const faviconJson = require('./config/favicon.json');
const langs = new Set();
function findLangs(json) {
if (json && json._i18n) {
Object.keys(json).forEach(key => {
if (key !== '_i18n') {
langs.add(key);
}
})
} else if (Array.isArray(json)) {
json.forEach(elem => findLangs(elem));
} else if (typeof json === 'object') {
Object.keys(json).forEach(key => findLangs(json[key]));
}
}
}),
new RemovePlugin({
after: {
test: [
{ folder: path.resolve(__dirname, `static/wp-${webpackVersion}`),
method: (filePath) => { return new RegExp(/\/.*-icons\/.*\.(xml|json|webapp)$/, 'm').test(filePath); },
recursive: true
}
]
findLangs(faviconJson);
function selectLang(lang, json) {
if (json && json._i18n) {
return json[lang];
} else if (Array.isArray(json)) {
return json.map(elem => selectLang(lang, elem));
} else if (typeof json === 'object') {
return Object.fromEntries(Object.entries(json).map(([k, v]) => [k, selectLang(lang, v)]));
} else {
return json;
}
}
})
const langJsons = {};
Array.from(langs).forEach(lang => {
langJsons[lang] = selectLang(lang, faviconJson);
});
return Array.from(langs).map(lang => {
const tmpobj = tmp.fileSync({ dir: ".", postfix: ".json" });
fs.writeSync(tmpobj.fd, JSON.stringify(langJsons[lang]));
fs.close(tmpobj.fd);
return [
new RealFaviconPlugin({
faviconJson: `./${tmpobj.name}`,
outputPath: path.resolve(__dirname, 'well-known', lang),
inject: false
}),
new CopyPlugin([
{ from: 'config/robots.txt', to: path.resolve(__dirname, 'well-known', lang, 'robots.txt') },
])
];
}).flat(1);
})(),
{ apply: compiler => compiler.hooks.afterEmit.tap('AfterEmitPlugin', compilation => {
Array.from(glob.sync(path.resolve(__dirname, 'well-known', '**', '*.@(png)'))).forEach(imageFile => {
execSync(`exiftool -overwrite_original -all= ${imageFile}`, { stdio: 'inherit' });
});
})
}
],
output: {