forked from MapComplete/MapComplete
		
	Refactoring: automatically generate code files from layer/theme files to avoid using 'Eval'
This commit is contained in:
		
							parent
							
								
									865b0bc44f
								
							
						
					
					
						commit
						39944a01fb
					
				
					 17 changed files with 269 additions and 31 deletions
				
			
		|  | @ -12,8 +12,8 @@ mkdir dist/assets 2> /dev/null | |||
| export NODE_OPTIONS="--max-old-space-size=8192" | ||||
| 
 | ||||
| # This script ends every line with '&&' to chain everything. A failure will thus stop the build | ||||
| npm run generate:editor-layer-index && | ||||
| npm run generate && | ||||
| # npm run generate:editor-layer-index && | ||||
| # npm run generate && | ||||
| npm run generate:layouts | ||||
| 
 | ||||
| if [ $? -ne 0 ]; then | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ import { Utils } from "../src/Utils" | |||
| import Script from "./Script" | ||||
| import { AllSharedLayers } from "../src/Customizations/AllSharedLayers" | ||||
| import { parse as parse_html } from "node-html-parser" | ||||
| import { ExtraFunctions } from "../src/Logic/ExtraFunctions" | ||||
| // This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files.
 | ||||
| // It spits out an overview of those to be used to load them
 | ||||
| 
 | ||||
|  | @ -395,10 +396,129 @@ class LayerOverviewUtils extends Script { | |||
|                 skippedLayers.length + | ||||
|                 " layers" | ||||
|         ) | ||||
|         // We always need the calculated tags of 'usersettings', so we export them separately
 | ||||
|         this.extractJavascriptCodeForLayer( | ||||
|             state.sharedLayers.get("usersettings"), | ||||
|             "./src/Logic/State/UserSettingsMetaTagging.ts" | ||||
|         ) | ||||
| 
 | ||||
|         return sharedLayers | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given: a fully expanded themeConfigJson | ||||
|      * | ||||
|      * Will extract a dictionary of the special code and write it into a javascript file which can be imported. | ||||
|      * This removes the need for _eval_, allowing for a correct CSP | ||||
|      * @param themeFile | ||||
|      * @private | ||||
|      */ | ||||
|     private extractJavascriptCode(themeFile: LayoutConfigJson) { | ||||
|         const allCode = [ | ||||
|             "import {Feature} from 'geojson'", | ||||
|             'import { ExtraFuncType } from "../../../Logic/ExtraFunctions";', | ||||
|             'import { Utils } from "../../../Utils"', | ||||
|             "export class ThemeMetaTagging {", | ||||
|             "   public static readonly themeName = " + JSON.stringify(themeFile.id), | ||||
|             "", | ||||
|         ] | ||||
|         for (const layer of themeFile.layers) { | ||||
|             const l = <LayerConfigJson>layer | ||||
|             const code = l.calculatedTags ?? [] | ||||
| 
 | ||||
|             allCode.push( | ||||
|                 "   public metaTaggging_for_" + | ||||
|                     l.id + | ||||
|                     "(feat: Feature, helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>) {" | ||||
|             ) | ||||
|             allCode.push("      const {" + ExtraFunctions.types.join(", ") + "} = helperFunctions") | ||||
|             for (const line of code) { | ||||
|                 const firstEq = line.indexOf("=") | ||||
|                 let attributeName = line.substring(0, firstEq).trim() | ||||
|                 const expression = line.substring(firstEq + 1) | ||||
|                 const isStrict = attributeName.endsWith(":") | ||||
|                 if (!isStrict) { | ||||
|                     allCode.push( | ||||
|                         "      Utils.AddLazyProperty(feat.properties, '" + | ||||
|                             attributeName + | ||||
|                             "', () => " + | ||||
|                             expression + | ||||
|                             " ) " | ||||
|                     ) | ||||
|                 } else { | ||||
|                     attributeName = attributeName.substring(0, attributeName.length - 2).trim() | ||||
|                     allCode.push("      feat.properties['" + attributeName + "'] = " + expression) | ||||
|                 } | ||||
|             } | ||||
|             allCode.push("   }") | ||||
|         } | ||||
| 
 | ||||
|         const targetDir = "./src/assets/generated/metatagging/" | ||||
|         if (!existsSync(targetDir)) { | ||||
|             mkdirSync(targetDir) | ||||
|         } | ||||
|         allCode.push("}") | ||||
| 
 | ||||
|         writeFileSync(targetDir + themeFile.id + ".ts", allCode.join("\n")) | ||||
|     } | ||||
| 
 | ||||
