scripts/build_data.js (200 lines of code) (raw):

/* eslint-disable no-console */ import chalk from 'chalk'; import fs from 'node:fs'; import stringify from 'json-stringify-pretty-compact'; import shell from 'shelljs'; import YAML from 'js-yaml'; import fetch from 'node-fetch'; import * as languageNames from './language_names.js'; // fontawesome icons import fontawesome from '@fortawesome/fontawesome-svg-core'; import { fas } from '@fortawesome/free-solid-svg-icons'; import { far } from '@fortawesome/free-regular-svg-icons'; import { fab } from '@fortawesome/free-brands-svg-icons'; import territoryInfo from 'cldr-core/supplemental/territoryInfo.json' assert {type: 'json'}; fontawesome.library.add(fas, far, fab); let _currBuild = null; // if called directly, do the thing. if (process.argv[1].indexOf('build_data.js') > -1) { buildData(); } else { module.exports = buildData; } function buildData() { if (_currBuild) return _currBuild; const START = '🏗 ' + chalk.yellow('Building data...'); const END = '👍 ' + chalk.green('data built'); console.log(''); console.log(START); console.time(END); // Create symlinks if necessary.. { 'target': 'source' } const symlinks = { 'land.html': 'dist/land.html', img: 'dist/img' }; for (let target of Object.keys(symlinks)) { if (!shell.test('-L', target)) { console.log(`Creating symlink: ${target} -> ${symlinks[target]}`); shell.ln('-sf', symlinks[target], target); } } // Start clean shell.rm('-f', [ 'data/territory_languages.json', 'dist/locales/en.json', 'dist/data/*', 'svg/fontawesome/*.svg', ]); // compile Font Awesome icons let faIcons = new Set([ // list here the icons we want to use in the UI that aren't tied to other data 'fas-filter', 'fas-i-cursor', 'fas-lock', 'fas-palette', 'fas-th-list', 'fas-user-cog' ]); // add icons for QA integrations readQAIssueIcons(faIcons); let territoryLanguages = generateTerritoryLanguages(); fs.writeFileSync('data/territory_languages.json', stringify(territoryLanguages, { maxLength: 9999 }) ); writeEnJson(); const languageInfo = languageNames.langNamesInNativeLang(); fs.writeFileSync('data/languages.json', stringify(languageInfo, { maxLength: 200 })); fs.writeFileSync('dist/data/languages.min.json', JSON.stringify(languageInfo)); // Save individual data files let tasks = [ minifyJSON('data/address_formats.json', 'dist/data/address_formats.min.json'), minifyJSON('data/imagery.json', 'dist/data/imagery.min.json'), minifyJSON('data/intro_graph.json', 'dist/data/intro_graph.min.json'), minifyJSON('data/intro_rapid_graph.json', 'dist/data/intro_rapid_graph.min.json'), minifyJSON('data/keepRight.json', 'dist/data/keepRight.min.json'), minifyJSON('data/languages.json', 'dist/data/languages.min.json'), minifyJSON('data/phone_formats.json', 'dist/data/phone_formats.min.json'), minifyJSON('data/qa_data.json', 'dist/data/qa_data.min.json'), minifyJSON('data/shortcuts.json', 'dist/data/shortcuts.min.json'), minifyJSON('data/territory_languages.json', 'dist/data/territory_languages.min.json'), // writeRapidConfig(), Promise.all([ // Fetch the icons that are needed by the expected tagging schema version fetch('https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/presets.min.json'), fetch('https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/preset_categories.min.json'), fetch('https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@3/dist/fields.min.json'), // WARNING: we fetch the bleeding edge data too to make sure we're always hosting the // latest icons, but note that the format could break at any time fetch('https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/presets.min.json'), fetch('https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/preset_categories.min.json'), fetch('https://raw.githubusercontent.com/openstreetmap/id-tagging-schema/main/dist/fields.min.json') ]) .then(responses => Promise.all(responses.map(response => response.json()))) .then((results) => { // compile the icons used by all the presets results.forEach(function(data) { for (var key in data) { var datum = data[key]; // fontawesome icon if (datum.icon && /^fa[srb]-/.test(datum.icon)) { faIcons.add(datum.icon); } } }); // copy over only those Font Awesome icons that we need writeFaIcons(faIcons); }) ]; return _currBuild = Promise.all(tasks) .then(() => { console.timeEnd(END); console.log(''); _currBuild = null; }) .catch((err) => { console.error(err); console.log(''); _currBuild = null; process.exit(1); }); } function readQAIssueIcons(faIcons) { const qa = JSON.parse(fs.readFileSync('data/qa_data.json', 'utf8')); for (const service in qa) { for (const item in qa[service].icons) { const icon = qa[service].icons[item]; // fontawesome icon, remember for later if (/^fa[srb]-/.test(icon)) { faIcons.add(icon); } } } } function generateTerritoryLanguages() { let allRawInfo = territoryInfo.supplemental; let territoryLanguages = {}; Object.keys(allRawInfo).forEach(territoryCode => { let territoryLangInfo = allRawInfo[territoryCode].languagePopulation; if (!territoryLangInfo) return; let langCodes = Object.keys(territoryLangInfo); territoryLanguages[territoryCode.toLowerCase()] = langCodes.sort((langCode1, langCode2) => { let popPercent1 = parseFloat(territoryLangInfo[langCode1]._populationPercent); let popPercent2 = parseFloat(territoryLangInfo[langCode2]._populationPercent); if (popPercent1 === popPercent2) { return langCode1.localeCompare(langCode2, 'en', { sensitivity: 'base' }); } return popPercent2 - popPercent1; }).map(langCode => langCode.replace('_', '-')); }); return territoryLanguages; } function writeEnJson() { const readCoreYaml = fs.readFileSync('data/core.yaml', 'utf8'); const readImagery = fs.readFileSync('node_modules/editor-layer-index/i18n/en.yaml', 'utf8'); const readCommunity = fs.readFileSync('node_modules/osm-community-index/i18n/en.yaml', 'utf8'); const readManualImagery = fs.readFileSync('data/manual_imagery.json', 'utf8'); return Promise.all([readCoreYaml, readImagery, readCommunity, readManualImagery]) .then(data => { let core = YAML.load(data[0]); let imagery = YAML.load(data[1]); let community = YAML.load(data[2]); let manualImagery = JSON.parse(data[3]); for (let i in manualImagery) { let layer = manualImagery[i]; let id = layer.id; for (let key in layer) { if (key === 'attribution') { for (let attrKey in layer[key]) { if (attrKey !== 'text') { delete layer[key][attrKey]; } } } else if (['name', 'description'].indexOf(key) === -1) { delete layer[key]; } } // tack on strings for additional imagery not included in the index imagery.en.imagery[id] = layer; } let enjson = core; let props = ['imagery', 'community', 'languageNames', 'scriptNames']; props.forEach(function(prop) { if (enjson.en[prop]) { console.error(`Error: Reserved property '${prop}' already exists in core strings`); process.exit(1); } }); enjson.en.imagery = imagery.en.imagery; enjson.en.community = community.en; enjson.en.languageNames = languageNames.languageNamesInLanguageOf('en'); enjson.en.scriptNames = languageNames.scriptNamesInLanguageOf('en'); fs.writeFileSync('dist/locales/en.min.json', JSON.stringify(enjson)); }); } function writeFaIcons(faIcons) { Array.from(faIcons).forEach(function(key) { const prefix = key.substring(0, 3); // `fas`, `far`, `fab` const name = key.substring(4); const def = fontawesome.findIconDefinition({ prefix: prefix, iconName: name }); try { fs.writeFileSync(`svg/fontawesome/${key}.svg`, fontawesome.icon(def).html.toString()); } catch (error) { console.error(`Error: No FontAwesome icon for ${key}`); throw (error); } }); } function minifyJSON(inPath, outPath) { return new Promise((resolve, reject) => { fs.readFile(inPath, 'utf8', (err, data) => { if (err) return reject(err); const minified = JSON.stringify(JSON.parse(data)); fs.writeFile(outPath, minified, (err) => { if (err) return reject(err); resolve(); }); }); }); } export default buildData;