UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,577 lines (1,393 loc) 60.4 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2017 Zenesis Ltd License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * John Spackman (john.spackman@zenesis.com, @johnspackman) ************************************************************************ */ const process = require("process"); const Gauge = require("gauge"); const semver = require("semver"); const path = require("upath"); const consoleControl = require("console-control-strings"); const fs = qx.tool.utils.Promisify.fs; require("app-module-path").addPath(process.cwd() + "/node_modules"); /** * Handles compilation of the project * @ignore(setImmediate) */ qx.Class.define("qx.tool.cli.commands.Compile", { extend: qx.tool.cli.commands.Command, statics: { YARGS_BUILDER: { target: { alias: "t", describe: "Set the target type: source or build or class name. Default is first target in config file", requiresArg: true, type: "string" }, "output-path-prefix": { describe: "Sets a prefix for the output path of the target - used to compile a version into a non-standard directory", type: "string" }, download: { alias: "d", describe: "Whether to automatically download missing libraries", type: "boolean", default: true }, locale: { alias: "l", describe: "Compile for a given locale", nargs: 1, requiresArg: true, type: "string", array: true }, "update-po-files": { alias: "u", describe: "enables detection of translations and writing them out into .po files", type: "boolean", default: false }, "library-po": { describe: "The policy for updating translations in libraries", type: ["ignore", "untranslated", "all"], default: "ignore" }, "write-all-translations": { describe: "enables output of all translations, not just those that are explicitly referenced", type: "boolean" }, "app-class": { describe: "sets the application class", nargs: 1, requiresArg: true, type: "string" }, "app-theme": { describe: "sets the theme class for the current application", nargs: 1, requiresArg: true, type: "string" }, "app-name": { describe: "sets the name of the current application", nargs: 1, requiresArg: true, type: "string" }, "app-group": { describe: "which application groups to compile (defaults to all)", nargs: 1, requiresArg: true, type: "string" }, "local-fonts": { describe: "whether to prefer local font files over CDN", type: "boolean" }, watch: { describe: "enables watching for changes and continuous compilation", type: "boolean", alias: "w" }, "watch-debug": { describe: "enables debug messages for watching", type: "boolean" }, "machine-readable": { alias: "M", describe: "output compiler messages in machine-readable format", type: "boolean" }, minify: { alias: "m", describe: "disables minification (build targets only)", choices: ["off", "minify", "mangle", "beautify"], default: "mangle" }, "mangle-privates": { describe: "Whether to mangle private variables", default: true, type: "boolean" }, "save-source-in-map": { describe: "Saves the source code in the map file (build target only)", type: "boolean", default: false }, "source-map-relative-paths": { describe: "If true, the source file will be saved in the map file if the target supports it. Can be overridden on a per application basis.", type: "boolean", default: false }, "save-unminified": { alias: "u", describe: "Saves a copy of the unminified version of output files (build target only)", type: "boolean", default: false }, "inline-external-scripts": { describe: "Inlines external Javascript", type: "boolean" }, erase: { alias: "e", describe: "Enabled automatic deletion of the output directory when compiler version or environment variables change", type: "boolean", default: true }, feedback: { describe: "Shows gas-gauge feedback", type: "boolean", alias: "f" }, typescript: { alias: "T", describe: "Outputs typescript definitions in qooxdoo.d.ts", type: "boolean", default: null }, "add-created-at": { describe: "Adds code to populate object's $$createdAt", type: "boolean" }, "verbose-created-at": { describe: "Adds additional detail to $$createdAt", type: "boolean" }, clean: { alias: "D", describe: "Deletes the target dir before compile", type: "boolean" }, "warn-as-error": { alias: "E", describe: "Handle compiler warnings as error", type: "boolean", default: false }, "write-library-info": { alias: "I", describe: "Write library information to the script, for reflection", type: "boolean", default: true }, "write-compile-info": { describe: "Write application summary information to the script, used mostly for unit tests", type: "boolean", default: false }, bundling: { alias: "b", describe: "Whether bundling is enabled", type: "boolean", default: true } }, getYargsCommand() { return { command: "compile", describe: "compiles the current application, using compile.json", builder: qx.tool.cli.commands.Compile.YARGS_BUILDER }; } }, events: { /** * Fired when application writing starts */ writingApplications: "qx.event.type.Event", /** * Fired when writing of single application starts; data is an object containing: * maker {qx.tool.compiler.makers.Maker} * target {qx.tool.compiler.targets.Target} * appMeta {qx.tool.compiler.targets.meta.ApplicationMeta} */ writingApplication: "qx.event.type.Data", /** * Fired when writing of single application is complete; data is an object containing: * maker {qx.tool.compiler.makers.Maker} * target {qx.tool.compiler.targets.Target} * appMeta {qx.tool.compiler.targets.meta.ApplicationMeta} * * Note that target.getAppMeta() will return null after this event has been fired */ writtenApplication: "qx.event.type.Data", /** * Fired after writing of all applications; data is an object containing an array, * each of which has previously been passed with `writeApplication`: * maker {qx.tool.compiler.makers.Maker} * target {qx.tool.compiler.targets.Target} * appMeta {qx.tool.compiler.targets.meta.ApplicationMeta} * * Note that target.getAppMeta() will return null after this event has been fired */ writtenApplications: "qx.event.type.Data", /** * Fired after writing of all meta data; data is an object containing: * maker {qx.tool.compiler.makers.Maker} */ writtenMetaData: "qx.event.type.Data", /** * Fired when a class is about to be compiled. * * The event data is an object with the following properties: * * dbClassInfo: {Object} the newly populated class info * oldDbClassInfo: {Object} the previous populated class info * classFile - {ClassFile} the qx.tool.compiler.ClassFile instance */ compilingClass: "qx.event.type.Data", /** * Fired when a class is compiled. * * The event data is an object with the following properties: * dbClassInfo: {Object} the newly populated class info * oldDbClassInfo: {Object} the previous populated class info * classFile - {ClassFile} the qx.tool.compiler.ClassFile instance */ compiledClass: "qx.event.type.Data", /** * Fired when the database is been saved * * data: * database: {Object} the database to save */ saveDatabase: "qx.event.type.Data", /** * Fired after all enviroment data is collected * * The event data is an object with the following properties: * application {qx.tool.compiler.app.Application} the app * enviroment: {Object} enviroment data */ checkEnvironment: "qx.event.type.Data", /** * Fired when making of apps begins */ making: "qx.event.type.Event", /** * Fired when making of apps is done. */ made: "qx.event.type.Event", /** * Fired when minification begins. * * The event data is an object with the following properties: * application {qx.tool.compiler.app.Application} the app being minified * part: {String} the part being minified * filename: {String} the part filename */ minifyingApplication: "qx.event.type.Data", /** * Fired when minification is done. * * The event data is an object with the following properties: * application {qx.tool.compiler.app.Application} the app being minified * part: {String} the part being minified * filename: {String} the part filename */ minifiedApplication: "qx.event.type.Data" }, members: { __gauge: null, __makers: null, __libraries: null, __outputDirWasCreated: false, /** @type{String} the path to the root of the meta files by classname */ __metaDir: null, /** @type{Boolean} whether the typescript output is enabled */ __typescriptEnabled: false, /** @type{String} the name of the typescript file to generate */ __typescriptFile: null, /* * @Override */ async process() { await super.process(); let configDb = await qx.tool.cli.ConfigDb.getInstance(); if (this.argv["feedback"] === null) { this.argv["feedback"] = configDb.db("qx.default.feedback", true); } if (this.argv.verbose) { console.log(` Compiler: v${this.getCompilerVersion()} in ${require.main.filename} Framework: v${await this.getQxVersion()} in ${await this.getQxPath()}`); } if (this.argv["machine-readable"]) { qx.tool.compiler.Console.getInstance().setMachineReadable(true); } else { let configDb = await qx.tool.cli.ConfigDb.getInstance(); let color = configDb.db("qx.default.color", null); if (color) { let colorOn = consoleControl.color(color.split(" ")); process.stdout.write(colorOn + consoleControl.eraseLine()); let colorReset = consoleControl.color("reset"); process.on("exit", () => process.stdout.write(colorReset + consoleControl.eraseLine()) ); let Console = qx.tool.compiler.Console.getInstance(); Console.setColorOn(colorOn); } if (this.argv["feedback"]) { var themes = require("gauge/themes"); var ourTheme = themes.newTheme( themes({ hasUnicode: true, hasColor: true }) ); let colorOn = qx.tool.compiler.Console.getInstance().getColorOn(); ourTheme.preProgressbar = colorOn + ourTheme.preProgressbar; ourTheme.preSubsection = colorOn + ourTheme.preSubsection; ourTheme.progressbarTheme.postComplete += colorOn; ourTheme.progressbarTheme.postRemaining += colorOn; this.__gauge = new Gauge(); this.__gauge.setTheme(ourTheme); this.__gauge.show("Compiling", 0); const TYPES = { error: "ERROR", warning: "Warning" }; qx.tool.compiler.Console.getInstance().setWriter((str, msgId) => { msgId = qx.tool.compiler.Console.MESSAGE_IDS[msgId]; if (!msgId || msgId.type !== "message") { this.__gauge.hide(); qx.tool.compiler.Console.log( colorOn + TYPES[(msgId || {}).type || "error"] + ": " + str ); this.__gauge.show(); } else { this.__gauge.show(colorOn + str); } }); } } if (this.__gauge) { this.addListener("writingApplications", () => this.__gauge.show("Writing Applications", 0) ); this.addListener("writtenApplications", () => this.__gauge.show("Writing Applications", 1) ); this.addListener("writingApplication", evt => this.__gauge.pulse( "Writing Application " + evt.getData().appMeta.getApplication().getName() ) ); this.addListener("compilingClass", evt => this.__gauge.pulse( "Compiling " + evt.getData().classFile.getClassName() ) ); this.addListener("minifyingApplication", evt => this.__gauge.pulse( "Minifying " + evt.getData().application.getName() + " " + evt.getData().filename ) ); } else { this.addListener("writingApplication", evt => { let appInfo = evt.getData(); qx.tool.compiler.Console.print( "qx.tool.cli.compile.writingApplication", appInfo.appMeta.getApplication().getName() ); }); this.addListener("minifyingApplication", evt => qx.tool.compiler.Console.print( "qx.tool.cli.compile.minifyingApplication", evt.getData().application.getName(), evt.getData().filename ) ); } this.addListener("making", evt => { if (this.__gauge) { this.__gauge.show("Compiling", 1); } else { qx.tool.compiler.Console.print("qx.tool.cli.compile.makeBegins"); } }); this.addListener("made", evt => { if (this.__gauge) { this.__gauge.show("Compiling", 1); } else { qx.tool.compiler.Console.print("qx.tool.cli.compile.makeEnds"); } }); this.addListener("writtenApplications", e => { if (this.argv.verbose) { qx.tool.compiler.Console.log( "\nCompleted all applications, libraries used are:" ); Object.values(this.__libraries).forEach(lib => qx.tool.compiler.Console.log( ` ${lib.getNamespace()} (${lib.getRootDir()})` ) ); } }); await this._loadConfigAndStartMaking(); if (!this.argv.watch) { let success = this.__makers.every(maker => maker.getSuccess()); let hasWarnings = this.__makers.every(maker => maker.getHasWarnings()); if (success && hasWarnings && this.argv.warnAsError) { success = false; } if ( !this.argv.deploying && !this.argv["machine-readable"] && this.argv["feedback"] && this.__outputDirWasCreated && this.argv.target === "build" ) { qx.tool.compiler.Console.warn( " *******************************************************************************************\n" + " ** **\n" + " ** Your compilation will include temporary files that are only necessary during **\n" + " ** development; these files speed up the compilation, but take up space that you would **\n" + " ** probably not want to put on a production server. **\n" + " ** **\n" + " ** When you are ready to deploy, try running `qx deploy` to get a minimised version **\n" + " ** **\n" + " *******************************************************************************************" ); } process.exitCode = success ? 0 : 1; } }, /** * Loads the configuration and starts the make * * @return {Boolean} true if all makers succeeded */ async _loadConfigAndStartMaking() { if ( !this.getCompilerApi().compileJsonExists() && !qx.tool.cli.Cli.getInstance().compileJsExists() ) { qx.tool.compiler.Console.error( "Cannot find either compile.json nor compile.js" ); process.exit(1); } var config = this.getCompilerApi().getConfiguration(); var makers = (this.__makers = await this.createMakersFromConfig(config)); if (!makers || !makers.length) { throw new qx.tool.utils.Utils.UserError( "Error: Cannot find anything to make" ); } let countMaking = 0; const collateDispatchEvent = evt => { if (countMaking == 1) { this.dispatchEvent(evt.clone()); } }; let isFirstWatcher = true; await qx.Promise.all( makers.map(async maker => { var analyser = maker.getAnalyser(); let cfg = await qx.tool.cli.ConfigDb.getInstance(); analyser.setWritePoLineNumbers( cfg.db("qx.translation.strictPoCompatibility", false) ); if (!(await fs.existsAsync(maker.getOutputDir()))) { this.__outputDirWasCreated = true; } if (this.argv["clean"]) { await maker.eraseOutputDir(); await qx.tool.utils.files.Utils.safeUnlink( analyser.getDbFilename() ); await qx.tool.utils.files.Utils.safeUnlink( analyser.getResDbFilename() ); } if (config.ignores) { analyser.setIgnores(config.ignores); } var target = maker.getTarget(); analyser.addListener("compilingClass", e => this.dispatchEvent(e.clone()) ); analyser.addListener("compiledClass", e => this.dispatchEvent(e.clone()) ); analyser.addListener("saveDatabase", e => this.dispatchEvent(e.clone()) ); target.addListener("checkEnvironment", e => this.dispatchEvent(e.clone()) ); let appInfos = []; target.addListener("writingApplication", async () => { let appInfo = { maker, target, appMeta: target.getAppMeta() }; appInfos.push(appInfo); await this.fireDataEventAsync("writingApplication", appInfo); }); target.addListener("writtenApplication", async () => { await this.fireDataEventAsync("writtenApplication", { maker, target, appMeta: target.getAppMeta() }); }); maker.addListener("writingApplications", collateDispatchEvent); maker.addListener("writtenApplications", async () => { await this.fireDataEventAsync("writtenApplications", appInfos); }); if (target instanceof qx.tool.compiler.targets.BuildTarget) { target.addListener("minifyingApplication", e => this.dispatchEvent(e.clone()) ); target.addListener("minifiedApplication", e => this.dispatchEvent(e.clone()) ); } let stat = await qx.tool.utils.files.Utils.safeStat("source/index.html"); if (stat) { qx.tool.compiler.Console.print( "qx.tool.cli.compile.legacyFiles", "source/index.html" ); } // Simple one of make if (!this.argv.watch) { maker.addListener("making", () => { countMaking++; if (countMaking == 1) { this.fireEvent("making"); } }); maker.addListener("made", () => { countMaking--; if (countMaking == 0) { this.fireEvent("made"); } }); return await maker.make(); } // Continuous make let watch = new qx.tool.cli.Watch(maker); config.applications.forEach(appConfig => { if (appConfig.runWhenWatching) { watch.setRunWhenWatching( appConfig.name, appConfig.runWhenWatching ); } }); if (this.argv["watch-debug"]) { watch.setDebug(true); } watch.addListener("making", () => { countMaking++; if (countMaking == 1) { this.fireEvent("making"); } }); watch.addListener("made", () => { countMaking--; if (countMaking == 0) { this.fireEvent("made"); } }); watch.addListener("configChanged", async () => { await watch.stop(); setImmediate(() => this._loadConfigAndStartMaking()); }); let arr = [this._compileJsFilename, this._compileJsonFilename].filter( str => Boolean(str) ); watch.setConfigFilenames(arr); if ( target instanceof qx.tool.compiler.targets.SourceTarget && isFirstWatcher ) { isFirstWatcher = false; try { await this.__attachTypescriptWatcher(watch); } catch (ex) { qx.tool.compiler.Console.error(ex); } } return watch.start(); }) ); if (!this.argv.watch) { try { await this.__attachTypescriptWatcher(null); } catch (ex) { qx.tool.compiler.Console.error(ex); } } }, async __attachTypescriptWatcher(watch) { let classFiles = []; // Scans a directory recursively to find all .js files const scanImpl = async filename => { let basename = path.basename(filename); let stat = await fs.promises.stat(filename); if (stat.isFile() && basename.match(/\.js$/)) { classFiles.push(filename); } else if ( stat.isDirectory() && (basename == "." || basename[0] != ".") ) { let files = await fs.promises.readdir(filename); for (let i = 0; i < files.length; i++) { let subname = path.join(filename, files[i]); await scanImpl(subname); } } }; // Do the initial scan qx.tool.compiler.Console.info(`Loading meta data ...`); let metaDb = new qx.tool.compiler.MetaDatabase().set({ rootDir: this.__metaDir }); await metaDb.load(); // Scan all library directories metaDb.getDatabase().libraries = {}; for (let lib of Object.values(this.__libraries)) { let dir = path.join(lib.getRootDir(), lib.getSourcePath()); metaDb.getDatabase().libraries[lib.getNamespace()] = { sourceDir: dir }; await scanImpl(dir); } for (let filename of classFiles) { await metaDb.addFile(filename, !!this.argv.clean); } await metaDb.reparseAll(); await metaDb.save(); await this.fireDataEventAsync("writtenMetaData", metaDb); // Do the inital write let tsWriter = null; if (this.__typescriptEnabled) { qx.tool.compiler.Console.info(`Generating typescript output ...`); tsWriter = new qx.tool.compiler.targets.TypeScriptWriter(metaDb); if (this.__typescriptFile) { tsWriter.setOutputTo(this.__typescriptFile); } else { tsWriter.setOutputTo(path.join(this.__metaDir, "..", "qooxdoo.d.ts")); } await tsWriter.process(); } if (!watch) { return; } // Redo the files that change, as they change classFiles = {}; let debounce = new qx.tool.utils.Debounce(async () => { let filesParsed = false; qx.tool.compiler.Console.info(`Loading meta data ...`); let addFilePromises = []; while (true) { let arr = Object.keys(classFiles); if (arr.length == 0) { break; } filesParsed = true; classFiles = {}; arr.forEach(filename => { if (this.argv.verbose) { qx.tool.compiler.Console.info( `Processing meta for ${filename} ...` ); } addFilePromises.push(metaDb.addFile(filename)); }); } if (filesParsed) { qx.tool.compiler.Console.info(`Generating typescript output ...`); await Promise.all(addFilePromises); await metaDb.reparseAll(); await metaDb.save(); if (this.__typescriptEnabled) { await tsWriter.process(); } } }); // Watch for changes watch.addListener("fileChanged", evt => { let data = evt.getData(); if (data.fileType == "source") { let filename = data.library.getFilename(data.filename); classFiles[filename] = true; debounce.run(); } }); }, /** * Processes the configuration from a JSON data structure and creates a Maker * * @param data {Map} * @return {qx.tool.compiler.makers.Maker} */ async createMakersFromConfig(data) { const Console = qx.tool.compiler.Console.getInstance(); var t = this; if (data.babelOptions) { if (!data?.babel?.options) { data.babel = data.babel || {}; data.babel.options = data.babelOptions; qx.tool.compiler.Console.print( "qx.tool.cli.compile.deprecatedBabelOptions" ); } else { qx.tool.compiler.Console.print( "qx.tool.cli.compile.deprecatedBabelOptionsConflicting" ); } delete data.babelOptions; } if (qx.lang.Type.isBoolean(data?.meta?.typescript)) { this.__typescriptEnabled = data.meta.typescript; } else if (qx.lang.Type.isString(data?.meta?.typescript)) { this.__typescriptEnabled = true; this.__typescriptFile = path.relative( process.cwd(), path.resolve(data?.meta?.typescript) ); } if (qx.lang.Type.isBoolean(this.argv.typescript)) { this.__typescriptEnabled = this.argv.typescript; } var argvAppNames = null; if (t.argv["app-name"]) { argvAppNames = {}; t.argv["app-name"] .split(",") .forEach(name => (argvAppNames[name] = true)); } var argvAppGroups = null; if (t.argv["app-group"]) { argvAppGroups = {}; t.argv["app-group"] .split(",") .forEach(name => (argvAppGroups[name] = true)); } /* * Calculate the the list of targets and applications; this is a many to many list, where an * application can be compiled for many targets, and each target has many applications. * * Each target configuration is updated to have `appConfigs[]` and each application configuration * is updated to have `targetConfigs[]`. */ data.targets.forEach( (targetConfig, index) => (targetConfig.index = index) ); let targetConfigs = []; let defaultTargetConfig = null; data.targets.forEach(targetConfig => { if (targetConfig.type === this.getTargetType()) { if ( !targetConfig["application-names"] && !targetConfig["application-types"] ) { if (defaultTargetConfig) { qx.tool.compiler.Console.print( "qx.tool.cli.compile.multipleDefaultTargets" ); } else { defaultTargetConfig = targetConfig; } } else { targetConfigs.push(targetConfig); } } }); let allAppNames = {}; data.applications.forEach((appConfig, index) => { if (appConfig.name) { if (allAppNames[appConfig.name]) { throw new qx.tool.utils.Utils.UserError( `Multiple applications with the same name '${appConfig.name}'` ); } allAppNames[appConfig.name] = appConfig; } if (appConfig.group) { if (typeof appConfig.group == "string") { appConfig.group = [appConfig.group]; } } appConfig.index = index; let appType = appConfig.type || "browser"; let appTargetConfigs = targetConfigs.filter(targetConfig => { let appTypes = targetConfig["application-types"]; if (appTypes && !qx.lang.Array.contains(appTypes, appType)) { return false; } let appNames = targetConfig["application-names"]; if ( appConfig.name && appNames && !qx.lang.Array.contains(appNames, appConfig.name) ) { return false; } return true; }); if (appTargetConfigs.length == 0) { if (defaultTargetConfig) { appTargetConfigs = [defaultTargetConfig]; } else { throw new qx.tool.utils.Utils.UserError( `Cannot find any suitable targets for application #${index} (named ${ appConfig.name || "unnamed" })` ); } } appTargetConfigs.forEach(targetConfig => { if (!targetConfig.appConfigs) { targetConfig.appConfigs = []; } targetConfig.appConfigs.push(appConfig); if (!appConfig.targetConfigs) { appConfig.targetConfigs = []; } appConfig.targetConfigs.push(targetConfig); }); }); if (defaultTargetConfig && defaultTargetConfig.appConfigs) { targetConfigs.push(defaultTargetConfig); } let libraries = (this.__libraries = {}); let librariesArray = []; for (let libPath of data.libraries) { let library = await qx.tool.compiler.app.Library.createLibrary(libPath); libraries[library.getNamespace()] = library; librariesArray.push(library); } // Search for Qooxdoo library if not already provided var qxLib = libraries["qx"]; if (!qxLib) { let qxPath = await qx.tool.config.Utils.getQxPath(); var library = await qx.tool.compiler.app.Library.createLibrary(qxPath); libraries[library.getNamespace()] = library; librariesArray.push(library); qxLib = libraries["qx"]; } if (this.argv.verbose) { Console.log("Qooxdoo found in " + qxLib.getRootDir()); } let errors = await this.__checkDependencies( Object.values(libraries), data.packages ); if (errors.length > 0) { if (this.argv.warnAsError) { throw new qx.tool.utils.Utils.UserError(errors.join("\n")); } else { qx.tool.compiler.Console.log(errors.join("\n")); } } /* * Figure out which will be the default application; this will need some work for situations * where there are multiple browser based targets */ targetConfigs.forEach(targetConfig => { let hasExplicitDefaultApp = false; targetConfig.defaultAppConfig = null; if (targetConfig.appConfigs) { targetConfig.appConfigs.forEach(appConfig => { if (appConfig.type && appConfig.type != "browser") { return; } let setDefault; if (appConfig.writeIndexHtmlToRoot !== undefined) { qx.tool.compiler.Console.print( "qx.tool.cli.compile.deprecatedCompileSeeOther", "application.writeIndexHtmlToRoot", "application.default" ); setDefault = appConfig.writeIndexHtmlToRoot; } else if (appConfig["default"] !== undefined) { setDefault = appConfig["default"]; } if (setDefault !== undefined) { if (setDefault) { if (hasExplicitDefaultApp) { throw new qx.tool.utils.Utils.UserError( "Error: Can only set one application to be the default application!" ); } hasExplicitDefaultApp = true; targetConfig.defaultAppConfig = appConfig; } } else if (!targetConfig.defaultAppConfig) { targetConfig.defaultAppConfig = appConfig; } }); if (!hasExplicitDefaultApp && targetConfig.appConfigs.length > 1) { targetConfig.defaultAppConfig = targetConfig.appConfigs[0]; } } }); /* * There is still only one target per maker, so convert our list of targetConfigs into an array of makers */ let targetOutputPaths = {}; let makers = []; this.__metaDir = data.meta?.output; if (!this.__metaDir) { this.__metaDir = path.relative( process.cwd(), path.resolve(targetConfigs[0].outputPath, "../meta") ); } targetConfigs.forEach(targetConfig => { if (!targetConfig.appConfigs) { qx.tool.compiler.Console.print( "qx.tool.cli.compile.unusedTarget", targetConfig.type, targetConfig.index ); return; } let appConfigs = targetConfig.appConfigs.filter(appConfig => { if (argvAppGroups) { let groups = appConfig.group || []; if (!groups.find(groupName => !!argvAppGroups[groupName])) { return false; } } if (argvAppNames && appConfig.name) { if (!argvAppNames[appConfig.name]) { return false; } } return true; }); if (!appConfigs.length) { return; } var outputPath = targetConfig.outputPath; if (this.argv.outputPathPrefix) { outputPath = path.join(this.argv.outputPathPrefix, outputPath); } if (!outputPath) { throw new qx.tool.utils.Utils.UserError( "Missing output-path for target " + targetConfig.type ); } let absOutputPath = path.resolve(outputPath); if (targetOutputPaths[absOutputPath]) { throw new qx.tool.utils.Utils.UserError( `Multiple output targets share the same target directory ${outputPath} - each target output must be unique` ); } targetOutputPaths[absOutputPath] = true; var maker = new qx.tool.compiler.makers.AppMaker(); if (!this.argv["erase"]) { maker.setNoErase(true); } var targetClass = targetConfig.targetClass ? this.resolveTargetClass(targetConfig.targetClass) : null; if (!targetClass && targetConfig.type) { targetClass = this.resolveTargetClass(targetConfig.type); } if (!targetClass) { throw new qx.tool.utils.Utils.UserError( "Cannot find target class: " + (targetConfig.targetClass || targetConfig.type) ); } /* eslint-disable new-cap */ var target = new targetClass(outputPath); /* eslint-enable new-cap */ if (targetConfig.uri) { qx.tool.compiler.Console.print( "qx.tool.cli.compile.deprecatedUri", "target.uri", targetConfig.uri ); } if (targetConfig.addTimestampsToUrls !== undefined) { target.setAddTimestampsToUrls(targetConfig.addTimestampsToUrls); } else { target.setAddTimestampsToUrls( target instanceof qx.tool.compiler.targets.BuildTarget ); } if (targetConfig.writeCompileInfo || this.argv.writeCompileInfo) { target.setWriteCompileInfo(true); } if (data.i18nAsParts) { target.setI18nAsParts(true); } target.setWriteLibraryInfo(this.argv.writeLibraryInfo); target.setUpdatePoFiles(this.argv.updatePoFiles); target.setLibraryPoPolicy(this.argv.libraryPo); let fontsConfig = targetConfig.fonts || {}; let preferLocalFonts = true; if (this.argv.localFonts !== undefined) { preferLocalFonts = this.argv.localFonts; } else if (fontsConfig.local !== undefined) { preferLocalFonts = fontsConfig.local; } target.setPreferLocalFonts(preferLocalFonts); if (fontsConfig.fontTypes !== undefined) { target.setFontTypes(fontsConfig.fontTypes); } // Take the command line for `minify` as most precedent only if provided var minify; if (process.argv.indexOf("--minify") > -1) { minify = t.argv["minify"]; } minify = minify || targetConfig["minify"] || t.argv["minify"]; if (typeof minify == "boolean") { minify = minify ? "minify" : "off"; } if (!minify) { minify = "mangle"; } if (typeof target.setMinify == "function") { target.setMinify(minify); } function chooseValue(...args) { for (let i = 0; i < args.length; i++) { if (args[i] !== undefined) { return args[i]; } } return undefined; } // Take the command line for `saveSourceInMap` as most precedent only if provided var saveSourceInMap = chooseValue( targetConfig["save-source-in-map"], t.argv["saveSourceInMap"] ); if ( typeof saveSourceInMap == "boolean" && typeof target.setSaveSourceInMap == "function" ) { target.setSaveSourceInMap(saveSourceInMap); } var sourceMapRelativePaths = chooseValue( targetConfig["source-map-relative-paths"], t.argv["sourceMapRelativePaths"] ); if ( typeof sourceMapRelativePaths == "boolean" && typeof target.setSourceMapRelativePaths == "function" ) { target.setSourceMapRelativePaths(sourceMapRelativePaths); } var saveUnminified = chooseValue( targetConfig["save-unminified"], t.argv["save-unminified"] ); if ( typeof saveUnminified == "boolean" && typeof target.setSaveUnminified == "function" ) { target.setSaveUnminified(saveUnminified); } var inlineExternal = chooseValue( targetConfig["inline-external-scripts"], t.argv["inline-external-scripts"] ); if (typeof inlineExternal == "boolean") { target.setInlineExternalScripts(inlineExternal); } else if (target instanceof qx.tool.compiler.targets.BuildTarget) { target.setInlineExternalScripts(true); } var deployDir = targetConfig["deployPath"]; if (deployDir && typeof target.setDeployDir == "function") { target.setDeployDir(deployDir); } var deployMap = targetConfig["deploy-source-maps"]; if ( typeof deployMap == "boolean" && typeof target.setDeployDir == "function" ) { target.setDeployMap(deployMap); } maker.setTarget(target); var manglePrivates = chooseValue( targetConfig["mangle-privates"], t.argv["mangle-privates"] ); if (typeof manglePrivates == "string") { maker.getAnalyser().setManglePrivates(manglePrivates); } else if (typeof manglePrivates == "boolean") { if (manglePrivates) { maker .getAnalyser() .setManglePrivates( target instanceof qx.tool.compiler.targets.BuildTarget ? "unreadable" : "readable" ); } else { maker.getAnalyser().setManglePrivates("off"); } } if (targetConfig["application-types"]) { maker .getAnalyser() .setApplicationTypes(targetConfig["application-types"]); } if (targetConfig["proxySourcePath"]) { maker .getAnalyser() .setProxySourcePath(targetConfig["proxySourcePath"]); } maker.setLocales(data.locales || ["en"]); if (data.writeAllTranslations) { maker.setWriteAllTranslations(data.writeAllTranslations); } if (typeof targetConfig.typescript == "string") { Console.warn( "The 'typescript' property inside a target definition is deprecated - please see top level 'meta.typescript' property" ); if (this.__typescriptFile) { Console.warn( "Multiple conflicting locations for the Typescript output - choosing to write to " + this.__typescriptFile + " and NOT " + targetConfig.typescript ); } else { this.__typescriptEnabled = true; this.__typescriptFile = path.relative( process.cwd(), path.resolve(targetConfig.typescript) ); } } if (data.environment) { maker.setEnvironment(data.environment); } /* Libraries have to be added first because there is qx library which includes a framework version */ for (let library of librariesArray) { maker.getAnalyser().addLibrary(library); } let targetEnvironment = { "qx.version": maker.getAnalyser().getQooxdooVersion(), "qx.compiler.targetType": target.getType(), "qx.compiler.outputDir": target.getOutputDir(), "qx.target.privateArtifacts": !!data["private-artifacts"] }; if (data["private-artifacts"]) { target.setPrivateArtifacts(true); } qx.lang.Object.mergeWith( targetEnvironment, targetConfig.environment, false ); target.setEnvironment(targetEnvironment); if (targetConfig.preserveEnvironment) { target.setPreserveEnvironment(targetConfig.preserveEnvironment); } if (data["path-mappings"]) { for (var from in data["path-mappings"]) { var to = data["path-mappings"][from]; target.addPathMapping(from, to); } } function mergeArray(dest, ...srcs) { srcs.forEach(function (src) { if (src) { src.forEach(function (elem) { if (!qx.lang.Array.contains(dest, src)) { dest.push(elem); } }); } }); return dest; } let babelConfig = qx.lang.Object.clone(data.babel || {}, true); babelConfig.options = babelConfig.options || {}; qx.lang.Object.mergeWith( babelConfig.options, targetConfig.babelOptions || {} ); maker.getAnalyser().setBabelConfig(babelConfig); let browserifyConfig = qx.lang.Object.clone(data.browserify || {}, true); browserifyConfig.options = browserifyConfig.options || {}; qx.lang.Object.mergeWith( browserifyConfig.options, targetConfig.browserifyOptions || {} ); maker.getAnalyser().setBrowserifyConfig(browserifyConfig); var addCreatedAt = targetConfig["addCreatedAt"] || t.argv["addCreatedAt"]; if (addCreatedAt) { maker.getAnalyser().setAddCreatedAt(true); } const verboseCreatedAt = targetConfig["verboseCreatedAt"] || t.argv["verboseCreatedAt"]; if (verboseCreatedAt) { maker.getAnalyser().setVerboseCreatedAt(true); } let allApplicationTypes = {}; appConfigs.forEach(appConfig => { var app = (appConfig.app = new qx.tool.compiler.app.Application( appConfig["class"] )); app.setTemplatePath(qx.tool.utils.Utils.getTemplateDir()); [ "type", "theme", "name", "environment", "outputPath", "bootPath", "loaderTemplate", "publish", "deploy", "standalone", "localModules" ].forEach(name => { if (appConfig[name] !== undefined) { var fname = "set" + qx.lang.String.firstUp(name); app[fname](appConfig[name]); } }); allApplicationTypes[app.getType()] = true; if (appConfig.uri) { qx.tool.compiler.Console.print( "qx.tool.cli.compile.deprecatedUri", "application.uri", appConfig.uri ); } if (appConfig.title) { app.setTitle(appConfig.title); } if (appConfig.description) { app.setDescription(appConfig.description); } appConfig.localModules = appConfig.localModules || {}; qx.lang.Object.mergeWith( appConfig.localModules, data.localModules || {}, false ); if (!qx.lang.Object.isEmpty(appConfig.localModules)) { app.setLocalModules(appConfig.localModules); } var parts = appConfig.parts || targetConfig.parts || data.parts; if (parts) { if (!parts.boot) { throw new qx.tool.utils.Utils.UserError( "Cannot determine a boot part for application " + (appConfig.index + 1) + " " + (appConfig.name || "") ); } for (var partName in parts) { var partData = parts[partName]; var include = typeof partData.include == "string" ? [partData.include] : partData.include; var exclude = typeof partData.exclude == "string" ? [partData.exclude] : partData.exclude; var part = new qx.tool.compiler.app.Part( partName, include, exclude ).set({ combine: Boolean(partData.combine), minify: Boolean(partData.minify) }); app.addPart(part); } } if (target.getType() == "source" && t.argv.bundling) { var bundle = appConfig.bundle || targetConfig.bundle || data.bundle; if (bundle) { if (bundle.include) { app.setBundleInclude(bundle.include); } if (bundle.exclude) { app.setBundleExclude(bundle.exclude); } } } app.set({ exclude: mergeArray( [], data.exclude, targetConfig.exclude, appConfig.exclude ), include: mergeArray( [], data.include, targetConfig.include, appConfig.include ) }); maker.addApplication(app); }); const CF = qx.tool.compiler.ClassFile; let globalSymbols = []; qx.lang.Array.append(globalSymbols, CF.QX_GLOBALS); qx.lang.Array.append(globalSymbols, CF.COMMON_GLOBALS); if (allApplicationTypes["browser"]) { qx.lang.Array.append(globalSymbols, CF.BROWSER_GLOBALS); } if (allApplicationTypes["node"]) { qx.lang.Array.append(globalSymbols, CF.NODE_GLOBALS); } if (allApplicationTypes["rhino"]) { qx.lang.Array.append(globalSymbols, CF.RHINO_GLOBALS); } maker.getAnalyser().setGlobalSymbols(globalSymbols); if ( targetConfig.defaultAppConfig && targetConfig.defaultAppConfig.app && (targetConfig.defaultAppConfig.type || "browser") === "browser" ) { targetConfig.defaultAppConfig.app.setWriteIndexHtmlToRoot(true); } else { qx.tool.utils.files.Utils.safeUnlink( target.getOutputDir() + "index.html" ); } const showMarkers = (classname, markers) => { if (markers) { markers.forEach(function (marker) { var str = qx.tool.compiler.Console.decodeMarker(marker); Console.warn(classname + ": " + str); }); } }; // Note - this will cause output multiple times,