// Imports const {basename, dirname, resolve: resolvePath} = require('canonical-path'); const {mkdirSync, readFileSync, writeFileSync} = require('fs'); const {parse: parseJson} = require('json5'); // Constants const FIREBASE_CONFIG_PATH = resolvePath(__dirname, '../firebase.json'); const NGSW_CONFIG_TEMPLATE_PATH = resolvePath(__dirname, '../ngsw-config.template.json'); const NGSW_CONFIG_OUTPUT_PATH = resolvePath(__dirname, '../src/generated/ngsw-config.json'); // Run _main(); // Helpers function _main() { const firebaseConfig = readJson(FIREBASE_CONFIG_PATH); const ngswConfig = readJson(NGSW_CONFIG_TEMPLATE_PATH); const firebaseRedirects = firebaseConfig.hosting.redirects; // Check that there are no regex-based redirects. const regexBasedRedirects = firebaseRedirects.filter(({regex}) => regex !== undefined); if (regexBasedRedirects.length > 0) { throw new Error( 'The following redirects use `regex`, which is currently not supported by ' + `${basename(__filename)}:` + regexBasedRedirects.map(x => `\n - ${JSON.stringify(x)}`).join('')); } // Check that there are no unsupported glob characters/patterns. const redirectsWithUnsupportedGlobs = firebaseRedirects .filter(({source}) => !/^(?:[/\w\-.*]|:\w+|@\([\w\-.|]+\))+$/.test(source)); if (redirectsWithUnsupportedGlobs.length > 0) { throw new Error( 'The following redirects use glob characters/patterns that are currently not supported by ' + `${basename(__filename)}:` + redirectsWithUnsupportedGlobs.map(x => `\n - ${JSON.stringify(x)}`).join('')); } // Compute additional ignore glob patterns to be added to `navigationUrls`. const additionalNavigationUrls = firebaseRedirects // Grab the redirect source glob. .map(({source}) => source) // Ignore redirects for files (since these are already ignored by the SW). .filter(glob => /\/[^/.]*$/.test(glob)) // Convert each Firebase-specific glob to a SW-compatible glob. .map(glob => `!${glob.replace(/:\w+/g, '*').replace(/@(\([\w\-.|]+\))/g, '$1')}`) // Add optional trailing `/` for globs that don't end with `*`. .map(glob => /\w$/.test(glob) ? `${glob}\/{0,1}` : glob) // Remove more specific globs that are covered by more generic ones. .filter((glob, _i, globs) => !isGlobRedundant(glob, globs)) // Sort the generated globs alphabetically. .sort(); // Add the additional `navigationUrls` globs and save the config. ngswConfig.navigationUrls.push(...additionalNavigationUrls); mkdirSync(dirname(NGSW_CONFIG_OUTPUT_PATH), {recursive: true}); writeJson(NGSW_CONFIG_OUTPUT_PATH, ngswConfig); } function isGlobRedundant(glob, globs) { // Find all globs that could cover other globs. // For simplicity, we only consider globs ending with `/**`. const genericGlobs = globs.filter(g => g.endsWith('/**')); // A glob is redundant if it starts with the path of a potentially generic glob (excluding the // trailing `**`) followed by a word character or a `*`. // For example, `/foo/bar/baz` is covered (and thus made redundant) by `/foo/**`, but `/foo/{0,1}` // is not. return genericGlobs.some(g => { const pathPrefix = g.slice(0, -2); return (glob !== g) && glob.startsWith(pathPrefix) && /^[\w*]/.test(glob.slice(pathPrefix.length)); }); } function readJson(filePath) { return parseJson(readFileSync(filePath, 'utf8')); } function writeJson(filePath, obj) { return writeFileSync(filePath, `${JSON.stringify(obj, null, 2)}\n`); }