UNPKG

openfl

Version:

A fast, productive library for 2D cross-platform development.

1,258 lines (866 loc) 34.5 kB
/* generate-es2015.js Commands: node generate-es2015 gen - This will generate the es2015 modules and save them under lib-esm/_gen/ node generate-es2015 other - This will generate the re-export modules as well as the index.js and index.d.ts barrel modules - re-export module example: lib-esm/openfl/display/Sprite.js - barrel modules examples: lib-esm/openfl/display/index.js and lib/openfl/display/index.d.ts */ const _fs = require('fs'); const _path = require('path'); const globby = require('globby'); const readline = require('readline'); let DelayFS = init(); let delayFS = new DelayFS(false); // Settings const inputLibDirname = 'lib'; const outputLibDirname = 'lib-esm'; const baseDir = '..'; let fullInputLibDirPath = _path.resolve(baseDir, inputLibDirname); let fullOutputLibDirPath = _path.resolve(baseDir, outputLibDirname); let inputGenPath = _path.resolve(baseDir, inputLibDirname, '_gen'); let outputGenPath = _path.resolve(baseDir, outputLibDirname, '_gen'); let inputLibOpenflPath = _path.resolve(baseDir, inputLibDirname, 'openfl'); let outputLibOpenflPath = _path.resolve(baseDir, outputLibDirname, 'openfl'); // Convert windows paths to posix paths fullInputLibDirPath = normalizePath(fullInputLibDirPath); fullOutputLibDirPath = normalizePath(fullOutputLibDirPath); inputGenPath = normalizePath(inputGenPath); outputGenPath = normalizePath(outputGenPath); inputLibOpenflPath = normalizePath(inputLibOpenflPath); outputLibOpenflPath = normalizePath(outputLibOpenflPath); const debug = false; let argv = process.argv; if (argv.length == 3) { start(argv[2]); } else { start(null); } async function start(option) { output('\n## convert-es2015.js...'); if (option == 'gen') { // Generate the es2015 modules in the lib-esm/_gen/ directory await startCreateEsmModules(); let totalFilesAddedOrModified = writeAllToFileSystem(); if (totalFilesAddedOrModified == 0) { output('\nComplete! No files were created or modified', true); return; } output(`\nComplete! ${totalFilesAddedOrModified} files were created or modified`, true); } else if (option == 'gen-confirm') { // Same as "gen" but prompts user before saving to file system await startCreateEsmModules(); let totalFilesAddedOrModified = writeAllToFileSystem(true); if (totalFilesAddedOrModified == 0) { output('\nComplete! No files were created or modified', true); return; } const r1 = readline.createInterface({ input: process.stdin, output: process.stdout }); r1.question('\n\nBased on the above, do you want to write the changes to the file system? [y/n]', answer => { r1.close(); if (answer == 'y') { writeAllToFileSystem(); output(`\nComplete! ${totalFilesAddedOrModified} files were created or modified`, true); } else { output('\nComplete! No files were created or modified', true); } }); } else if (option == 'non-gen') { await startCreateDefaultReExportEsms(); await startCreateIndexModules(); let totalFilesAddedOrModified = writeAllToFileSystem(true); if (totalFilesAddedOrModified == 0) { output('\nComplete! No files were created or modified', true); return; } const r1 = readline.createInterface({ input: process.stdin, output: process.stdout }); r1.question('\n\nBased on the above, do you want to write the changes to the file system? [y/n]', answer => { r1.close(); if (answer == 'y') { writeAllToFileSystem(); output(`\nComplete! ${totalFilesAddedOrModified} files were created or modified`, true); } else { output('\nComplete! No files were created or modified', true); } }); } else if (option == 'circular') { // Search for circular dependencies that may cause run-time errors await startFind(); output('Complete!', true); } else { output('\nOption unrecognized or not specified. Nothing to be done. Exiting script.', true); return; } } function startFind() { output('Searching for runtime circular dependency related errors:'); return globby([_path.resolve(`${outputGenPath}`, `**/*.js`)]).then((paths) => { // First we search for circular references... let result = new EsmModuleResult(); for (let i = 0; i < paths.length; i++) { let path = paths[i]; result.level = 0; processModule(result, path); } // ... then we look for circular imports that would cause a runtime error result.circulars.forEach(circular => { analyzeCircular(circular); }); }); } function analyzeCircular(circularChain) { circularChain.map(module => { Array.from(module.directImports.values()).map(importedModuleInfo => { if (circularChain.some(module => importedModuleInfo.module)) { if (importedModuleInfo.inCircular) { if (module.content.search(new RegExp(`\\$extend\\(${RegExp.escape(importedModuleInfo.localName)}\\.prototype`)) > -1) { console.log('\nPossible runtime error:'); console.log(module.fullFilePath, 'extends', importedModuleInfo.localName); console.log(circularChain.map(module => module.shortName).join(' -> ') + ' -> ' + circularChain[0].shortName); } let initCommentLine = module.content.search(/^\/\/ Init/m); if (initCommentLine > -1) { let lastPart = ''; initCommentLine += '// Init'.length; let exportCommentLine = module.content.search(/^\/\/ Export/m); if (exportCommentLine > -1) lastPart = module.content.substring(initCommentLine, exportCommentLine); else lastPart = module.content.substring(initCommentLine); let localNameUsedIndex = lastPart.search(new RegExp(`^[^\t]*?${RegExp.escape(importedModuleInfo.localName)}`, 'm')); if (localNameUsedIndex > -1) { console.log('\nPossible runtime error:'); console.log(module.fullFilePath, 'depends on', importedModuleInfo.localName); console.log(circularChain.map(module => module.shortName).join(' -> ') + ' -> ' + circularChain[0].shortName); } } } } }); }); } RegExp.escape = function(str) { if (!arguments.callee.sRE) { var specials = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$' ]; arguments.callee.sRE = new RegExp( '(\\' + specials.join('|\\') + ')', 'gim' ); } return str.replace(arguments.callee.sRE, '\\$1'); } function processModule(result, moduleFilePath) { let module = new EsmModule(); if (typeof moduleFilePath == 'string') module = result.getOrCreateModule(moduleFilePath); else module = moduleFilePath; if (module.importsProcessed) { return; } if (module.isAbsolute) return; let dirname = _path.dirname(module.fullFilePath); let content = _fs.readFileSync(module.fullFilePath, 'utf8'); let regex = /^import \{ default as (.+?)[, ].*?} from "(.+?)";/gm; module.content = content; // We look for lines like this: // import { default as openfl_display_DisplayObject } from "./../../openfl/display/DisplayObject"; // And we want to extract these parts: // openfl_display_DisplayObject // ./../../openfl/display/DisplayObject while (matches = regex.exec(content)) { let importPath = matches[2]; let importFullFilePath = _path.resolve(dirname, matches[2] + '.js'); importFullFilePath = normalizePath(importFullFilePath); let importedModule; if (importPath[0] != '.' && importPath[0] != '/') { // absolute import path importedModule = result.getOrCreateModule(importPath); importedModule.isAbsolute = true; } else { importedModule = result.getOrCreateModule(importFullFilePath); } // Such as "openfl_display_DisplayObject" let localName = matches[1]; module.addDirectImport(importedModule, localName); } module.importsProcessed = true; result.chain.push(module); module.directImports.forEach((importedModuleInfo, key) => { if (result.chain.has(importedModuleInfo.module)) { result.foundCircular(result.chain, importedModuleInfo.module, module); // We must not go further or we get trapped in an endless loop return; } result.level++; processModule(result, importedModuleInfo.module); result.level--; }); result.chain.pop(); } class ImportChain { constructor() { this.chain = []; } setAsCircular(startModule, endModule) { } getModulesBetween(start, end) { let index = this.chain.indexOf(start); let modules = []; while (true) { modules.push(this.chain[index]); if (this.chain[index] == end) { break; } index++; } return modules; } has(module) { return this.chain.indexOf(module) > -1; } push(module) { this.chain.push(module); } pop() { this.chain.pop(); } } class EsmModuleResult { constructor() { this.chain = new ImportChain(); this.modules = new Map(); this.circulars = []; } foundCircular(chain, startModule, endModule) { let modules = chain.getModulesBetween(startModule, endModule); modules.forEach(module => { let importedModuleInfos = Array.from(module.directImports.values()).filter(importedModuleInfo => modules.some(module2 => module2 == importedModuleInfo.module)); importedModuleInfos.forEach(importedModuleInfo => { importedModuleInfo.inCircular = true; }); }); this.circulars.push(modules); } addModule(module) { this.modules.set(module.fullFilePath, module); } hasModule(fullFilePath) { return this.modules.has(fullFilePath); } moduleProcessed(module) { return module.processed; } getOrCreateModule(fullFilePath) { if (this.hasModule(fullFilePath)) { return this.modules.get(fullFilePath); } let module = new EsmModule(); module.shortName = _path.basename(fullFilePath); module.fullFilePath = fullFilePath; this.addModule(module); return module; } } class EsmModule { constructor() { this.isAbsolute = false; this.directImports = new Map(); this.indirectImports = new Map(); this.isInCircularImportChain = false; this.fullFilePath = null; this.importsProcessed = false; } addDirectImport(module, localName) { let importedModuleInfo = {}; importedModuleInfo.module = module; importedModuleInfo.localName = localName; importedModuleInfo.inCircular = false; this.directImports.set(module.fullFilePath, importedModuleInfo); } } function startCreateEsmModules() { return globby([_path.resolve(`${inputGenPath}`, `**/*.js`)]).then((paths) => { for (let path of paths) { if (path.match(/\.esm\.js$/) != null) { continue; } let content = delayFS.readFileSyncUTF8(path, 'cjs-module'); let result = createEsmModule(content, path); let esmFilePath = path.replace(fullInputLibDirPath + '/', fullOutputLibDirPath + '/'); // change: ".../openfl/lib/_gen/openfl/display/Sprite.js" to ".../openfl/lib-esm/_gen/openfl/display/Sprite.js" delayFS.mkDirByPathSync(_path.dirname(esmFilePath)); delayFS.writeFileSyncUTF8(esmFilePath, result, 'esm-module'); } }); } // Here we create the es2015 version of the commonjs modules that contain: // module.exports = require("./../../_gen/openfl/display/Sprite").default; // The generated es2015 module will contain: // export { default } from "./../../_gen/openfl/display/Sprite"); function startCreateDefaultReExportEsms() { return globby([_path.resolve(`${inputLibOpenflPath}`, `**/*.js`)]).then(paths => { for (let path of paths) { // Skip the barrel index.js and index.esm.js files if (path.match(/index(\.js|\.d\.ts)$/) != null) { continue; } let content = delayFS.readFileSyncUTF8(path); let esmFilePath = path.replace(fullInputLibDirPath + '/', fullOutputLibDirPath + '/'); let result = createDefaultReExportEsm(content, path); delayFS.mkDirByPathSync(_path.dirname(esmFilePath)); if (path.indexOf('AssetLibrary') > -1) { delayFS.writeFileSyncUTF8(esmFilePath, result, {tags: 'rexport', createOnly: true}); } else { delayFS.writeFileSyncUTF8(esmFilePath, result, 'rexport'); } } }); } function startCreateIndexModules() { // Look for all the index.js files under lib/openfl/ and create index.esm.js versions of them return globby([_path.resolve(inputLibOpenflPath, '**/index.js')]).then((paths) => { for (let path of paths) { let content = delayFS.readFileSyncUTF8(path); let esmFilePath = path.replace(fullInputLibDirPath + '/', fullOutputLibDirPath + '/'); let result = createIndexModules(content, path); delayFS.mkDirByPathSync(_path.dirname(esmFilePath)); delayFS.writeFileSyncUTF8(esmFilePath, result, 'index'); var dTSFilePath = path.replace(/\.js$/, '.d.ts'); delayFS.writeFileSyncUTF8(dTSFilePath, result, 'index'); } }); } function createEsmModule(content, filePath) { var result = content; // We must remove these lines at the top as they are only needed in commonjs modules result = result.replace('$global.Object.defineProperty(exports, "__esModule", {value: true});', ''); result = result.replace('Object.defineProperty(exports, "__esModule", {value: true});', ''); // TODO: Remove this line ONLY if $global is not used anywhere else in the module //result = result.replace('var $global = typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : this', ''); // Replace // var $hxClasses = require("./../../hxClasses_stub").default; // with // import { default as $hxClasses } from "./../../hxClasses_stub"; result = result.replace(/^var (.+?) = require\(['"](.+?)['"]\)\.default;/gm, 'import { default as $1 } from "$2";'); // Replace // function openfl_display_DisplayObject() {return require("./../../openfl/display/DisplayObject");} // with // import { default as openfl_display_DisplayObject } from "./../../openfl/display/DisplayObject"; result = result.replace(/^function (.+?)\(\) {return require\(['"]\.\/(.+?)['"]\);}/gm, 'import { default as $1 } from "./$2";'); result = result.replace(/exports\.default =/g, 'export default'); // Replace // exports.default = Sprite // with // export { Sprite as default }; // // Here we deal with circular dependency related bugs // if (filePath.indexOf('openfl/display/Bitmap.') > -1) { result = importAndCallInit(result, 'openfl_display_DisplayObject', 'Bitmap'); } if (filePath.indexOf('Context3DMaskShader.') > -1) { result = importAndCallInit(result, 'openfl_display_BitmapData', 'Context3DMaskShader'); } if (filePath.indexOf('openfl/display/DisplayObject.') > -1) { result = exportInit(result, 'DisplayObject'); // Because the following imports are used in the module context of DisplayObject.js, that is, // outside any function attached to the prototype of DisplayObject or static function. We must // make sure these imports are processed before the import that leads to the circular dependency result = moveImportsToTop(result, [ 'import { default as haxe_ds_StringMap } from "./../../haxe/ds/StringMap";', 'import { default as lime_utils_ObjectPool } from "./../../lime/utils/ObjectPool";' ]); } if (filePath.indexOf('openfl/display/BitmapData.') > -1) { result = exportInit(result, 'BitmapData'); result = moveImportsToTop(result, [ 'import { default as openfl_geom_Rectangle } from "./../../openfl/geom/Rectangle";', 'import { default as lime_graphics_Image } from "./../../lime/graphics/Image";', 'import { default as lime_math_Vector2 } from "./../../lime/math/Vector2";' ]); } // // howler // result = result.replace('function lime_media_howlerjs_Howl() {return require("howler");}', 'import { Howl } from "howler";'); // Replace // function lime_media_howlerjs_Howl() {return require("howler");} // with // import { Howl } from "howler"; result = result.replace(/new \(lime_media_howlerjs_Howl\(\)\.Howl\)/g, 'new Howl'); // Replace // new (lime_media_howlerjs_Howl().Howl) // with // new Howl // // filesaver // if (result.indexOf('require (\'file-saverjs\')') > -1) { result = appendImport(result, 'import fileSaverJs from "file-saverjs";'); result = result.replace(/require \('file-saverjs'\)/g, 'fileSaverJs'); } if (result.indexOf('require (\'file-saver\')') > -1) { result = appendImport(result, 'import * as fileSaver from "file-saver";'); result = result.replace(/require \('file-saver'\)/g, 'fileSaver'); } // // pako // if (result.indexOf('require ("pako").deflateRaw') > -1) result = appendImport(result, 'import { deflateRaw, inflateRaw } from "pako";'); if (result.indexOf('require ("pako").inflate') > -1) result = appendImport(result, 'import { deflate, inflate } from "pako";'); result = result.replace(/require \("pako"\)\./g, ''); // Replace // var data = require ("pako").inflate(bytes.getData()); // with // var data = inflate(bytes.getData()); result = result.replace("var js_Boot = require('./js/Boot');", 'import { default as js_Boot } from "./js/Boot";'); result = result.replace("var HxOverrides = require('./HxOverrides');", 'import { default as HxOverrides } from "./HxOverrides";'); result = result.replace(/HxOverrides\.default/g, 'HxOverrides'); // Replace // (openfl_display_DisplayObject().default).call(this); // with // openfl_display_DisplayObject.call(this); // I had to add the [^(] character negation (instead of .) to deal with this case: // (symbol1,(openfl__$internal_symbols_BitmapSymbol().default)) // Had I instead used the dot metacharacter, it would have transformed it into this: // symbol1,(openfl__$internal_symbols_BitmapSymbol) // as opposed to the correct form: // (symbol1,openfl__$internal_symbols_BitmapSymbol) result = result.replace(/\(([^(]+?)\(\)\.default\)/gm, '$1'); return result; } function createDefaultReExportEsm(content, filePath) { var result = content; result = result.replace(/^\s*module\.exports\s*=\s*require\(['"](.+?)['"]\);/gm, 'export { default } from "$1";'); // Replace: // "module.exports = require("./../../_gen/openfl/display/Graphics");" // with // "export { default } from "./../../_gen/openfl/display/Graphics.esm"; result = result.replace(/Object\.defineProperty \(module.exports, "__esModule", { value: true }\)(,|;)?/, ''); // Remove these lines // Object.defineProperty (module.exports, "__esModule", { value: true }); result = result.replace(/module\.exports\..+? = module\.exports\.default = {/gm, 'export default {'); // Replace // module.exports.Endian = module.exports.default = { // with // export default { result = result.replace(/(module\.)?exports\.default =/g, 'export default'); // Replace // "exports.default =" OR "module.exports.default =" // with // export default result = result.replace(/^\s*var (.*?) = require \(['"](.*?)['"]\)\.default;/gm, 'import { default as $1 } from "$2";'); // Replace // var Lib = require ("./../../_gen/openfl/Lib").default; // with // import { default as Lib } from "./../../_gen/openfl/Lib"; result = result.replace('module.exports._internal = internal;', 'export { internal as _internal };'); // Replace (in lib-esm/openfl/utils/AssetLibrary.js) // module.exports._internal = internal; // with // export { internal as _internal }; return result; } function createIndexModules(content, filePath) { var result = content; // Handle comment lines result = result.replace(/^\s*\/\/(.+?): require\(["'](.+?)["']\)\.default.*/gm, '// export { default as $1 } from "$2";'); // Replaces: // "// Application: require("./Application").default," // with // "// export { default as Application } from "./Application.esm";" result = result.replace(/^\s*(.+?): require\(["'](.+?)["']\)\.default.*/gm, 'export { default as $1 } from "$2";'); //result = result.replace(/^\s*(.+?): require\(["'](.+?)["']\)\.default.*/gm, 'export { default as $1 } from "$2";'); // Replaces: // "Bitmap: require("./Bitmap").default," // with // "export { default as Bitmap } from "./Bitmap.esm";" // Deal with the barrel index modules that are re-rexported // Note: Must call this AFTER the previous replace() call result = result.replace(/^\s*(.+?): require\(["'](.+?)["']\).*/gm, (match, p1, p2) => { try { let fullPath = _path.resolve(_path.dirname(filePath), p2 + '.js'); if (_fs.statSync(fullPath).isFile()) { return 'export { default as ' + p1 + ' } from "' + p2 + '";'; } } catch (error) { } return 'export * from "' + p2 + '";'; }); // Replaces: // textures: require("./textures"), // with // export * from "./textures"; // Remove the "module.exports = {" lines result = result.replace('module.exports = {', ''); // And remove the end "}" result = result.replace(/}\s*$/gm, ''); // Add additiona exports not present in the original index barrel module //if (filePath.indexOf('openfl/utils/index.js') > -1) // result += 'export { default as AssetLibrary } from "./AssetLibrary";'; return result; } function writeAllToFileSystem(dryRun) { let summary = delayFS.commit(dryRun); // output(`Stats: ${inputGenPath} \n\tfiles read from filesystem: ${summary.getRead('cjs-module').length}`); //output(''); let added = summary.getAdded('esm-module').length; let modified = summary.getModified('esm-module').length; let unmodified = summary.getUnmodified('esm-module').length; if (added > 0 || modified > 0 || unmodified > 0) info(`Stats: ${outputGenPath} \n\tfiles added: ${added}, modified: ${modified}, unmodified: ${unmodified}`); added = summary.getAdded('rexport').length; modified = summary.getModified('rexport').length; unmodified = summary.getUnmodified('rexport').length; if (added > 0 || modified > 0 || unmodified > 0) info(`Stats: ${outputLibOpenflPath} \n\tfiles added: ${added}, modified: ${modified}, unmodified: ${unmodified}`); added = summary.getAdded('index').length; modified = summary.getModified('index').length; unmodified = summary.getUnmodified('index').length; if (added > 0 || modified > 0 || unmodified > 0) info(`Stats: index.js and index.d.ts \n\tfiles added: ${added}, modified: ${modified}, unmodified: ${unmodified}`); info(''); info(`directories created: ${summary.directoriesCreated}`); info(`totalCharsWritten: ${summary.totalWritten}`); let modifiedAll = summary.getModified().length; let addedAll = summary.getAdded(); let unmodifiedAll = summary.getUnmodified(); let ignoredAll = summary.getIgnored(); if (addedAll.length > 0) { if (!dryRun) output(`\n${addedAll.length} files created`); else { output(`\n${addedAll.length} files will be created. Some of them:`); for (let i = 0; i < 3; i++) { output(`${addedAll[i]}`); } } } if (unmodifiedAll.length > 0) { if (!dryRun) output(`\n${unmodifiedAll.length} files were not modified`); else output(`\n${unmodifiedAll.length} files will not be modified`); } if (ignoredAll.length > 0) { output(`\nThe following modules are left alone since they contain custom modifications:`); output(summary.getIgnored().join('\n')); } if (modifiedAll > 0) { if (dryRun) { output(`\n${modifiedAll} files will be modified. Here is the list:`); //output('\nList of files that will be modified:'); } else { output(`\n${modifiedAll} files modified. Here is the list:`); //output('\nList of modified files:'); } output(summary.getModified().join('\n')); } return summary.totalFilesAddedOrModified; } function moveImportsToTop(content, importLines) { for (let importLine of importLines) { content = content.replace(importLine, ''); } let importLineStr = importLines.reduce((prev, value) => { return prev + "\n" + value; }, ''); content = content.replace('// Imports', '// Imports\n' + importLineStr); return content; } function appendImport(content, importStatement) { content = content.replace('// Constructor', importStatement + '\n\n// Constructor'); return content; } function importAndCallInit(content, lookFor, className) { let rep = ` init${lookFor}(); var ${className} = function(`; content = content.replace(new RegExp(`var ${className} = function\\(`), rep); r = `import { default as ${lookFor}, init as init${lookFor} } from `; content = content.replace(new RegExp(`import { default as ${lookFor} } from `), r); return content; } function exportInit(content, className) { let rep = ` var ${className}; export function init() { if (${className}) return; ${className} = function(`; content = content.replace(new RegExp(`var ${className} = function\\(`), rep); // We need to use "export { Sprite as default }"" as opposed to "export default Sprite" in order to // take advantage of live bindings. This way our module init functions can initialize our classes // when other modules call the init function. content = content.replace(`export default ${className};`, `}\ninit();\nexport { ${className} as default };`); return content; } function info(message) { if (debug) console.log(message); } function output(message, lastOutput) { console.log.apply(console, [message]); if (lastOutput) console.log('\n## end of convert-es2015.js'); } function error() { console.log.apply(console, arguments); } function normalizePath(path) { let segs = path.split(/[/\\]+/); path = segs.join('/'); return path; } function init() { class DelayFS { constructor(debug) { this.virtualFileSystem = new Map(); this.directoriesToCreate = new Map(); this.debug = debug; } updateOrAddToVirtualFileSystem(fullFilePath, content, options) { if (options == null) { options = {}; } let value; if (this.virtualFileSystem.has(fullFilePath)) { value = this.virtualFileSystem.get(fullFilePath); } else { value = {}; this.virtualFileSystem.set(fullFilePath, value); } if (options.readFromRealFileSystem != null && options.readFromRealFileSystem) { value.originalContent = content; } if (options.performWrite) { value.performWrite = true; } if (options.tags != null) { if (value.tags != null) { value.tags.push(options.tags); } else { value.tags = [options.tags]; } } if (options.createOnly != null) { value.createOnly = options.createOnly; } value.content = content; //this.log(fullFilePath); //this.log(value); } readFileSyncUTF8(filePath, extra) { let fullFilePath = _path.resolve(filePath); if (this.virtualFileSystem.has(fullFilePath)) { return this.virtualFileSystem.get(fullFilePath).content; } let content = _fs.readFileSync(fullFilePath, 'utf8'); let tags = null; if (typeof extra == 'string') { tags = extra; } else if (typeof extra == 'object') { if (extra.tags != null) tags = extra.tags; } this.updateOrAddToVirtualFileSystem(fullFilePath, content, {readFromRealFileSystem: true, tags: tags}); return content; } writeFileSyncUTF8(filePath, content, extra) { let fullFilePath = _path.resolve(filePath); let createOnly = null; let tags = null; if (typeof extra == 'string') { tags = extra; } else if (typeof extra == 'object') { if (extra.createOnly != null) createOnly = extra.createOnly; else if (extra.tags != null) tags = extra.tags; } this.updateOrAddToVirtualFileSystem(fullFilePath, content, {performWrite: true, tags: tags, createOnly: createOnly}); } commit(dryRun) { this.directoriesThatExist = new Map(); let summary = new Summary(); if (dryRun) { //output('Doing dry run...'); } this.directoriesToCreate.forEach((value, key) => { // Create all the directories that were requested let created = this._mkDirByPathSync(key, dryRun); summary.directoryCreatedCount(created); }); this.virtualFileSystem.forEach((value, fullFilePath) => { // If not null then the file was read from the real file system if (value.originalContent != null) { summary.readFile(fullFilePath, value.tags); } // Skip those in which no write was requested if (!value.performWrite) { return; } // Check if file we are saving already exists on the file system... if (value.originalContent != null || _fs.existsSync(fullFilePath)) { let originalContent; if (value.originalContent == null) { originalContent = _fs.readFileSync(fullFilePath); } else { originalContent = value.originalContent; } if (value.content == originalContent) { // No modifications made to file summary.unmodifiedFile(fullFilePath, value.tags); } else { // Mofications made to existing file if (value.createOnly != null && value.createOnly) { summary.unmodifiedFile(fullFilePath, value.tags, true); return; } summary.modifiedFile(fullFilePath, value.tags); summary.totalWritten += value.content.length; if (dryRun) { } else { _fs.writeFileSync(fullFilePath, value.content, 'utf8'); } } } // ... otherwise we are adding new file else { summary.addedFile(fullFilePath, value.tags); summary.totalWritten += value.content.length; if (dryRun) { } else { _fs.writeFileSync(fullFilePath, value.content, 'utf8'); } } }); return summary; } mkDirByPathSync(targetDir, {isRelativeToScript = false} = {}) { this.directoriesToCreate.set(targetDir, true); } _mkDirByPathSync(targetDir, dryRun, {isRelativeToScript = false} = {}) { const sep = _path.posix.sep; const initDir = _path.isAbsolute(targetDir) ? sep : ''; const baseDir = isRelativeToScript ? __dirname : '.'; let directoriesCreated = 0; targetDir.split(sep).reduce((parentDir, childDir) => { const curDir = _path.resolve(baseDir, parentDir, childDir); if (this.directoriesThatExist.has(curDir)) { //console.log('exists1', curDir); return curDir; } if (_fs.existsSync(curDir)) { //console.log('exists2', curDir); this.directoriesThatExist.set(curDir, true); return curDir; } try { if (dryRun) { //console.log('dryRun - true'); directoriesCreated++; } else { //info(curDir); _fs.mkdirSync(curDir); } this.directoriesThatExist.set(curDir, true); } catch (err) { if (err.code !== 'EEXIST') { throw err; } } return curDir; }, initDir); return directoriesCreated; } log() { if (this.debug) { console.log.apply(console, arguments); } } } class Summary { constructor() { this.added = []; this.modified = []; this.unmodified = []; this.read = []; this.data = new Map(); this.directoriesCreated = 0; this.totalWritten = 0; this.totalFilesAddedOrModified = 0; } directoryCreatedCount(count) { this.directoriesCreated += count; } readFile(path, tags) { this.read.push({tags, path}); } addedFile(path, tags) { this.totalFilesAddedOrModified++; this.added.push({tags, path}); } modifiedFile(path, tags) { this.totalFilesAddedOrModified++; this.modified.push({tags, path}); } unmodifiedFile(path, tags, ignored) { this.unmodified.push({tags, path, ignored}); } toString() { return 'summary'; } getAdded(tags) { if (tags == null) { return this.added.map(value => value.path); } return this.added.filter(value => { return value.tags && value.tags.some(value => value == tags) }).map(value => value.path); } getModified(tags) { if (tags == null) { return this.modified.map(value => value.path); } return this.modified.filter(value => { return value.tags && value.tags.some(value => value == tags) }).map(value => value.path); } getUnmodified(tags) { if (tags == null) { return this.unmodified.map(value => value.path); } return this.unmodified.filter(value => { return value.tags && value.tags.some(value => value == tags) }).map(value => value.path); } getIgnored(tags) { if (tags == null) { return this.unmodified.filter(value => value.ignored).map(value => value.path); } return this.unmodified.filter(value => { return value.tags && value.tags.some(value => value == tags) && value.ignored }).map(value => value.path); } getRead(tags) { if (tags == null) { return this.read.map(value => value.path); } return this.read.filter(value => { return value.tags && value.tags.some(value => value == tags) }).map(value => value.path); } } return DelayFS; }