Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 1 | // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | 'use strict'; |
| 5 | const fs = require('fs'); |
| 6 | const path = require('path'); |
| 7 | |
| 8 | const utils = require('../utils'); |
| 9 | |
| 10 | const FRONTEND_PATH = path.resolve(__dirname, '..', '..', 'front_end'); |
| 11 | const BUILD_GN_PATH = path.resolve(__dirname, '..', '..', 'BUILD.gn'); |
Yang Guo | 75beda9 | 2019-10-28 08:29:25 +0100 | [diff] [blame^] | 12 | const SPECIAL_CASE_NAMESPACES_PATH = path.resolve(__dirname, '..', 'build', 'special_case_namespaces.json'); |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 13 | |
| 14 | /* |
| 15 | * This is used to extract a new module from an existing module by: |
| 16 | * - Moving selected files into new modules (including relevant |
| 17 | * css files) |
| 18 | * - Renaming all identifiers to the new namespace |
| 19 | * - Updating the BUILD.gn and module.json files (including extensions) |
| 20 | * ========================================== |
| 21 | * START EDITING HERE - TRANSFORMATION INPUTS |
| 22 | * ========================================== |
| 23 | */ |
| 24 | |
| 25 | const APPLICATION_DESCRIPTORS = [ |
| 26 | 'devtools_app.json', |
| 27 | 'js_app.json', |
| 28 | 'shell.json', |
| 29 | 'worker_app.json', |
| 30 | 'inspector.json', |
| 31 | 'toolbox.json', |
| 32 | 'integration_test_runner.json', |
| 33 | 'formatter_worker.json', |
| 34 | 'heap_snapshot_worker.json', |
| 35 | ]; |
| 36 | |
| 37 | /* |
| 38 | * If the transformation removes all the files of a module: |
| 39 | * ['text_editor'] |
| 40 | */ |
| 41 | const MODULES_TO_REMOVE = ['profiler_test_runner', 'heap_snapshot_test_runner']; |
| 42 | |
| 43 | /** |
| 44 | * If moving to a new module: |
| 45 | * {file: 'common/Text.js', new: 'a_new_module'} |
| 46 | * |
| 47 | * If moving to an existing module: |
| 48 | * {file: 'ui/SomeFile.js', existing: 'common'} |
| 49 | */ |
| 50 | const JS_FILES_MAPPING = [ |
| 51 | // {file: 'heap_snapshot_test_runner/HeapSnapshotTestRunner.js', new: 'heap_profiler_test_runner'}, |
| 52 | // {file: 'profiler_test_runner/ProfilerTestRunner.js', new: 'cpu_profiler_test_runner'}, |
| 53 | {file: 'network_log/HAREntry.js', existing: 'browser_sdk'}, |
| 54 | ]; |
| 55 | |
| 56 | /** |
| 57 | * List all new modules here: |
| 58 | * mobile_throttling: { |
| 59 | * dependencies: ['sdk'], |
| 60 | * dependents: ['console'], |
| 61 | * applications: ['inspector.json'], |
| 62 | * autostart: false, |
| 63 | * } |
| 64 | */ |
| 65 | const MODULE_MAPPING = { |
| 66 | // heap_profiler_test_runner: { |
| 67 | // dependencies: ['heap_snapshot_worker', 'test_runner'], |
| 68 | // dependents: [], |
| 69 | // applications: ['integration_test_runner.json'], |
| 70 | // autostart: false, |
| 71 | // }, |
| 72 | // cpu_profiler_test_runner: { |
| 73 | // dependencies: ['profiler', 'test_runner'], |
| 74 | // dependents: [], |
| 75 | // applications: ['integration_test_runner.json'], |
| 76 | // autostart: false, |
| 77 | // }, |
| 78 | }; |
| 79 | |
| 80 | /** |
| 81 | * If an existing module will have a new dependency on an existing module: |
| 82 | * console: ['new_dependency'] |
| 83 | */ |
| 84 | const NEW_DEPENDENCIES_BY_EXISTING_MODULES = { |
| 85 | // resources: ['components'], |
| 86 | }; |
| 87 | |
| 88 | /** |
| 89 | * If an existing module will no longer have a dependency on a module: |
| 90 | * console: ['former_dependency'] |
| 91 | */ |
| 92 | const REMOVE_DEPENDENCIES_BY_EXISTING_MODULES = { |
| 93 | // console_test_runner: ['main'] |
| 94 | }; |
| 95 | |
| 96 | /* |
| 97 | * ========================================== |
| 98 | * STOP EDITING HERE |
| 99 | * ========================================== |
| 100 | */ |
| 101 | |
| 102 | const DEPENDENCIES_BY_MODULE = Object.keys(MODULE_MAPPING).reduce((acc, module) => { |
| 103 | acc[module] = MODULE_MAPPING[module].dependencies; |
| 104 | return acc; |
| 105 | }, {}); |
| 106 | |
| 107 | const APPLICATIONS_BY_MODULE = Object.keys(MODULE_MAPPING).reduce((acc, module) => { |
| 108 | acc[module] = MODULE_MAPPING[module].applications; |
| 109 | return acc; |
| 110 | }, {}); |
| 111 | |
| 112 | const DEPENDENTS_BY_MODULE = Object.keys(MODULE_MAPPING).reduce((acc, module) => { |
| 113 | acc[module] = MODULE_MAPPING[module].dependents; |
| 114 | return acc; |
| 115 | }, {}); |
| 116 | |
| 117 | function extractModule() { |
| 118 | const modules = new Set(); |
| 119 | for (let fileObj of JS_FILES_MAPPING) { |
| 120 | let moduleName = fileObj.file.split('/')[0]; |
| 121 | modules.add(moduleName); |
| 122 | } |
| 123 | const newModuleSet = JS_FILES_MAPPING.reduce((acc, file) => file.new ? acc.add(file.new) : acc, new Set()); |
| 124 | const targetToOriginalFilesMap = JS_FILES_MAPPING.reduce((acc, f) => { |
| 125 | let components = f.file.split('/'); |
| 126 | components[0] = f.new || f.existing; |
| 127 | acc.set(components.join('/'), f.file); |
| 128 | return acc; |
| 129 | }, new Map()); |
| 130 | |
| 131 | const cssFilesMapping = findCSSFiles(); |
| 132 | console.log('cssFilesMapping', cssFilesMapping); |
| 133 | const identifiersByFile = calculateIdentifiers(); |
| 134 | const identifierMap = mapIdentifiers(identifiersByFile, cssFilesMapping); |
| 135 | console.log('identifierMap', identifierMap); |
| 136 | const extensionMap = removeFromExistingModuleDescriptors(modules, identifierMap, cssFilesMapping); |
| 137 | |
| 138 | // Find out which files are moving extensions |
| 139 | for (let e of extensionMap.keys()) { |
| 140 | for (let [f, identifiers] of identifiersByFile) { |
| 141 | if (identifiers.includes(e)) |
| 142 | console.log(`extension: ${e} in file: ${f}`); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | moveFiles(cssFilesMapping); |
| 147 | createNewModuleDescriptors(extensionMap, cssFilesMapping, identifiersByFile, targetToOriginalFilesMap); |
| 148 | updateExistingModuleDescriptors(extensionMap, cssFilesMapping, identifiersByFile, targetToOriginalFilesMap); |
| 149 | addDependenciesToDescriptors(); |
| 150 | renameIdentifiers(identifierMap); |
| 151 | updateBuildGNFile(cssFilesMapping, newModuleSet); |
| 152 | for (let descriptor of APPLICATION_DESCRIPTORS) |
| 153 | updateApplicationDescriptor(descriptor, newModuleSet); |
| 154 | |
| 155 | for (let m of MODULES_TO_REMOVE) |
| 156 | utils.removeRecursive(path.resolve(FRONTEND_PATH, m)); |
| 157 | } |
| 158 | |
| 159 | String.prototype.replaceAll = function(search, replacement) { |
| 160 | let target = this; |
| 161 | return target.replace(new RegExp('\\b' + search + '\\b', 'g'), replacement); |
| 162 | }; |
| 163 | |
| 164 | Set.prototype.union = function(setB) { |
| 165 | let union = new Set(this); |
| 166 | for (let elem of setB) |
| 167 | union.add(elem); |
| 168 | |
| 169 | return union; |
| 170 | }; |
| 171 | |
| 172 | function mapModuleToNamespace(module) { |
| 173 | const specialCases = require(SPECIAL_CASE_NAMESPACES_PATH); |
| 174 | return specialCases[module] || toCamelCase(module); |
| 175 | |
| 176 | function toCamelCase(module) { |
| 177 | return module.split('_').map(a => a.substring(0, 1).toUpperCase() + a.substring(1)).join(''); |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | function findCSSFiles() { |
| 182 | let cssFilesMapping = new Map(); |
| 183 | for (let fileObj of JS_FILES_MAPPING) |
| 184 | cssFilesMapping.set(fileObj.file, scrapeCSSFile(fileObj.file)); |
| 185 | |
| 186 | |
| 187 | function scrapeCSSFile(filePath) { |
| 188 | let cssFiles = new Set(); |
| 189 | const fullPath = path.resolve(FRONTEND_PATH, filePath); |
| 190 | let content = fs.readFileSync(fullPath).toString(); |
| 191 | let lines = content.split('\n'); |
| 192 | for (let line of lines) { |
| 193 | let match = line.match(/'(.+\.css)'/); |
| 194 | if (!match) |
| 195 | continue; |
| 196 | let matchPath = match[1]; |
| 197 | cssFiles.add(path.basename(path.resolve(FRONTEND_PATH, matchPath))); |
| 198 | } |
| 199 | return cssFiles; |
| 200 | } |
| 201 | |
| 202 | return cssFilesMapping; |
| 203 | } |
| 204 | |
| 205 | function calculateIdentifiers() { |
| 206 | const identifiersByFile = new Map(); |
| 207 | for (let fileObj of JS_FILES_MAPPING) { |
| 208 | const fullPath = path.resolve(FRONTEND_PATH, fileObj.file); |
| 209 | let content = fs.readFileSync(fullPath).toString(); |
| 210 | identifiersByFile.set(fileObj.file, scrapeIdentifiers(content, fileObj)); |
| 211 | } |
| 212 | return identifiersByFile; |
| 213 | |
| 214 | function scrapeIdentifiers(content, fileObj) { |
| 215 | let identifiers = []; |
| 216 | let lines = content.split('\n'); |
| 217 | for (let line of lines) { |
| 218 | let match = |
| 219 | line.match(new RegExp(`^\\s*([a-z_A-Z0-9\.]+)\\s=`)) || line.match(new RegExp(`^\\s*([a-z_A-Z0-9\.]+);`)); |
| 220 | if (!match) |
| 221 | continue; |
| 222 | let name = match[1]; |
| 223 | |
| 224 | var currentModule = fileObj.file.split('/')[0]; |
| 225 | if (name.split('.')[0] !== mapModuleToNamespace(currentModule)) { |
| 226 | console.log(`POSSIBLE ISSUE: identifier: ${name} found in ${currentModule}`); |
| 227 | // one-off |
| 228 | if (name.includes('UI.')) { |
| 229 | console.log(`including ${name} anyways`); |
| 230 | identifiers.push(name); |
| 231 | } |
| 232 | } else { |
| 233 | identifiers.push(name); |
| 234 | } |
| 235 | } |
| 236 | return identifiers; |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | function moveFiles(cssFilesMapping) { |
| 241 | for (let fileObj of JS_FILES_MAPPING) { |
| 242 | let sourceFilePath = path.resolve(FRONTEND_PATH, fileObj.file); |
| 243 | let targetFilePath = getMappedFilePath(fileObj); |
| 244 | let moduleDir = path.resolve(targetFilePath, '..'); |
| 245 | if (!fs.existsSync(moduleDir)) |
| 246 | fs.mkdirSync(moduleDir); |
| 247 | |
| 248 | move(sourceFilePath, targetFilePath); |
| 249 | if (cssFilesMapping.has(fileObj.file)) { |
| 250 | cssFilesMapping.get(fileObj.file).forEach((file) => { |
| 251 | let module = fileObj.new || fileObj.existing; |
| 252 | move(path.resolve(FRONTEND_PATH, fileObj.file.split('/')[0], file), path.resolve(FRONTEND_PATH, module, file)); |
| 253 | }); |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | function move(sourceFilePath, targetFilePath) { |
| 258 | try { |
| 259 | fs.writeFileSync(targetFilePath, fs.readFileSync(sourceFilePath)); |
| 260 | fs.unlinkSync(sourceFilePath); |
| 261 | } catch (err) { |
| 262 | console.log(`error moving ${sourceFilePath} -> ${targetFilePath}`); |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | function getMappedFilePath(fileObj) { |
| 267 | let components = fileObj.file.split('/'); |
| 268 | components[0] = fileObj.existing || fileObj.new; |
| 269 | return path.resolve(FRONTEND_PATH, components.join('/')); |
| 270 | } |
| 271 | } |
| 272 | |
| 273 | function updateBuildGNFile(cssFilesMapping, newModuleSet) { |
| 274 | let content = fs.readFileSync(BUILD_GN_PATH).toString(); |
| 275 | let newSourcesToAdd = []; |
| 276 | let partialPathMapping = calculatePartialPathMapping(); |
| 277 | for (let module of MODULES_TO_REMOVE) { |
| 278 | partialPathMapping.set(`"front_end/${module}/module.json",\n`, ''); |
| 279 | partialPathMapping.set(`"$resources_out_dir/${module}/${module}_module.js",\n`, ''); |
| 280 | } |
| 281 | const newNonAutostartModules = [...newModuleSet] |
| 282 | .filter(module => !MODULE_MAPPING[module].autostart) |
| 283 | .map(module => `"$resources_out_dir/${module}/${module}_module.js",`); |
| 284 | |
| 285 | let newContent = addContentToLinesInSortedOrder({ |
| 286 | content, |
| 287 | startLine: 'generated_non_autostart_non_remote_modules = [', |
| 288 | endLine: ']', |
| 289 | linesToInsert: newNonAutostartModules, |
| 290 | }); |
| 291 | |
| 292 | for (let pair of partialPathMapping.entries()) |
| 293 | newContent = newContent.replace(pair[0], pair[1]); |
| 294 | |
| 295 | newContent = addContentToLinesInSortedOrder({ |
| 296 | content: newContent, |
| 297 | startLine: 'all_devtools_files = [', |
| 298 | endLine: ']', |
| 299 | linesToInsert: newSourcesToAdd.concat([...newModuleSet].map(module => `"front_end/${module}/module.json",`)), |
| 300 | }); |
| 301 | |
| 302 | fs.writeFileSync(BUILD_GN_PATH, newContent); |
| 303 | |
| 304 | function calculatePartialPathMapping() { |
| 305 | let partialPathMapping = new Map(); |
| 306 | for (let fileObj of JS_FILES_MAPPING) { |
| 307 | let components = fileObj.file.split('/'); |
| 308 | let sourceModule = components[0]; |
| 309 | let targetModule = fileObj.existing || fileObj.new; |
| 310 | components[0] = targetModule; |
| 311 | partialPathMapping.set(`"front_end/${fileObj.file}",\n`, ''); |
| 312 | newSourcesToAdd.push(`"front_end/${components.join('/')}",`); |
| 313 | if (cssFilesMapping.has(fileObj.file)) { |
| 314 | for (let cssFile of cssFilesMapping.get(fileObj.file)) { |
| 315 | partialPathMapping.set(`"front_end/${sourceModule}/${cssFile}",\n`, ''); |
| 316 | newSourcesToAdd.push(`"front_end/${targetModule}/${cssFile}",`); |
| 317 | } |
| 318 | } |
| 319 | } |
| 320 | return partialPathMapping; |
| 321 | } |
| 322 | |
| 323 | function top(array) { |
| 324 | return array[array.length - 1]; |
| 325 | } |
| 326 | |
| 327 | function addContentToLinesInSortedOrder({content, startLine, endLine, linesToInsert}) { |
| 328 | if (linesToInsert.length === 0) |
| 329 | return content; |
| 330 | let lines = content.split('\n'); |
| 331 | let seenStartLine = false; |
| 332 | let contentStack = linesToInsert.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).reverse(); |
| 333 | for (var i = 0; i < lines.length; i++) { |
| 334 | let line = lines[i].trim(); |
| 335 | let nextLine = lines[i + 1].trim(); |
| 336 | if (line === startLine) |
| 337 | seenStartLine = true; |
| 338 | |
| 339 | if (line === endLine && seenStartLine) |
| 340 | break; |
| 341 | |
| 342 | if (!seenStartLine) |
| 343 | continue; |
| 344 | |
| 345 | const nextContent = top(contentStack) ? top(contentStack).toLowerCase() : ''; |
| 346 | if ((line === startLine || nextContent >= line.toLowerCase()) && |
| 347 | (nextLine === endLine || nextContent <= nextLine.toLowerCase())) |
| 348 | lines.splice(i + 1, 0, contentStack.pop()); |
| 349 | } |
| 350 | if (contentStack.length) |
| 351 | lines.splice(i, 0, ...contentStack); |
| 352 | return lines.join('\n'); |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | function mapIdentifiers(identifiersByFile, cssFilesMapping) { |
| 357 | const filesToTargetModule = new Map(); |
| 358 | for (let fileObj of JS_FILES_MAPPING) |
| 359 | filesToTargetModule.set(fileObj.file, fileObj.existing || fileObj.new); |
| 360 | |
| 361 | |
| 362 | const map = new Map(); |
| 363 | for (let [file, identifiers] of identifiersByFile) { |
| 364 | let targetModule = filesToTargetModule.get(file); |
| 365 | for (let identifier of identifiers) { |
| 366 | let components = identifier.split('.'); |
| 367 | components[0] = mapModuleToNamespace(targetModule); |
| 368 | let newIdentifier = components.join('.'); |
| 369 | map.set(identifier, newIdentifier); |
| 370 | } |
| 371 | } |
| 372 | for (let [jsFile, cssFiles] of cssFilesMapping) { |
| 373 | let fileObj = JS_FILES_MAPPING.filter(f => f.file === jsFile)[0]; |
| 374 | let sourceModule = fileObj.file.split('/')[0]; |
| 375 | let targetModule = fileObj.existing || fileObj.new; |
| 376 | for (let cssFile of cssFiles) { |
| 377 | let key = `${sourceModule}/${cssFile}`; |
| 378 | let value = `${targetModule}/${cssFile}`; |
| 379 | map.set(key, value); |
| 380 | } |
| 381 | } |
| 382 | return map; |
| 383 | } |
| 384 | |
| 385 | function renameIdentifiers(identifierMap) { |
| 386 | walkSync('front_end', write, true); |
| 387 | |
Kent Tamura | c464a2e | 2018-11-25 22:33:43 +0000 | [diff] [blame] | 388 | walkSync('../../web_tests/http/tests/devtools', write, false); |
| 389 | walkSync('../../web_tests/http/tests/inspector-protocol', write, false); |
| 390 | walkSync('../../web_tests/inspector-protocol', write, false); |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 391 | |
| 392 | function walkSync(currentDirPath, process, json) { |
| 393 | fs.readdirSync(currentDirPath).forEach(function(name) { |
| 394 | let filePath = path.join(currentDirPath, name); |
| 395 | let stat = fs.statSync(filePath); |
| 396 | if (stat.isFile() && (filePath.endsWith('.js') || filePath.endsWith('.html') || filePath.endsWith('.xhtml') || |
| 397 | filePath.endsWith('-expected.txt') || (json && filePath.endsWith('.json')))) { |
| 398 | if (filePath.includes('ExtensionAPI.js')) |
| 399 | return; |
| 400 | if (filePath.includes('externs.js')) |
| 401 | return; |
Paul Irish | c9966b8 | 2018-11-30 01:05:03 +0000 | [diff] [blame] | 402 | if (filePath.includes('eslint') || filePath.includes('lighthouse-dt-bundle.js') || filePath.includes('/cm/') || |
Blink Reformat | 4c46d09 | 2018-04-07 15:32:37 +0000 | [diff] [blame] | 403 | filePath.includes('/xterm.js/') || filePath.includes('/acorn/')) |
| 404 | return; |
| 405 | if (filePath.includes('/cm_modes/') && !filePath.includes('DefaultCodeMirror') && |
| 406 | !filePath.includes('module.json')) |
| 407 | return; |
| 408 | process(filePath); |
| 409 | } else if (stat.isDirectory()) { |
| 410 | walkSync(filePath, process, json); |
| 411 | } |
| 412 | }); |
| 413 | } |
| 414 | |
| 415 | function write(filePath) { |
| 416 | let content = fs.readFileSync(filePath).toString(); |
| 417 | let newContent = content; |
| 418 | for (let key of identifierMap.keys()) { |
| 419 | let originalIdentifier = key; |
| 420 | let newIdentifier = identifierMap.get(key); |
| 421 | newContent = newContent.replaceAll(originalIdentifier, newIdentifier); |
| 422 | } |
| 423 | |
| 424 | if (content !== newContent) |
| 425 | fs.writeFileSync(filePath, newContent); |
| 426 | } |
| 427 | } |
| 428 | |
| 429 | function removeFromExistingModuleDescriptors(modules, identifierMap, cssFilesMapping) { |
| 430 | let extensionMap = new Map(); |
| 431 | let moduleFileMap = new Map(); |
| 432 | |
| 433 | for (let fileObj of JS_FILES_MAPPING) { |
| 434 | let components = fileObj.file.split('/'); |
| 435 | let module = components[0]; |
| 436 | let fileName = components[1]; |
| 437 | |
| 438 | if (!moduleFileMap.get(module)) |
| 439 | moduleFileMap.set(module, []); |
| 440 | |
| 441 | moduleFileMap.set(module, moduleFileMap.get(module).concat(fileName)); |
| 442 | } |
| 443 | |
| 444 | for (let module of modules) { |
| 445 | let moduleJSONPath = path.resolve(FRONTEND_PATH, module, 'module.json'); |
| 446 | let content = fs.readFileSync(moduleJSONPath).toString(); |
| 447 | let moduleObj = parseJSON(content); |
| 448 | let removedScripts = removeScripts(moduleObj, module); |
| 449 | removeResources(moduleObj, removedScripts); |
| 450 | removeExtensions(moduleObj); |
| 451 | fs.writeFileSync(moduleJSONPath, stringifyJSON(moduleObj)); |
| 452 | } |
| 453 | |
| 454 | return extensionMap; |
| 455 | |
| 456 | function removeScripts(moduleObj, module) { |
| 457 | let remainingScripts = []; |
| 458 | let removedScripts = []; |
| 459 | let moduleFiles = moduleFileMap.get(module); |
| 460 | for (let script of moduleObj.scripts) { |
| 461 | if (!moduleFiles.includes(script)) |
| 462 | remainingScripts.push(script); |
| 463 | else |
| 464 | removedScripts.push(module + '/' + script); |
| 465 | } |
| 466 | moduleObj.scripts = remainingScripts; |
| 467 | return removedScripts; |
| 468 | } |
| 469 | |
| 470 | function removeResources(moduleObj, removedScripts) { |
| 471 | if (!moduleObj.resources) |
| 472 | return; |
| 473 | let remainingResources = []; |
| 474 | let removedResources = new Set(); |
| 475 | for (let script of removedScripts) |
| 476 | removedResources = removedResources.union(cssFilesMapping.get(script)); |
| 477 | |
| 478 | |
| 479 | for (let resource of moduleObj.resources) { |
| 480 | if (!removedResources.has(resource)) |
| 481 | remainingResources.push(resource); |
| 482 | } |
| 483 | moduleObj.resources = remainingResources; |
| 484 | } |
| 485 | |
| 486 | function removeExtensions(moduleObj) { |
| 487 | if (!moduleObj.extensions) |
| 488 | return; |
| 489 | let remainingExtensions = []; |
| 490 | for (let extension of moduleObj.extensions) { |
| 491 | if (!objectIncludesIdentifier(extension)) { |
| 492 | remainingExtensions.push(extension); |
| 493 | } else { |
| 494 | if (extensionMap.has(objectIncludesIdentifier(extension))) { |
| 495 | let existingExtensions = extensionMap.get(objectIncludesIdentifier(extension)); |
| 496 | extensionMap.set(objectIncludesIdentifier(extension), existingExtensions.concat(extension)); |
| 497 | } else { |
| 498 | extensionMap.set(objectIncludesIdentifier(extension), [extension]); |
| 499 | } |
| 500 | } |
| 501 | } |
| 502 | moduleObj.extensions = remainingExtensions; |
| 503 | } |
| 504 | |
| 505 | function objectIncludesIdentifier(object) { |
| 506 | for (let key in object) { |
| 507 | let value = object[key]; |
| 508 | if (identifierMap.has(value)) |
| 509 | return value; |
| 510 | } |
| 511 | return false; |
| 512 | } |
| 513 | } |
| 514 | |
| 515 | function createNewModuleDescriptors(extensionMap, cssFilesMapping, identifiersByFile, targetToOriginalFilesMap) { |
| 516 | let filesByNewModule = getFilesByNewModule(); |
| 517 | |
| 518 | for (let module of filesByNewModule.keys()) { |
| 519 | let moduleObj = {}; |
| 520 | |
| 521 | let scripts = getModuleScripts(module); |
| 522 | let extensions = getModuleExtensions(scripts, module); |
| 523 | if (extensions.length) |
| 524 | moduleObj.extensions = extensions; |
| 525 | |
| 526 | moduleObj.dependencies = DEPENDENCIES_BY_MODULE[module]; |
| 527 | |
| 528 | moduleObj.scripts = scripts; |
| 529 | |
| 530 | let resources = getModuleResources(moduleObj.scripts, module); |
| 531 | if (resources.length) |
| 532 | moduleObj.resources = resources; |
| 533 | |
| 534 | let moduleJSONPath = path.resolve(FRONTEND_PATH, module, 'module.json'); |
| 535 | fs.writeFileSync(moduleJSONPath, stringifyJSON(moduleObj)); |
| 536 | } |
| 537 | |
| 538 | function getFilesByNewModule() { |
| 539 | let filesByNewModule = new Map(); |
| 540 | for (let fileObj of JS_FILES_MAPPING) { |
| 541 | if (!fileObj.new) |
| 542 | continue; |
| 543 | if (!filesByNewModule.has(fileObj.new)) |
| 544 | filesByNewModule.set(fileObj.new, []); |
| 545 | |
| 546 | filesByNewModule.set(fileObj.new, filesByNewModule.get(fileObj.new).concat([fileObj.file])); |
| 547 | } |
| 548 | return filesByNewModule; |
| 549 | } |
| 550 | |
| 551 | function getModuleScripts(module) { |
| 552 | return filesByNewModule.get(module).map((file) => file.split('/')[1]); |
| 553 | } |
| 554 | |
| 555 | function getModuleResources(scripts, module) { |
| 556 | let resources = []; |
| 557 | scripts.map(script => module + '/' + script).forEach((script) => { |
| 558 | script = targetToOriginalFilesMap.get(script); |
| 559 | if (!cssFilesMapping.has(script)) |
| 560 | return; |
| 561 | |
| 562 | resources = resources.concat([...cssFilesMapping.get(script)]); |
| 563 | }); |
| 564 | return resources; |
| 565 | } |
| 566 | |
| 567 | function getModuleExtensions(scripts, module) { |
| 568 | let extensions = []; |
| 569 | let identifiers = |
| 570 | scripts.map(script => module + '/' + script) |
| 571 | .reduce((acc, file) => acc.concat(identifiersByFile.get(targetToOriginalFilesMap.get(file))), []); |
| 572 | for (let identifier of identifiers) { |
| 573 | if (extensionMap.has(identifier)) |
| 574 | extensions = extensions.concat(extensionMap.get(identifier)); |
| 575 | } |
| 576 | return extensions; |
| 577 | } |
| 578 | } |
| 579 | |
| 580 | function calculateFilesByModuleType(type) { |
| 581 | let filesByNewModule = new Map(); |
| 582 | for (let fileObj of JS_FILES_MAPPING) { |
| 583 | if (!fileObj[type]) |
| 584 | continue; |
| 585 | if (!filesByNewModule.has(fileObj[type])) |
| 586 | filesByNewModule.set(fileObj[type], []); |
| 587 | |
| 588 | filesByNewModule.set(fileObj[type], filesByNewModule.get(fileObj[type]).concat([fileObj.file])); |
| 589 | } |
| 590 | return filesByNewModule; |
| 591 | } |
| 592 | |
| 593 | function updateExistingModuleDescriptors(extensionMap, cssFilesMapping, identifiersByFile, targetToOriginalFilesMap) { |
| 594 | let filesByExistingModule = calculateFilesByModuleType('existing'); |
| 595 | for (let module of filesByExistingModule.keys()) { |
| 596 | let moduleJSONPath = path.resolve(FRONTEND_PATH, module, 'module.json'); |
| 597 | let content = fs.readFileSync(moduleJSONPath).toString(); |
| 598 | let moduleObj = parseJSON(content); |
| 599 | |
| 600 | let scripts = getModuleScripts(module); |
| 601 | let existingExtensions = moduleObj.extensions || []; |
| 602 | let extensions = existingExtensions.concat(getModuleExtensions(scripts, module)); |
| 603 | if (extensions.length) |
| 604 | moduleObj.extensions = extensions; |
| 605 | |
| 606 | moduleObj.scripts = moduleObj.scripts.concat(scripts); |
| 607 | |
| 608 | let existingResources = moduleObj.resources || []; |
| 609 | let resources = existingResources.concat(getModuleResources(scripts, module)); |
| 610 | if (resources.length) |
| 611 | moduleObj.resources = resources; |
| 612 | |
| 613 | fs.writeFileSync(moduleJSONPath, stringifyJSON(moduleObj)); |
| 614 | } |
| 615 | |
| 616 | |
| 617 | function getModuleScripts(module) { |
| 618 | return filesByExistingModule.get(module).map((file) => file.split('/')[1]); |
| 619 | } |
| 620 | |
| 621 | function getModuleResources(scripts, module) { |
| 622 | let resources = []; |
| 623 | scripts.map(script => module + '/' + script).forEach((script) => { |
| 624 | script = targetToOriginalFilesMap.get(script); |
| 625 | if (!cssFilesMapping.has(script)) |
| 626 | return; |
| 627 | |
| 628 | resources = resources.concat([...cssFilesMapping.get(script)]); |
| 629 | }); |
| 630 | return resources; |
| 631 | } |
| 632 | |
| 633 | function getModuleExtensions(scripts, module) { |
| 634 | let extensions = []; |
| 635 | let identifiers = |
| 636 | scripts.map(script => module + '/' + script) |
| 637 | .reduce((acc, file) => acc.concat(identifiersByFile.get(targetToOriginalFilesMap.get(file))), []); |
| 638 | for (let identifier of identifiers) { |
| 639 | if (extensionMap.has(identifier)) |
| 640 | extensions = extensions.concat(extensionMap.get(identifier)); |
| 641 | } |
| 642 | return extensions; |
| 643 | } |
| 644 | } |
| 645 | |
| 646 | function addDependenciesToDescriptors() { |
| 647 | for (let module of getModules()) { |
| 648 | let moduleJSONPath = path.resolve(FRONTEND_PATH, module, 'module.json'); |
| 649 | let content = fs.readFileSync(moduleJSONPath).toString(); |
| 650 | let moduleObj = parseJSON(content); |
| 651 | |
| 652 | let existingDependencies = moduleObj.dependencies || []; |
| 653 | let dependencies = |
| 654 | existingDependencies.concat(getModuleDependencies(module)) |
| 655 | .filter((depModule) => !MODULES_TO_REMOVE.includes(depModule)) |
| 656 | .filter((depModule) => !(REMOVE_DEPENDENCIES_BY_EXISTING_MODULES[module] || []).includes(depModule)); |
| 657 | let newDependenciesForExistingModule = NEW_DEPENDENCIES_BY_EXISTING_MODULES[module]; |
| 658 | if (newDependenciesForExistingModule) |
| 659 | dependencies = dependencies.concat(newDependenciesForExistingModule); |
| 660 | if (dependencies.length) |
| 661 | moduleObj.dependencies = dependencies; |
| 662 | let newStringified = stringifyJSON(moduleObj); |
| 663 | if (stringifyJSON(moduleObj) !== stringifyJSON(parseJSON(content))) |
| 664 | fs.writeFileSync(moduleJSONPath, newStringified); |
| 665 | } |
| 666 | |
| 667 | function getModuleDependencies(existingModule) { |
| 668 | let newDeps = []; |
| 669 | for (let newModule in DEPENDENTS_BY_MODULE) { |
| 670 | let dependents = DEPENDENTS_BY_MODULE[newModule]; |
| 671 | if (dependents.includes(existingModule)) |
| 672 | newDeps.push(newModule); |
| 673 | } |
| 674 | return newDeps; |
| 675 | } |
| 676 | } |
| 677 | |
| 678 | function updateApplicationDescriptor(descriptorFileName, newModuleSet) { |
| 679 | let descriptorPath = path.join(FRONTEND_PATH, descriptorFileName); |
| 680 | let newModules = [...newModuleSet].filter(m => APPLICATIONS_BY_MODULE[m].includes(descriptorFileName)); |
| 681 | if (newModules.length === 0) |
| 682 | return; |
| 683 | let includeNewModules = (acc, line) => { |
| 684 | if (line.includes('{') && line.endsWith('}')) { |
| 685 | line += ','; |
| 686 | acc.push(line); |
| 687 | return acc.concat(newModules.map((m, i) => { |
| 688 | // Need spacing to preserve indentation |
| 689 | let string; |
| 690 | if (MODULE_MAPPING[m].autostart) |
| 691 | string = ` { "name": "${m}", "type": "autostart" }`; |
| 692 | else |
| 693 | string = ` { "name": "${m}" }`; |
| 694 | if (i !== newModules.length - 1) |
| 695 | string += ','; |
| 696 | return string; |
| 697 | })); |
| 698 | } |
| 699 | return acc.concat([line]); |
| 700 | }; |
| 701 | let removeModules = (acc, line) => MODULES_TO_REMOVE.every(m => !line.includes(m)) ? acc.concat([line]) : acc; |
| 702 | let lines = |
| 703 | fs.readFileSync(descriptorPath).toString().split('\n').reduce(includeNewModules, []).reduce(removeModules, []); |
| 704 | fs.writeFileSync(descriptorPath, lines.join('\n')); |
| 705 | } |
| 706 | |
| 707 | function getModules() { |
| 708 | return fs.readdirSync(FRONTEND_PATH).filter(function(file) { |
| 709 | return fs.statSync(path.join(FRONTEND_PATH, file)).isDirectory() && |
| 710 | utils.isFile(path.join(FRONTEND_PATH, file, 'module.json')); |
| 711 | }); |
| 712 | } |
| 713 | |
| 714 | function parseJSON(string) { |
| 715 | return JSON.parse(string); |
| 716 | } |
| 717 | |
| 718 | function stringifyJSON(obj) { |
| 719 | return unicodeEscape(JSON.stringify(obj, null, 4) + '\n'); |
| 720 | } |
| 721 | |
| 722 | // http://stackoverflow.com/questions/7499473/need-to-escape-non-ascii-characters-in-javascript |
| 723 | function unicodeEscape(string) { |
| 724 | function padWithLeadingZeros(string) { |
| 725 | return new Array(5 - string.length).join('0') + string; |
| 726 | } |
| 727 | |
| 728 | function unicodeCharEscape(charCode) { |
| 729 | return '\\u' + padWithLeadingZeros(charCode.toString(16)); |
| 730 | } |
| 731 | |
| 732 | return string.split('') |
| 733 | .map(function(char) { |
| 734 | var charCode = char.charCodeAt(0); |
| 735 | return charCode > 127 ? unicodeCharEscape(charCode) : char; |
| 736 | }) |
| 737 | .join(''); |
| 738 | } |
| 739 | |
| 740 | if (require.main === module) |
| 741 | extractModule(); |