| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| 'use strict'; |
| const fs = require('fs'); |
| const path = require('path'); |
| |
| const utils = require('../utils'); |
| |
| const FRONTEND_PATH = path.resolve(__dirname, '..', '..', 'front_end'); |
| const BUILD_GN_PATH = path.resolve(__dirname, '..', '..', 'BUILD.gn'); |
| const SPECIAL_CASE_NAMESPACES_PATH = path.resolve(__dirname, '..', 'special_case_namespaces.json'); |
| |
| /* |
| * This is used to extract a new module from an existing module by: |
| * - Moving selected files into new modules (including relevant |
| * css files) |
| * - Renaming all identifiers to the new namespace |
| * - Updating the BUILD.gn and module.json files (including extensions) |
| * ========================================== |
| * START EDITING HERE - TRANSFORMATION INPUTS |
| * ========================================== |
| */ |
| |
| const APPLICATION_DESCRIPTORS = [ |
| 'devtools_app.json', |
| 'js_app.json', |
| 'shell.json', |
| 'worker_app.json', |
| 'inspector.json', |
| 'toolbox.json', |
| 'integration_test_runner.json', |
| 'formatter_worker.json', |
| 'heap_snapshot_worker.json', |
| ]; |
| |
| /* |
| * If the transformation removes all the files of a module: |
| * ['text_editor'] |
| */ |
| const MODULES_TO_REMOVE = ['profiler_test_runner', 'heap_snapshot_test_runner']; |
| |
| /** |
| * If moving to a new module: |
| * {file: 'common/Text.js', new: 'a_new_module'} |
| * |
| * If moving to an existing module: |
| * {file: 'ui/SomeFile.js', existing: 'common'} |
| */ |
| const JS_FILES_MAPPING = [ |
| // {file: 'heap_snapshot_test_runner/HeapSnapshotTestRunner.js', new: 'heap_profiler_test_runner'}, |
| // {file: 'profiler_test_runner/ProfilerTestRunner.js', new: 'cpu_profiler_test_runner'}, |
| {file: 'network_log/HAREntry.js', existing: 'browser_sdk'}, |
| ]; |
| |
| /** |
| * List all new modules here: |
| * mobile_throttling: { |
| * dependencies: ['sdk'], |
| * dependents: ['console'], |
| * applications: ['inspector.json'], |
| * autostart: false, |
| * } |
| */ |
| const MODULE_MAPPING = { |
| // heap_profiler_test_runner: { |
| // dependencies: ['heap_snapshot_worker', 'test_runner'], |
| // dependents: [], |
| // applications: ['integration_test_runner.json'], |
| // autostart: false, |
| // }, |
| // cpu_profiler_test_runner: { |
| // dependencies: ['profiler', 'test_runner'], |
| // dependents: [], |
| // applications: ['integration_test_runner.json'], |
| // autostart: false, |
| // }, |
| }; |
| |
| /** |
| * If an existing module will have a new dependency on an existing module: |
| * console: ['new_dependency'] |
| */ |
| const NEW_DEPENDENCIES_BY_EXISTING_MODULES = { |
| // resources: ['components'], |
| }; |
| |
| /** |
| * If an existing module will no longer have a dependency on a module: |
| * console: ['former_dependency'] |
| */ |
| const REMOVE_DEPENDENCIES_BY_EXISTING_MODULES = { |
| // console_test_runner: ['main'] |
| }; |
| |
| /* |
| * ========================================== |
| * STOP EDITING HERE |
| * ========================================== |
| */ |
| |
| const DEPENDENCIES_BY_MODULE = Object.keys(MODULE_MAPPING).reduce((acc, module) => { |
| acc[module] = MODULE_MAPPING[module].dependencies; |
| return acc; |
| }, {}); |
| |
| const APPLICATIONS_BY_MODULE = Object.keys(MODULE_MAPPING).reduce((acc, module) => { |
| acc[module] = MODULE_MAPPING[module].applications; |
| return acc; |
| }, {}); |
| |
| const DEPENDENTS_BY_MODULE = Object.keys(MODULE_MAPPING).reduce((acc, module) => { |
| acc[module] = MODULE_MAPPING[module].dependents; |
| return acc; |
| }, {}); |
| |
| function extractModule() { |
| const modules = new Set(); |
| for (let fileObj of JS_FILES_MAPPING) { |
| let moduleName = fileObj.file.split('/')[0]; |
| modules.add(moduleName); |
| } |
| const newModuleSet = JS_FILES_MAPPING.reduce((acc, file) => file.new ? acc.add(file.new) : acc, new Set()); |
| const targetToOriginalFilesMap = JS_FILES_MAPPING.reduce((acc, f) => { |
| let components = f.file.split('/'); |
| components[0] = f.new || f.existing; |
| acc.set(components.join('/'), f.file); |
| return acc; |
| }, new Map()); |
| |
| const cssFilesMapping = findCSSFiles(); |
| console.log('cssFilesMapping', cssFilesMapping); |
| const identifiersByFile = calculateIdentifiers(); |
| const identifierMap = mapIdentifiers(identifiersByFile, cssFilesMapping); |
| console.log('identifierMap', identifierMap); |
| const extensionMap = removeFromExistingModuleDescriptors(modules, identifierMap, cssFilesMapping); |
| |
| // Find out which files are moving extensions |
| for (let e of extensionMap.keys()) { |
| for (let [f, identifiers] of identifiersByFile) { |
| if (identifiers.includes(e)) |
| console.log(`extension: ${e} in file: ${f}`); |
| } |
| } |
| |
| moveFiles(cssFilesMapping); |
| createNewModuleDescriptors(extensionMap, cssFilesMapping, identifiersByFile, targetToOriginalFilesMap); |
| updateExistingModuleDescriptors(extensionMap, cssFilesMapping, identifiersByFile, targetToOriginalFilesMap); |
| addDependenciesToDescriptors(); |
| renameIdentifiers(identifierMap); |
| updateBuildGNFile(cssFilesMapping, newModuleSet); |
| for (let descriptor of APPLICATION_DESCRIPTORS) |
| updateApplicationDescriptor(descriptor, newModuleSet); |
| |
| for (let m of MODULES_TO_REMOVE) |
| utils.removeRecursive(path.resolve(FRONTEND_PATH, m)); |
| } |
| |
| String.prototype.replaceAll = function(search, replacement) { |
| let target = this; |
| return target.replace(new RegExp('\\b' + search + '\\b', 'g'), replacement); |
| }; |
| |
| Set.prototype.union = function(setB) { |
| let union = new Set(this); |
| for (let elem of setB) |
| union.add(elem); |
| |
| return union; |
| }; |
| |
| function mapModuleToNamespace(module) { |
| const specialCases = require(SPECIAL_CASE_NAMESPACES_PATH); |
| return specialCases[module] || toCamelCase(module); |
| |
| function toCamelCase(module) { |
| return module.split('_').map(a => a.substring(0, 1).toUpperCase() + a.substring(1)).join(''); |
| } |
| } |
| |
| function findCSSFiles() { |
| let cssFilesMapping = new Map(); |
| for (let fileObj of JS_FILES_MAPPING) |
| cssFilesMapping.set(fileObj.file, scrapeCSSFile(fileObj.file)); |
| |
| |
| function scrapeCSSFile(filePath) { |
| let cssFiles = new Set(); |
| const fullPath = path.resolve(FRONTEND_PATH, filePath); |
| let content = fs.readFileSync(fullPath).toString(); |
| let lines = content.split('\n'); |
| for (let line of lines) { |
| let match = line.match(/'(.+\.css)'/); |
| if (!match) |
| continue; |
| let matchPath = match[1]; |
| cssFiles.add(path.basename(path.resolve(FRONTEND_PATH, matchPath))); |
| } |
| return cssFiles; |
| } |
| |
| return cssFilesMapping; |
| } |
| |
| function calculateIdentifiers() { |
| const identifiersByFile = new Map(); |
| for (let fileObj of JS_FILES_MAPPING) { |
| const fullPath = path.resolve(FRONTEND_PATH, fileObj.file); |
| let content = fs.readFileSync(fullPath).toString(); |
| identifiersByFile.set(fileObj.file, scrapeIdentifiers(content, fileObj)); |
| } |
| return identifiersByFile; |
| |
| function scrapeIdentifiers(content, fileObj) { |
| let identifiers = []; |
| let lines = content.split('\n'); |
| for (let line of lines) { |
| let match = |
| line.match(new RegExp(`^\\s*([a-z_A-Z0-9\.]+)\\s=`)) || line.match(new RegExp(`^\\s*([a-z_A-Z0-9\.]+);`)); |
| if (!match) |
| continue; |
| let name = match[1]; |
| |
| var currentModule = fileObj.file.split('/')[0]; |
| if (name.split('.')[0] !== mapModuleToNamespace(currentModule)) { |
| console.log(`POSSIBLE ISSUE: identifier: ${name} found in ${currentModule}`); |
| // one-off |
| if (name.includes('UI.')) { |
| console.log(`including ${name} anyways`); |
| identifiers.push(name); |
| } |
| } else { |
| identifiers.push(name); |
| } |
| } |
| return identifiers; |
| } |
| } |
| |
| function moveFiles(cssFilesMapping) { |
| for (let fileObj of JS_FILES_MAPPING) { |
| let sourceFilePath = path.resolve(FRONTEND_PATH, fileObj.file); |
| let targetFilePath = getMappedFilePath(fileObj); |
| let moduleDir = path.resolve(targetFilePath, '..'); |
| if (!fs.existsSync(moduleDir)) |
| fs.mkdirSync(moduleDir); |
| |
| move(sourceFilePath, targetFilePath); |
| if (cssFilesMapping.has(fileObj.file)) { |
| cssFilesMapping.get(fileObj.file).forEach((file) => { |
| let module = fileObj.new || fileObj.existing; |
| move(path.resolve(FRONTEND_PATH, fileObj.file.split('/')[0], file), path.resolve(FRONTEND_PATH, module, file)); |
| }); |
| } |
| } |
| |
| function move(sourceFilePath, targetFilePath) { |
| try { |
| fs.writeFileSync(targetFilePath, fs.readFileSync(sourceFilePath)); |
| fs.unlinkSync(sourceFilePath); |
| } catch (err) { |
| console.log(`error moving ${sourceFilePath} -> ${targetFilePath}`); |
| } |
| } |
| |
| function getMappedFilePath(fileObj) { |
| let components = fileObj.file.split('/'); |
| components[0] = fileObj.existing || fileObj.new; |
| return path.resolve(FRONTEND_PATH, components.join('/')); |
| } |
| } |
| |
| function updateBuildGNFile(cssFilesMapping, newModuleSet) { |
| let content = fs.readFileSync(BUILD_GN_PATH).toString(); |
| let newSourcesToAdd = []; |
| let partialPathMapping = calculatePartialPathMapping(); |
| for (let module of MODULES_TO_REMOVE) { |
| partialPathMapping.set(`"front_end/${module}/module.json",\n`, ''); |
| partialPathMapping.set(`"$resources_out_dir/${module}/${module}_module.js",\n`, ''); |
| } |
| const newNonAutostartModules = [...newModuleSet] |
| .filter(module => !MODULE_MAPPING[module].autostart) |
| .map(module => `"$resources_out_dir/${module}/${module}_module.js",`); |
| |
| let newContent = addContentToLinesInSortedOrder({ |
| content, |
| startLine: 'generated_non_autostart_non_remote_modules = [', |
| endLine: ']', |
| linesToInsert: newNonAutostartModules, |
| }); |
| |
| for (let pair of partialPathMapping.entries()) |
| newContent = newContent.replace(pair[0], pair[1]); |
| |
| newContent = addContentToLinesInSortedOrder({ |
| content: newContent, |
| startLine: 'all_devtools_files = [', |
| endLine: ']', |
| linesToInsert: newSourcesToAdd.concat([...newModuleSet].map(module => `"front_end/${module}/module.json",`)), |
| }); |
| |
| fs.writeFileSync(BUILD_GN_PATH, newContent); |
| |
| function calculatePartialPathMapping() { |
| let partialPathMapping = new Map(); |
| for (let fileObj of JS_FILES_MAPPING) { |
| let components = fileObj.file.split('/'); |
| let sourceModule = components[0]; |
| let targetModule = fileObj.existing || fileObj.new; |
| components[0] = targetModule; |
| partialPathMapping.set(`"front_end/${fileObj.file}",\n`, ''); |
| newSourcesToAdd.push(`"front_end/${components.join('/')}",`); |
| if (cssFilesMapping.has(fileObj.file)) { |
| for (let cssFile of cssFilesMapping.get(fileObj.file)) { |
| partialPathMapping.set(`"front_end/${sourceModule}/${cssFile}",\n`, ''); |
| newSourcesToAdd.push(`"front_end/${targetModule}/${cssFile}",`); |
| } |
| } |
| } |
| return partialPathMapping; |
| } |
| |
| function top(array) { |
| return array[array.length - 1]; |
| } |
| |
| function addContentToLinesInSortedOrder({content, startLine, endLine, linesToInsert}) { |
| if (linesToInsert.length === 0) |
| return content; |
| let lines = content.split('\n'); |
| let seenStartLine = false; |
| let contentStack = linesToInsert.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).reverse(); |
| for (var i = 0; i < lines.length; i++) { |
| let line = lines[i].trim(); |
| let nextLine = lines[i + 1].trim(); |
| if (line === startLine) |
| seenStartLine = true; |
| |
| if (line === endLine && seenStartLine) |
| break; |
| |
| if (!seenStartLine) |
| continue; |
| |
| const nextContent = top(contentStack) ? top(contentStack).toLowerCase() : ''; |
| if ((line === startLine || nextContent >= line.toLowerCase()) && |
| (nextLine === endLine || nextContent <= nextLine.toLowerCase())) |
| lines.splice(i + 1, 0, contentStack.pop()); |
| } |
| if (contentStack.length) |
| lines.splice(i, 0, ...contentStack); |
| return lines.join('\n'); |
| } |
| } |
| |
| function mapIdentifiers(identifiersByFile, cssFilesMapping) { |
| const filesToTargetModule = new Map(); |
| for (let fileObj of JS_FILES_MAPPING) |
| filesToTargetModule.set(fileObj.file, fileObj.existing || fileObj.new); |
| |
| |
| const map = new Map(); |
| for (let [file, identifiers] of identifiersByFile) { |
| let targetModule = filesToTargetModule.get(file); |
| for (let identifier of identifiers) { |
| let components = identifier.split('.'); |
| components[0] = mapModuleToNamespace(targetModule); |
| let newIdentifier = components.join('.'); |
| map.set(identifier, newIdentifier); |
| } |
| } |
| for (let [jsFile, cssFiles] of cssFilesMapping) { |
| let fileObj = JS_FILES_MAPPING.filter(f => f.file === jsFile)[0]; |
| let sourceModule = fileObj.file.split('/')[0]; |
| let targetModule = fileObj.existing || fileObj.new; |
| for (let cssFile of cssFiles) { |
| let key = `${sourceModule}/${cssFile}`; |
| let value = `${targetModule}/${cssFile}`; |
| map.set(key, value); |
| } |
| } |
| return map; |
| } |
| |
| function renameIdentifiers(identifierMap) { |
| walkSync('front_end', write, true); |
| |
| walkSync('../../LayoutTests/http/tests/devtools', write, false); |
| walkSync('../../LayoutTests/http/tests/inspector-protocol', write, false); |
| walkSync('../../LayoutTests/inspector-protocol', write, false); |
| |
| function walkSync(currentDirPath, process, json) { |
| fs.readdirSync(currentDirPath).forEach(function(name) { |
| let filePath = path.join(currentDirPath, name); |
| let stat = fs.statSync(filePath); |
| if (stat.isFile() && (filePath.endsWith('.js') || filePath.endsWith('.html') || filePath.endsWith('.xhtml') || |
| filePath.endsWith('-expected.txt') || (json && filePath.endsWith('.json')))) { |
| if (filePath.includes('ExtensionAPI.js')) |
| return; |
| if (filePath.includes('externs.js')) |
| return; |
| if (filePath.includes('eslint') || filePath.includes('lighthouse-background.js') || filePath.includes('/cm/') || |
| filePath.includes('/xterm.js/') || filePath.includes('/acorn/')) |
| return; |
| if (filePath.includes('/cm_modes/') && !filePath.includes('DefaultCodeMirror') && |
| !filePath.includes('module.json')) |
| return; |
| process(filePath); |
| } else if (stat.isDirectory()) { |
| walkSync(filePath, process, json); |
| } |
| }); |
| } |
| |
| function write(filePath) { |
| let content = fs.readFileSync(filePath).toString(); |
| let newContent = content; |
| for (let key of identifierMap.keys()) { |
| let originalIdentifier = key; |
| let newIdentifier = identifierMap.get(key); |
| newContent = newContent.replaceAll(originalIdentifier, newIdentifier); |
| } |
| |
| if (content !== newContent) |
| fs.writeFileSync(filePath, newContent); |
| } |
| } |
| |
| function removeFromExistingModuleDescriptors(modules, identifierMap, cssFilesMapping) { |
| let extensionMap = new Map(); |
| let moduleFileMap = new Map(); |
| |
| for (let fileObj of JS_FILES_MAPPING) { |
| let components = fileObj.file.split('/'); |
| let module = components[0]; |
| let fileName = components[1]; |
| |
| if (!moduleFileMap.get(module)) |
| moduleFileMap.set(module, []); |
| |
| moduleFileMap.set(module, moduleFileMap.get(module).concat(fileName)); |
| } |
| |
| for (let module of modules) { |
| let moduleJSONPath = path.resolve(FRONTEND_PATH, module, 'module.json'); |
| let content = fs.readFileSync(moduleJSONPath).toString(); |
| let moduleObj = parseJSON(content); |
| let removedScripts = removeScripts(moduleObj, module); |
| removeResources(moduleObj, removedScripts); |
| removeExtensions(moduleObj); |
| fs.writeFileSync(moduleJSONPath, stringifyJSON(moduleObj)); |
| } |
| |
| return extensionMap; |
| |
| function removeScripts(moduleObj, module) { |
| let remainingScripts = []; |
| let removedScripts = []; |
| let moduleFiles = moduleFileMap.get(module); |
| for (let script of moduleObj.scripts) { |
| if (!moduleFiles.includes(script)) |
| remainingScripts.push(script); |
| else |
| removedScripts.push(module + '/' + script); |
| } |
| moduleObj.scripts = remainingScripts; |
| return removedScripts; |
| } |
| |
| function removeResources(moduleObj, removedScripts) { |
| if (!moduleObj.resources) |
| return; |
| let remainingResources = []; |
| let removedResources = new Set(); |
| for (let script of removedScripts) |
| removedResources = removedResources.union(cssFilesMapping.get(script)); |
| |
| |
| for (let resource of moduleObj.resources) { |
| if (!removedResources.has(resource)) |
| remainingResources.push(resource); |
| } |
| moduleObj.resources = remainingResources; |
| } |
| |
| function removeExtensions(moduleObj) { |
| if (!moduleObj.extensions) |
| return; |
| let remainingExtensions = []; |
| for (let extension of moduleObj.extensions) { |
| if (!objectIncludesIdentifier(extension)) { |
| remainingExtensions.push(extension); |
| } else { |
| if (extensionMap.has(objectIncludesIdentifier(extension))) { |
| let existingExtensions = extensionMap.get(objectIncludesIdentifier(extension)); |
| extensionMap.set(objectIncludesIdentifier(extension), existingExtensions.concat(extension)); |
| } else { |
| extensionMap.set(objectIncludesIdentifier(extension), [extension]); |
| } |
| } |
| } |
| moduleObj.extensions = remainingExtensions; |
| } |
| |
| function objectIncludesIdentifier(object) { |
| for (let key in object) { |
| let value = object[key]; |
| if (identifierMap.has(value)) |
| return value; |
| } |
| return false; |
| } |
| } |
| |
| function createNewModuleDescriptors(extensionMap, cssFilesMapping, identifiersByFile, targetToOriginalFilesMap) { |
| let filesByNewModule = getFilesByNewModule(); |
| |
| for (let module of filesByNewModule.keys()) { |
| let moduleObj = {}; |
| |
| let scripts = getModuleScripts(module); |
| let extensions = getModuleExtensions(scripts, module); |
| if (extensions.length) |
| moduleObj.extensions = extensions; |
| |
| moduleObj.dependencies = DEPENDENCIES_BY_MODULE[module]; |
| |
| moduleObj.scripts = scripts; |
| |
| let resources = getModuleResources(moduleObj.scripts, module); |
| if (resources.length) |
| moduleObj.resources = resources; |
| |
| let moduleJSONPath = path.resolve(FRONTEND_PATH, module, 'module.json'); |
| fs.writeFileSync(moduleJSONPath, stringifyJSON(moduleObj)); |
| } |
| |
| function getFilesByNewModule() { |
| let filesByNewModule = new Map(); |
| for (let fileObj of JS_FILES_MAPPING) { |
| if (!fileObj.new) |
| continue; |
| if (!filesByNewModule.has(fileObj.new)) |
| filesByNewModule.set(fileObj.new, []); |
| |
| filesByNewModule.set(fileObj.new, filesByNewModule.get(fileObj.new).concat([fileObj.file])); |
| } |
| return filesByNewModule; |
| } |
| |
| function getModuleScripts(module) { |
| return filesByNewModule.get(module).map((file) => file.split('/')[1]); |
| } |
| |
| function getModuleResources(scripts, module) { |
| let resources = []; |
| scripts.map(script => module + '/' + script).forEach((script) => { |
| script = targetToOriginalFilesMap.get(script); |
| if (!cssFilesMapping.has(script)) |
| return; |
| |
| resources = resources.concat([...cssFilesMapping.get(script)]); |
| }); |
| return resources; |
| } |
| |
| function getModuleExtensions(scripts, module) { |
| let extensions = []; |
| let identifiers = |
| scripts.map(script => module + '/' + script) |
| .reduce((acc, file) => acc.concat(identifiersByFile.get(targetToOriginalFilesMap.get(file))), []); |
| for (let identifier of identifiers) { |
| if (extensionMap.has(identifier)) |
| extensions = extensions.concat(extensionMap.get(identifier)); |
| } |
| return extensions; |
| } |
| } |
| |
| function calculateFilesByModuleType(type) { |
| let filesByNewModule = new Map(); |
| for (let fileObj of JS_FILES_MAPPING) { |
| if (!fileObj[type]) |
| continue; |
| if (!filesByNewModule.has(fileObj[type])) |
| filesByNewModule.set(fileObj[type], []); |
| |
| filesByNewModule.set(fileObj[type], filesByNewModule.get(fileObj[type]).concat([fileObj.file])); |
| } |
| return filesByNewModule; |
| } |
| |
| function updateExistingModuleDescriptors(extensionMap, cssFilesMapping, identifiersByFile, targetToOriginalFilesMap) { |
| let filesByExistingModule = calculateFilesByModuleType('existing'); |
| for (let module of filesByExistingModule.keys()) { |
| let moduleJSONPath = path.resolve(FRONTEND_PATH, module, 'module.json'); |
| let content = fs.readFileSync(moduleJSONPath).toString(); |
| let moduleObj = parseJSON(content); |
| |
| let scripts = getModuleScripts(module); |
| let existingExtensions = moduleObj.extensions || []; |
| let extensions = existingExtensions.concat(getModuleExtensions(scripts, module)); |
| if (extensions.length) |
| moduleObj.extensions = extensions; |
| |
| moduleObj.scripts = moduleObj.scripts.concat(scripts); |
| |
| let existingResources = moduleObj.resources || []; |
| let resources = existingResources.concat(getModuleResources(scripts, module)); |
| if (resources.length) |
| moduleObj.resources = resources; |
| |
| fs.writeFileSync(moduleJSONPath, stringifyJSON(moduleObj)); |
| } |
| |
| |
| function getModuleScripts(module) { |
| return filesByExistingModule.get(module).map((file) => file.split('/')[1]); |
| } |
| |
| function getModuleResources(scripts, module) { |
| let resources = []; |
| scripts.map(script => module + '/' + script).forEach((script) => { |
| script = targetToOriginalFilesMap.get(script); |
| if (!cssFilesMapping.has(script)) |
| return; |
| |
| resources = resources.concat([...cssFilesMapping.get(script)]); |
| }); |
| return resources; |
| } |
| |
| function getModuleExtensions(scripts, module) { |
| let extensions = []; |
| let identifiers = |
| scripts.map(script => module + '/' + script) |
| .reduce((acc, file) => acc.concat(identifiersByFile.get(targetToOriginalFilesMap.get(file))), []); |
| for (let identifier of identifiers) { |
| if (extensionMap.has(identifier)) |
| extensions = extensions.concat(extensionMap.get(identifier)); |
| } |
| return extensions; |
| } |
| } |
| |
| function addDependenciesToDescriptors() { |
| for (let module of getModules()) { |
| let moduleJSONPath = path.resolve(FRONTEND_PATH, module, 'module.json'); |
| let content = fs.readFileSync(moduleJSONPath).toString(); |
| let moduleObj = parseJSON(content); |
| |
| let existingDependencies = moduleObj.dependencies || []; |
| let dependencies = |
| existingDependencies.concat(getModuleDependencies(module)) |
| .filter((depModule) => !MODULES_TO_REMOVE.includes(depModule)) |
| .filter((depModule) => !(REMOVE_DEPENDENCIES_BY_EXISTING_MODULES[module] || []).includes(depModule)); |
| let newDependenciesForExistingModule = NEW_DEPENDENCIES_BY_EXISTING_MODULES[module]; |
| if (newDependenciesForExistingModule) |
| dependencies = dependencies.concat(newDependenciesForExistingModule); |
| if (dependencies.length) |
| moduleObj.dependencies = dependencies; |
| let newStringified = stringifyJSON(moduleObj); |
| if (stringifyJSON(moduleObj) !== stringifyJSON(parseJSON(content))) |
| fs.writeFileSync(moduleJSONPath, newStringified); |
| } |
| |
| function getModuleDependencies(existingModule) { |
| let newDeps = []; |
| for (let newModule in DEPENDENTS_BY_MODULE) { |
| let dependents = DEPENDENTS_BY_MODULE[newModule]; |
| if (dependents.includes(existingModule)) |
| newDeps.push(newModule); |
| } |
| return newDeps; |
| } |
| } |
| |
| function updateApplicationDescriptor(descriptorFileName, newModuleSet) { |
| let descriptorPath = path.join(FRONTEND_PATH, descriptorFileName); |
| let newModules = [...newModuleSet].filter(m => APPLICATIONS_BY_MODULE[m].includes(descriptorFileName)); |
| if (newModules.length === 0) |
| return; |
| let includeNewModules = (acc, line) => { |
| if (line.includes('{') && line.endsWith('}')) { |
| line += ','; |
| acc.push(line); |
| return acc.concat(newModules.map((m, i) => { |
| // Need spacing to preserve indentation |
| let string; |
| if (MODULE_MAPPING[m].autostart) |
| string = ` { "name": "${m}", "type": "autostart" }`; |
| else |
| string = ` { "name": "${m}" }`; |
| if (i !== newModules.length - 1) |
| string += ','; |
| return string; |
| })); |
| } |
| return acc.concat([line]); |
| }; |
| let removeModules = (acc, line) => MODULES_TO_REMOVE.every(m => !line.includes(m)) ? acc.concat([line]) : acc; |
| let lines = |
| fs.readFileSync(descriptorPath).toString().split('\n').reduce(includeNewModules, []).reduce(removeModules, []); |
| fs.writeFileSync(descriptorPath, lines.join('\n')); |
| } |
| |
| function getModules() { |
| return fs.readdirSync(FRONTEND_PATH).filter(function(file) { |
| return fs.statSync(path.join(FRONTEND_PATH, file)).isDirectory() && |
| utils.isFile(path.join(FRONTEND_PATH, file, 'module.json')); |
| }); |
| } |
| |
| function parseJSON(string) { |
| return JSON.parse(string); |
| } |
| |
| function stringifyJSON(obj) { |
| return unicodeEscape(JSON.stringify(obj, null, 4) + '\n'); |
| } |
| |
| // http://stackoverflow.com/questions/7499473/need-to-escape-non-ascii-characters-in-javascript |
| function unicodeEscape(string) { |
| function padWithLeadingZeros(string) { |
| return new Array(5 - string.length).join('0') + string; |
| } |
| |
| function unicodeCharEscape(charCode) { |
| return '\\u' + padWithLeadingZeros(charCode.toString(16)); |
| } |
| |
| return string.split('') |
| .map(function(char) { |
| var charCode = char.charCodeAt(0); |
| return charCode > 127 ? unicodeCharEscape(charCode) : char; |
| }) |
| .join(''); |
| } |
| |
| if (require.main === module) |
| extractModule(); |