blob: 99b086727022571f154703d381e90b892035bcbd [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:37 +00001// 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';
5const fs = require('fs');
6const path = require('path');
7
8const utils = require('../utils');
9
10const FRONTEND_PATH = path.resolve(__dirname, '..', '..', 'front_end');
11const BUILD_GN_PATH = path.resolve(__dirname, '..', '..', 'BUILD.gn');
Yang Guo75beda92019-10-28 08:29:25 +010012const SPECIAL_CASE_NAMESPACES_PATH = path.resolve(__dirname, '..', 'build', 'special_case_namespaces.json');
Blink Reformat4c46d092018-04-07 15:32:37 +000013
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
25const 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 */
41const 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 */
50const 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 */
65const 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 */
84const 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 */
92const REMOVE_DEPENDENCIES_BY_EXISTING_MODULES = {
93 // console_test_runner: ['main']
94};
95
96/*
97 * ==========================================
98 * STOP EDITING HERE
99 * ==========================================
100 */
101
102const DEPENDENCIES_BY_MODULE = Object.keys(MODULE_MAPPING).reduce((acc, module) => {
103 acc[module] = MODULE_MAPPING[module].dependencies;
104 return acc;
105}, {});
106
107const APPLICATIONS_BY_MODULE = Object.keys(MODULE_MAPPING).reduce((acc, module) => {
108 acc[module] = MODULE_MAPPING[module].applications;
109 return acc;
110}, {});
111
112const DEPENDENTS_BY_MODULE = Object.keys(MODULE_MAPPING).reduce((acc, module) => {
113 acc[module] = MODULE_MAPPING[module].dependents;
114 return acc;
115}, {});
116
117function 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
159String.prototype.replaceAll = function(search, replacement) {
160 let target = this;
161 return target.replace(new RegExp('\\b' + search + '\\b', 'g'), replacement);
162};
163
164Set.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
172function 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
181function 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
205function 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
240function 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
273function 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
356function 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
385function renameIdentifiers(identifierMap) {
386 walkSync('front_end', write, true);
387
Kent Tamurac464a2e2018-11-25 22:33:43 +0000388 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 Reformat4c46d092018-04-07 15:32:37 +0000391
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 Irishc9966b82018-11-30 01:05:03 +0000402 if (filePath.includes('eslint') || filePath.includes('lighthouse-dt-bundle.js') || filePath.includes('/cm/') ||
Blink Reformat4c46d092018-04-07 15:32:37 +0000403 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
429function 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
515function 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
580function 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
593function 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
646function 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
678function 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
707function 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
714function parseJSON(string) {
715 return JSON.parse(string);
716}
717
718function 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
723function 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
740if (require.main === module)
741 extractModule();