|     private extractJavascriptCodeForLayer(l: LayerConfigJson, targetPath?: string) { | ||||
|         let importPath = "../../../" | ||||
|         if (targetPath) { | ||||
|             const l = targetPath.split("/") | ||||
|             if (l.length == 1) { | ||||
|                 importPath = "./" | ||||
|             } else { | ||||
|                 importPath = "" | ||||
|                 for (let i = 0; i < l.length - 3; i++) { | ||||
|                     const _ = l[i] | ||||
|                     importPath += "../" | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         const allCode = [ | ||||
|             `import { Utils } from "${importPath}Utils"`, | ||||
|             `/** This code is autogenerated - do not edit. Edit ./assets/layers/${l.id}/${l.id}.json instead */`, | ||||
|             "export class ThemeMetaTagging {", | ||||
|             "   public static readonly themeName = " + JSON.stringify(l.id), | ||||
|             "", | ||||
|         ] | ||||
|         const code = l.calculatedTags ?? [] | ||||
| 
 | ||||
|         allCode.push( | ||||
|             "   public metaTaggging_for_" + l.id + "(feat: {properties: Record<string, string>}) {" | ||||
|         ) | ||||
|         for (const line of code) { | ||||
|             const firstEq = line.indexOf("=") | ||||
|             let attributeName = line.substring(0, firstEq).trim() | ||||
|             const expression = line.substring(firstEq + 1) | ||||
|             const isStrict = attributeName.endsWith(":") | ||||
|             if (!isStrict) { | ||||
|                 allCode.push( | ||||
|                     "      Utils.AddLazyProperty(feat.properties, '" + | ||||
|                         attributeName + | ||||
|                         "', () => " + | ||||
|                         expression + | ||||
|                         " ) " | ||||
|                 ) | ||||
|             } else { | ||||
|                 attributeName = attributeName.substring(0, attributeName.length - 2).trim() | ||||
|                 allCode.push("      feat.properties['" + attributeName + "'] = " + expression) | ||||
|             } | ||||
|         } | ||||
|         allCode.push("   }") | ||||
|         allCode.push("}") | ||||
| 
 | ||||
|         const targetDir = "./src/assets/generated/metatagging/" | ||||
|         if (!targetPath) { | ||||
|             if (!existsSync(targetDir)) { | ||||
|                 mkdirSync(targetDir) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         writeFileSync(targetPath ?? targetDir + "layer_" + l.id + ".ts", allCode.join("\n")) | ||||
|     } | ||||
| 
 | ||||
|     private buildThemeIndex( | ||||
|         licensePaths: Set<string>, | ||||
|         sharedLayers: Map<string, LayerConfigJson>, | ||||
|  | @ -436,6 +556,7 @@ class LayerOverviewUtils extends Script { | |||
|         }) | ||||
| 
 | ||||
|         const skippedThemes: string[] = [] | ||||
| 
 | ||||
|         for (let i = 0; i < themeFiles.length; i++) { | ||||
|             const themeInfo = themeFiles[i] | ||||
|             const themePath = themeInfo.path | ||||
|  | @ -443,6 +564,7 @@ class LayerOverviewUtils extends Script { | |||
| 
 | ||||
|             const targetPath = | ||||
|                 LayerOverviewUtils.themePath + "/" + themePath.substring(themePath.lastIndexOf("/")) | ||||
| 
 | ||||
|             const usedLayers = Array.from( | ||||
|                 LayerOverviewUtils.extractLayerIdsFrom(themeFile, false) | ||||
|             ).map((id) => LayerOverviewUtils.layerPath + id + ".json") | ||||
|  | @ -504,6 +626,8 @@ class LayerOverviewUtils extends Script { | |||
| 
 | ||||
|                 this.writeTheme(themeFile) | ||||
|                 fixed.set(themeFile.id, themeFile) | ||||
| 
 | ||||
|                 this.extractJavascriptCode(themeFile) | ||||
|             } catch (e) { | ||||
|                 console.error("ERROR: could not prepare theme " + themePath + " due to " + e) | ||||
|                 throw e | ||||
|  |  | |||
|  | @ -200,6 +200,26 @@ function asLangSpan(t: Translation, tag = "span"): string { | |||
|     return values.join("\n") | ||||
| } | ||||
| 
 | ||||
| let cspCached: string = undefined | ||||
| function generateCsp(): string { | ||||
|     if (cspCached !== undefined) { | ||||
|         return cspCached | ||||
|     } | ||||
| 
 | ||||
|     const csp = { | ||||
|         "default-src": "'self'", | ||||
|         "script-src": "'self'", | ||||
|         "img-src": "*", | ||||
|         "connect-src": "*", | ||||
|     } | ||||
|     const content = Object.keys(csp) | ||||
|         .map((k) => k + ": " + csp[k]) | ||||
|         .join("; ") | ||||
| 
 | ||||
|     cspCached = `<meta http-equiv="Content-Security-Policy" content="${content}">` | ||||
|     return cspCached | ||||
| } | ||||
| 
 | ||||
| async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alreadyWritten) { | ||||
|     Locale.language.setData(layout.language[0]) | ||||
|     const targetLanguage = layout.language[0] | ||||
|  | @ -279,6 +299,7 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr | |||
|             Translations.t.general.poweredByOsm.textFor(targetLanguage) | ||||
|         ) | ||||
|         .replace(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific) | ||||
|         .replace(/<!-- CSP -->/, generateCsp()) | ||||
|         .replace( | ||||
|             /<!-- DESCRIPTION START -->.*<!-- DESCRIPTION END -->/s, | ||||
|             asLangSpan(layout.shortDescription) | ||||
|  | @ -298,7 +319,12 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr | |||
| 
 | ||||
| async function createIndexFor(theme: LayoutConfig) { | ||||
|     const filename = "index_" + theme.id + ".ts" | ||||
|     writeFileSync(filename, `import layout from "./src/assets/generated/themes/${theme.id}.json"\n`) | ||||
| 
 | ||||
|     const imports = [ | ||||
|         `import layout from "./src/assets/generated/themes/${theme.id}.json"`, | ||||
|         `import { ThemeMetaTagging } from "./src/assets/generated/metatagging/${theme.id}"`, | ||||
|     ] | ||||
|     writeFileSync(filename, imports.join("\n") + "\n") | ||||
| 
 | ||||
|     appendFileSync(filename, codeTemplate) | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue